v5.44.0 5 June 2026 Improvement Fix

The Billing Domain Joins the Kernel — Trial Rules in Source Control, Single-Writer Subscription State, Consulted Idempotency

Why This Matters

SpeyBooks went live with a billing surface that had never been through the kernel method: trial behaviour lived in deployed code rather than reviewed source, subscription status had five independent writers (two of them guessing), and a webhook delivered twice on launch night was processed twice. None of it touched a ledger, but money-adjacent code deserves the same discipline as money. This release puts the commercial rules themselves under the kernel’s governance: customer number two’s first hour is now governed entirely by code that was designed, reviewed, and proven before they arrive.


The Trial Model, Unified

  • One rule, one module, self-executing The first 50 customers receive a 90-day trial; thereafter 30 days. A customer is an organisation that has subscribed, counted from an immutable first-subscription record. When seat 50 fills, the rule returns 30 on its own: nothing to deploy, nothing to remember.

  • Seats only ever decrement Cancelling never reopens a founder seat, and a returning customer goes straight to billing rather than receiving a fresh trial. The cancel-and-resubscribe free-trial loop is closed by arithmetic: one first subscription per organisation, ever.

  • Grace is not the trial Registration grants a 7-day cardless grace window: time to reach Checkout. The trial proper starts at Checkout, where the card is validated (nothing due that day), and Stripe owns its clock from that moment. Trial lengths are constants; no wall-clock arithmetic determines a commercial term.

Single-Writer Subscription State

  • Stripe’s events own the status, verbatim Subscription status is now written only by Stripe’s own subscription lifecycle events, copying Stripe’s value exactly. Five other writers were removed, including two that guessed the resulting state and one that granted gate access to organisations that had never subscribed.

  • Honest vocabulary end to end The stored status speaks Stripe’s full vocabulary, the type system matches it, and a missing status now means exactly what it should: no subscription exists.

Idempotency, Proven Against the Real Thing

  • The gate is consulted Every webhook event is processed inside one transaction with a uniqueness gate at the front. Duplicate deliveries of processed events are skipped; deliveries of failed events are reprocessed; processing failures return a non-success response so Stripe’s retry becomes the recovery mechanism, with the failure trail preserved for monitoring. The motivating evidence was a checkout event delivered twice on launch night and processed twice; that class of defect is closed.

Disclosure and Pricing Truth

  • Displayed prices corrected to live prices The plan listing now matches what Stripe charges: GBP 15.00 and GBP 25.00 monthly. Display-only defect; charging always read Stripe’s own price object.

  • Card-network compliance scoped with a date A pre-charge reminder at least seven days before a trial’s first charge is required by the card networks for card-upfront trials and is tracked with a hard deadline ahead of the first customer charge.


Threat Closure

ThreatStatus BeforeStatus AfterEnforcement Layer
Trial terms diverging from the stated commercial offerOpenClosed (Class M)Single rule module reading immutable subscription history
Repeat free trials via cancel-and-resubscribeOpenClosed (Class M)Immutable first-subscription record, database trigger
Subscription status drift from competing writersOpenClosed (Class M)Single writer set, Stripe values verbatim
Duplicate webhook deliveries processed twiceOpenClosed (Class M)Transactional uniqueness gate, consulted
Never-subscribed organisations passing the subscription gateOpenClosed (Class M)Status nulled for never-subscribed organisations; gate fails closed

Kernel Closure Statement

Axiom: AX-BIL-001 — First-subscription history immutability

Status: CLOSED as of SpeyBooks v5.44.0

Enforcement layers verified:
  Database layer     — immutability trigger; write-once guard
  Application layer  — set-once write pattern in the subscription
                       event handler

CI verification:
  Migration determinism (AX-MIG-002)      — PASS
  Migration self-registration (AX-MIG-003) — PASS
  Axiom registry coverage (AX-KERN-001)   — PASS (registry v2.16)

Residual risk:
  Concurrent first conversions at the final founder seat can
  over-grant founder terms (never under-grant); bounded by checkout
  sessions in flight. Accepted and recorded in the design document.

Next dependent axiom:
  None — the billing domain's first registry entry.

Proof anchor: enforced by a database trigger plus application write-once guard — migration_098.


Operational Impact

  • Zero status guesses: every stored subscription status is a value Stripe sent
  • Duplicate webhook deliveries skip in both concurrent orderings
  • Failed webhook processing now self-recovers via Stripe retry instead of waiting for a manual resend
  • The founder-seat count starts at its true value: one
  • Nine pre-existing never-subscribed organisations lost gate access they should never have had

Files Changed

Backend: billing routes, registration, Stripe webhook handler and its documentation siblings, admin organisation endpoints, plan configuration, one migration with dry-run twin.

Frontend: None this release (trial banner and admin vocabulary labels tracked).