Chapter 05 — Twenty-Two Requirements, Part Two
The first eleven described what the DSL is. The next eleven describe what it does over time — under random input, under rename, under audit, under replay, under parallel hands — and what the package is trying to be.
Completing the enumeration
Chapter 04 walked through the first eleven Requirements — the ones that declare the DSL's surface: bootstrap, discoverability, editor integration, dog-food, round-trip, live feedback, low-friction ACs, orthogonal toggling, source-of-truth, TUI modernity, visual traceability. Those are the REQs you read on arrival. They describe the shape of the object.
This chapter completes the enumeration. The eleven REQs here divide into three affinity clusters, and the rest of the chapter is organised around them.
Structural guarantees — three REQs that describe how the DSL withstands the passage of time and the friction of random input.
REQ-PROPERTY-INVARIANTS— the pure functions must not crash on adversarial input.REQ-REFACTOR-SAFE— renames must propagate atomically across every linked artefact.REQ-VERSIONED-TRACEABILITY— every spec node carries an immutable version and content hash.
Workflow and orchestration — four REQs that describe how the DSL runs processes across artefacts.
REQ-SCENARIO-DRIVEN-WORKFLOW— a declarative scenario drives the end-to-end generation.REQ-MULTI-FSM-ORCHESTRATION— three coordinated typed-FSMs drive scenario playback.REQ-SCAFFOLD-EXTENSIBILITY— test scaffolding is pluggable via a registry.REQ-AUDIT-TRAIL-HOOKS— HistoryEntry reserves typed slots for future signed sign-off.
Meta-ambition — four REQs that frame what the package is trying to be, as distinct from what it currently does.
REQ-AI-AS-IMPLEMENTER-ADAPTER— AI assistance is a pluggable adapter, never a prerequisite.REQ-VOCAB-INDUSTRY-ALIGNED— traceability verbs match SysML / ISO-29148 / BDD practice.REQ-PARALLEL-DELIVERABLE— work partitions into at least four conflict-free parallel streams.REQ-DSL-COMPLETE— the package that is named requirements must track Requirements.
The first cluster is about robustness. The second is about process. The third is about identity. Together with the eleven from chapter 04, they compose the twenty-two-REQ claim the DSL advances about itself.
One note before the enumeration begins. Of the three clusters, the meta-ambition cluster is where the strongest claims live — and the strongest claims are the most contestable. REQ-DSL-COMPLETE, the last entry in this chapter and the keystone of the set, is itself a claim that these twenty-two REQs together form a complete requirements-tracking DSL, modulo the open questions catalogued in chapter 22. The closing section of this chapter returns to that claim and asks what would falsify it.
The reading schema, recalled
Each subsection below follows the same schema as chapter 04. A fragment of it, for the reader arriving here directly.
Source quote. A compact rendering of the REQ class's most load-bearing fields — id, title, the statement (EARS-patterned trigger plus response), the rationale.claim sentence, one representative fitCriterion (chosen for the one that most clearly shows how the REQ is measured), and the risk.ifNotMet projection.
Paraphrase. One or two sentences restating the REQ in plain prose, stripped of EARS scaffolding. This is the sentence a reader would use to explain the REQ to a colleague over coffee.
Why it exists. One or two paragraphs that place the REQ inside the series narrative. Why this specific concern is separate enough from neighbouring REQs to deserve its own class; what failure mode the absence of the REQ would produce; what the relationship is to the existing traceability graph.
How it's satisfied. A forward link into chapter 06 — Twenty-Five Features Mapped, naming the Feature classes whose @Satisfies(...) argument list includes this REQ. If the REQ is still at status Draft and has no satisfier yet, the subsection explicitly says so.
Running-example connection. One sentence per REQ that locates the running example — FEATURE-TRACE-EXPLORER-TUI — with respect to it. The TUI satisfies three REQs explicitly: REQ-DISCOVERABLE-TRACEABILITY (chapter 04), REQ-DOG-FOOD (chapter 04), and REQ-PARALLEL-DELIVERABLE (this chapter). For the remaining ten REQs in this half the subsection acknowledges the non-satisfaction and names, where possible, a different Feature from chapter 06 that does satisfy the REQ.
With the schema recalled, the enumeration begins.
REQ-PROPERTY-INVARIANTS — Pure functions satisfy algebraic invariants under property + fuzzy testing
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-property-invariants.ts.
id:REQ-PROPERTY-INVARIANTStitle: Pure functions satisfy algebraic invariants under property + fuzzy testingpriority: High ·status: Approved ·kind: NonFunctionalstatement.pattern:ubiquitous— verify every smart constructor, every id-derivation helper, every generator, and every validator against fast-check property tests so that random and adversarial inputs cannot crash the package.rationale.claim: Industrial adopters cannot ship a typed DSL whose pure functions might throw on unanticipated inputs; fuzzing surfaces those classes before customers do.fitCriterion[0]:unit-test— every smart constructor satisfies its accept/reject regex property under random ASCII strings, bound tosmartConstructorsAcceptIfAndOnlyIfRegexMatches.risk.ifNotMet: Adversarial inputs surface in customer environments; un-tested input classes corrupt specs or crash builds.
Paraphrase. Every pure function the package exposes — the smart constructors for branded primitives (RequirementId, FeatureId, AcName, IsoDate, Sentence, Percentage), the id-derivation helpers (kebabOfId, featureClassName, requirementClassName), the JSON serialiser (specToJson), the runtime validator (validateSpec), the diff engine (diffSpecToDisk) — must be tested against random inputs, not just fixture inputs, under a property-testing framework. The contract is algebraic: the constructor accepts if and only if the regex matches; the helpers are idempotent and reversible; the serialiser is deterministic and round-trips; the validator never crashes; the diff of equal content yields all-unchanged. Sixteen invariants, two hundred runs each.
Why it exists. There is a kind of package that passes hundreds of hand-picked unit tests and then crashes the first time a customer feeds it a unicode zero-width space or a string that happens to start with a hyphen. Fuzzing is the cheap, boring, well-understood answer to that class of failure. The QuickCheck tradition — Claessen and Hughes 2000, ported to twenty-odd languages since — has been the safety-critical bar in ML, Coq, and Haskell for a generation. fast-check brings that bar to TypeScript. This REQ makes the bar load-bearing: the package does not get to claim industrial readiness without property invariants on every pure function that sits on the public surface.
The subtle move is that this REQ is not a generic coverage REQ — coverage is not mentioned in the statement at all, and in fact coverage is split across other REQs (REQ-DOG-FOOD for the testing style, the vitest unit gates for the numeric target). What this REQ insists on is a specific kind of test: one that enumerates the input space, not one that checks a handful of handcrafted points. The distinction matters because a package can have 100% line coverage and still crash on the first adversarial byte it has never seen.
How it's satisfied. Several Feature classes in chapter 06 satisfy this REQ: FEATURE-PROPERTY-FUZZY-TESTS is the primary, with one AC per fast-check invariant; FEATURE-COMPLIANCE-REPORT satisfies it indirectly through the validateSpec invariants bound in its fitCriteria. The binds arrays on the REQ's fitCriteria name five test methods (smartConstructorsAcceptIfAndOnlyIfRegexMatches, idHelpersAreIdempotentAndReversible, specToJsonIsDeterministicAndRoundtrips, validateSpecNeverCrashesOnRandomInput, diffOfEqualContentYieldsAllUnchanged) — each of which is an @Verifies method on the corresponding @FeatureTest class, each of which wraps a fc.property or fc.assert call. The compliance scanner follows the binds back to the method and confirms it exists on a @FeatureTest-decorated class.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI renderer is not a pure function; its ACs concern terminal I/O, keyboard input, and graph traversal, none of which lend themselves to a fast-check invariant. The property-invariants bar is held by upstream Features that sit below the TUI in the dependency tree.
REQ-REFACTOR-SAFE — Identifier renames must propagate atomically across all linked artefacts
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-refactor-safe.ts.
id:REQ-REFACTOR-SAFEtitle: Identifier renames must propagate atomically across all linked artefactspriority: Medium ·status: Draft ·kind: NonFunctionalstatement.pattern:event-driven— when a user runsrequirements feature rename <old> <new>orrequirements requirement rename <old> <new>, rename the class, the file, the spec.json, every@Verifies<OldFeature>()call-site, and every@Satisfies(Old)reference atomically in a single commit-ready diff, or refuse if any site would be ambiguous.rationale.claim: Renaming a Feature or AC today is scary because references live in N test files and M Requirement classes; a codemod removes the fear, keeps vocabulary coherent, and lowers the cost of naming mistakes.fitCriterion[0]:unit-test— feature rename updates class name, file name, spec.json, and every@Verifies<T>site, bound tofeatureRenameUpdatesAllReferences.risk.ifNotMet: Users avoid renames; names rot; vocabulary drifts across generations of contributors; the DSL accumulates frozen mistakes.
Paraphrase. When a user renames a Feature or a Requirement, the package must do the whole rename — the class name, the source file name, the generated spec.json, every call site in every test file that references the class through @Verifies<T> or @Satisfies(T) — in a single commit-ready diff. If any reference would become ambiguous under the rename, the tool refuses and explains why. Dry-run is the default.
Why it exists. The invisible cost of a naming DSL is the cost of changing a name. When a rename requires opening thirty files and doing thirty search-and-replaces, the rename stops happening. Contributors route around the naming they inherit. They stack the wrong name under a comment (historical note: this is actually X now). Over a year, the glossary drifts out of sync with the code. The project acquires a set of fossil names no one dares disturb.
The remedy is a codemod. Every mature TypeScript project eventually ships one — ts-morph, jscodeshift, morph, rope (Python) — because cross-file rename is precisely the class of refactor that gets skipped without tooling. The Liu / Feldthaus / Møller 2012 study cited in the rationale evidence is one of the empirical papers that makes the case: without atomic rename tooling, codebases accumulate refactor debt at a measurable rate.
This REQ is Draft because the rename codemod is not yet implemented. The statement and the fitCriteria are Phase 0 artefacts: they freeze the contract the codemod will honour, so that when FEATURE-RENAME-CODEMOD is implemented in a later milestone, the test harness is already there.
How it's satisfied. Not yet. The REQ is Draft; the Feature that will satisfy it (FEATURE-RENAME-CODEMOD, enumerated in chapter 06 as a roadmap Feature with enabled = false) is not yet implemented. The binds arrays reference method names — featureRenameUpdatesAllReferences, featureRenameRefusesConflict, requirementRenameUpdatesSatisfiesArgs — that will live on the test class when the codemod ships. For now the REQ is a contract waiting for a satisfier. Chapter 22 (the roadmap) revisits this state.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI is a read-only view over the traceability graph; it does not mutate source files. The rename codemod, when it lands, will be a disjoint Feature with its own test class.
REQ-VERSIONED-TRACEABILITY — Every Requirement / Feature / AC carries an immutable version + content hash
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-versioned-traceability.ts.
id:REQ-VERSIONED-TRACEABILITYtitle: Every Requirement / Feature / AC carries an immutable version + content hash so bindings pin a specific revisionpriority: High ·status: Draft ·kind: NonFunctionalstatement.pattern:ubiquitous— compute aVersionInfo(monotonic integer + deterministic content-hash + since-date) for every Requirement, Feature, and AC, and failcompliance --strictwhen a Feature pins a prior version of a Requirement whose current version differs.rationale.claim: Without immutable version pins on the traceability graph, regulated customers cannot prove that a specific release shipped against a specific revision of the Requirement — an audit trail built on mutable state is not an audit trail.fitCriterion[0]:unit-test— versionInfo computed deterministically from spec content, bound toversionInfoComputesContentHashDeterministicallyandversionInfoComputesMonotonicInteger.risk.ifNotMet: Regulated customers (pharma / safety / finance) reject the DSL outright because the traceability artefact does not carry immutable version semantics. Therisk.levelisCritical.
Paraphrase. Every spec node — Requirement, Feature, AC — carries a VersionInfo: a monotonic integer that increments on every substantive edit, a deterministic hash of the spec's normalised content, and the ISO date the version was cut. When a Feature declares @Satisfies(REQ-X) and REQ-X later changes, compliance --strict fails with a clear diagnostic — the Feature is pinning a stale Requirement version; either re-audit the Feature or update the pin.
Why it exists. This is the REQ that separates a traceability artefact from an audit artefact. A mutable traceability graph is fine for a product team iterating: the Feature says it satisfies REQ-X, the REQ drifts, the Feature tags along, the graph updates silently, no one is the worse. A regulated environment cannot work that way. When a release of the package ships, the auditor wants to know: this release, dated this day, was certified against this specific revision of the Requirement, identified by this hash. If the Requirement changes afterwards, the older release's traceability claim must remain intact — frozen at the version it pinned.
The precedent is git. Git does not have mutable references to mutable content; every commit is a content-addressed snapshot. The content-hash pattern is how audit survives time. This REQ brings the pattern to the traceability graph. The 21 CFR Part 11 and IEC 61508 citations in the rationale are the regulatory weight; they are the standards that require, in their text, an immutable audit trail for electronic records and safety-critical systems.
The REQ is Draft because the VersionInfo field is not yet added to every base class, and the content-hash normalisation function is not yet specified. The key design decision — called out in the rationale.assumptions field — is that the content-hash must normalise formatting so that cosmetic edits (whitespace, import order) do not bump versions. The fitCriterion's inspection.checklist calls out three sub-requirements the normalisation must preserve: history itself is excluded from the hash; schema-version is excluded; supersedes links chain semantic history across renames.
How it's satisfied. Not yet. FEATURE-VERSION-INFO (a roadmap Feature in chapter 06, status Draft) will carry the version computation. FEATURE-COMPLIANCE-REPORT will grow a diagnostic for unpinned-drift. Both Features declare this REQ in their @Satisfies(...) list as a forward reference; the binds arrays on the fitCriteria name the test methods (versionInfoComputesContentHashDeterministically, versionInfoComputesMonotonicInteger, complianceStrictFailsOnUnpinnedReqDrift, diffCoreDetectsVersionPinMismatch) that will verify them once the feature ships.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ directly, but it is a downstream consumer of VersionInfo: once versioning lands, the TUI will display the version number and hash prefix of each node it renders. The REQ sits below the TUI in the dependency tree rather than orthogonal to it.
REQ-SCENARIO-DRIVEN-WORKFLOW — A declarative scenario file drives end-to-end generation
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-scenario-driven-workflow.ts.
id:REQ-SCENARIO-DRIVEN-WORKFLOWtitle: A single declarative scenario file shall drive the end-to-end generation of a typed Requirements projectpriority: High ·status: Draft ·kind: Functionalstatement.pattern:event-driven— whenrequirements scenario play <file>is invoked, the player shall load, validate, and replay the scenario non-interactively — producing requirements/, features/, scaffolded tests, and (optionally) AC implementations, with no manual file editing.rationale.claim: Demonstrations, onboarding workshops, and regression runs all need the same input → output pipeline; a scenario file freezes that pipeline into a reviewable artifact.fitCriterion[0]:unit-test— scenario play produces a valid four-tier project (Requirements, Features, ACs, scaffolded tests), bound toscenarioPlayProducesFourTierProjectandscenarioPlayRunsComplianceStrictAtEnd.risk.ifNotMet: Demos and onboarding remain hand-driven; reproducibility collapses; regressions escape.
Paraphrase. A scenario is a JSON file that names a set of Requirements, a set of Features with their ACs, and the satisfies-edges between them. When fed to requirements scenario play, the file produces — non-interactively, byte-deterministically, without any manual editing — a compiling four-tier project: requirements/ directory, features/ directory, scaffolded test classes under test/ with @FeatureTest and @Verifies decorators, and (optionally, under --ai) filled-in AC bodies. At the end of the replay, compliance --strict passes.
Why it exists. A wizard is the user-facing entry point — low-friction, interactive, forgiving. A scenario is the replayable form of the same operation — reviewable, auditable, diffable. The two are dual: the wizard is for the person who does not know what they want yet; the scenario is for the person who wants to re-run last week's result exactly. Onboarding workshops, regression runs, and demonstrations all need the scenario form, because all three need the output to be byte-identical across runs.
The REQ carries a specific fit criterion that is worth quoting in full: Replay is byte-deterministic under a fixed --seed. Determinism is the hinge. Without a --seed parameter and a scenario player that routes every non-deterministic input (current date, random UUIDs, ordering of iteration over hash maps) through the seed, the output drifts and the golden-master test at the end collapses. REQ-MULTI-FSM-ORCHESTRATION (the next REQ below) is the other half of this claim: the FSMs that drive the player must themselves be deterministic, or the scenario cannot be.
How it's satisfied. FEATURE-SCENARIO-PLAY (enumerated in chapter 06) is the primary satisfier, with ACs covering scenario schema validation (via ajv and scenario.schema.json), forward-reference cross-validation (a scenario can mention satisfiedByFeatureIds before the Feature is created in the same scenario — the validator handles that), and the byte-identical-under-seed golden-master test. FEATURE-SCENARIO-SCHEMA supplies the JSON Schema. FEATURE-SCENARIO-GOLDEN-MASTER is the Feature whose single AC is the golden-master diff.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI is a viewer over a project that already exists, not a generator of projects. The scenario player produces the corpus the TUI explores; the two Features sit on opposite ends of the pipeline.
REQ-MULTI-FSM-ORCHESTRATION — Three coordinated typed-FSMs drive scenario playback with full determinism
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-multi-fsm-orchestration.ts.
id:REQ-MULTI-FSM-ORCHESTRATIONtitle: Three coordinated typed-FSMs shall drive scenario playback with full determinismpriority: High ·status: Draft ·kind: NonFunctionalstatement.pattern:ubiquitous— orchestrate scenario playback with three coordinated FSMs (ProjectLifecycle, PerFeature, PerAc) whose composition is declared via@FiniteStateMachinedecorators and whose only coupling is typed events.rationale.claim: A monolithic playback loop is untestable; three small FSMs with a clean event contract are individually unit-testable and replayable from a recorded event log.fitCriterion[0]:unit-test— every declared transition of the 3 FSMs is exercised by at least one unit test, bound toeveryProjectLifecycleTransitionIsTested,everyPerFeatureTransitionIsTested,everyPerAcTransitionIsTested.risk.ifNotMet: Non-deterministic playback breaks golden-master tests; demos drift; state is uninspectable.
Paraphrase. Scenario playback is not one state machine; it is three, composed. ProjectLifecycle manages the whole run (load, validate, generate, compile, verify, report). PerFeature runs once per Feature in the scenario (create-dir, write-class, scaffold-tests, compile-check). PerAc runs once per AC within a Feature (stub, optionally implement-via-AI, verify). The three share no mutable state; they communicate only through typed events. Every declared transition on every one of the three FSMs is exercised by at least one test. A greppable quality gate checks the scenario code for new Date() or Math.random() and refuses any non-test match, because those are the two easiest sources of non-determinism to leak into a run.
Why it exists. A scenario player is a rewrite of a shell script: load files, call tools, check results, report. The rewrite buys nothing unless the new form is more testable than the old one. A monolithic playback loop has three typical failure modes: the state is a union of local variables that no test can inspect in isolation; the transitions are implicit (the next branch of the loop is the next state, but the state has no name); determinism is a runtime property of the machine the test happens to run on.
Three small FSMs with a typed event contract fix all three. Each machine's state type is a finite enum; each transition is a named event with a typed payload. Tests can construct any state directly and assert on any transition without running the whole player. A recorded event log is a replay: feed the same events in the same order, get the same output byte-for-byte.
The rationale.evidence names typed-fsm (the sibling package in the monorepo) as the precedent. typed-fsm is already the package-wide answer for orchestration state; this REQ makes the answer mandatory for the scenario player.
How it's satisfied. FEATURE-SCENARIO-PLAY satisfies this REQ jointly with REQ-SCENARIO-DRIVEN-WORKFLOW. Its implementation in src/cli/scenario/ declares three @FiniteStateMachine(...) classes — ProjectLifecycleFsm, PerFeatureFsm, PerAcFsm. The fit criterion's inspection checklist enforces the no-mutable-state rule: No file in src/cli/scenario/ imports mutable state from another FSM module. The rg quality gate enforces the determinism rule. The REQ is Draft because the three FSMs are not yet wired up — but the contract is frozen and the test methods named.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI has its own FSM (a single one — the navigation state of the interactive viewer) and does not satisfy this REQ. The TUI's FSM is covered by REQ-TUI-MODERN (chapter 04). The three FSMs at stake here are scenario-player internals.
REQ-SCAFFOLD-EXTENSIBILITY — Test scaffolding covers every TestLevel via a pluggable registry
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-scaffold-extensibility.ts.
id:REQ-SCAFFOLD-EXTENSIBILITYtitle: Test scaffolding must cover every TestLevel (Unit, Functional, E2E, A11y, Visual, Performance) via a pluggable registrypriority: High ·status: Approved ·kind: NonFunctionalstatement.pattern:ubiquitous— provide one TestScaffolder per TestLevel via a registry that projects can extend by registering custom scaffolders (e.g. load-test, security-scan) without forking the package.rationale.claim: Hard-coding onlyunitande2escaffolders ignores half the TestLevel enum; projects should not have to hand-author a11y / visual / perf harnesses.fitCriterion[0]:unit-test— registry returns a scaffolder for every built-in TestLevel, bound toregistryCoversEveryBuiltInTestLevel.risk.ifNotMet: Users hand-write a11y/visual/perf test scaffolds themselves; the DSL looks incomplete; adoption stalls for projects with strict quality gates.
Paraphrase. The TestLevel enum declares seven levels — Unit, Functional, EndToEnd, Accessibility, Internationalization, Visual, Performance. For each of those levels, the package ships a TestScaffolder implementation that generates an empty test class decorated with @FeatureTest and @Verifies at the appropriate suffix and directory. Projects that need a scaffolder for a level the built-ins do not cover — load-test, security-scan, mutation, contract — register a custom scaffolder in the StyleRegistry without forking. Every scaffolder output uses @FeatureTest/@Verifies, never describe/it, enforcing REQ-DOG-FOOD at generation time.
Why it exists. A DSL that generates only unit and e2e test harnesses is half-finished. Half the reason TestLevel is an enum (as opposed to a boolean isUnit) is that the DSL recognises seven distinct testing concerns — contrast is an a11y concern, latency is a perf concern, CLS is a visual concern — and each concern wants a differently-shaped harness. If the scaffolder ships only unit and e2e, projects with strict quality gates end up hand-authoring the other five, which defeats the purpose of having a scaffolder at all.
The extensibility half — the pluggable registry — is what makes the REQ more than a checkbox. A project that needs a mutation-testing scaffolder registers its own, plugging into the same planWrites machinery that drives the built-ins. The TestScaffolder port is defined in packages/requirements/src/cli/scaffold-core.ts; its contract is minimal ({ level, defaultDir, filenameSuffix, description, generate(feature, uncovered) }); every built-in is an instance of the same interface.
How it's satisfied. FEATURE-SCAFFOLD-REGISTRY (enumerated in chapter 06) satisfies this REQ. The seven built-in scaffolders — UnitScaffolder, FunctionalScaffolder, E2eScaffolder, A11yScaffolder, I18nScaffolder, VisualScaffolder, PerfScaffolder — live under packages/requirements/src/cli/scaffolders/. The REQ's first three fitCriteria translate directly to test methods: registryCoversEveryBuiltInTestLevel, scaffolderOutputUsesFeatureTestVerifiesOnly, registerScaffolderOverridesDefault. The fourth — planWritesEnumeratesAllExpectedLevels — verifies the integration with @Expects-driven plan generation.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI does not generate test files. It is, however, a consumer of the scaffolder output: when the TUI lands on an AC node that has no verifying test, it offers a shortcut — scaffold a test for this AC — that calls into FEATURE-SCAFFOLD-REGISTRY. The two Features compose at the UX layer without overlapping at the satisfies layer.
REQ-AUDIT-TRAIL-HOOKS — HistoryEntry reserves typed slots for future signed sign-off
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-audit-trail-hooks.ts.
id:REQ-AUDIT-TRAIL-HOOKStitle: HistoryEntry extended with Reviewed / ApprovedBy / SignedOff change kinds + optional signature field (implementation deferred to Tier 3)priority: Medium ·status: Draft ·kind: Constraintstatement.pattern:ubiquitous— support review / approval / sign-off semantics on history entries as typed no-op decorators and an optionalsignaturefield, so 21 CFR Part 11 Part E integration can be added later without a schema migration.rationale.claim: Schema migrations are disproportionately expensive for traceability artefacts because every downstream consumer (auditors, regulators, other repos) re-reads the corpus. Reserving the hooks now is cheap; reserving them later is a breaking change.fitCriterion[0]:unit-test—ChangeKinddiscriminated union includes Reviewed, ApprovedBy, SignedOff, bound tochangeKindUnionIncludesReviewedAndApprovedBy.risk.ifNotMet: Absence of hooks means a breaking schema migration later when a regulated customer wants signed sign-off; a migration cascades through every corpus on disk.
Paraphrase. The HistoryEntry type that every spec carries (its append-only audit log) must reserve, from day one, change kinds for review, approval, and sign-off, plus an optional signature field typed as a discriminated union of supported signature schemes (Ed25519 or RSA-SHA256). Two no-op decorators — @Reviewed and @SignedOff — exist and register metadata. None of this is implemented as a verification routine in this tier; the crypto is Tier 3. What ships now is the schema shape, so that when Tier 3 lands, existing corpora do not need to migrate.
Why it exists. Audit-trail schemas are sticky. Every downstream consumer — the auditor's tooling, the regulator's submission pipeline, the sister repo that cross-references — re-reads the schema on every run. Migrating the schema requires coordinating migrations across all of them. The cost scales with the number of consumers, not the number of producers. That asymmetry is what makes reserving the field now cheap and adding the field later expensive. This REQ bakes the asymmetry into the design up front: the schema today carries three change kinds it does not yet use and a signature slot it does not yet populate, because in three years when those become mandatory, the corpora written today must still validate.
The 21 CFR Part 11 Part E citation is the specific regulatory weight. Part E governs electronic signatures on electronic records in FDA-regulated domains (pharma, medical devices, clinical trials). When a customer from that domain eventually adopts the DSL, they do not want to rewrite their history log; they want to add a signature field to new entries, verify it against their crypto provider, and keep everything else intact.
How it's satisfied. Partially. FEATURE-HISTORY-ENTRY-SCHEMA (in chapter 06) satisfies the schema-shape half: the ChangeKind discriminated union includes the three reserved kinds, the HistoryEntry interface carries the optional signature slot, and the two no-op decorators (@Reviewed, @SignedOff) are exported. The crypto-verification half is deferred to Tier 3 and is not satisfied by any Feature at this date. This split is deliberate — the REQ's risk.mitigations calls it out: Land only the hooks (types + no-op decorators), no crypto or verification logic.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI reads history entries and displays them; it does not review, approve, or sign them. It is a downstream consumer of the schema.
REQ-AI-AS-IMPLEMENTER-ADAPTER — AI implementation is a pluggable adapter with deterministic stub fallback
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-ai-as-implementer-adapter.ts.
id:REQ-AI-AS-IMPLEMENTER-ADAPTERtitle: AI implementation of ACs is a pluggable adapter with prompt caching and deterministic stub fallbackpriority: Medium ·status: Draft ·kind: Functionalstatement.pattern:optional— when the player is invoked with--ai, thePerAcFsmshall delegate AC implementation to anAiAdapterport; without--ai, it shall emit deterministic throw stubs and the project shall compile and its scaffolded tests fail with a clear "not yet implemented" message.rationale.claim: The player must be demonstrable without network access; AI assistance is a productivity boost, never a prerequisite.fitCriterion[0]:unit-test— the stub adapter returns a deterministic throw-stub AC body and never touches the network, bound tostubAdapterReturnsDeterministicThrowStubandstubAdapterPerformsNoNetworkIo.risk.ifNotMet: Player becomes coupled to a specific LLM vendor and un-runnable offline; tests break without an API key.
Paraphrase. When the scenario player reaches the implement AC body step, it can either emit a deterministic throw-stub (the default) or call an AiAdapter port (the --ai flag). The port has two implementations out of the box: a stub adapter that returns a fixed string and never touches the network, and a Claude-API adapter that lives in its own file under src/cli/scenario/adapters/claude-*.ts. No file outside that adapter directory is allowed to import @anthropic-ai/sdk or call fetch(. Prompt caching is on by default when the Claude adapter is active.
Why it exists. There are two failure modes this REQ forbids. The first is vendor lock-in: if the player's implementation step is hard-wired to one specific LLM provider's SDK, the package cannot run offline, cannot run in air-gapped environments, cannot run on CI without an API key, and cannot run under a customer's own LLM provider. The adapter port prevents all four.
The second is AI-as-prerequisite: if the default path requires AI, every demo is a network round-trip; every demo is non-deterministic; every demo is vulnerable to API latency and rate-limit flakes. The REQ specifies --no-ai as the default explicitly for this reason — the package must be demonstrable without network access.
The prompt-caching clause is a nod to the claude-api skill registered in this project — when the Claude adapter is active, prompt caching is on, because cache hit rate is the first-order cost metric for any non-trivial Claude application.
How it's satisfied. FEATURE-SCENARIO-PLAY satisfies this REQ jointly with REQ-SCENARIO-DRIVEN-WORKFLOW and REQ-MULTI-FSM-ORCHESTRATION. The three REQs compose into the player's architecture. FEATURE-AI-ADAPTER-STUB and FEATURE-AI-ADAPTER-CLAUDE (in chapter 06) are the two concrete implementations; the stub is shipping, the Claude adapter is Phase 1. The rg quality gate — rg "anthropic|fetch\(" src/cli/scenario/ -g "!**/adapters/claude-*.ts" must return zero matches — is a compile-independent inspection that guards the adapter boundary.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ. The TUI performs no network I/O and has no AI integration. The TUI is, in fact, the inverse shape — a deliverable whose value is specifically that it works entirely from local filesystem state, at zero latency, with zero external dependencies. The contrast between the TUI and the AI adapter illustrates the composition-via-adapter-boundary principle REQ-AI-AS-IMPLEMENTER-ADAPTER expresses.
REQ-VOCAB-INDUSTRY-ALIGNED — Traceability verbs must match SysML / ISO-29148 / BDD vocabulary
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-vocab-industry-aligned.ts.
id:REQ-VOCAB-INDUSTRY-ALIGNEDtitle: Traceability verbs must match SysML / ISO-29148 / BDD vocabularypriority: Medium ·status: Approved ·kind: NonFunctionalstatement.pattern:ubiquitous— name traceability-link verbs in alignment with SysML, INCOSE, ISO/IEC/IEEE 29148, and BDD practice:satisfies,verifies,refines,specifies; reports use the inverse formssatisfiedBy,verifiedBy,refinedBy,specifiedBy.rationale.claim: A project-specific DSL that invents its own vocabulary forces every new contributor to learn translations; standards alignment buys instant industry recognition.fitCriterion[0]:unit-test—@Satisfies+@Refinespopulate bidirectional link registries, bound tosatisfiesDecoratorRegistersBidirectionalLinkandrefinesDecoratorRegistersParentChildLink.risk.ifNotMet: DSL invents jargon; onboarding cost rises, SysML/INCOSE practitioners reject it as ad-hoc.
Paraphrase. The four traceability verbs the DSL uses — satisfies (Feature → Requirement), verifies (Test → AC), refines (Requirement → Requirement), specifies (AC → Feature) — are not chosen arbitrarily. Each one matches a verb already in SysML or INCOSE or ISO/IEC/IEEE 29148 or BDD. The report layer uses the inverse forms for the back-edge (satisfiedBy, verifiedBy, refinedBy, specifiedBy), which is the convention SysML deriveReqt and 29148 both use. The GLOSSARY cites the source standard for each term.
Why it exists. A DSL that invents its own vocabulary makes every contributor a translator. Someone arrives from an INCOSE background and has to learn that the package says @Implements where INCOSE says «satisfy». Someone arrives from BDD and has to learn that the package says @Verifies while BDD would say «test scenario». Each translation adds friction; cumulatively the friction is what makes a DSL feel ad-hoc and amateur.
The alternative is to read the standards, list the verbs, pick them. SysML 1.7 uses «satisfy», «verify», «refine», «derive», «trace». ISO/IEC/IEEE 29148:2018 catalogues the same relations with slight naming variation. BDD adds «scenario» and «step». The intersection — satisfies, verifies, refines, specifies — is what the DSL ships. A practitioner from any of the three traditions recognises the vocabulary on first look. Onboarding cost drops to zero.
The decision shows up in a surface-level way: the original typed-specs series called the test-to-AC decorator @Implements, which is not one of the standard verbs for that relation. The new package renames it @Verifies, because in SysML a test verifies an AC; it does not implement it. The rename is one of the concrete deltas between typed-specs and @frenchexdev/requirements, and this REQ is the reason.
How it's satisfied. FEATURE-DECORATOR-SURFACE (the umbrella Feature for the decorator module, enumerated in chapter 06) satisfies this REQ. Its ACs include satisfiesDecoratorRegistersBidirectionalLink, refinesDecoratorRegistersParentChildLink, verifiesDecoratorBindsTestToAc, and so on. The inspection checklist on the fitCriterion covers the documentation half — README uses canonical verbs, GLOSSARY.md cites source standard per term, requirement show uses inverse forms — and is discharged by the prose Features (FEATURE-README, FEATURE-GLOSSARY).
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ, but it is a prominent consumer of it: the TUI displays every traceability edge using the canonical verb. When the user lands on a Feature node, the right-hand panel lists satisfies (pointing to Requirements) and verified-by (pointing to tests). A reader familiar with SysML recognises the vocabulary on first view.
REQ-PARALLEL-DELIVERABLE — Work partitions into ≥ 4 conflict-free parallel streams
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-parallel-deliverable.ts.
id:REQ-PARALLEL-DELIVERABLEtitle: Work partitions into ≥ 4 conflict-free parallel streams against frozen contractspriority: Medium ·status: Approved ·kind: NonFunctionalstatement.pattern:ubiquitous— partition the implementation into ≥ 4 parallel workstreams sharing only frozen Phase 0 interface types, with zero file overlap.rationale.claim: A monolithic plan that takes one agent a week is worse than a partitioned plan that takes 5 agents a day; parallelism is the multiplier.fitCriterion[0]:inspection— checklist: Phase 0 PR merges before any Phase 1 stream opens; each Phase 1 PR touches only files in its declared deliverable list; no two Phase 1 streams import from each other. Reviewers: maintainer.risk.ifNotMet: Work serialises; agents collide on shared files; merge conflicts balloon.
Paraphrase. Each significant feature addition partitions into at least four parallel workstreams. Phase 0 lands first and freezes the interface types shared across the streams. Phase 1 streams open in parallel afterwards, each touching a disjoint set of files, each importing only from Phase 0's frozen types. Streams never import from each other. A maintainer inspects the PR list and verifies the partition holds.
Why it exists. The REQ acknowledges a reality of this project's economics: the author is a single human, the coding agents are multiple, and the rate at which features ship is determined by how well the work partitions. A five-day monolithic plan assigned to one agent is worse than a one-day partitioned plan assigned to five agents, even accounting for the partition overhead — because the five-agent plan ships in a day, and the one-agent plan ships in a week. The multiplier is real. The REQ makes the multiplier a first-class design constraint rather than a tactical afterthought.
There is a second reason, less visible on first read: partitions reveal bad architectures. A feature that cannot partition into four streams against frozen contracts almost certainly has hidden coupling that will bite somewhere else. The exercise of drawing the partition — the Phase 0 PR — is itself an architectural review. When an implementation plan insists on concentrating a feature in one monolithic agent-week, that concentration is a signal that the feature's interfaces are not yet clean.
How it's satisfied. FEATURE-TRACE-EXPLORER-TUI is the running example of this REQ satisfied in the concrete. Its Phase 0 PR (the port types for FileSystemPort and PromptPort and the GraphSnapshot interface) froze first; four Phase 1 streams opened afterwards — the renderer stream (Puppeteer-free ASCII graph), the navigation stream (keybindings + mode stack), the graph-builder stream (resolver + cross-reference cache), and the tests stream (all @FeatureTest classes). Each stream touched its own files only. No two streams imported from each other. The REQ's inspection checklist was satisfied by the PR list for the TUI implementation.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI is the running example, and it satisfies this REQ directly. The @Satisfies(...) list on the class includes ReqParallelDeliverableRequirement as the third argument, alongside ReqDiscoverableTraceabilityRequirement and ReqDogFoodRequirement. This is the satisfies-edge promised in chapter 00 when the TUI was introduced — the third of the three requirement-level loyalties the TUI declares.
REQ-DSL-COMPLETE — The DSL must track Requirements, not just Features
Source quote. The REQ lives at packages/requirements/requirements/requirements/req-dsl-complete.ts.
id:REQ-DSL-COMPLETEtitle: The DSL must track Requirements, not just Featurespriority: Critical ·status: Approved ·kind: Functionalstatement.pattern:ubiquitous— let users define Requirement instances and link Features to them via@Satisfiesso traceability spans the full Requirement → Feature → AC → Test chain.rationale.claim: A package named "requirements" that does not track Requirements is a misnamed lie; this is the keystone that makes the rest coherent.fitCriterion[0]:unit-test—satisfiesDecoratorRegistersBidirectionalLinkpopulates bothfeature.satisfies()andrequirement.satisfiedBy(), bound tosatisfiesDecoratorRegistersBidirectionalLink.risk.ifNotMet: The package misrepresents itself; users rationally reject it as an over-promising DSL. Therisk.levelisCritical.
Paraphrase. The package is named @frenchexdev/requirements. The name has to mean something. Specifically, the package has to model Requirement as a first-class type (not a folder name, not a tag, not a comment), has to let Features declare which Requirements they satisfy, and has to present the four-tier chain (Requirement → Feature → AC → Test) end-to-end. If any of those three is missing, the name is not truthful and the package has to be renamed or the gap has to be closed.
Why it exists. This is the keystone. Every other REQ in the 22-REQ set is in some sense a property of the DSL; this one is the existence of the DSL. It asserts that the package type system actually carries what the package name claims. Chapter 00 of this series (Named but Not Modelled) is the long-form argument for why this REQ was needed: the predecessor typed-specs series used the word Requirement throughout its prose but only ever modelled Feature, and that gap was the seed of the current package.
The priority is Critical because without this REQ, the package is either a rhetorical exercise or a mis-named library. The risk-if-not-met is not a performance regression or a failed audit; it is the package cannot honestly be called what it is called. A customer who downloads @frenchexdev/requirements expecting to track requirements and finds that the only first-class type is Feature has been misled. No amount of fit-criterion-passing elsewhere can recover from that.
The REQ's first fit criterion is, fittingly, the single test method whose existence most directly discharges the core claim: satisfiesDecoratorRegistersBidirectionalLink. If @Satisfies(Req) on a Feature results in both feature.satisfies() returning [Req] and requirement.satisfiedBy() returning [Feature] — the bidirectional registry — then the four-tier chain exists and is navigable in both directions. That one test is the minimum proof of existence.
How it's satisfied. FEATURE-DECORATOR-SURFACE is the primary satisfier — the @Satisfies decorator, the bidirectional link registry, the Requirement base class, the Feature base class. FEATURE-REQUIREMENT-NEW-WIZARD is a secondary satisfier: it is the interactive command that lets a user define a Requirement without writing the TypeScript by hand, which is the user-facing half of lets users define Requirement instances. FEATURE-README and FEATURE-GLOSSARY discharge the inspection checklist — the README landing page documents the four-tier chain, the GLOSSARY defines Requirement with a 29148 citation. Three Features for one keystone REQ, jointly discharging a claim that is itself the precondition for every other REQ making sense.
Running-example connection. FEATURE-TRACE-EXPLORER-TUI does not satisfy this REQ directly, but its existence depends on this REQ. The TUI is a viewer over the four-tier chain; if the four-tier chain did not exist (if only the feature-to-test chain existed, as in typed-specs), the TUI would be a three-tier viewer, and its claim to be a trace explorer would be half-fulfilled. Every time the TUI renders a satisfies edge between a Feature and a Requirement, it is trading on the fact that REQ-DSL-COMPLETE is satisfied.
The eleven REQs at a glance
The three clusters are not disjoint in practice. REQ-VERSIONED-TRACEABILITY is the substrate on which REQ-AUDIT-TRAIL-HOOKS builds (versioning gives the immutable history; audit-trail-hooks adds the review / sign-off semantics on top). REQ-SCENARIO-DRIVEN-WORKFLOW is driven by REQ-MULTI-FSM-ORCHESTRATION (the scenario player's internal machine). REQ-AI-AS-IMPLEMENTER-ADAPTER plugs into the same scenario-player machinery. REQ-PARALLEL-DELIVERABLE is the shape every scenario tends to produce. REQ-VOCAB-INDUSTRY-ALIGNED names the edges of the four-tier chain that REQ-DSL-COMPLETE asserts exists. And REQ-DSL-COMPLETE, as the keystone, grounds all three clusters — without the Requirement type, none of the other ten REQs would have subject matter.
The diagram is not a dependency graph in the strict @Refines sense. None of these REQs refine each other; they are all peers. The dashed arrows are semantic dependencies — one REQ's content quietly presupposes another's. The compliance scanner does not enforce semantic dependencies; that is what chapter 19's anti-pattern discussion and chapter 22's roadmap exist for.
Closing — the completeness claim and what would falsify it
REQ-DSL-COMPLETE is the last REQ in the enumeration, and it is the one that most deserves a closing reflection — because it is itself a claim about the enumeration. Specifically, it claims that the twenty-two REQs taken together form a complete DSL for requirements tracking, modulo the open questions catalogued in chapter 22.
A claim of completeness is a strong one. It invites the obvious question: how would you know if it were false? What observation, what user report, what Feature proposal would falsify the claim? If the answer is nothing could, then the claim is not an engineering claim at all, and the REQ is a kind of self-congratulatory exercise. If the answer is this specific thing, then the claim is testable, and the test is one of the things chapter 22 has to keep honest about.
Two falsifiers are plausible.
Falsifier one — a user-surfaced need that cannot be expressed as a combination of existing REQs. A user arrives with a requirement of their own ("the traceability graph must be exportable as an OpenTelemetry-compatible trace") and the package cannot host it. Not that the package rejects the user — the package can always add a new REQ — but that no combination of the existing 22 REQs, extended with standard Feature additions, can satisfy the user's need. The gap is structural: the user needs something the DSL does not know how to name.
This is the kind of falsifier that would push REQ-DSL-COMPLETE from Approved back to Draft, because the completeness claim would have been shown conditional on the user universe seen so far. The remedy is one of two things: either a 23rd REQ (extending the DSL) or a refinement of an existing REQ (specialising one of the 22 to cover the case). Chapter 22 treats the open questions this way — each is a hypothesis about what a 23rd REQ might need to say.
Falsifier two — a Feature that cannot find any satisfies-edge in the set. A Feature is drafted that does meaningful work in the package, but when the time comes to put @Satisfies(...) on it, nothing in the existing set fits. The Feature exists, it is clearly valuable, but no Requirement rationalises its existence. This is the inverse falsifier: the Feature is orphaned at the REQ tier.
An orphan Feature is not automatically a falsifier — chapter 19 (the anti-pattern chapter) discusses the Feature-without-REQ pattern explicitly, and calls it a code smell rather than a fatal error. But if the pattern becomes systematic, if ten out of twenty-five Features cannot find satisfies-edges, the completeness claim has to bend. Either the ten Features are not really Features (they are implementation details masquerading as user-visible value), or the REQ set is missing ten policies that ought to be there. Either way, the claim is falsified in spirit even if no single Feature is individually falsifying.
Note what does not count as a falsifier.
- A REQ that is still
Draftdoes not falsify the claim.REQ-REFACTOR-SAFE,REQ-VERSIONED-TRACEABILITY,REQ-SCENARIO-DRIVEN-WORKFLOW,REQ-MULTI-FSM-ORCHESTRATION,REQ-AUDIT-TRAIL-HOOKS,REQ-AI-AS-IMPLEMENTER-ADAPTERare allDraftat the time of writing. Draft is a legitimate status; the completeness claim is about coverage of the concern space, not implementation completion of every concern. - A REQ whose
fitCriteriaare not yet all bound to test methods does not falsify the claim. Some fit criteria areinspectionordemonstrationkinds that do not bind to tests at all. - A disagreement about wording does not falsify. Chapter 19 catalogues several REQ titles that could be written differently without changing the semantics; that is linting, not falsification.
The completeness claim sits, deliberately, between chapter 19 (the anti-pattern chapter, which lists the things the DSL should not be) and chapter 22 (the roadmap, which lists the things the DSL does not yet know how to say). It is the claim that the DSL knows how to say at least what it needs to say, today, for the use case it advertises. Tomorrow the claim may be falsified; tomorrow the 23rd REQ may land; tomorrow's reader will see a different set. For today, it is the shape of the whole.
REQ-DSL-COMPLETE being the last REQ in the enumeration is no accident. It is the only REQ whose subject matter is the existence of the other twenty-one. It is the REQ that the rest of the series has been quietly pointing at. Chapter 00 named the gap; chapters 01–03 set up the vocabulary to describe the gap; chapter 04 and this chapter listed the specific REQs that close the gap; chapter 06 enumerates the Features that discharge them. The argument for why the package is called requirements bottoms out here.
Running-example recap
Of the eleven REQs in this half, FEATURE-TRACE-EXPLORER-TUI satisfies exactly one directly: REQ-PARALLEL-DELIVERABLE. The TUI's Phase 0 PR froze the port types; four Phase 1 streams opened afterwards (renderer, navigation, graph-builder, tests); each stream touched its own files only; no two streams imported from each other; the inspection checklist was satisfied by the PR list. The TUI is an existence proof that the REQ is satisfiable in practice, not just specifiable in theory.
For the other ten REQs in this half, the TUI does not satisfy but often consumes: it consumes the VersionInfo that REQ-VERSIONED-TRACEABILITY will compute (rendering version numbers in its node labels); it consumes the canonical verbs REQ-VOCAB-INDUSTRY-ALIGNED mandates (rendering satisfies and verified-by in the panel UI); it consumes the scaffolder registry that REQ-SCAFFOLD-EXTENSIBILITY provides (offering scaffold a test for this AC as a shortcut when the user lands on an uncovered AC). Consumption is not satisfaction — consuming a Feature's output is not the same as discharging a REQ's fit criteria — but the pattern is worth naming, because most Features in any reasonably connected DSL will have more consuming relationships than satisfying ones.
Together with the two REQs the TUI satisfied from chapter 04 (REQ-DISCOVERABLE-TRACEABILITY, REQ-DOG-FOOD), the TUI now has three satisfies-edges out. Three is the minimal non-trivial number the DSL can express — it demonstrates the many-to-many shape explicitly (one Feature → many Requirements). Every chapter that comes after this one uses that three-way shape as the example of what @Satisfies(...) makes possible.
Related Reading
- 04 — Twenty-Two Requirements, Part One — the first eleven REQs (what the DSL is).
- 06 — Twenty-Five Features Mapped — the Features that satisfy these REQs (and the ones that do not yet have satisfiers).
- 22 — What Is Not Yet — the roadmap that catalogues the open questions against which
REQ-DSL-COMPLETEcan be falsified. - 19 — Anti-Patterns — the companion to the completeness claim; the failure modes the DSL explicitly refuses.
- typed-specs/07-roadmap.md — the earlier sketch of several of these REQs (
tracedTo, versioned specs, rename codemod) before they had a Requirement type to carry them.
Previous: 04 — Twenty-Two Requirements, Part One / Next: 06 — Twenty-Five Features Mapped