* chore(#2992): widen the when= grammar and key the section manifest per workflow Epic #1671 Phase 6.1. Two blockers stopped the fragment model reaching any file beyond execute-phase.md: the when= vocabulary was frozen at 4 atoms (3 execute-phase-specific), and the section manifest was single-workflow by construction with 'execute-phase' hardcoded into buildSectionManifestField. - widen WHEN_VOCABULARY 4 -> 14 via a coordinated ADR-1671 amendment; the grammar stays CLOSED (one atom, no operators, negation or nesting) and WHEN_PREDICATES stays a hand-written literal map, never deriving a predicate from its atom string - InvocationFacts gains flags: ReadonlySet<string> plus three computed state booleans; add the missing reverse vocabulary/predicate parity guard - key the manifest artifact per workflow; a stale flat {sections:[...]} artifact now fails shape validation instead of being misattributed - wire the field into six init entry points and parse the flags each needs An atom ships only with both a real consuming section and a fact the init seam actually computes. Six surveyed atoms are withheld because their workflows have no dedicated init entry point; an atom without a computed fact evaluates false forever and silently disables its own section. Fixes a defect found while wiring: parseNamedArgs always materializes a boolean flag key, so folding its false into the absent sentinel is required or every flag reads as present and gating is silently always-on. Also resolves ADR-1671:194 by measurement: --mvp stays unmarkable, because its interleaved sites are always-run flag resolution and a ~340 byte block that already delegates lazily. Refs #2992 * fix(#2992): treat any falsy option value as an absent flag and reject unsafe manifest read paths Findings from two orthogonal reviews (Claude /code-review + an isolated adversarial pass); both independently reproduced the first one. - MAJOR: the flags-builder treated only `undefined` as absent, but parseNamedArgs yields `null` for an absent value-flag and `false` for an absent boolean-flag, so `--granularity` read as present on every plan-phase invocation. Fixed at the root: a flag is present iff its option value is truthy. The six per-handler `|| undefined` folds are now redundant and removed, which also closes the duplicate-translation and missed-onboard-handler findings. - MAJOR: state:needs-codebase-map had zero coverage. Added unit, property and real-CLI integration tests. - MINOR: reject absolute, UNC/drive and `..`-traversing `read` paths in the manifest, degrading the whole load to null like every other shape violation. Verified: `/etc/passwd` previously reached section_manifest.read. - MINOR: corrected a stale "4 to 20" doc comment; the vocabulary is 14. Refs #2992 * test(#2992): update the generator suite for the per-workflow manifest shape The remote matrix went red with 5 unique failures, identical on linux-node22 and linux-node24, all in tests/gen-section-manifest.test.cjs. Re-keying the artifact to {workflows:{...}} left this suite asserting the old flat {sections:[...]} shape; nothing else in the tree still does. - three tests read manifest.sections.length, now undefined; retargeted at workflows.<name> with their original intent preserved (a fenced or loop-host marker still asserts NO section is produced, not merely a changed count) - the stale-manifest test wrote its fixture in the OLD shape, so it tripped shape validation and stopped exercising staleness at all. Its fixture is now valid-but-mismatched so FAIL_STALE is genuinely reached again. - added the coverage that exposed: a pre-6.1 flat artifact must report FAIL_MANIFEST_MALFORMED_SHAPE. That is the real upgrade path for an installed tree and nothing covered it. Refs #2992 * chore(#2992): backfill changeset pr number to 3013 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -381,23 +381,33 @@ grammar, the frozen `when=` vocabulary, and fail-closed authoring rules, and
|
||||
[ADR-1671](adr/1671-dynamic-context-management-platform.md) (open questions 1 and 2) for why
|
||||
in-file markers were chosen over separate fragment files or a sidecar manifest.
|
||||
|
||||
### Section Manifest (`src/section-manifest.cts`, ADR-1671 Phase 5)
|
||||
### Section Manifest (`src/section-manifest.cts`, ADR-1671 Phases 5 and 6.1)
|
||||
|
||||
Two seams turn a workflow's `gsd:section` markers into per-invocation applicability data.
|
||||
`scripts/gen-section-manifest.cjs --write` (wired into `build` after `build:lib`, and into
|
||||
`lint:generated-sync`) scans `gsd-core/workflows/*.md` and writes the committed
|
||||
`gsd-core/workflows/section-manifest.json`: a `{id, when, read}` triple per marked section,
|
||||
where `read` is the path of the step file the section's body was extracted to. It reuses
|
||||
`gsd-core/workflows/section-manifest.json`, keyed **per workflow** —
|
||||
`{workflows: {"<name>": [{id, when, read}]}}` — where `read` is the path of the step file the
|
||||
section's body was extracted to. A workflow with no marked sections contributes **no key at
|
||||
all**: an absent key means degraded/unknown (the caller reads every section, the safe superset),
|
||||
while a key present with an empty array means "computed, nothing applies". The generator reuses
|
||||
`parseWorkflowSections` unchanged rather than re-implementing marker parsing, and fails closed
|
||||
(`--check`) on a marker naming a step file that does not exist or a step file no marker
|
||||
references.
|
||||
(`--check`) on a marker naming a step file that does not exist, a step file no marker
|
||||
references, or a committed artifact still carrying the pre-6.1 flat `{sections: [...]}` shape.
|
||||
|
||||
A separate pure evaluator, `src/section-manifest.cts` (compiled to
|
||||
`gsd-core/bin/lib/section-manifest.cjs` per ADR-457), maps one invocation's facts —
|
||||
`{waveFlag, phaseNumber, hasPriorPhases}` — to an included/excluded partition of section ids
|
||||
via `selectSections`. Per Greenspun's Tenth Rule, this is a total lookup over the frozen
|
||||
`when=` vocabulary, never a parser: it never tokenizes or interprets `when=` structure, and an
|
||||
unrecognized value fails closed rather than being silently excluded.
|
||||
`{flags, phaseNumber, hasPriorPhases}` plus the optional `needsCodebaseMap`, `phaseMvpMode` and
|
||||
`worktreesEnabled` booleans — to an included/excluded partition of section ids via
|
||||
`selectSections`. `flags` is a `ReadonlySet<string>` of flag tokens; because `parseNamedArgs`
|
||||
always materializes a boolean flag key (`false` when the token was absent, never `undefined`),
|
||||
presence is **truthiness**, and the init router folds a boolean flag's own `false` into the
|
||||
absent sentinel before the facts are built. Per Greenspun's Tenth Rule, the evaluator is a total
|
||||
lookup over the frozen 14-atom `when=` vocabulary, never a parser: `WHEN_PREDICATES` is a
|
||||
hand-written literal map that never derives a predicate from its atom string, and an
|
||||
unrecognized value fails closed rather than being silently excluded. An atom is admitted only
|
||||
when it has both a real consuming section and a fact the init seam actually computes — an atom
|
||||
without the latter would evaluate `false` forever and silently disable its own section.
|
||||
|
||||
`execute-phase.md`'s `partial-wave` and `gap-closure-artifacts` sections — previously inlined
|
||||
directly per #2930's pilot — now delegate to dedicated step files under
|
||||
|
||||
Reference in New Issue
Block a user