Resolve seven open decisions from the deferred-issue audit - #330
Merged
Conversation
Resolves #200. Closes #200. When the same variable key appears more than once within a single --set or --set-string group, the CLI now emits a warning to stderr so the collision is visible. The last value still wins (unchanged behaviour). Using the same key in both --set and --set-string remains a hard error. Warning strings (stable contract): warning: variable 'x' is set more than once by --set; the last value wins warning: variable 'x' is set more than once by --set-string; the last value wins Shape constraint (AD-224-3 / AD-224-5): - New RuntimeVars struct carries vars + duplicate_set_keys + duplicate_set_string_keys. - emit_duplicate_var_warnings owns the quiet check; safe_inline(key) is written syntactically at the interpolation site (not hoisted) to satisfy the print-discipline lexical guard. Call-site policy (9 sites): Emit (5): build.rs run_build, main.rs run_check, lint.rs, watch.rs:1146 (file_watch_startup), watch.rs:2064 (dir_watch_startup) Discard (4): watch.rs:935 (rebuild_file), watch.rs:1789 (rebuild_dir_batch), watch.rs:1914 (handle_dir_event), watch.rs:2185 (dir_watch_startup baseline — double-print trap; I9 is the sole mechanical guard) Negative-control observations (I8/I9): Perturbation 1 (emit at :935): I8 count rose to 7 (expected >= 3). Perturbation 2 (emit at :2185): I9 count became 2 with no edits (expected 2). Perturbation 3 (emit at :1914): I9 count grew to 4 after one rebuild (expected >= 2). All three perturbations reverted; I8/I9 pass clean. Tests added: U1-U5 (unit), I1-I9 (integration), plus cli_commands.rs stderr assertion on the pre-existing set_flag_duplicate_key_last_wins test. Docs: CHANGELOG [Unreleased]/Added, spec.md §7.2/§7.5, README.md, 8 clap doc comments.
Adds §5 "Project Root" subsection explaining the .mdsroot/.git walk-up, two starting points, the non-precedence rule between markers, the depth-256 silent fallback, and the two governed behaviours (import containment and source-map relativization). Disambiguates §7.8 so "project root" no longer refers informally to the repository root, and clarifies that mds.json discovery is an independent walk whose relative paths resolve against the config-containing directory. Adds import-containment bullet to §4.6. Closes #201 (part) Co-Authored-By: Claude <noreply@anthropic.com>
Adds a new "## Project root" section explaining the containment boundary, the walk-up discovery mechanism, when to create .mdsroot, and the narrowing footgun. Adds a one-line cross-reference at the first mention of multi-file templates. Extends the path-traversal bullet in SECURITY.md to define the project root and flag .mdsroot placement as security-relevant. Appends links to examples/source-maps/README.md and examples/python/README.md. Closes #201 Co-Authored-By: Claude <noreply@anthropic.com>
Adds an `# S-tag provenance legend` section to the `//!` module doc in `sourcemap.rs`. Four tags are defined (S3, S6, S7, S8), each derived from the call sites in tracked source only. Three sub-sections clarify common confusion: * `## S5 is not Stage 5` — proves via the `S5/stage-2` and `S5/stage-4` test case names in `source_map_vfs.rs` that the S-tag numbering and the Stage finalization pipeline are two independent schemes. No positive meaning for S5 is stated (it lives only in an untracked local document). * `## S9 is struck` — records that S9 has zero occurrences in `crates/mds-core/src/` in both the working tree and all of git history, and names where the resource-bound work it would have covered (segments_dropped, no_sources_content, MAX_SOURCEMAP_SEGMENTS, MAX_SOURCES_CONTENT_BYTES) actually shipped (untagged, documented at each item). * `## Scope of this legend` — names the four files carrying S-tags, excludes PF-*/ADR-*/AC-* identifiers, notes the B1 modification to S7 (source_map_mode gate dropped, Origin now unconditional), and clarifies that S1/S2/S14 elsewhere are unrelated slice labels. Doctest count: 51 before and after insertion (no new doctests introduced by the legend). Cargo doc: zero warnings before and after. Closes part of #188. Co-Authored-By: Claude <noreply@anthropic.com>
Issue #114 (narrowed on 2026-08-26) now covers the deferred per-region source-map attribution work for @extends modules. Three comments that still referenced S8 for that work are re-tagged to #114: * `ResolvedModule::prompt_map` field doc: "(S8 will add those)" → "(tracked as #114)" * `finalize_extends` body: "is deferred to S8; spanless is the safe interim." → "is tracked as #114; spanless is the safe interim." * `finalize_extends` Ok block: "is deferred to S8." → "is tracked as #114." A fourth site — the map-off `else` branch comment at the @extends finalize call (no S8 token, but named by #114 as the sibling path) — gains "(#114)" at its closing line so the issue reference is traceable without adding a new phase label. No executable code changes; comments only. Closes part of #188. Co-Authored-By: Claude <noreply@anthropic.com>
C1: remove `- Source maps` from §10 deferred list (shipped in v0.4.0, already documented in §7.5). C2: add --source-map, --no-source-map, --inline, --embed-sources rows to the §7.2 build options table, with conflicts-with / requires relationships derived from the clap attributes. Add one example line to the §7.2 code block (aligned to column 44). Messages-mode behaviour noted: a warning is emitted at both resolver code paths and no source map is produced. C2b: add build.source_map (bool, default false) and build.embed_sources (bool, default false) to the §7.8 mds.json config-field table. --no-source-map's description references build.source_map; the table row makes that field discoverable without chasing a flag description. Co-Authored-By: Claude <noreply@anthropic.com>
spec.md had two sections numbered "## 9." — "Complete Example" and "Editor Integration". Renumber so §9 through §13 are sequential: ## 9. Complete Example (unchanged — first occurrence) ## 9. Editor Integration → ## 10. Editor Integration ### 9.1 File Association → ### 10.1 File Association ### 9.2 Frontmatter … → ### 10.2 Frontmatter … ### 9.3 MDS-Specific … → ### 10.3 MDS-Specific … ## 10. What's NOT in v0.2 → ## 11. What's NOT in v0.2 ## 11. Grammar Summary → ## 12. Grammar Summary ## 12. Status → ## 13. Status No spec.md anchor links exist anywhere in the repo, and no §9–§12 cross-references exist in spec.md, so no in-repo link breaks. Known follow-up: "## 11. What's NOT in v0.2" still carries a stale version label — retitling is a separate editorial decision and is out of scope for this commit. Co-Authored-By: Claude <noreply@anthropic.com>
…or the #114 reference sourcemap.rs: The S9 verification claim ("zero occurrences in the working tree and in all of git history") was falsified by the very commit that introduced it — commit 99fa56d added the legend text containing S9 itself, so both the working-tree grep and git log immediately return non-empty results. Fixed by scoping the claim: "outside this legend text, S9 has zero occurrences" (verified against 6d41777, the revision immediately before the legend was added), and naming 99fa56d as the sole introduction commit. Applies PF-015 (avoid absolute completeness claims) and PF-028 (claims decay silently — re-verify before asserting). resolver.rs: The (#114) reference on the map-off else-branch was attached to the final sentence — "The source-map branch above keeps spans correct by evaluating per-region with each region's own origin" — making shipped, working behaviour read as deferred work. Moved (#114) to the limitation clause ("no single source can attribute every node's offset"), which is the actual scope of issue #114, and restored the final sentence to end without a tag.
The section lists permanent language-design exclusions — no in-template JS/TS scripting, no macros/async/streaming, no URL imports, no function calls in @if/@for, no parenthesized sub-expressions, no negative slice() indexing, no array element indexing. None of these were v0.2-era deferrals; they are deliberate, indefinite design choices. A version-pinned heading ("What's NOT in v0.2") was already stale at v0.4 and would restale again at each future release. "Out of Scope" is version-agnostic and accurate. No cross-references to the old heading were found in any other tracked file; no TOC or anchor links required updating.
Closes sub-item #34 of issue #219 (Rust minor cleanups from PR #196 review). The owner confirmed this is the intended site; the original issue text named a symbol `finalize_sub` that has never existed in repo history — the correct site is `finalize` in MapBuilder. The underflow (fm_prefix_len > final_body.len()) is unreachable in practice: fm_prefix_len is the byte length of a prepended frontmatter block, which cannot exceed the total final body length under normal compiler flow. The saturating_sub is therefore a zero-cost latent-defect guard — if the impossible case were ever hit, it saturates to 0, which causes clamp_trailing_trim to drop all segments and the existing body_clean_len == 0 early-return to produce an empty SourceMap, a correct and benign result rather than a panic. Behaviour for all currently reachable inputs is unchanged. Note: #219 remains open (sub-item #28 is still outstanding).
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A 2026-08-26 audit verified 57 "deferred past v0.4.0" issues on roadmap tracker #69 against
main. Seven items needed an owner ruling rather than engineering. All seven now have an approved disposition: four were pure tracker changes (already applied — see below), three required code or documentation work, which is what this PR contains.Nothing here is on the v0.4.0 tag path.
Commits
feat(cli): warn on duplicate --set / --set-string keysdocs(spec): define the project root and disambiguate mds.json discoverydocs(readme): document the .mdsroot project-root markerdocs(sourcemap): add the S-tag provenance legenddocs(resolver): re-tag @extends attribution comments to #114docs(spec): source maps shipped; document the build source-map flagsdocs(spec): fix duplicate section 9 numberingdocs(sourcemap,resolver): scope the S9 verification claim and re-anchor the #114 referencedocs(spec): retitle section 11 to "Out of Scope"fix(sourcemap): guard body_clean_len subtraction with saturating_subdocs(sourcemap): correct clamp_trailing_trim doc comment for the saturating boundCommit 7 is deliberately isolated and can be dropped without reworking commits 1-6.
#200 — warn on duplicate
--set/--set-stringkeysRepeating a key within one flag group silently kept the last value. It now warns; last-value-wins behaviour is unchanged. Using the same key in both flags remains a hard error, and that check still runs first.
Stable user-facing contract:
build_runtime_varsreturns aRuntimeVarsstruct carrying the duplicated keys; a namedemit_duplicate_var_warningsowns thequietcheck and wraps each key assafe_inline(key)syntactically at the interpolation site (AD-224-3 / AD-224-5), which is why noALLOWED_UNTRACED_HELPER_ARGSentry was needed —print_discipline.rsis unchanged.Returning pre-formatted message strings was rejected: the print-discipline guard cannot follow a value across a function boundary, and a pre-existing
("build.rs", "w", …)allowlist entry would have auto-exempted the new loop, silently inheriting a justification that would then be false.Call sites: 5 emit, 4 discard. Emitting sites run once per process; rebuild paths never call the emitter, so watch mode produces no repeated noise.
dir_watch_startupcallsbuild_runtime_varstwice — the second call discards, or directory-watch startup would double-print.Negative controls
"Warns exactly once" passes vacuously if the warning never fires, so each perturbation was run, observed, and reverted:
watch.rs:935watch.rs:2185watch.rs:1914Tests: U1–U5 unit, I1–I9 integration. I7 covers WIRE-escaping with a hostile key, asserting non-vacuity before absence (PF-013) and using braced
\u{1b}/\u{202e}escapes (PF-018).#201 — document
.mdsrootPreviously undocumented, so users hitting import-containment errors had no way to discover the fix.
There is no precedence between
.gitand.mdsroot— the nearest ancestor holding either marker wins. The docs state this positively and name the resulting footgun: adding.mdsrootbelow an existing.gitnarrows the containment envelope. A mechanicalgrep -niE 'override|priorit|takes precedence|wins over'confirms none of those words appear in the new prose.Documented: both starting points (the compiled file's directory, or the base directory a string compile supplies), depth 256, silent fallback, marker-not-config (contents never read), resolved once per compilation, and the two governed behaviours — import containment and SMv3
sources[]relativization.mds.jsondiscovery is an independent walk sharing only the depth constant.The open UX question — whether
find_project_rootshould warn or error on a missing marker — is deliberately not settled here.#188 CX-6 — S-tag provenance legend
The
S-prefixed tags inmds-corehad no legend in tracked source, andS8was actively wrong. Adds a//!legend covering S3, S6, S7, S8, plus three disambiguations:S5is notStage 5. Proven from tracked sources only:source_map_vfs.rsnames casesS5/stage-2andS5/stage-4, pairing one S-tag with two Stage numbers. The legend states only this negative — S5's positive meaning exists solely in an untracked note the legend declares non-authoritative.S9is struck. Zero occurrences insrc/at any commit; the resource-bound work it would have named shipped untagged and is documented at each item.B1because it modifies S7 — it dropped thesource_map_modegate, soOriginis now populated unconditionally (verified atevaluator.rs:653).Three
S8comments inresolver.rsre-tagged to #114, narrowed on 2026-08-26 to exactly this remaining work, plus a fourth previously-untraceable site.Spec staleness
Deleted
- Source mapsfrom the exclusions list in spec.md §11 — the section was titled "What's NOT in v0.2" at the time of that commit and is titled "Out of Scope" today, retitled later in this same PR by34af1ca. Source maps shipped in v0.4, and §7.5 already spends ~30 lines on source-map path semantics.Added
--source-map,--no-source-map,--inline,--embed-sourcesto §7.2, with conflicts/requires traced to the clap attributes, plusbuild.source_map/build.embed_sourcesto §7.8 (required companion — the--no-source-maprow references the config field).Fixed the duplicate
## 9heading. Anchor-safety re-proven before renumbering: nospec.md#link exists anywhere in-repo, and no§9–§12cross-reference exists in tracked source. All eight surviving§references still resolve.Retitled the renumbered heading to the version-agnostic
## 11. Out of Scope(34af1ca). Renumbering had left## 11. What's NOT in v0.2— a stale version number in a v0.4 spec. The owner chose a version-agnostic title over a mechanical bump to "What's NOT in v0.4" because the section lists permanent language-design exclusions, not v0.2-era deferrals; a version-pinned heading would just go stale again every release.A repo-wide sweep before the retitle found no TOC entry, cross-reference, or markdown anchor targeting the old heading, and confirmed no in-repo link targets
spec.md#…at all. The otherv0.2mention inspec.md— thev0.2.0 - Language enrichment releaseentry under## 13. Status— was deliberately left untouched: that is a legitimate version-history record, not a stale title.#219 sub-item #34 —
saturating_subguard insourcemap.rs2a5d252changeslet body_clean_len = final_body.len() - fm_prefix_len;tosaturating_subinsidepub(crate) fn finalize(crates/mds-core/src/sourcemap.rs:823). The owner confirmed this is the site the original reviewer meant — the symbol the review text named (finalize_sub) has zero occurrences in the entire git history, so the location was reconstructed and then confirmed rather than recovered.No behavior change.
body_clean_lenhas exactly two uses infinalize:clamp_trailing_trim(points, body_clean_len), which drops every segment when the bound is 0, and an immediately followingif body_clean_len == 0early return producing an emptySourceMap. A saturated 0 therefore yields the empty map that is already the correct result whenfm_prefix_len >= final_body.len(). No regression test was added: the underflow is unreachable by design, and the acceptance criteria required onlysaturating_subplus a clippy-clean build.The issue's stale
sourcemap.rs:744anchor was re-anchored to:823in the same pass — the S-tag legend commit in this PR shifted the file, and:744is now the closing brace of the unrelatedpub(crate) fn source_index.Doc comment corrected (
4af7ef4). The doc block onclamp_trailing_trimstill described the pre-2a5d252arithmetic:It now names
.saturating_sub(fm_prefix_len). No behavioral clause was added — the surrounding doc block is terse, and a caveat there would not have matched its register. Correcting it is part of leaving the end state rather than the transition: a comment describing arithmetic the caller no longer performs is exactly the residue a fix tends to leave behind.#219 stays open for sub-item #28 (unbounded loop form in
formatter.rs). This PR does not close it.Verification
Measured at
4af7ef4cf146fee082e0f47c272afc28ff1e4ca0— the branch head when this table was stamped — with a clean working tree (git status --porcelainempty). The results below are a claim about that commit only. Any commit landing on this branch afterwards invalidates them until the suite is re-run and this table re-stamped; read an unmatched head SHA as "unverified", not as "passing".cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo nextest run --workspacecargo test --docnode scripts/verify-no-control-bytes.mjsnpm run test:gatesThe doctest count is load-bearing: an unadorned ``` fence inside a
//!comment compiles as a doctest, so the legend commit had to leave the count identical, not merely passing.An alignment audit checked all 19 negative constraints and 10 positive requirements by reading diffs rather than commit messages. It found two real defects, fixed in commit 8:
6d41777.(#114)was attached to a clause describing what the source-map branch already does correctly, making shipped behaviour read as deferred. Moved onto the map-off path's actual limitation.Related Issues
Closes #200
Closes #201
Closes #188
Tracker dispositions applied separately (no code): #197 closed as not planned; #198 reframed as a feature decision and kept open; #219 sub-item #53 dropped; #214 AC1 amended to
FileNotFoundErrorand marked satisfied. Follow-ups filed as #326, #327, #328, #329.