Billing & Credit Architecture
Every credit is sourced from the database, never hardcoded. Every payment processor (Stripe today; Razorpay, Chargebee, Paddle on the roadmap) plugs in without touching the ledger. Internal cost numbers stay internal. The customer sees a stable, predictable bill grounded in 20% of equivalent manual labour cost. 99%+ gross margin at scale, idempotent reservations, zero charge for failed jobs.
Section 1 · Why a credit architecture matters
Most usage-based SaaS billing collapses under one of four pressures: (a) tight coupling to the payment processor forcing a rebuild when expanding to a new region; (b) leaking internal cost into customer-facing surfaces; (c) bills swinging wildly on legitimately-large jobs; (d) double-charging on payment-processor retries. Arcitopsia's design tackles each one explicitly.
Ledger has zero Stripe imports. Razorpay, Chargebee, Paddle add new handler files — never touch the core.
Internal cost & gross margin live in WorkflowExecutionTelemetry. Customer ledger lives in CreditTransaction. The two never cross.
10 runtime factors, weighted, normalised, then logarithmic dampening. Captures real cost variation without runaway bills.
@@unique(executionId, type) on every ledger row. Stripe webhook re-delivery is a no-op. Settle called twice → second call returns {alreadySettled:true}.
Section 2 · PSP-Agnostic Invariant (PAYG §9.6)
Per PAYG §9.6: the credit ledger (credit-engine.ts, credit-service.ts,
complexity-calculator.ts, and the data-model tables themselves) contains zero PSP-specific types,
IDs, or imports. Stripe is isolated to stripe-credits.ts + the webhook handler file. This is
checked mechanically in the Phase 3 hand-test by grepping every file under lib/billing/ for
import.*stripe; exactly one file matches.
flowchart LR
subgraph PSP["PSP Layer (isolated)"]
SC[stripe-credits.ts]:::psp
WH[webhooks/stripe/route.ts]:::psp
end
subgraph CORE["Credit Ledger Core — PSP-AGNOSTIC"]
CE[credit-engine.ts
reserve / settle / release]:::core
CS[credit-service.ts
DB-driven config lookup]:::core
CC[complexity-calculator.ts
10-factor + log₂]:::core
DB[(Tables
CreditTransaction
TenantCreditBalance
CreditReservation)]:::db
end
subgraph FUTURE["Future PSPs (drop-in, v1.5+)"]
RZ[razorpay-credits.ts
v1.5 India]:::future
CB[chargebee-bridge.ts
v2 Enterprise AR]:::future
PD[paddle-credits.ts
v3 MOR]:::future
end
WH --> SC
SC -- "topUpCreditsFromPurchase(
purchaseId, pspPaymentId)" --> CE
CE --> CS
CE --> CC
CE --> DB
RZ -. "same entry point" .-> CE
CB -. "same entry point" .-> CE
PD -. "same entry point" .-> CE
classDef psp fill:#3f1d1d,stroke:#fb7185,color:#fff;
classDef core fill:#0d1a0d,stroke:#a3e635,color:#fff;
classDef db fill:#0d1a0d,stroke:#a3e635,color:#fff,stroke-dasharray:4 2;
classDef future fill:#1e2a3a,stroke:#38bdf8,color:#fff,stroke-dasharray:4 2;
The PSP-agnostic boundary. Every PSP funnels into the same topUpCreditsFromPurchase entry point — and that function knows nothing about Stripe.
Section 3 · Two-Layer Measurement (PAYG §7.0)
Every probe execution writes to two parallel layers. The internal layer (WorkflowExecutionTelemetry)
captures iArchitron's actual cost — compute units, tokens, LLM cost, gross margin. The external layer
(CreditTransaction) records the customer-facing deduction. Code that reads one never reads the other.
| Layer | Audience | Table | Records | API surface |
|---|---|---|---|---|
| Internal telemetry | iArchitron ops + finance | WorkflowExecutionTelemetry |
compute units, tokens in/out/cached, storage bytes, LLM cost, internal cost, gross margin % | /api/admin/billing/margin-analytics (admin-gated) |
| External credits | Customer-facing | CreditTransaction, TenantCreditBalance, CreditReservation |
fixed activity-type baseCredits × complexityMultiplier × tierMultiplier × globalMultiplier |
/api/billing/balance, /api/billing/transactions (tenant-scoped) |
/api/billing/config GET endpoint defines a
selectClientSafe() that explicitly lists which columns leave the server — Stripe customer IDs and
payment-method IDs are deliberately absent. The Phase 4 hand-test (PR #31) asserts they are absent from the
response shape.
Section 4 · 20% Value-Capture Pricing (PAYG §5.0)
Every credit-bearing activity is priced as a fraction of the documented manual cost to produce the same outcome. This is the foundation of the pricing model and the 95–99% gross margin. Internal/LLM cost is irrelevant to what the customer is charged — what they're charged is anchored to their alternative.
baseCredits = round(manualCostBasisUSD × captureRatePct) where captureRatePct defaults to 0.20 (negotiable 0.15–0.25 per TenantCreditContract)
| Activity | Manual basis | Capture | Base credits |
|---|---|---|---|
| Architecture Document (full) | $4,000 · 40h × $100/hr EA Consultant | 0.20 | 800 |
| SOC2 / HIPAA / PCI Compliance Report | $7,000 · 47h × $150/hr | 0.20 | 1,400 |
| Full Compliance Assessment | $2,000 · 13h × $150/hr | 0.20 | 400 |
| Architecture Simulation Run | $1,000 · 10h × $100/hr | 0.20 | 200 |
| Code Generation (per component) | $400 · 4h × $100/hr | 0.20 | 80 |
| IaC Generation (per module) | $600 · 6h × $100/hr Sr. DevOps | 0.20 | 120 |
| Diagram Generation (per set) | $300 · 3h × $100/hr EA Architect | 0.20 | 60 |
| Probe Discovery Run | $500 · 5h × $100/hr | 0.20 | 100 |
| EA Artefact AI Draft | $250 · 2.5h × $100/hr | 0.20 | 50 |
| Bulk Import (per 100 records) | $50 · value-based not 20% capture | 1.00 | 100 |
Section 5 · Customer Tier Multiplier (PAYG §5.8.3)
Set once at contract time (TenantCreditContract.customerTierKey); applied multiplicatively to every
credit deduction. Captures structural overhead (compliance automation, multi-region data residency, SLA cover,
dedicated infrastructure) — independent of any runtime metric. Distinct from the marketing plan tier (Startup /
Professional / Enterprise / Strategic / Global) which controls subscription price.
| Tier | Multiplier | Structural overhead represented |
|---|---|---|
INDIVIDUAL | 0.75 | Single-user, small graph, no compliance overlay, no SLA |
SMB | 0.90 | Small team, moderate graph, occasional compliance |
ENTERPRISE default | 1.00 | Multi-team, large graph (50K+ records), approval chains, compliance automation |
MULTINATIONAL | 1.30 | Global ops, multi-region data residency, GDPR/SOX/HIPAA overlay, dedicated infra |
MISSION_CRITICAL | 1.60 | Financial / Government / Healthcare, real-time audit, air-gapped, 99.9% SLA |
customerTierMultiplier, not 1.0. The
displayed "max credits" matches what will be reserved, so MULTINATIONAL customers never see a number that's
smaller than reality.
Section 6 · 10-Factor Complexity Multiplier (PAYG §5.8.4–5)
Per-execution complexity is derived from 10 runtime dimensions captured by the workflow runner. Each is normalised against the activity's baseline profile, capped to prevent any one dimension from dominating, weight-summed, then logarithmically dampened. The result is multiplied onto the base credits.
// For each dimension i: normalized_i = actual_i / baseline_i // baseline per activity factorScore_i = min(normalized_i, capMultiplier_i) // per-factor cap weightedSum += factorScore_i × weight_i // platform defaults sum to 1.00 complexityScore = weightedSum / Σ(weight_i) rawMultiplier = log₂(complexityScore + 1) × 1.44 // scalingConstant complexityMultiplier = clamp(rawMultiplier, contract.minComplexityMultiplier, // default 0.5 contract.maxComplexityMultiplier) // default 3.0
| factorKey | Weight | Cap | Normalisation unit |
|---|---|---|---|
child_count | 0.25 | 5.0 | 1 (count) |
token_intensity | 0.22 | 4.0 | 1,000 (per-1K tokens) |
context_size_kb | 0.15 | 3.0 | 100 (per-100 KB) |
wall_clock_ms | 0.10 | 2.5 | 1 (ms) |
hierarchy_depth | 0.08 | 3.0 | 1 (level) |
peak_concurrency | 0.06 | 2.0 | 1 (jobs) |
model_tier | 0.05 | 5.0 | 1 (Haiku=1, Sonnet=2, Opus=5) |
cache_miss_rate | 0.04 | 2.0 | 1.0 (=100% miss) |
retry_count | 0.03 | 1.5 | 1 (retries) |
external_api_calls | 0.02 | 1.5 | 1 (calls) |
| Σ weights | 1.00 | ||
finalCredits = round( baseCredits × complexityMultiplier // from runtime metrics (this section) × customerTierMultiplier // from TenantCreditContract.customerTierKey (§5) × globalCreditMultiplier // volume discount × (isByollm ? byollmMultiplier : 1.0) )
child_count + token_intensity together account for
47% of the weighting; both are heavily driven by probe behaviour, by design.
Section 7 · BYOLLM Economics (PAYG §5.6)
When a tenant supplies their own LLM API keys, iArchitron does not bear LLM token cost. Credit prices are reduced by a fixed multiplier; a separate license-fee discount applies at the subscription layer. Gross margin on BYOLLM-discounted credits still exceeds 95% because internal cost drops to ~$3–8 (compute + storage only, no LLM).
| Setting | Value | Source |
|---|---|---|
Tenant.byollmEnabled | Boolean | Column on Tenant |
Tenant.byollmProvider | ANTHROPIC | OPENAI | GOOGLE_GEMINI | AWS_BEDROCK | AZURE_OPENAI | VERTEX_AI | Column on Tenant |
TenantCreditContract.byollmCreditMultiplier | 0.62 default (negotiable 0.50–0.70) | Reflects LLM cost being ~38% of internal cost |
WorkflowCreditConfig.byollmCreditMultiplier | 0.62 default per activity | Per-activity override possible |
| License-fee BYOLLM discount | flat 15% | Subscription billing (separate from credit ledger) |
Section 8 · Reserve → Settle → Release Lifecycle (PAYG §8.3)
Every credit-bearing execution follows this lifecycle. Reserve happens before work starts and holds the worst-case against the balance. Settle happens at successful completion and computes the actual via complexity. Release returns the full reservation if execution fails — customers never pay for our failures.
sequenceDiagram
autonumber
participant U as User
participant API as /api/…
participant CE as credit-engine
participant DB as Ledger DB
U->>API: Approve & Run Now (executionId)
API->>CE: reserveCredits(executionId, baseCredits)
CE->>DB: read TenantCreditContract + Config (5-min cache)
CE->>DB: compute maxReserve, write CreditReservation(HELD)
CE-->>API: ok, balance debited by maxReserve
Note over U,DB: Workflow executes…
alt success
API->>CE: settleCredits(executionId, runtimeVector)
CE->>DB: complexity calc, write CreditTransaction(DEDUCTION)
CE->>DB: flip CreditReservation to SETTLED + release unused
CE-->>API: settled = actualCredits
else failure
API->>CE: releaseReservation(executionId)
CE->>DB: flip CreditReservation to RELEASED_ON_FAILURE
CE->>DB: full reservedCredits returned to balance
CE-->>API: zero charged
end
Note over CE,DB: Idempotency: @@unique(executionId, type)
second settle / release call → no-op
The lifecycle. Idempotent on every step — Stripe webhook retries and BullMQ job retries are safe.
maxReserve = baseCredits
× maxComplexityMultiplier // from contract, default 3.0
× customerTierMultiplier // e.g. 1.30 for MULTINATIONAL
× globalCreditMultiplier // volume discount, e.g. 0.80 for Scale-pack
Every CreditTransaction row has a unique index on (executionId, type). The first
settleCredits call inserts a DEDUCTION row. A second call for the same
executionId raises a unique-constraint violation; the engine catches it and returns
{alreadySettled: true}. No double-charge possible — verified in the Phase 2 hand-test (PR #29).
Section 9 · Credit Ledger Data Model
Per PAYG §8.2. The credit ledger lives in six tables. Two are platform-level catalogues (CreditPack,
WorkflowCreditConfig with tenantId=null rows); four are tenant-scoped with cascade delete.
| Table | Scope | Purpose | PSP fields (isolated) |
|---|---|---|---|
CreditPack | Platform | Catalogue of purchasable packs (5 SKUs in v1) | stripePriceId |
TenantCreditBalance | Tenant (1 row) | Source of truth — includedCredits + purchasedCredits = totalBalance | none |
CreditTransaction | Tenant (N) | Immutable ledger — every credit movement. @@unique(executionId, type) | none |
CreditPackPurchase | Tenant (N) | One row per pack purchase. Links PSP payment to credit grant. | stripePaymentId, stripeInvoiceId |
CreditReservation | Tenant (N) | Pre-execution hold; status flips HELD → SETTLED or RELEASED_ON_FAILURE | none |
TenantBillingConfig | Tenant (1) | Auto-top-up + alert preferences | stripeCustomerId, stripePaymentMethodId |
| Table | Holds | Override pattern |
|---|---|---|
WorkflowCreditConfig | Per-workflow-type credit config (manual cost basis, capture rate, base credits) | tenantId=null = platform; non-null = tenant override (resolved via Postgres NULLS LAST) |
WorkflowComplexityProfile | Per-activity baseline (baselineChildCount, baselineTokenCount, etc.) for normalisation | Same tenant-override pattern |
ComplexityFactorConfig | Per-dimension weight + cap. Platform defaults sum to 1.00. | Same tenant-override pattern |
TenantCreditContract | 1 per tenant: customer tier + multiplier + capture rate + complexity bounds + flat-pricing flag | n/a — already per-tenant |
WorkflowExecutionTelemetry (per execution; compute/storage/LLM cost + gross margin %),
CreditDeficitAlert (rare — post-execution balance went negative).
Both gated behind hasAdminAccess; never returned to tenant-scoped routes.
Section 10 · DB-Driven Configuration Principle (PAYG §8.1 #2)
Every credit value — base credits per activity, factor weights, complexity baselines, tier multipliers, included-credits-per-plan, BYOLLM multipliers — lives in DB tables. Tenant-specific contract overrides (negotiated capture rates, flat pricing for regulated industries) take effect immediately, without code deploys.
lib/billing/credit-engine.ts calls resolveWorkflowCreditConfig() + resolveTenantContract() — never imports a constantconst PROBE_DISCOVERY_BASE_CREDITS = 100 anywhere in lib/billing/invalidateCreditCaches(tenantId) exposed for hot-reload after contract editsNULLS LAST ordering (orderBy: [{ tenantId: { sort: "desc", nulls: "last" } }]) — tenant override row sorts before the platform defaultprisma/seed/credit-packs.ts + prisma/seed/workflow-credit-config.ts is idempotent on the unique key — safe for productioncompliance-report
from $7,000 to $7,500 manual basis becomes a single seed-script update + re-run. No application redeploy.
Tenants negotiating a 15% capture rate get a new TenantCreditContract row inserted; the 5-min cache
expires, every subsequent settle uses the new rate. Auditability via CreditTransaction.manualCostBasis
column (manual basis snapshot at txn time) and the effectiveFrom / effectiveTo on contract rows.
Section 11 · Stripe Integration Surface
Per PAYG §9.1: Stripe is the v1 launch PSP. The integration is intentionally narrow —
lib/billing/stripe-credits.ts is the only file in lib/billing/ that imports
the Stripe SDK (mechanically verified in the Phase 3 hand-test). Public-API surface: Checkout sessions for
pack purchases + webhook ingestion.
| Primitive | Used for | Notes |
|---|---|---|
Stripe Customer | One per tenant | Lazy-created on first purchase; persisted on TenantBillingConfig.stripeCustomerId |
Stripe Product + Price | One per CreditPack SKU | stripePriceId stored on the pack row; populated by deploy-time sync script |
Stripe Checkout Session | One per purchase attempt | Created with metadata.purchaseId for round-trip identification |
Stripe PaymentIntent | Stored on completed purchases | One-time-payment mode |
Stripe Subscription | Strategic Annual pack (recurring) | Subscription mode; uses Stripe Meters for usage billing (v1.5) |
Stripe Tax (Automatic) | Compute tax/VAT/GST at checkout | automatic_tax: { enabled: true } on every Checkout session |
Stripe Webhook signatures | HMAC-SHA256 verification | STRIPE_WEBHOOK_SECRET; SDK constructEvent handles timestamp tolerance |
/admin/credits "Buy Credits" tab → POST /api/billing/credit-packs/[id]/purchaseCreditPackPurchase row first (stable purchaseId for round-trip)TenantBillingConfigcheckout.sessions.create({mode, customer, line_items, automatic_tax, metadata: {purchaseId, tenantId, packId}, ...}){purchaseId, checkoutUrl}
STRIPE_API_VERSION=2024-12-18.acacia set in both env and the client init. Bumped in lockstep with
Stripe SDK upgrades; never silently floated. Stripe deprecation warnings surface on every SDK upgrade —
the team reviews behavioural changes per Stripe's upgrade guide before bumping.
Section 12 · Webhook Event Handling
POST /api/billing/webhooks/stripe is the single webhook endpoint. Forces Node runtime (raw body
required for HMAC), dynamic = "force-dynamic" (no caching), 400 on signature failure (Stripe retries),
200 on every successfully-ingested event (handled or ignored — non-2xx would trigger infinite retries for
events we never plan to handle).
| Event | Action | Idempotency mechanism |
|---|---|---|
checkout.session.completed |
Calls topUpCreditsFromPurchase({purchaseId, pspPaymentId, pspInvoiceId}) |
executionId = "purchase:" + purchaseId + type=TOPUP on @@unique index |
payment_intent.payment_failed |
Marks CreditPackPurchase.status = FAILED, stores PaymentIntent ID |
Re-delivery overwrites with same data → no-op |
invoice.paid |
v1.5 — Strategic Annual renewal flow. v1 stub: returns 200 with ignored: "annual renewal handling deferred to v1.5" |
n/a in v1 |
| All others | Ignored, return 200 with handled: false + ignored reason |
Idempotent by design (no-op) |
customer.created, customer.subscription.updated, charge.dispute.*, etc.).
Returning non-2xx for an event we don't care about forces Stripe to retry it forever. We log the event type
we ignored (audit trail) and 200 immediately. Listed event types are explicitly enabled in the Stripe Dashboard
webhook config.
Section 13 · International Strategy (PAYG §9)
Stripe Tax computes sales tax, VAT, and GST automatically at checkout for customers in the US (all 50 states), EU (all 27 countries), UK, Australia, Japan, Singapore, Canada, Switzerland, Norway, India (state-specific GST), South Korea, and 30+ additional jurisdictions. Tax compliance is Stripe's problem, not ours. iArchitron registers in jurisdictions per Stripe Tax's nexus dashboard recommendations.
Automatic computation at Stripe Checkout via automatic_tax: {enabled:true}. Stripe handles exemption certificates (B2B), reverse charge (intra-EU), digital-services categorisation, and remittance reporting.
Stripe Multi-Currency settles in 30+ presentment currencies. Customers see prices in their local currency at checkout. iArchitron settles to USD at Stripe's FX rate (transparent FX margin).
Stripe is PCI Level 1 certified. Card data never reaches our servers. The hosted Checkout page reduces our PCI scope to SAQ-A — the lightest tier.
Strong Customer Authentication (EU SCA / 3D Secure) handled automatically by Stripe Checkout. Required for EU + UK + India + select other jurisdictions.
SEPA Direct Debit, BACS, ACH, Bancontact, iDEAL, Sofort, Klarna, Apple/Google Pay, Link — enabled per region in the Stripe Dashboard.
Stripe's reporting API exposes settlement reports, tax reports, and a Sigma SQL surface for revenue recognition. iArchitron's internal /admin/billing-internal dashboards consume the local CreditTransaction ledger; Stripe Sigma is reserved for AR reconciliation.
Section 14 · Phased PSP Roadmap (PAYG §9.4)
Each PSP addition is approximately one new file + one new webhook route + one IAM secret reference. The credit ledger doesn't move. The customer-facing UI doesn't change. Triggers are revenue-mix driven, not engineering-driven — we add the PSP when the customer base or revenue mix justifies the operational cost.
| Phase | PSP | Why | Trigger to add | Engineering cost |
|---|---|---|---|---|
| v1 (live) | Stripe + Stripe Tax + Stripe Meters | ~85% of likely Arcitopsia ICP coverage; PCI Level 1; automatic tax | Launch | Shipped — PR #28-#33 |
| v1.5 | Razorpay | India PSP — INR-native, India-domiciled GST, UPI rails | India revenue > ~10% OR > 20 active India tenants | ~2 weeks |
| v2 | Chargebee (on top of Stripe) | Enterprise AR orchestration: PO-driven invoicing, NET-60/90 terms, multi-entity AR consolidation | ENTERPRISE + MULTINATIONAL + MISSION_CRITICAL revenue mix > ~30% of total | ~4–6 weeks |
| v3 | Paddle (merchant-of-record evaluation) | Paddle takes seller-of-record responsibility; skips US 50-state + EU 27-country + UK + AU + 40+ jurisdiction registrations. ~5% fee vs Stripe's ~3.5%. | Global expansion mandate + ops headcount unavailable for tax compliance work | ~3 weeks (or replace Stripe entirely) |
| v3+ | Region-specific: Adyen, dLocal, Flutterwave, Mercado Pago | On-demand per strategic customer ask | Per-customer | ~1–2 weeks each |
lib/billing/<psp>-credits.ts
+ a new webhook handler route + new env vars + a Stripe-tax-like tax provider plugin. The credit ledger
(credit-engine.ts, credit-service.ts, complexity-calculator.ts) doesn't
change. The customer-facing /admin/credits doesn't change. The internal
/admin/billing-internal gains an additional reconciliation surface per PSP. No schema migration.
Section 15 · Customer Surface — /admin/credits
The in-app dashboard for tenant users to manage their Intelligence Credits. PAYG §7.0 two-layer rule strictly enforced — only the customer-facing ledger view, never the internal telemetry. Stripe customer/payment-method IDs are deliberately excluded from the API response shape (verified in PR #31 hand-test).
Total available, included monthly (with next-reset date), purchased, last-30-days consumption, lifetime stats.
5-tile pack catalogue: Starter / Growth / Scale / Enterprise / Strategic Annual. Click "Buy" → Stripe Checkout redirect.
Newest-first CreditTransaction ledger with type-filter chips and "Load more" cursor pagination.
Auto-top-up: enable + threshold + pack + max-per-month. Low-balance alerts. Notification emails.
Section 16 · Internal Surface — /admin/billing-internal
hasAdminAccess. Architect FinanceThe internal dashboard for iArchitron ops + finance. The four APIs (margin-analytics, anomalies, stripe-reconciliation, finance-export) are all gated at the route level — non-admin authenticated callers get 403. The Phase 6 hand-test (PR #33) asserts this for every endpoint.
1–365 day timeframes. Totals + per-activity rollup + top-25 tenants by spend. Margin coloured (≥95% green, ≥80% amber, <80% red).
4 classes in one round trip: low-margin tenants, failed purchases, unresolved deficits, large refunds.
Completed / completed-without-Stripe-ID / abandoned-PENDING buckets. v1 ledger inspection only; v1.5 adds stripe.payouts.list round-trip.
Date range → CSV download. 10K row cap; columns: id, createdAt, tenantId, tenantSlug, tenantName, type, credits, balanceAfter, activityType, executionId, purchaseId, packRateUSD, usdEquivalent, manualCostBasis, isByollm, memo.
CredentialAccessLog (probe credentials), CreditTransaction (immutable ledger), and
WorkflowExecutionTelemetry (compute audit) together cover SOC 2 Common Criteria 8 (audit logging).
Section 17 · Anomaly Detection
The Anomalies tab queries four parallel signals in a single round trip and renders them on one page so the ops team triages them daily. Thresholds are constants in v1 (margin floor 80%, large-refund 1,000 credits); v2 makes them tenant-configurable.
| Class | Source | Threshold | What it signals |
|---|---|---|---|
| Low-margin tenants | WorkflowExecutionTelemetry aggregated per tenant |
< 80% gross margin over window | Likely undercharged customer (negotiated bad capture rate) OR runaway LLM cost — sales investigates |
| Failed purchases | CreditPackPurchase.status = FAILED in window |
Any failed purchase in the last N days | Stripe error reason needs investigation (card declined, 3DS failure, fraud rule, etc.) |
| Unresolved deficits | CreditDeficitAlert.resolvedAt IS NULL |
Any unresolved | Balance went negative after settle — should be very rare; reserve-worst-case prevents in normal operation |
| Large refunds | CreditTransaction.type = REFUND |
|credits| ≥ 1,000 | Customer-service ticket likely behind it; sales-finance acknowledges |
Section 18 · Worked Example — Full Lifecycle
Scenario: 200 tables, 50 stored procedures (10 require AI summarisation), 4 EA artefacts drafted per tech across 7 techs (subset). Tenant on MULTINATIONAL tier (×1.30) with 20% Scale-pack discount (globalCreditMultiplier = 0.80), non-BYOLLM, complexity enabled.
WorkflowCreditConfig)| Line item | Activity | Manual basis | Base credits |
|---|---|---|---|
| Probe Discovery Run | probe-discovery-run | $500 | 100 |
| Bulk Import 200 tables | bulk-import-per-100-records | $50 / 100 | 200 |
| AI Classification × 10 routines | ai-enrichment-per-record | $100 / record | 200 |
| Hierarchy Synthesis × 4 artefacts × subset of techs | probe-ea-artifact-draft | $250 / artefact | 200 |
| Σ baseCredits | 700 | ||
maxReserve = 700 × 3.0 × 1.30 × 0.80 = 2,184 credits // ≈ $1,747 at Scale-pack rate
| Dimension | Actual | Baseline | Normalised | Capped | × Weight |
|---|---|---|---|---|---|
child_count | 30 | 1 | 30 | 5.0 | 1.250 |
token_intensity (per 1K) | 18 | 5 | 3.6 | 3.6 | 0.792 |
context_size_kb (per 100KB) | 1.8 | 0.5 | 3.6 | 3.0 | 0.450 |
wall_clock_ms | 95K | 30K | 3.16 | 2.5 | 0.250 |
hierarchy_depth | 3 | 1 | 3.0 | 3.0 | 0.240 |
peak_concurrency | 4 | 1 | 4.0 | 2.0 | 0.120 |
model_tier (Sonnet) | 2 | 2 | 1.0 | 1.0 | 0.050 |
cache_miss_rate | 0.40 | 0.30 | 1.33 | 1.33 | 0.053 |
retry_count | 0 | 0 | 0 | 0 | 0.000 |
external_api_calls | 1 | 0 | (treated as 1) | 1.0 | 0.020 |
| Σ / Σweight = complexityScore | 3.225 | ||||
rawMultiplier = log₂(3.225 + 1) × 1.44 = log₂(4.225) × 1.44 = 2.99 complexityMultiplier = clamp(2.99, min=0.5, max=3.0) = 2.99
finalCredits = round(700 × 2.99 × 1.30 × 0.80) = round(2,177.4) = 2,177
creditsBilled = 2,177 internalCostUSD = $0.18 LLM + $0.04 compute + $0.02 storage = $0.24 usdEquivalent = 2,177 × $0.80/credit = $1,741.60 grossMarginPct = ($1,741.60 − $0.24) / $1,741.60 = 99.99%
releaseReservation(executionId) flips reservation to RELEASED_ON_FAILURE. Full 2,184
credits returned to balance. Zero credits consumed per PAYG §5.4. Internal telemetry still recorded
(compute consumed by the failed run is iArchitron's cost — captured in WorkflowExecutionTelemetry
for capacity planning).
Section 19 · v1 scope vs v1.5+ deferrals
@@unique(executionId, type) idempotency/admin/credits customer dashboard (4 tabs)/admin/billing-internal internal dashboard (4 tabs)invoice.paid webhook — v1.5Section 20 · Glossary
architecture-document, compliance-report, probe-discovery-run). Each has a row in WorkflowCreditConfig with manual cost basis + capture rate + base credits.TenantCreditContract. baseCredits = round(manualCostBasisUSD × captureRatePct).WorkflowCreditConfig.baseCredits.log₂(score+1) × 1.44 dampened, clamped to [contract.min, contract.max].TenantCreditContract.globalCreditMultiplier. Reflects pack tier (e.g. 0.80 for Scale-pack = 20% off; 0.65 for Enterprise-pack = 35% off).Tenant.byollmEnabled = true.CreditReservation with status HELD → SETTLED or RELEASED_ON_FAILURE.CreditTransaction(DEDUCTION) + flips reservation to SETTLED + returns unused to balance.CreditTransaction (no DEDUCTION). Zero credits consumed.CreditTransaction(TOPUP) with executionId = "purchase:" + purchaseId + type=TOPUP for idempotency.WorkflowExecutionTelemetry with compute units, tokens, LLM cost, gross margin %) from customer-facing credits (CreditTransaction). The two never cross.credit-engine.ts, credit-service.ts, complexity-calculator.ts) contains zero PSP-specific types/IDs/imports. Mechanically verified in the hand-test.@@unique(executionId, type) on CreditTransaction. Stripe delivers at-least-once; we accept that without double-crediting.WorkflowCreditConfig, WorkflowComplexityProfile, ComplexityFactorConfig, TenantCreditContract rows. Pricing committee can change values via seed-script re-run; no application redeploy.TenantCreditContract.flatPricingEnabled = true → complexity multiplier always returns 1.0. Predictable bill, no scope-based variability./admin/billing-internal Anomalies tab: low-margin tenant, failed purchase, unresolved deficit, large refund.CreditPackPurchase) against PSP payment records (Stripe payouts). v1: ledger inspection only. v1.5: stripe.payouts.list roundtrip + line-item match.Book a demo and we'll walk through the credit ledger, run a worked example, and show the customer + internal dashboards side by side.
by iArchitron Software Inc. · iarchitron.ai