Proof of concept

The same discipline, in any language

One billing system, four languages. On the left, idiomatic source. On the right, its Trellis sidecar — the same structured statement of intent, whatever the code is written in.

A .trellis sidecar states a unit’s contract — what it provides, what it consumes, what must always be true, and what is explicitly not its job — in one uniform format that does not change from language to language. The handles adapt to each ecosystem’s idioms (Go package paths, TypeScript Type: and Event: prefixes, SQL Table: and View: objects); the structure, and the discipline it imposes, stay identical.

That uniformity is the point. The sidecar is highlighted below by the same grammar that ships in the Trellis editor extension — not faked for the page.

create_subscription.rb
# frozen_string_literal: true

# Creates a user subscription and performs the initial charge.
# Idempotent on (user, plan_id, idempotency_key).
class CreateSubscription
  class PaymentError < StandardError; end

  def initialize(gateway: PaymentGateway.new)
    @gateway = gateway
  end

  # @return [Subscription]
  # @raise  [PaymentError] when the gateway declines the charge
  def call(user:, plan_id:, idempotency_key:)
    plan = Plan.find(plan_id)

    charge = @gateway.charge(
      token: user.payment_token,
      amount: plan.price_cents,
      idempotency_key: idempotency_key,
    )
    raise PaymentError, charge.error unless charge.ok?

    subscription = Subscription.create!(user: user, plan: plan, status: :active)
    Events.publish("subscription.created", subscription_id: subscription.id)
    subscription
  end
end
create_subscription.rb.trellis
@owner: BillingTeam
@stability: stable
@composition: [Authenticatable, Payable]
@since: 2025-11-04
@reviewed: 2026-03-15

Feature: Create Subscription
  "Handles the idempotent creation of a user subscription and initial billing."

  Provides:
    - Subscription.create(user, plan_id) -> Subscription | raises PaymentError
    - Event: subscription.created

  Consumes:
    - PaymentGateway.charge(token, amount) -> ChargeResult
    - UserRecord (must respond to: id, email, payment_token)

  Invariants:
    - A user MUST NOT have two active 'pro' subscriptions
    - Charges SHALL be idempotent on (user_id, plan_id, idempotency_key)

  OutOfScope:
    - Refunds (handled by RefundService)
    - Plan upgrades (handled by ChangeSubscriptionPlan)

  Scenario (happy-path): Successful checkout
    Given a User with a valid stripe_token
    When the create method is called with plan_id
    Then a Subscription record is created
    And the PaymentGateway receives a charge request
    And the User status becomes active

  Scenario (negative): Expired card
    Given a User with an expired card
    When the create method is called
    Then it MUST raise PaymentError
    And no Subscription record is created

left the code · right its .trellis sidecar — the intent both you and an agent read before touching the code.

proration.go
// Package billing computes prorated charges for mid-cycle plan changes.
package billing

import (
	"errors"
	"time"
)

// ErrInvalidPeriod is returned when a period's End precedes its Start.
var ErrInvalidPeriod = errors.New("billing: period end precedes start")

// Money is a count of minor units (e.g. cents) in a single currency.
type Money int64

// Period is the billing window a proration is computed against.
type Period struct {
	Start time.Time
	End   time.Time
}

// Proration computes the amount owed when a subscription changes plan
// partway through a billing period. Time enters through now so the unit
// stays pure and testable.
type Proration struct {
	now func() time.Time
}

// Calculate returns the amount owed for the remainder of period when
// moving from oldPlan to newPlan. A negative result is a credit.
func (p Proration) Calculate(period Period, oldPlan, newPlan Money) (Money, error) {
	if period.End.Before(period.Start) {
		return 0, ErrInvalidPeriod
	}
	elapsed := p.now().Sub(period.Start).Seconds()
	total := period.End.Sub(period.Start).Seconds()
	remaining := 1 - elapsed/total
	delta := float64(newPlan-oldPlan) * remaining
	return Money(roundHalfUp(delta)), nil
}
proration.go.trellis
@owner: BillingTeam
@stability: stable
@layer: domain
@since: 2025-11-04
@reviewed: 2026-03-15

Feature: Proration
  "Computes the credit or charge owed when a subscription changes plan mid-period."

  Provides:
    - billing.Proration.Calculate @source("symbol:Calculate") -> Money | error

  Consumes:
    - billing.Period (must carry: Start, End)
    - billing.roundHalfUp @source("symbol:roundHalfUp") -> int64

  Invariants:
    - The result MUST be denominated in the period's currency
    - A downgrade with time remaining MUST yield a credit (negative Money)
    - Calculate MUST NOT read the wall clock directly; time enters through the injected now()

  OutOfScope:
    - Applying the proration to an invoice (handled by billing.Invoice.Apply)
    - Currency conversion (handled by fx.Convert)

  Scenario (happy-path): Upgrade halfway through the period
    Given a monthly period that is 50% elapsed
    When Calculate is called with a higher newPlan
    Then it returns a positive charge for the remaining half

  Scenario (negative): Inverted period bounds
    Given a Period whose End precedes its Start
    When Calculate is called
    Then it MUST return ErrInvalidPeriod

left the code · right its .trellis sidecar — the intent both you and an agent read before touching the code.

invoice_service.ts
import type { ApiClient } from "../platform/api-client";
import { publish } from "../platform/events";

export interface InvoiceDTO {
  id: string;
  customerId: string;
  amountDue: number; // minor units
  currency: string;
  status: "open" | "paid" | "void";
}

/** Creates open invoices for customers and announces them. */
export class InvoiceService {
  constructor(private readonly api: ApiClient) {}

  async createInvoice(
    customerId: string,
    amountDue: number,
    currency: string,
  ): Promise<InvoiceDTO> {
    if (amountDue <= 0) {
      throw new RangeError("amountDue must be positive minor units");
    }

    const invoice = await this.api.post<InvoiceDTO>("/invoices", {
      customerId,
      amountDue,
      currency,
      status: "open",
    });

    publish("invoice.created", { invoiceId: invoice.id, customerId });
    return invoice;
  }
}
invoice_service.ts.trellis
@owner: BillingTeam
@stability: stable
@layer: application
@since: 2025-11-04
@reviewed: 2026-03-15

Feature: Invoice Service
  "Creates open invoices for customers and announces them to the rest of the system."

  Provides:
    - billing.InvoiceService.createInvoice @source("symbol:createInvoice") -> InvoiceDTO
    - Type: Billing.InvoiceDTO @source("symbol:InvoiceDTO")
    - Event: invoice.created

  Consumes:
    - ApiClient: Platform.post -> InvoiceDTO
    - billing.events.publish @source("symbol:publish")

  Invariants:
    - amountDue MUST be positive minor units
    - Every created invoice MUST start in the 'open' status
    - invoice.created MUST be published only after the invoice persists

  OutOfScope:
    - Charging the customer (handled by billing.PaymentService.charge)
    - Dunning and retries (handled by billing.DunningJob)

  Scenario (happy-path): Create an open invoice
    Given a customer and a positive amountDue
    When createInvoice is called
    Then it persists an 'open' invoice
    And it publishes invoice.created with the new invoice id

  Scenario (negative): Non-positive amount
    Given an amountDue of 0
    When createInvoice is called
    Then it MUST raise a RangeError
    And it MUST NOT publish invoice.created

left the code · right its .trellis sidecar — the intent both you and an agent read before touching the code.

monthly_revenue.sql
-- analytics.monthly_revenue
-- Recognized revenue per calendar month, in each invoice's settlement currency.
CREATE OR REPLACE VIEW analytics.monthly_revenue AS
SELECT
    date_trunc('month', p.captured_at AT TIME ZONE 'UTC') AS month,
    i.currency,
    SUM(p.amount_minor) AS revenue_minor
FROM billing.payments AS p
JOIN billing.invoices AS i
    ON i.id = p.invoice_id
WHERE p.status = 'captured'
  AND i.status = 'paid'
GROUP BY 1, 2
ORDER BY 1, 2;
monthly_revenue.sql.trellis
@owner: AnalyticsTeam
@stability: stable
@layer: reporting
@since: 2025-11-04
@reviewed: 2026-03-15

Feature: Monthly Revenue
  "Recognized revenue per calendar month, in each invoice's settlement currency."

  Provides:
    - View: analytics.monthly_revenue @source("symbol:monthly_revenue")

  Consumes:
    - Table: billing.payments (must carry: amount_minor, status, captured_at, invoice_id)
    - Table: billing.invoices (must carry: id, currency, status)

  Invariants:
    - Revenue MUST count only 'captured' payments against 'paid' invoices
    - Months MUST be bucketed in UTC
    - Amounts MUST stay in minor units; the view MUST NOT convert currencies

  OutOfScope:
    - Refund netting (handled by View: analytics.net_revenue)
    - Conversion to a single reporting currency (handled by View: analytics.revenue_usd)

  Scenario (happy-path): A captured payment on a paid invoice
    Given a captured payment of 1000 minor units on a paid USD invoice in March
    When monthly_revenue is queried
    Then March / USD revenue includes those 1000 minor units

  Scenario (edge): A pending payment
    Given a payment with status 'pending'
    When monthly_revenue is queried
    Then that payment MUST NOT contribute to any month's revenue

left the code · right its .trellis sidecar — the intent both you and an agent read before touching the code.