Chapter 00 — Named but Not Modelled
The word "requirement" was already in the title of every typed-specs post. The type never was.
The predecessor to this series — typed-specs/ — is one of the pieces of writing on this site I am most attached to. It introduced a TypeScript pattern (Features as abstract classes, acceptance criteria as abstract methods) that, for the first time, let the compiler carry the specification load instead of a ticket tracker. It reached 112 acceptance criteria across 20 features on a single project, with keyof T-checked decorator chains that turned AC name typos from runtime surprises into compile-time errors.
It also contained a gap I did not see at the time. The word Requirement appears in its frontmatter, its titles, its descriptions, its tag list, its folder names. But the type it modelled was only ever Feature. The word was rhetorical. The model was not.
This chapter is a close reading of two pieces of that earlier series — typed-specs/04-features.md and typed-specs/05-decorators.md — re-read through the lens of what @frenchexdev/requirements now models. The aim is not to dismiss the earlier work; it is to locate precisely where the ladder ended and where the new roof begins.
A running example, carried across the series
Before the close reading, a piece of scaffolding you will see in every chapter of this series: a primary running example.
The example is FEATURE-TRACE-EXPLORER-TUI — the interactive TTY browser over the traceability graph that npx requirements trace explore will eventually ship. It is a small feature (four numbered ACs, a handful of integration ACs, one end-to-end AC), so its code is quotable in full. It also satisfies two Requirements at once:
REQ-DISCOVERABLE-TRACEABILITY— "a user browsing the graph must be able to discover relations without prior knowledge of the vocabulary"REQ-DOG-FOOD— "the DSL must be tested with itself; zerodescribe/it"
Two Requirements, one Feature. This two-to-one shape — the smallest non-trivial shape the new DSL lets you express — will be how this chapter distinguishes what typed-specs could say from what the new package can say.
The full class, for reference:
import { Feature, Priority, Satisfies, type ACResult } from '../../src';
import { ReqDiscoverableTraceabilityRequirement } from '../requirements/req-discoverable-traceability';
import { ReqDogFoodRequirement } from '../requirements/req-dog-food';
import { ReqParallelDeliverableRequirement } from '../requirements/req-parallel-deliverable';
@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)
export abstract class FeatureTraceExplorerTuiFeature extends Feature {
readonly id = 'FEATURE-TRACE-EXPLORER-TUI';
readonly title = '`requirements explore` — interactive TTY browser over the traceability graph';
readonly priority = Priority.Low;
readonly enabled = false;
abstract traceExplorerBuildsGraph(): ACResult;
abstract traceExplorerHandlesArrowKeyNavigation(): ACResult;
abstract traceExplorerDrillsDownFromAnyNode(): ACResult;
abstract traceExplorerOpensHelpOverlayOnQuestionMark(): ACResult;
abstract traceExplorerJumpsBackUpWithBackspace(): ACResult;
abstract traceExplorerRefusesToStartOnNonTty(): ACResult;
abstract traceExplorerExitsCleanlyOnCtrlC(): ACResult;
abstract traceExplorerUsesFileSystemPortForDiscovery(): ACResult;
abstract traceExplorerUsesPromptPortForInteraction(): ACResult;
abstract endToEndNavigatesReqToFeatToAcToTest(): ACResult;
}import { Feature, Priority, Satisfies, type ACResult } from '../../src';
import { ReqDiscoverableTraceabilityRequirement } from '../requirements/req-discoverable-traceability';
import { ReqDogFoodRequirement } from '../requirements/req-dog-food';
import { ReqParallelDeliverableRequirement } from '../requirements/req-parallel-deliverable';
@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)
export abstract class FeatureTraceExplorerTuiFeature extends Feature {
readonly id = 'FEATURE-TRACE-EXPLORER-TUI';
readonly title = '`requirements explore` — interactive TTY browser over the traceability graph';
readonly priority = Priority.Low;
readonly enabled = false;
abstract traceExplorerBuildsGraph(): ACResult;
abstract traceExplorerHandlesArrowKeyNavigation(): ACResult;
abstract traceExplorerDrillsDownFromAnyNode(): ACResult;
abstract traceExplorerOpensHelpOverlayOnQuestionMark(): ACResult;
abstract traceExplorerJumpsBackUpWithBackspace(): ACResult;
abstract traceExplorerRefusesToStartOnNonTty(): ACResult;
abstract traceExplorerExitsCleanlyOnCtrlC(): ACResult;
abstract traceExplorerUsesFileSystemPortForDiscovery(): ACResult;
abstract traceExplorerUsesPromptPortForInteraction(): ACResult;
abstract endToEndNavigatesReqToFeatToAcToTest(): ACResult;
}Notice what is there and what is not. The @Satisfies(...) list is three class references, not three strings. The requirements this Feature exists to meet are themselves typed entities, importable, checkable, referenceable. The abstract methods are the same shape as the typed-specs NavigationFeature example — the innovation does not live there. It lives in the decorator on top and the class-valued arguments that decorator carries.
Hold onto this class. Every time the chapter moves between what typed-specs could say and what the new package can say, the explorer TUI will be the thing said.
The word was already there
Pull up the frontmatter of the typed-specs series in a text window and scan. The word Requirement (or Requirements, capitalised) appears in places that now read as a small taxonomy:
- In the
tags:array of typed-specs/01-why.md —["TypeScript", "Requirements", "Feature Tracking", ...]. - In the tooltips of typed-specs/04-features.md — "The M2 vocabulary — Feature, Priority, ACResult, and the decorators that link them to Requirements." (The sentence is a mixed metaphor: ACResult is not a link, the decorators do not link to Requirements, nothing in the file declares a Requirement type. The word is there anyway.)
- In the description of typed-specs/05-decorators.md — "Three decorators that close the feature-to-test chain, making the TypeScript type system carry the requirements tracking load." (Closer, but still: the chain is feature-to-test, and the "requirements tracking load" being carried is, concretely, the Feature-to-AC relation.)
- In the filename of
requirements/features/navigation.ts— the folder was namedrequirements/, even though every file inside it declared aFeaturesubclass. Not a single file declared aRequirement. - In the description of typed-specs/06-compliance.md — "A 300-line scanner that cross-references features and tests, enforces critical-feature AC coverage, and reports on the requirements graph." The phrase "requirements graph" is used to mean "feature-to-test graph".
- In the project-wide tag
Requirementsthat typed-specs shares with requirement-vs-feature.md — butrequirement-vs-feature.mdexplicitly introduces the distinction the typed-specs series then collapses. - And, tellingly, in the
tracedTofield name of typed-specs/07-roadmap.md — "A future refinement will let ACs declare atracedTofield pointing at a higher-level requirement." The roadmap admits the gap. The package does not yet close it.
Seven places, one pattern. Every use of Requirement in the typed-specs series is rhetorical or gestural. The type system does not hold a Requirement. The decorators do not reference a Requirement. The compliance scanner does not enumerate Requirements. The DSL models Feature. The word Requirement sits on top of it the way "user story" sits on top of an implementation ticket.
This is not a flaw to apologise for. It is the natural boundary of a ladder. A ladder stops somewhere; so did typed-specs. The point of naming the gap is to be honest about where we started, so the new work has a measurable meaning.
What typed-specs actually modelled
Re-read typed-specs/04-features.md — the section titled The Base Types — with a careful eye on the word abstract and the three things it modifies:
export enum Priority {
Critical = 'critical',
High = 'high',
Medium = 'medium',
Low = 'low',
}
export interface ACResult {
satisfied: boolean;
reason?: string;
}
export abstract class Feature {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
}export enum Priority {
Critical = 'critical',
High = 'high',
Medium = 'medium',
Low = 'low',
}
export interface ACResult {
satisfied: boolean;
reason?: string;
}
export abstract class Feature {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
}Three types. That is the entire M2 vocabulary of typed-specs. A four-valued priority enum. An ACResult result-type that is never actually returned (the comment in that article explicitly notes: "ACResult is not used (yet)"). And a Feature abstract class with three abstract fields.
This was the ladder's strength. Three types, and nothing else. No Requirement. No Link. No Refinement. No Satisfaction relation. No Style. A student could read these four lines and implement typed-specs in an afternoon.
And this was also its boundary. Because the only object the language could name was Feature, everything that a requirements system normally calls a Requirement had to be folded into the Feature. A policy that said "every admin screen must be keyboard-navigable" had to become a Feature called KeyboardAccessibleAdminScreensFeature — or it had to not exist. A safety property that said "no user data shall be logged in production" had to become a Feature called NoUserDataInProductionLogsFeature — or it had to not exist. The difference between a rule and a deliverable was not in the vocabulary. The vocabulary only had deliverables.
You can see this collapse in the Feature Inventory section of typed-specs/04-features.md. Twenty Features, 112 ACs. Scan the names:
navigation,scroll-spy,build-pipeline,search,search-scoring,theme,accent,keyboard,overlays,mobile,copy-buttons,hire-modal,slugify,frontmatter,mermaid-config,visual,performance,req-track,contrast,accessibility.
Some of these are Features in the usual sense — concrete deliverables (search, hire-modal, copy-buttons). Some of them, read carefully, are actually Requirements masquerading as Features:
accessibility(really a family of Requirements: WCAG AA contrast, keyboard reachability, ARIA correctness — each a separable rule with its own sources and fit criteria)performance(really a family of Requirements: LCP budget, CLS budget, bundle size budget — each a separable rule with its own numeric target)visual(really a family of Requirements: baseline stability across themes, variance tolerance, diff review policy)contrast(really a Requirement refinement ofaccessibility, not a sibling Feature)
In a proper Requirement/Feature split, ContrastRequirement refines AccessibilityRequirement, and both are satisfied by concrete Features — the theme-switcher, the colour tokens, the dark-mode CSS layer. In typed-specs, accessibility and contrast had to be flattened to the same layer as navigation and hire-modal, because there was only one layer.
I did not see this at the time. I see it now.
Reading Part V with fresh eyes
typed-specs/05-decorators.md introduces three decorators that, the series explains, "close the chain":
@FeatureTest(Feature)— class-level, links a test class to a Feature.@Implements<Feature>('acName')— method-level, links a test method to a Feature AC.@Exclude()— method-level, marks helpers to skip.
Re-read with fresh eyes, note what is absent:
- There is no
@Satisfies. No way for a Feature to declare which higher-level thing it exists to deliver. - There is no
@Refines. No way for one Requirement to be a specialisation of another. - There is no
@Verifies— the typed-specs decorator is named@Implements, which is the noun a Feature-only world can justify (a test "implements" an AC). In a world with Requirements, the test verifies an AC; the AC implements (in a broader sense) a Requirement; the Feature satisfies the Requirement. Three different verbs for three different links. Typed-specs had one verb — implements — because it had one link to name.
Count the type parameters in the whole file. @FeatureTest<T extends abstract new (...args: any[]) => Feature>(feature: T) — one generic, over Feature. @Implements<T extends Feature>(ac: keyof T & string) — one generic, over Feature, with keyof T carrying the AC-name safety. @Exclude() — no generic. Every type parameter ultimately resolves to Feature.
This is not a criticism of the typed-specs implementation. Within its own model — a single M2 type — it is exquisite. keyof T & string is still the most elegant piece of the whole pattern. It is just not a requirements DSL. It is a Feature DSL with the word requirement in the title.
The consequence, in practice: in typed-specs, a test file lived entirely on the axis this test proves this AC. The backward question which higher-level policy does this AC exist to enforce? had no type-level answer. It had to live in a comment, in a Jira link, or in the reader's head.
The five axes of level-up
If typed-specs modelled {Priority, ACResult, Feature} and one relation (test→AC), then the new package differs on five measurable axes. This series is structured around these five axes; the list here is the index:
The first axis — nominal vs real stratum
typed-specs used the word Requirement rhetorically. @frenchexdev/requirements declares:
export abstract class Requirement<S extends RequirementStyle> {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
abstract readonly status: 'Draft' | 'Proposed' | 'Approved' | 'Deprecated';
abstract readonly kind: 'Functional' | 'Constraint' | 'Quality' | 'Interface';
abstract readonly statement: S['StatementTypes'];
abstract readonly rationale: Rationale;
abstract readonly fitCriteria: ReadonlyArray<S['FitCriterionTypes']>;
abstract readonly verificationMethod: VerificationMethod;
abstract readonly source: RequirementSource;
abstract readonly risk: RiskStatement;
// ... refines, tracedTo, history
}export abstract class Requirement<S extends RequirementStyle> {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
abstract readonly status: 'Draft' | 'Proposed' | 'Approved' | 'Deprecated';
abstract readonly kind: 'Functional' | 'Constraint' | 'Quality' | 'Interface';
abstract readonly statement: S['StatementTypes'];
abstract readonly rationale: Rationale;
abstract readonly fitCriteria: ReadonlyArray<S['FitCriterionTypes']>;
abstract readonly verificationMethod: VerificationMethod;
abstract readonly source: RequirementSource;
abstract readonly risk: RiskStatement;
// ... refines, tracedTo, history
}Eleven abstract fields (and more, depending on the Style). The package ships twenty-two concrete REQ-* subclasses that fill them in. This is not a rhetorical move. It is a second abstract class, sibling to Feature in the M2 vocabulary, that every subsequent chapter will explore in depth.
This axis alone is the difference between writing about requirements and having a Requirement type. The difference between tags: ["Requirements"] in frontmatter and extends Requirement<DefaultStyleType> in code. The difference between a folder called requirements/ that contains only Feature subclasses, and a folder called requirements/requirements/ that contains twenty-two actual Requirement subclasses.
The second axis — the refinement graph
typed-specs had no relation between Features. NavigationFeature and KeyboardFeature were siblings in a flat list. If a reader noticed that KeyboardFeature was really a refinement of AccessibilityFeature, there was no place to say so.
@frenchexdev/requirements adds:
@Refines(ReqAccessibilityRequirement)
export abstract class ReqContrastRequirement extends Requirement<DefaultStyleType> {
// ...
}@Refines(ReqAccessibilityRequirement)
export abstract class ReqContrastRequirement extends Requirement<DefaultStyleType> {
// ...
}Requirements compose into a parent-child tree. @Refines is the SysML «deriveReqt» relation made a TypeScript decorator. The compliance scanner follows it. The traceability graph renders it. A change to the parent Requirement raises diagnostics on every child.
No analogue in typed-specs. It could not have been added without the Requirement type first.
The third axis — many-to-many satisfaction
The heart of the shape. typed-specs had an implicit 1:1 — a test verified an AC, an AC lived on a Feature, and a Feature was itself the requirement. One-to-one, collapsed.
@frenchexdev/requirements has @Satisfies, which accepts a list of Requirement classes:
@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)
export abstract class FeatureTraceExplorerTuiFeature extends Feature { ... }@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)
export abstract class FeatureTraceExplorerTuiFeature extends Feature { ... }Three Requirements, one Feature. And the reverse also holds: ReqDogFoodRequirement is satisfied by the TUI Feature, by FEATURE-COMPLIANCE-REPORT, by FEATURE-WATCH-MODE, by every Feature in the package that produces tests — because the Requirement applies to every testable deliverable. One Requirement, many Features.
This many-to-many is not a cosmetic upgrade. It is the shape that distinguishes a policy (applies across deliverables) from a ticket (produces one deliverable). typed-specs could model tickets. It could not model policies, because policies are the thing that spans tickets.
The running-example Feature — FEATURE-TRACE-EXPLORER-TUI — shows the shape minimally. In typed-specs, the explorer TUI would have been one flat Feature with ten ACs. In the new package, it is one Feature that declares its two requirement-level loyalties (to discoverability and to dog-fooding) with type-valued arguments that the compliance scanner follows in both directions.
The fourth axis — style pluralism
typed-specs had one prose register. Features had an id, a title, a priority, and ACs with JSDoc comments. That was the language.
@frenchexdev/requirements introduces a RequirementStyle type parameter:
export abstract class Requirement<S extends RequirementStyle> { ... }export abstract class Requirement<S extends RequirementStyle> { ... }with five registered styles out of the box: DefaultStyle (ISO/IEC/IEEE 29148 + Volere + EARS), IndustrialStyle (IEC 61508 + SIL 1–4), LeanStyle (Toyota A3 + VSM + Kaizen), AgileStyle (INVEST + BDD), KanbanStyle (Classes of Service). Each Style plugs in a StyleVocabulary, a set of StyleValidators, a RequirementReporter, and a list of FitCriterionAdapters.
A single Requirement is written once. The same underlying facts render in five registers. The industrial register emits SIL-indexed safety cases; the lean register emits A3 reports; the agile register emits INVEST-validated user stories; the kanban register emits WIP-limited workflow cards. The facts do not change. The prose and the validation rules do.
typed-specs could not have added this without first distinguishing Requirements from Features, because the Style varies at the Requirement level: what counts as well-formed is a property of the policy, not of the deliverable. A dedicated Style chapter per register is in this series (ch 09 through ch 11); ch 08 introduces the registry.
The fifth axis — meta-circle at the decorator layer
typed-specs-product/10-dogfood.md gestured at dog-fooding — the tspec product tracks tspec itself. But the loop it drew closed at the product level (backlog, reports, compliance output).
@frenchexdev/requirements closes the loop one layer deeper: the decorators used to define the package are used to validate the package. There is no describe. There is no it. Every one of the 778 tests in the package is a method on a class decorated with @FeatureTest(Feat) and @Verifies<Feat>('acName'), and @FeatureTest resolves to a Feature that has @Satisfies(Req1, Req2, …) on it, which resolves to Requirements including REQ-DOG-FOOD — whose statement says, literally: "use @FeatureTest and @Verifies for every test of every new command".
The loop:
REQ-DOG-FOODmandates decorator-only testing.- Features declare they satisfy
REQ-DOG-FOOD. - Tests verify ACs on those Features, decorated with
@FeatureTest/@Verifies. - The compliance scanner reads the decorators to check (3) actually happened.
- The compliance scanner is itself a Feature that satisfies
REQ-DOG-FOOD, and its own tests use@FeatureTest/@Verifies. - Go to 1.
This is the meta-circle the rest of the series unpacks. Typed-specs could not have written down (1), because it had no Requirement type to write it into. Typed-specs-product gestured at the loop at the product surface; the loop at the decorator-source surface is new.
The gap, drawn
Two strata. One relation becomes four (verifies, belongs-to, satisfies, refines). One class-valued generic becomes two class-valued generics plus a list-valued decorator argument.
Data model, side-by-side
The two-box world and the eleven-box-plus-associations world are not competitors. They are levels of the same stairway. Chapter 01 walks from one to the other by construct.
The five-axis radar
(Read the chart as: points high on the vertical axis are modelled in the type system; points high on the horizontal axis belong to the new package. typed-specs has one modelled point — the Feature stratum — and one rhetorical point — the word Requirement in its prose. The five axes all cluster in the upper-right: modelled, new.)
Walking around the example
Take the running example and walk around all five axes, concretely.
Axis 1, on the explorer TUI
In typed-specs, the explorer would have been a single abstract class:
// Hypothetical typed-specs version.
export abstract class TraceExplorerTuiFeature extends Feature {
readonly id = 'TRACE-EXPLORER-TUI';
readonly title = 'Interactive TTY browser over the traceability graph';
readonly priority = Priority.Low;
abstract buildsGraph(): ACResult;
// ... ten abstract methods
}// Hypothetical typed-specs version.
export abstract class TraceExplorerTuiFeature extends Feature {
readonly id = 'TRACE-EXPLORER-TUI';
readonly title = 'Interactive TTY browser over the traceability graph';
readonly priority = Priority.Low;
abstract buildsGraph(): ACResult;
// ... ten abstract methods
}The fact that this Feature exists because the package has a Requirement called Discoverable Traceability, and because the package has a Requirement called Dog-Food, and because both of those Requirements require the explorer to exist as a way of making the graph walkable by a human — none of that would be in the code. It would be in a comment, if anywhere.
In the new package, the @Satisfies(Req1, Req2, Req3) list on the class is that information. The explorer is a Feature with three requirement-level parents, each importable, each inspectable, each with its own status, rationale, risk, and fit criteria.
Axis 2, on the explorer TUI
ReqDiscoverableTraceabilityRequirement is, in the current package, a leaf — it does not declare @Refines on another Requirement. But suppose in a later version we introduce ReqHumanInTheLoopAnalysisRequirement as the umbrella policy "the traceability artefacts must be walkable by a human, not only machine-consumed", and mark the discoverable-traceability one as @Refines(ReqHumanInTheLoopAnalysisRequirement).
The explorer TUI Feature does not need to change. The @Refines edge is added one layer above. The scanner now reports that FEATURE-TRACE-EXPLORER-TUI indirectly satisfies ReqHumanInTheLoopAnalysisRequirement through its direct satisfaction of the refinement. None of this language exists in typed-specs.
Axis 3, on the explorer TUI
The @Satisfies(...) line is the many-to-many shape in code. Re-read it:
@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)@Satisfies(
ReqDiscoverableTraceabilityRequirement,
ReqDogFoodRequirement,
ReqParallelDeliverableRequirement,
)Three Requirements, one Feature. The inverse: ReqDogFoodRequirement is satisfied by approximately every producing Feature in the package. In the compliance report, REQ-DOG-FOOD shows a satisfier list of several dozen Features; FEATURE-TRACE-EXPLORER-TUI shows a satisfies list of three Requirements. Two bipartite fan-outs, one in each direction. typed-specs had one link of multiplicity 1:N (a Feature has many ACs); the new package has a 1:N link above Feature and a 1:N link below it. The world has three levels, not two.
Axis 4, on the explorer TUI
The Requirements this Feature satisfies all live in the DefaultStyle. Rewriting one of them — say, ReqDiscoverableTraceabilityRequirement — in the IndustrialStyle would not change the explorer TUI's @Satisfies list; the class reference is the same. What would change is the Requirement's statement, fitCriteria, and RequirementReporter output: the safety-critical register would demand a SIL level, a hazard-and-operability entry, a traceable source from a standards clause. The explorer TUI, as a Feature, is neutral to this. It satisfies the Requirement, whichever register the Requirement is authored in.
This decoupling is the point of the Style axis. The delivery (Feature) and the rule-prose (Style on the Requirement) are separately variable.
Axis 5, on the explorer TUI
REQ-DOG-FOOD is one of the three Requirements FEATURE-TRACE-EXPLORER-TUI satisfies. That means the explorer TUI is under the rule its own tests must use @FeatureTest/@Verifies, never describe/it. Browse packages/requirements/test/unit/ — the test class for the explorer TUI is a @FeatureTest(FeatureTraceExplorerTuiFeature)-decorated class whose methods carry @Verifies<FeatureTraceExplorerTuiFeature>('traceExplorerBuildsGraph') and so on. The Feature that implements REQ-DOG-FOOD (via @Satisfies) has its own tests written in the style the Requirement mandates. The circle is closed at the decorator layer. Chapter 02 formalises the circle; chapter 18 draws it five ways.
What this chapter did not do
It did not attack typed-specs. It did not claim the earlier ladder was wrong. A ladder reaches somewhere, and there it ends. The typed-specs ladder reached a compiler-checked feature-to-test chain with keyof T AC-name safety, a pattern the site still uses in every src/lib/ module it tests. Nothing in this series deprecates that.
It did not walk the migration. Going construct by construct from @FeatureTest(Feature) to @Verifies<Feature>('ac') and the expansion of @Implements into the @Satisfies/@Verifies pair is chapter 01's job.
It did not narrate the history — the why of Feature first, Requirement later. That is chapter 01b, the historical-path chapter.
It did not argue the dog-food axiom. REQ-DOG-FOOD is the subject of chapter 02, including the meta-circular paradox (a Requirement inside a package mandating how that package's own tests are written) and its resolution.
It did not introduce the decorators individually. That is chapter 03.
What this chapter did do
Three things.
First, it set up the spine thesis in its cleanest form: the word "Requirement" in the old series was rhetoric; here it becomes a type. Every later chapter refers back to this sentence, because every later chapter is a dimension along which rhetoric becomes type.
Second, it introduced the running example — FEATURE-TRACE-EXPLORER-TUI — and showed, once, how to talk about a Feature along each of the five axes. From chapter 01 onwards, the explorer TUI appears as a reminder at the top and a recap at the bottom of every chapter that structurally applies to it.
Third, it drew the gap three times — as a two-stratum flowchart, as a side-by-side class diagram, and as a radar over the five axes. Those three diagrams are the visual vocabulary for the rest of the series. When a later chapter says "on axis 3", it means the many-to-many ribbon between Requirement and Feature in Figure 00.1. When it says "in the typed-specs column", it means the three boxes on the left of Figure 00.2. The diagrams are maps, not decoration.
Running example — what this chapter added
On FEATURE-TRACE-EXPLORER-TUI:
- We have seen the full class, including the three-argument
@Satisfiesdecorator. - We have identified the two Requirements it is the smallest non-trivial satisfier of (
REQ-DISCOVERABLE-TRACEABILITYandREQ-DOG-FOOD, plusREQ-PARALLEL-DELIVERABLEwhich shows up in several Features). - We have seen, once, how its typed-specs-shaped twin would have been written — flat, requirement-less, all information collapsed into the Feature name.
The next chapter picks up from the same class and walks through the migration construct by construct — which typed-specs constructs persist, which are renamed, which are replaced, which are new.
Versioning and modularity — the 22c–22f sub-series
Readers who arrived at this chapter from the question "how do we version and enable modularity in Requirements and Features?" should jump directly to the four-part sub-series that answers it, rather than working through the migration chapters first. The sub-series is self-contained (it re-establishes the monorepo shape and the DSL vocabulary in its opening sections) and designed to be read as one continuous argument across three axes plus synthesis:
- Chapter 22c — Modularity: Sub-Packages and Style Composition — the physical split of the flat
requirements/directory into bundles (@frenchexdev/requirements,-platform,-ssg), the shared-kernel pattern, and the proposedcomposeStyle(DefaultStyle, overrides)primitive that lets consumers extend the Style vocabulary without forking it. Ends with a three-step reversible migration path. - Chapter 22d — Lifecycle Discipline: Versioning Without Semver — the canonical versioning story:
statusas a Style-declared lifecycle FSM,history: readonly HistoryEntry[]as the append-only audit trail,@Refines(ParentReq)as the SysML refine edge,VersionInfo { version, contentHash, since, supersedes? }on Requirements,VersionPin { targetId, pinnedVersion, pinnedHash }on Features, anddetectVersionDriftemittingreq-evolved/pin-stale/unpinned-req. The four ways to evolve a Requirement are laid out as type-level choices. - Chapter 22e — Schema Evolution: Versioning the DSL Itself — date-based
$schemaVersion: '2026-04-14'instead of semver, the additive-only rule for Style vocabulary changes within a date, the codemod contract (requirements migrate --from <date> --to <date>) for breaking changes, and why library semver and schema date are two independent clocks. - Chapter 22f — The Full Picture: End-to-End Discipline — synthesis. One master diagram covers stakeholder → Requirement → Feature → AC → Test → source → bindings → compliance gate. Ten disciplines tie the three axes into a single operating procedure.
The sub-series assumes familiarity with the @Satisfies / @Verifies / @FeatureTest decorators (established in Chapter 03) and with the compliance gate (established in Chapter 13). A reader coming in cold who has not read the rest of the series can still follow the sub-series — every concept is re-introduced on first mention — but the migration chapters (01 through 12) provide the motivating examples (ProgressiveEnhancementBaselineFeature, NoJavaScriptRequirement, AuditHooksFeature) that the sub-series treats as already-known.
Related Reading
- typed-specs/04-features.md — "Features as Abstract Classes" — the M2 vocabulary this chapter close-read.
- typed-specs/05-decorators.md — "The three decorators" — the decorator chain this chapter reads against the grain.
- requirement-vs-feature.md — the foundational definitional post. Prerequisite reading for the whole series; explicitly cited by the hub as a pre-read.
- typed-specs-product/10-dogfood.md — the product-level dog-fooding gesture; complemented, not duplicated, by chapter 02 of this series.
- typed-specs/07-roadmap.md — where the earlier series admits the
tracedTogap; this series is the concrete closure of that admission. - closing-the-loop/01-the-closed-loop.html — the broader feedback-loop philosophy that the decorator-layer meta-circle is an instance of.