Testing

Quality by design: acceptance criteria and invariants exist before the code. The API is where everything gets proven.

The API is the test boundary

All business logic sits behind the API. That single rule makes the whole system testable without a browser, and gives agents a fast feedback loop.

The API is the test boundary: business tests, invariants, permission tests and contract tests all run against the API. Only a few UI tests run against the React frontend. React UIdisplay and input only API: the test boundary Everything else: core, extensions, processes, data all business logic lives here, so every business test can reach it through the API Scenario testsorder to cash, dropship Invariantsafter every scenario Permission testsevery role and scope Contract testsno breaking changes Unit tests inside: parser, compiler, expressions, functionsIntegration tests against real PostgreSQL in containers
Fig. 8 Because the frontend holds no logic, end to end means API to database. A handful of browser tests cover the UI itself.

Test levels

LevelWhatHow
UnitParser, compiler, validator, expressionsVitest
Function contractsEvery function against its schema and idempotencyContract tests with fake SMTP and PDF
DefinitionsThe application definition loads and all references resolvecore validate in CI
ProcessesDeclarative: given, when, expected steps, records and outputsRunner in test mode, step by step
API integrationGeneric endpoints against real PostgreSQLTestcontainers
PermissionsEvery role and scope: allowed and denied, including partnersAPI tests with different accounts
ContractNo unintended breaking changesOpenAPI diff as a CI gate
Invariants and end to endReality map level 5 scenariosBlack box against the API
FrontendDisplay and input onlyA few Playwright tests

Invariants test the whole

Modules can pass every test while the system as a whole drifts. Invariants are business rules that must hold at any time, checked after every scenario.

  • Stock movements and stock value match the documents
  • Open items equal invoices minus payments minus credits
  • No negative available stock, ever
  • A partner never sees another partner's data
// Must hold after every scenario, for every customer.
export const openItemsBalance = invariant("open items equal invoices minus payments minus credits",
  async (api) => {
    for (const c of await api.query("customer")) {
      const invoiced = await api.sum("invoice", "total", { customer: c.id })
      const paid     = await api.sum("payment", "amount", { customer: c.id })
      const credited = await api.sum("creditNote", "total", { customer: c.id })
      const open     = await api.sum("openItem", "amount", { customer: c.id })
      expect(open).toEqualMoney(invoiced.minus(paid).minus(credited))
    }
  })

What gets measured

The same metrics for every framework and model. A baseline run without any framework shows what the frameworks actually add.

Acceptance score

weighted share of passing acceptance tests

Reality map

status per check, five levels

Prompts and interventions

every time I step in, with a reason

Self correction rate

errors agents find and fix on their own

Time

wall clock and my active time

Cost

tokens and subscriptions

Code quality

lint, coverage, static analysis, security scan

Changeability

prompts needed for the same change request

RunWorkflowBuilderReviewerStatus
1Flow-NextClaudeother model familynext
0No framework, baselinesame modelnoneplanned
2GitHub Spec Kittbdtbdplanned
3SuperpowersClaudetbdplanned
4BMAD Methodtbdtbdplanned
CROpenSpec, for the change request testtbdtbdplanned

Fair play: same specs, same tests, same time box, tool versions and model IDs recorded, at least one run repeated to show variance.