Loom

Type to search across the guides, specification and decision records.

↑↓ navigate ↵ open esc close Pagefind

Specification

Loom Architectural Fitness Functions

Status: Normative. These are the kernel’s design invariants, made executable — every PR proves them, so the architecture is enforced continuously rather than by reviewer memory. They complement the Kernel Specification (the what) by pinning that it stays true. Gated in CI and by pnpm fitness.

How they run

  • pnpm fitness (root) = build → typecheck → lint → test. The ABI/schema pins, SPI conformance kits, replay/golden checks, and the G2 ratio gate all run inside pnpm test as ordinary tests, so there is one command and one CI gate.
  • CI (.github/workflows/ci.yml) runs build, typecheck, lint, test on every PR.
  • Absolute benchmark numbers (pnpm bench, benchmarks.md) stay advisory — they are machine-relative, so they are not a hard gate; the machine-stable checks below are.

The invariants

#Fitness functionWhat it provesWhere it is enforced
1Replay unchangedFolding a stored log reconstructs identical state; a code change can’t silently re-fold historical logs differently.@loom/eval (replayAsFixture/diffRuns), @loom/deploy-sim (simulateDeploy), golden fixtures (captureGolden), wire fold-vectors (packages/wire-abi/src/wire-abi.test.ts).
2ABI unchangedThe K1 syscall surface can’t drift silently (add = MINOR, remove = MAJOR).packages/workflow-runtime/src/abi.test.ts (pins KERNEL_SYSCALLS + version).
3Event schema compatibleThe K5 event-type set can’t change without bumping the version + pin; stored logs stay foldable.packages/replay-vm/src/eventSchema.test.ts (pins knownEventTypes() + EVENT_SCHEMA_VERSION); @loom/verify replay-safety rule for cross-version resume.
4Zero-overhead when disabled (G2)An unwired optional capability adds no work.Behavioural: disabled-path tests (e.g. verify mode=off never builds input, verify.test.ts; scheduler Infinity fast-path). Relative-ratio canary: packages/verify/src/g2ratio.perf.test.ts (off ≫ warn) — in the PERF suite (pnpm test:perf, run locally and in the manual flake hunt), because a timing ratio never gates a merge (see the timing row below); the behavioural tests are the blocking guard.
5Benchmarks passedPerf is measured, not assumed; key hot paths don’t regress.Timing ratios and budgets live in the PERF suite (*.perf.test.ts, pnpm test:perf): run locally and by the manual flake hunt (.github/workflows/flake-hunt.yml), never gating a merge. The blocking suite keeps only deterministic evidence or far-from-noise ceilings (e.g. foldScale.test.ts’s quadratic guard). Absolute baselines in benchmarks.md are advisory (pnpm bench).
6Conformance passedA storage/dispatch SPI implementation honours its contract.run<Spi>Conformance kits (@loom/conformance). Each implementation runs the kit against itself — in-memory (packages/conformance/src/index.test.ts, which also pins the per-SPI version constants), SQLite (plugins/sqlite/src/conformance.test.ts), and the durable-state/read-model/content/crypto/worker-runtime-local packages in kind. loom plugin check enforces that a 3P plugin wires a kit too.
7Compiler validation passedFlows are structurally valid, non-determinism is caught pre-dispatch, and what a flow reaches is checked past its declared steps.@loom/compiler (analyzeNonDeterminism, analyzeLoopIdentity, analyzeRunnerReach — all three emitted into loom-manifest.json and auto-injected), @loom/verify (loom verify / loom doctor), validateFlowGraph (@loom/app), loom check.
+Maturity is self-checkingA capability’s badge can’t claim evidence it lacks.@loom/spec spec.test.ts (asserts each capability’s version constant / conformance test / bench actually exists).
+Maturity is completeShipped work can’t skip declaring a badge — the converse of the row above. Every accepted ADR is a declared capability or is explicitly listed as grading nothing.@loom/spec spec.test.ts (capability coverage). Added after ADRs 0074–0077 landed in one day with no entry, making four shipped capabilities invisible in the generated docs.
+A seam cannot ship undocumentedAdding an SPI/plugin seam can’t leave the docs that enumerate seams behind. The mapping is derived from the seam registry itself, and the assertion matches a TABLE ROW rather than a mention anywhere in the file.tools/scaffold/src/seamDocs.test.ts (co-located with SEAM_KEYS). Added after the policy seam shipped with 5 code sites updated and 5 doc sites not — and immediately found an older instance, llm, that a manual audit of the same file had walked past.
+A rule cannot ship undocumentedAdding a @loom/verify rule can’t leave the two documents that enumerate rules behind. Derived from RULE_ORDER; matches a table row / list bullet, and cross-checks the ADR’s stated rule count.packages/verify/src/ruleDocs.test.ts (co-located with RULE_ORDER). Added on the fourth instance of the class: the README’s table was missing two rules while ADR 0028’s amendment still called the eighth rule “a seventh”.
+A ctx.* call cannot ship without its durabilityAdding to ExecutionContext can’t leave the canonical usage table behind, and every row must say whether the call puts a fact in the log. Both directions: every member has a row, every row names a real member.packages/workflow-runtime/src/contextDocs.test.ts (co-located with ExecutionContext). Building it found the larger drift it was written to prevent — ctx.execute, ctx.decide and ctx.negotiateBudget appeared nowhere in usage.md, so the canonical table documented the Loom 1.x surface.
+The root project graph is completetsconfig.json’s references ARE the graph tsc --build walks, so a package missing from it is not slower — it is silently unchecked.packages/spec/src/projectGraph.test.ts. Added after packages/isolate and plugins/crypto were found producing no output from a root build at all, with three more (two of them kernel packages) built only transitively.
+Reference docs don’t cite line numbersdocs/loom-2.0/* describes the current system; source line citations rot on the next edit, silently and authoritatively. Cite the file, let the prose name the symbol.packages/spec/src/docCitations.test.ts. Added after an audit found 40 citations landing on a blank line, a closing brace or a comment terminator — five of them made wrong by a single afternoon’s refactor. ADRs and requirements docs are excluded: they are dated records, and “fixing” their line numbers would falsify them.
+The minimal kernel stays minimalNo kernel or SPI package may reach a plugins/* adapter, transitively or otherwise — the invariant that keeps adapters swappable.packages/spec/src/layering.test.ts (ADR 0043), which walks the real workspace dependency graph rather than a declared list.
+Source carries no raw control bytesA literal control character in source is invisible in review and changes behaviour — a NUL written raw into a composite key separator is the case that prompted it.packages/spec/src/sourceHygiene.test.ts, sweeping packages/plugins/tools/docs.
+Every workspace package has a section in the API referencedocs/api-reference.org opens by promising each package a section, which is a claim about the whole workspace and wrong the moment a package ships without one. Enumerates the workspace and asserts a HEADING, not a mention.packages/spec/src/apiReferenceDocs.test.ts. Added after an audit found 16 of 61 packages with no section at all — including @loom/knowledge, @loom/verify and @loom/plugin-read-model, so a reader could not tell “undocumented” from “does not exist”.
+Documented config examples use options that existDoc snippets are never compiled, so a defineConfig example can name a flat key that throws or a storage kind that silently loses data. Parses every such call in a current doc’s code block and checks its option names against the source.packages/spec/src/docConfigExamples.test.ts. Added after an audit found a sqlite storage kind, which does not exist, taught in four docs.
+A doc that claims the whole ADR corpus has it“The full decision history (0001–NNNN)” is a completeness claim, so it is wrong the moment a record lands after it — and wrong in the way hardest to notice, since the sentence still reads as authoritative. Both halves are asserted: the range ends at the newest ADR, and the record-by-record walk reaches it.packages/spec/src/adrIndexDocs.test.ts. Added on the third surface of one class — the CHANGELOG and the capability registry each already had a guard, and this list, maintained by remembering, had drifted four records behind.
+A blocking test never gates on wall-clock noiseCI runs every suite concurrently on a shared runner, so a timing RATIO or a tight ceiling on a millisecond-scale operation measures the runner’s load as much as the code. Outside *.perf.test.ts: no performance.now(), no ratio of timings, and any elapsed Date.now() upper bound carries a // timing-bound: justification and is ≥ 5 s. Prefer deterministic evidence (call counts, rows scanned, round trips) or no bound at all when the slow path is a hang.packages/spec/src/testTiming.test.ts, sweeping packages/plugins/tools. Added after main went red four times in two days on foldScale O(n) ratios (10.7×–26.9× against 10, nothing regressed); its first run found two more tight bounds an audit had missed. Paired with .github/workflows/flake-hunt.yml (manual only — it costs N× CI minutes), which runs the full suite N times to surface a suspected flake.
+A fitness function cannot ship undocumentedThis table is normative: a list of enforced invariants that omits enforced invariants reads as complete while being wrong. Registry → file exists → named here.packages/spec/src/fitnessDocs.test.ts. Added after two guards landed in one session with neither reaching this table — and on its first run it found two MORE that had been missing for longer (layering, source hygiene).

Adding a capability

An additive capability (a K/T ring, an SPI, a runtime) MUST, before merge:

  1. Preserve replay (#1) and, if it touches the schema/ABI, bump the version and the pin (#2/#3).
  2. Be opt-in with a disabled-path test and — for a hot path — a ratio canary (#4/#5).
  3. If it’s an SPI, ship a conformance kit and a version constant (#6), and register a capability in @loom/spec at the level its evidence supports (the maturity check, +).
  4. Surface through an existing idiom (one defineConfig key / ctx.* method / flow$/tool$ option) — the DX bar.

Source: docs/spec/fitness-functions.md in the loom repository.