Skip to content

chore: spell out legacy decision-log citations inline and add the ledger-citation gate (comment-only) - #393

Merged
dean0x merged 4 commits into
mainfrom
chore/legacy-decision-id-sweep
Sep 15, 2026
Merged

dean0x merged 4 commits into
mainfrom
chore/legacy-decision-id-sweep

Conversation

@dean0x

@dean0x dean0x commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Summary

Two decision logs shared one id space: the old repo log (.devflow/decisions/decisions.md, 22 entries, removed from git on 2026-06-27 in 88ddbcc; recoverable with git show 88ddbcc~1:.devflow/decisions/decisions.md) and the current learning ledger, which mints ids automatically as max+1. Roughly seventy source comments still cited the old log's numbers, and six of those numbers now mean something else in the current ledger. This PR fixes the source side: every legacy citation in tracked source is replaced by the decision spelled out inline, and a mechanical gate keeps unminted or retired-meaning ids out of tracked source from now on. Ledger entries are untouched; released CHANGELOG sections are immutable and untouched.

What changed

Commits 1–2 (sweep, no code change). 70 lines across 15 files. watch.rs names the two invariants once in its module doc (freshness rule: the dependency set and the --vars file are re-derived from fresh compile output / from disk on every rebuild; reconcile rule: the idle tick only re-arms watches cheaply, a full rescan happens only on watch loss/recovery) and uses those names at every site; cli_watch.rs points at that module doc. resolver.rs, resolver_tests.rs, error.rs, evaluator.rs spell out frontmatter-first import ordering, leaf re-validation of dynamically assembled content, and origin-rides-along-the-data. Bundler packages (loader.ts, vite-plugin/src/index.ts, hmr-harness.mjs, the four HMR/watch e2e specs) get the same treatment. .gitignore:62 now explains the .devflow/ history in words. One [Unreleased] CHANGELOG line loses a parenthetical id.

Commit 3 (RED) + commit 4 (gate). scripts/verify-ledger-citations.mjs scans tracked files (excluding .devflow/** and CHANGELOG.md) for ledger-id tokens and fails on (R1) a number above the frozen ceiling {ADR: 17, PF: 53} — an id the ledger has not minted, i.e. a plan-local number — or (R2) one of the six frozen denylisted ADR numbers that carried a source-local meaning until this sweep. Exports CEILING, DENYLIST, classify, scanText; exit 1 on findings or zero files, exit 2 on git failure; the token regex is assembled from pieces so the script is self-clean. The spec (8 tests) covers the real tree (≥500 files, ≥1000 tokens — measured 550 / 1217), both rejection rules with positive controls, a legitimate id, scope exclusions, the empty-repo fail-closed path, self-cleanliness of script and spec, and the golden constants. It is picked up by the existing npm run test:gates glob, so CI's source-hygiene job enforces it with no ci.yml or package.json change. Documented residual: a semantic re-collision on an id that is both minted and legitimately cited is not mechanically detectable; the added-lines grep in every PR is the control.

Proofs (each "expect nothing" check paired with a planted positive control that fired)

  • Post-sweep zero-check over the denylisted / above-ceiling id space, scope :!.devflow :!CHANGELOG.md: 70 lines before, no output after; control (ledger/KB files still match) printed.
  • Legacy pitfall-id sites in cli_watch.rs (5) and hmr-harness.mjs (2): gone; the current-meaning sites elsewhere are untouched (control prints two in watch.rs).
  • Added-lines id check over the whole diff and over all four commit messages: no output; controls fired.
  • Comment-only proof, TypeScript/mjs and .gitignore: no output; controls fired. Rust: 3 hit pairs, all in crates/mds-cli/tests/cli_watch.rs (~2826, ~3173, ~3289) — backslash-continued string literals inside assert_eq! panic messages that the sweep table mandated; message text only, no executable change. Reported here rather than treated as clean.
  • git diff --name-only: exactly 17 files (the 15 sweep files + the two new scripts/ files); no .devflow/**, no package.json, no .github/**. The pre-existing local .gitignore working-tree hunk is not in any commit.
  • CHANGELOG diff: one line, above the first released heading.

Gates (local, on dfaaac2)

  • cargo fmt --all -- --check clean (the Rust sweep is a byte-for-byte fmt no-op); both clippy invocations clean; cargo nextest run -p mds-core --test assert_promotions 1 passed
  • cargo nextest run -p mds-core -p mds-cli: 2361 passed (unchanged vs main); doctests 53; cargo +1.88 check clean
  • npm run test:gates: 220 pass / 0 fail (212 + 8); node scripts/verify-ledger-citations.mjs✓ ledger-citation gate: scanned 550 file(s), 1217 citation token(s); none unminted or retired-meaning; positive controls in a throwaway repo: a retired-meaning id → exit 1 "source-local meaning", an unminted id → exit 1 "not minted"
  • node scripts/verify-no-control-bytes.mjs ✓; npm run build -w @mdscript/bundler-utils -w @mdscript/vite-plugin clean

Snyk code scan unavailable (MCP ENOENT); the security/snyk CI check is SCA-only.

Not a release-surface PR. No issue is closed by this PR; context lives in the local v0.4.3 step-D hand-off (decision DEC-1, fix the source side). The learning ledger's own entry about this collision is the Learning agent's to amend after merge.

…h the decisions spelled out inline (no code change)
…gnore and the unreleased CHANGELOG entry (no code change)
…ed and retired-meaning positive controls, scope, non-vacuity, self-clean
…-local) or retired-meaning ledger ids in tracked source outside .devflow/ and CHANGELOG.md
@dean0x
dean0x merged commit 10b9bd6 into main Sep 15, 2026
46 checks passed
@dean0x
dean0x deleted the chore/legacy-decision-id-sweep branch September 15, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant