SaaS Billing Example
A complete walkthrough of modeling a subscription billing platform with explicit module boundaries, financial invariants, and AI-safe change plans.
The Problem
Subscription billing platforms are deceptively complex. They span user identity, workspace management, plan tiers, payment processing, and invoice generation. In traditional codebases, the boundaries between these concerns blur over time: billing logic leaks into user controllers, workspace membership checks get duplicated across services, and downgrade rules end up scattered in half a dozen files.
When an AI agent is asked to "add team billing," it has no way to know which invariants must hold, which modules are affected, or what downstream entities need updating. It guesses from file names and comments. SysMARA makes all of this explicit.
System Structure
Modules
The system is divided into three modules with clear boundaries:
- users — Identity and authentication. Owns user profiles and credentials.
- workspaces — Multi-tenant workspace management. Owns membership, roles, and ownership transfer.
- billing — Subscription lifecycle and invoicing. Owns plans, payment state, and invoice generation.
Each module declares its own entities, capabilities, and invariants. Cross-module references are explicit — for example, billing references workspaces.workspace to bind a subscription to a workspace.
Entities
| Entity | Module | Description |
|---|---|---|
user | users | A registered user with email, name, and role |
workspace | workspaces | A tenant container with an owner and member list |
subscription | billing | An active plan binding between a workspace and a billing tier |
invoice | billing | A financial record with line items, status, and payment reference |
Capabilities
The system defines 14 capabilities across its three modules:
users module
create_user— Register a new user accountget_user— Retrieve user profile by ID
workspaces module
create_workspace— Provision a new workspaceget_workspace— Retrieve workspace detailstransfer_workspace— Transfer ownership to another memberlist_members— List all members of a workspaceadd_member— Invite a user to join a workspace
billing module
create_subscription— Start a new subscription for a workspacecancel_subscription— Cancel an active subscriptionupgrade_subscription— Move to a higher-tier plandowngrade_subscription— Move to a lower-tier plangenerate_invoice— Create an invoice for the current billing periodvoid_invoice— Mark an invoice as voidedget_invoice— Retrieve invoice details by ID
Key Invariants
The system enforces 8 invariants. These are not guidelines or comments — they are formal constraints that the SysMARA compiler validates and that AI agents must respect when proposing changes.
| Invariant | Severity | Description |
|---|---|---|
cannot_downgrade_with_unpaid_invoices | critical | A subscription cannot be downgraded while there are outstanding unpaid invoices. This prevents revenue loss from plan manipulation. |
workspace_must_have_owner | critical | Every workspace must have exactly one owner at all times. |
subscription_requires_workspace | critical | A subscription must reference a valid, active workspace. |
invoice_amount_non_negative | critical | Invoice amounts must be zero or positive. |
voided_invoice_immutable | critical | Once an invoice is voided, no fields can be modified. |
one_active_subscription_per_workspace | critical | A workspace may have at most one active subscription at a time. |
transfer_target_must_be_member | error | Workspace ownership can only be transferred to an existing member. |
cancelled_subscription_no_new_invoices | error | No new invoices may be generated for a cancelled subscription. |
Why cannot_downgrade_with_unpaid_invoices Matters
This invariant is a good example of why formal constraints matter for AI-driven development. Without it, an AI agent asked to "let users downgrade their plan" would generate a straightforward downgrade endpoint with no awareness that unpaid invoices create a financial risk. The invariant makes this rule explicit and machine-readable. The compiler will reject any change plan that introduces a downgrade path without checking invoice status.
Policy Priority Ordering
Access control uses 5 policies with explicit priority values. Higher priority policies are evaluated first and can override lower-priority ones:
admin_full_access(priority: 100) — System administrators can perform any operationbilling_admin(priority: 90) — Billing administrators can manage subscriptions and invoicesworkspace_owner(priority: 80) — Workspace owners can manage their own workspace and membersworkspace_member(priority: 50) — Members can read workspace data but not modify billingauthenticated_user(priority: 10) — Base-level access for any logged-in user
Flows
Three flows define the primary multi-step processes:
workspace_onboarding
Steps: create_user → create_workspace → create_subscription. Ensures a new user gets a workspace and an initial subscription in the correct order.
billing_cycle
Steps: generate_invoice → payment processing (external) → invoice status update. Represents the recurring billing loop.
ownership_transfer
Steps: list_members → validate target → transfer_workspace. Ensures the transfer target is a current member before executing the transfer.
Module Boundaries
The module boundaries in this example are intentionally strict. The billing module references workspaces.workspace but does not reference users.user directly — it gets user context through the workspace's owner and member relationships. This means:
- Billing logic never queries the users module directly
- Adding a new user field (like phone number) has zero impact on the billing module
- The impact analysis for changes to the
userentity stops at the workspace boundary
Change Plan Example: add-team-billing.yaml
Suppose you want to add team billing — a new plan tier that charges per-seat. Here is what the change plan looks like:
# change-plans/add-team-billing.yaml
name: add-team-billing
description: Add per-seat team billing tier with seat count tracking
add:
entities:
- name: seat_allocation
module: billing
fields:
workspace_id:
type: reference
target: workspaces.workspace
seat_count:
type: integer
price_per_seat:
type: decimal
capabilities:
- name: update_seat_count
module: billing
entity: seat_allocation
type: mutation
- name: get_seat_allocation
module: billing
entity: seat_allocation
type: query
invariants:
- name: seat_count_minimum_one
module: billing
severity: critical
condition: "seat_allocation.seat_count >= 1"
description: "A team plan must have at least one seat"
modify:
entities:
- name: subscription
module: billing
add_fields:
plan_type:
type: enum
values: [individual, team]
seat_allocation_id:
type: reference
target: billing.seat_allocation
nullable: true CLI Commands
Working with this example project using the SysMARA CLI:
Validate the system
npx sysmara validate Parses all spec files, builds the system graph, and reports any invariant violations, unresolved references, or policy conflicts.
Analyze impact of a change
npx sysmara impact change-plans/add-team-billing.yaml Shows which modules, entities, capabilities, and invariants are affected by the proposed change. For the team billing example, this would report impact on the billing module and the subscription entity, and flag the new invariant seat_count_minimum_one for review.
Explain a capability
npx sysmara explain capability billing.downgrade_subscription Outputs the full context for a capability: which entity it operates on, which invariants constrain it, which policies govern access, and which flows include it. For downgrade_subscription, this would show the cannot_downgrade_with_unpaid_invoices invariant and the billing_admin and admin_full_access policies.
What This Example Demonstrates
- Multi-module systems with explicit cross-module references
- Financial invariants that prevent unsafe state transitions
- Policy priority ordering for layered access control
- Change plans that AI agents can propose and the compiler can validate
- Impact analysis that traces changes through module boundaries