Files
msd-core/docs/features/runtime-identity.md
Tom Boucher 36375513b9 feat(#3840): generate docs/FEATURES.md from per-feature fragments (#3845)
* feat(#3840): generate docs/FEATURES.md from per-feature fragments

docs/FEATURES.md was hand-maintained, and every feature PR wrote into two
shared mutable cells: the '### N.' heading whose integer was hand-allocated at
authoring time, and the hand-maintained table of contents. Concurrent PRs all
picked the same next integer, and two PRs adding differently numbered features
still collided on the TOC. #3831 was renumbered 165 -> 166 -> 167 -> 168 across
successive rebases, each collision also costing a full matrix verification run
because the sha-keyed pass marker dies with the rebase.

Mechanism: one fragment per feature at docs/features/<slug>.md carrying
id/title/group (and an optional order) in frontmatter, consolidated by
scripts/gen-features.cjs --write|--check into a marker-delimited region of
docs/FEATURES.md that holds BOTH the TOC and every section body. Group headings
and their order are derived too - a group sorts by its lowest-ordered member -
so there is no shared registry to edit either; optional per-group prose lives in
docs/features/_groups/<slug>.md. A contributor adds exactly one new file.
Wired into regen:derived and lint:generated-sync alongside the eight existing
generators, matching gen-adr-index.cjs's CLI shape and typed-REASON reporting.

Migration froze all 168 existing numbers verbatim: identical section set,
identical order, identical bodies. Two defects found in the tree are fixed
inline rather than carried forward - the '## Related' block had been spliced
into the middle of the document, orphaning §142's Reference line, and four
inbound anchors were already broken on next (FEATURES.md#runtime-identity in
two files, and #143-spec-phase-edge-completeness-probe off by one). Since the
repo has no link checker, --check now validates every inbound
FEATURES.md#anchor by resolved target, so that class cannot ship silently
again; locale FEATURES.md files resolve elsewhere and stay out of scope.

Refs #3840

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#3840): carry upstream §69 delta into its fragment and harden the generator

Review found section 69 missing '[--strict]' and REQ-STATE-05/06 versus
origin/next. Root cause was a stale base, not extraction loss: those lines
landed in 394bf384b (#3844) AFTER this branch forked at 63abcface, and
'git diff 63abcface origin/next -- docs/FEATURES.md' is exactly that hunk.
Merging origin/next auto-applied the hunk into the GENERATED region, which
--check immediately reported as stale; the delta is now carried in
docs/features/statemd-consistency-gates.md and regenerated from there.

--write is now fail-closed. It previously rendered the region even with
violations outstanding, warning only on stderr and exiting 0, so a
'--write && git commit' chain could commit a FEATURES.md carrying two
colliding sections. It now refuses and exits 1; --force is the explicit
override and says so in the report. The test that pinned the old behavior now
pins the refusal, plus the --force override and its scoping.

Marker forgery is rejected at two layers. A fragment body containing
'<!-- FEATURES:START' or '<!-- FEATURES:END' is a typed
body_forges_region_marker violation (fragments and group notes alike), and
spliceIntoFeatures anchors the end boundary with lastIndexOf instead of
indexOf, so a marker that reaches the document by any other route can only
make the generated region grow, never shrink. Matching is on marker PREFIXES,
so a decorated variant comment cannot slip past.

Symlinked corpus entries are refused with a typed dirent_not_regular_file
rather than read. A fork PR could otherwise commit docs/features/evil.md as a
symlink to any readable path and have the generator inline those bytes into
the committed docs/FEATURES.md on the next regen.

Equivalence re-verified with a method that cannot cancel out. The first
check extracted both operands with the same body-normalising helper, so
anything that helper dropped was dropped on both sides. The replacement runs
two independent passes: a global content-line multiset diff with no
per-section logic at all (0 gained, 19 lost, all 19 the stale hand-written
mini-TOC links this change deliberately deletes), and a per-section
byte-exact body diff carrying a coverage assertion that fails loudly per file
when the extractor accounts for fewer lines than the file contains. That
assertion caught two blind spots in the checker itself. 168/168 sections
present, order identical, one intended body difference (§142 regains the
Reference line orphaned by the misplaced '## Related' block).

Refs #3840

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#3840): backfill changeset PR number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:49:00 -04:00

3.2 KiB

id, title, group
id title group
168 Runtime Identity v1.7.0 Features

Purpose: The predecessor package get-shit-done-cc publishes a binary named gsd-tools, and so does this one. They answer some of the same verb names with different semantics. #3129 is the worked example: phases.clear archives here and deletes there. Both print success-shaped output, and .planning/ is gitignored by default, so a user lost 43 phase directories with no error, no warning, and nothing recoverable from git. The failure was silent in both directions — the workflow could not tell it had reached the wrong handler, and the handler could not tell it had been called by a workflow written for a different contract (#3146).

Behavior: the launcher's PATH resolution branch now looks for gsd_run instead of gsd-tools. Only this package publishes gsd_run; the predecessor publishes gsd-tools and gsd-sdk. Our gsd_run follows its own symlink chain and executes the gsd-tools.cjs sitting beside it, so resolving it cannot land on a foreign handler. Separately, gsd-tools runtime-identity reports this runtime's package coordinates, so a human or a support thread can settle "which tool am I actually running?" in one command.

This eliminates the failure rather than reporting it. An earlier iteration of this work asserted identity inside the shared launcher preamble and warned when it could not be confirmed. That approach was abandoned for two reasons. First, a warning only helps a reader who acts on it. Second, and decisively, the preamble is inlined into 113 shipped files, several of which sit within single-digit bytes of frozen size ceilings — agents/gsd-verifier.md had 2 bytes of headroom — and those caps are red lines, not budgets. The resolver change is smaller than what it replaces, so every one of those files got slightly further from its ceiling.

It fails closed. If no gsd_run is reachable, the resolver falls through its remaining path-based branches and finally errors with an install command. It does not fall back to executing whatever gsd-tools happens to be on PATH — that fallback was the vulnerability.

A doubly-sourced preamble cannot build a recursive launcher. command -v gsd_run finds the shell function on a second source and would return the bare string gsd_run; an executability guard rejects a non-path result, so the function can never be defined in terms of itself.

Known limits: an installation of this package old enough to predate bin/gsd_run (#381) is no longer reachable through the PATH branch and must be upgraded or invoked through one of the path-based branches. The runtime-identity verb is a manual diagnostic, not an automatic gate — nothing currently asserts identity on every invocation, which remains open for a future release now that the byte budget is understood. Path-based resolution branches are unchanged and still trust their configured location.

Reference: runtime-identity · Diagnose which gsd-tools is running