From ac2bf0b6e044a3bfcced95e93cc635e013762fb7 Mon Sep 17 00:00:00 2001 From: sokoly Date: Fri, 1 May 2026 19:44:45 -0400 Subject: [PATCH 1/4] trace: seed SYS-029 + HLR-069 + LLR-076 + TEST-083 for editor-duplicate guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System-level claim: the tool's source tree shall be free of editor- duplicate artifacts (` N.` filenames produced when interactive shell ops or save-as dialogs go wrong). These artifacts compile, hash, and inflate floor counts silently — a manual `find` sweep catches them today, but only retroactively. Mechanical guard lands as `editor_duplicates_locked.rs` in the follow-up commit. Same shape as `rot_prone_markers_locked`: assert the tree is clean, with a positive + negative dogfood pair and a RESERVED_DUPLICATE_PATHS escape hatch (initially empty). cert/floors.toml bumps trace_sys 28→29, trace_hlr 68→69, trace_llr 75→76, trace_test 80→81. Co-Authored-By: Claude Opus 4.7 (1M context) --- cert/floors.toml | 8 ++++---- cert/trace/hlr.toml | 42 ++++++++++++++++++++++++++++++++++++++++++ cert/trace/llr.toml | 43 +++++++++++++++++++++++++++++++++++++++++++ cert/trace/sys.toml | 41 +++++++++++++++++++++++++++++++++++++++++ cert/trace/tests.toml | 31 +++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 4 deletions(-) diff --git a/cert/floors.toml b/cert/floors.toml index 0dffe53..9c37383 100644 --- a/cert/floors.toml +++ b/cert/floors.toml @@ -50,13 +50,13 @@ diagnostic_codes = 151 terminal_codes = 13 # cert/trace/sys.toml — System Requirements. -trace_sys = 28 +trace_sys = 29 # cert/trace/hlr.toml — High-Level Requirements. -trace_hlr = 68 +trace_hlr = 69 # cert/trace/llr.toml — Low-Level Requirements. -trace_llr = 75 +trace_llr = 76 # cert/trace/tests.toml — Test Cases. -trace_test = 80 +trace_test = 81 # evidence_core::trace::surfaces::KNOWN_SURFACES length — hand-curated # catalog of CLI verbs + named observable contracts. Matching HLR diff --git a/cert/trace/hlr.toml b/cert/trace/hlr.toml index ff4c40b..6af7aca 100644 --- a/cert/trace/hlr.toml +++ b/cert/trace/hlr.toml @@ -1654,3 +1654,45 @@ The intercept should reuse that render tree, not paper over it. verification_methods = ["test"] traces_to = ["385c2c4c-748c-4486-bd8c-7622b12c9273"] surfaces = ["agent MCP surface"] + +[[requirements]] +uid = "202c2281-f2d7-4e43-8a6e-93da96f92add" +id = "HLR-069" +title = "Repository contains no editor-duplicate artifacts (` N.` filenames)" +owner = "tool" +scope = "component" +description = """ +A mechanical gate refuses any path under the repository whose +basename matches the editor-duplicate pattern: stem + ` ` (single +ASCII space) + small-integer suffix + extension, for any of the +extensions the project recognizes as cert-relevant +(`.rs`, `.toml`, `.yml`, `.yaml`, `.md`, `.json`, `.lock`). + +Concrete pattern, fixed in the test: + + - Regex (basename, anchored): `^.+ ([0-9]{1,2})\\.(rs|toml|yml|yaml|md|json|lock)$`. + - Walks `crates/**`, `cert/**`, `tool/**`, `.github/**`, plus + repo-root `*.md` / `*.toml`. Excludes `target/`, `.git/`, + `node_modules/`, `fixtures/`, `cert/trace/` (audit journal). + - Also walks `.git/refs/` for branch-name duplicates (the + `main 2` ref artifact a misbehaving git client occasionally + leaves behind). + +Failure mode the gate prevents: a file like +`crates/cargo-evidence/tests/cli 2.rs` lands silently, compiles +into the test binary, inflates `#[test]` counts seen by the +floors gate, and survives in-tree until a manual sweep catches +it. The user's auto-memory captures the same trap +(`project_stray_2rs_artifacts.md`) — promoting it to a project- +level mechanical gate makes the rule observable in CI rather +than relying on memory recall. + +Reserved-text-style escape hatch: the test exposes a +`RESERVED_DUPLICATE_PATHS` const, initially empty. An editor +artifact that is genuinely load-bearing (say, a third-party +file the project consumes whose name happens to match the +pattern) gets an entry here with written justification. The +default state is zero exemptions. +""" +verification_methods = ["test"] +traces_to = ["4b63a601-9770-4c3a-bfe4-e1d70e050c65"] diff --git a/cert/trace/llr.toml b/cert/trace/llr.toml index 0c76634..15d18f9 100644 --- a/cert/trace/llr.toml +++ b/cert/trace/llr.toml @@ -2409,3 +2409,46 @@ The intercept lives at the top of `main()` (before the errors out. """ verification_methods = ["test"] + +[[requirements]] +uid = "f634ed35-6c3e-4d71-90a2-dbaee1d086ab" +id = "LLR-076" +title = "editor_duplicates_locked walks the repo and fires on ` N.` filenames" +owner = "tool" +traces_to = ["202c2281-f2d7-4e43-8a6e-93da96f92add"] +modules = ["evidence_core::tests::editor_duplicates_locked"] +derived = false +description = """ +Integration test under `crates/evidence-core/tests/`. Walks the +workspace from `CARGO_MANIFEST_DIR`'s grandparent (the repo root) +using `walkdir::WalkDir::new(root).follow_links(false)`, prunes +the conventional skip directories +(`target`, `.git`, `node_modules`, `fixtures`), and emits a +collected list of every basename matching the pinned regex: + + ```text + ^.+ ([0-9]{1,2})\\.(rs|toml|yml|yaml|md|json|lock)$ + ``` + +The leading-space-then-digit anchor matters: it specifically +catches the `cp old.rs 'old 2.rs'` artifact pattern without +flagging legitimate filenames that happen to contain a number +(e.g. `mcdc_2024.rs` is fine because there's no space before +the digits). + +Test fails via `assert!` with a complete file:line listing for +every offending entry. Mirrors the failure shape of +`rot_prone_markers_locked` — the test's failure message is the +diagnostic; no `Diagnostic` wire shape, no `RULES` entry. + +Exemption escape hatch: `RESERVED_DUPLICATE_PATHS` const in the +test module names workspace-relative paths (no glob; suffix +match) where the editor-duplicate-shaped name is actually +intentional. Initially empty. Each entry requires written +justification beside the const. + +Cross-platform note: walks paths via `walkdir` and normalizes +separators to `/` before suffix-matching the reserved list, per +the `project_path_separator_on_windows` memory. +""" +verification_methods = ["test"] diff --git a/cert/trace/sys.toml b/cert/trace/sys.toml index dfc294b..b3d62b9 100644 --- a/cert/trace/sys.toml +++ b/cert/trace/sys.toml @@ -711,3 +711,44 @@ noticed. The observable is now structured. """ verification_methods = ["test"] traces_to = [] + +[[requirements]] +uid = "4b63a601-9770-4c3a-bfe4-e1d70e050c65" +id = "SYS-029" +title = "Tool's source tree shall be free of editor-duplicate artifacts" +owner = "soi" +scope = "soi" +description = """ +Editors and shell tools occasionally produce duplicate-named +files when interactive operations like `cp old new` go wrong, or +when a save-as dialog defaults to the original name. The +resulting filenames carry a numeric suffix on the basename +(e.g. `helpers 2.rs`, `audit 2.yml`, `sys 2.toml`, +`main 2` git ref). These artifacts compile, run, and are hashed +just like first-class files — but they: + + - Inflate `#[test]` counts (the floor gate ratchets to a + measurement that includes phantom tests), + - Pollute the trace-link bijection (a sibling `tests 2.toml` + silently doubles every entry in the regular `tests.toml`), + - Pollute SHA256SUMS (every editor-duplicate landing in a + cert bundle becomes a hashed audit artifact), + - Survive in-tree until a manual `find -name '* 2.*'` sweep + catches them. + +The system-level claim: the tool's own source tree, the bundle- +output paths it walks, and the trace store under `cert/trace/` +shall be mechanically free of these artifacts. A pre-commit / +CI gate fires on any path matching the editor-duplicate naming +pattern across the repository, refusing to land changes that +introduce one. + +Out of scope: artifacts a downstream project under audit may +have in their own source tree are theirs to manage; this rule +applies only to the tool's repo. The mechanical guard's +filename pattern is fixed (regex pinned in the test) so an +editor that uses a different suffix shape (e.g. `~`, `.bak`) +gets a separate exemption story rather than silent acceptance. +""" +verification_methods = ["test"] +traces_to = [] diff --git a/cert/trace/tests.toml b/cert/trace/tests.toml index b554d6b..51ec69c 100644 --- a/cert/trace/tests.toml +++ b/cert/trace/tests.toml @@ -1229,3 +1229,34 @@ test_selectors = [ "help_listing::test_cargo_evidence_help_lists_subcommands", "help_listing::test_cargo_evidence_dash_h_exits_zero", ] + +[[tests]] +uid = "d7e510f6-733b-451e-9902-db37efba063d" +id = "TEST-083" +title = "editor_duplicates_locked: clean tree + dogfood fires on synthetic duplicate" +owner = "tool" +traces_to = ["f634ed35-6c3e-4d71-90a2-dbaee1d086ab"] +description = """ +Three branches mirroring the pattern of +`rot_prone_markers_locked`: + + - Load-bearing regression: the current tree is clean. Walks + the workspace via `walkdir::WalkDir::new(root) + .follow_links(false)`, applies the editor-duplicate regex + to each basename, asserts the hit list is empty. + + - Positive dogfood: writes a synthetic `crates/fake/src/ + helpers 2.rs` into a tempdir tree and asserts the same scan + function fires on it (1+ hit, the basename appears in the + failure listing). + + - Negative dogfood: writes a tempdir tree containing + `mcdc_2024.rs` (a legitimate filename whose digits happen to + follow non-space characters) and asserts zero hits — the + leading-space anchor must not over-flag. +""" +test_selectors = [ + "editor_duplicates_locked::current_tree_is_clean", + "editor_duplicates_locked::fires_on_synthetic_duplicate", + "editor_duplicates_locked::passes_on_legitimate_digits_filename", +] From d02b1326e45b2379c2a714f1ad305dc04467c40f Mon Sep 17 00:00:00 2001 From: sokoly Date: Fri, 1 May 2026 19:52:21 -0400 Subject: [PATCH 2/4] feat(stewardship): editor-duplicates gate + CODEOWNERS + CONTRIBUTING + cli/README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo-stewardship sweep for v0.1.4. Closes four review-flagged TODOs at modest cost: 1. `editor_duplicates_locked.rs` — mechanical guard against ` N.` editor-duplicate filenames (`helpers 2.rs`, `tests 2.toml`, `audit 2.yml`, etc.). Walks the workspace, fires on any basename matching the pinned regex. Same shape as `rot_prone_markers_locked`. Three tests: clean tree regression, positive dogfood (synthetic duplicate), negative dogfood (legitimate digit-bearing filename). 2. `CODEOWNERS` — single-maintainer default with cert-track paths (`cert/`, `cert/trace/`) called out separately so a future split between code and certification review is mechanical. 3. `CONTRIBUTING.md` — PR loop, local CI gates, trace-first convention, floors-only-up rule, the high-impact style snapshots. New contributors get the rules without having to back-derive them from CLAUDE.md. 4. `crates/cargo-evidence/src/cli/README.md` — taxonomy of the 24 CLI files grouped by lifecycle phase (bundle-producing vs bundle-consuming vs source-tree-inspection vs self-describing). Adding-a-new-verb checklist included. cert/floors.toml bumps per_crate.evidence-core.test_count 358→361 (+3 from the new locked test). Co-Authored-By: Claude Opus 4.7 (1M context) --- CODEOWNERS | 25 +++ CONTRIBUTING.md | 125 +++++++++++++ cert/floors.toml | 2 +- crates/cargo-evidence/src/cli/README.md | 113 ++++++++++++ .../tests/editor_duplicates_locked.rs | 167 ++++++++++++++++++ 5 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 crates/cargo-evidence/src/cli/README.md create mode 100644 crates/evidence-core/tests/editor_duplicates_locked.rs diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..cbf4c86 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,25 @@ +# CODEOWNERS for cargo-evidence. +# +# Single-maintainer project today. Default owner gets review +# requests on every PR. As the contributor base grows, narrow +# this file to per-directory ownership rather than expanding the +# global wildcard. +# +# Cert-track files (boundary, floors, trace) are called out +# separately so a future split between code review and +# certification review is mechanical: these paths own the +# DO-178C/DO-330 evidence shape, and a change there warrants a +# cert-aware reviewer regardless of how the codebase grows. + +* @luofang34 + +# Cert-track configuration +/cert/ @luofang34 +/cert/boundary.toml @luofang34 +/cert/floors.toml @luofang34 +/cert/trace/ @luofang34 + +# Workflow + repo-stewardship files +/.github/ @luofang34 +/CODEOWNERS @luofang34 +/CONTRIBUTING.md @luofang34 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d46c9f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# Contributing to cargo-evidence + +This project ships software-of-interest (SoI) for DO-178C / DO-330 +certification evidence. Every change has to clear the same gates a +downstream cert program would expect: trace coverage, ratchet floors, +deterministic bundles. The PR loop below mechanizes those gates. + +If anything below is wrong or unclear, open an issue or PR — the +guidance evolves with the toolchain. + +--- + +## PR loop + +A landed PR carries: + +1. **A trace-seed commit** (when adding a behavior an auditor would + review): SYS / HLR / LLR / TEST entries under `cert/trace/`, + added in the first commit on the branch, before any + implementation. The trace chain is the contract; the code + implements it. See [Trace-first convention](#trace-first-convention) + below. +2. **An implementation commit** that satisfies the seed. +3. **A guardrail in the same PR** — a test, lint, or CI check that + prevents this same regression from recurring. A fix without a + guardrail is temporary. + +Both commits must be locally CI-clean before pushing. The PR landing +that's "almost green" tomorrow gets reverted today. + +## Local CI + +The minimum command set, mirroring `.github/workflows/ci.yml`: + +```bash +cargo fmt --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace --all-targets +RUSTDOCFLAGS='-D missing_docs -D rustdoc::broken_intra_doc_links' \ + cargo doc --workspace --no-deps +cargo build --release --workspace +cargo run -p cargo-evidence -- evidence trace --validate +cargo run -p cargo-evidence -- evidence floors --format=jsonl +``` + +The trace + floors gates are project-internal — they catch most +self-cert regressions before they reach CI. Don't skip them. + +## Trace-first convention + +Default: every PR seeds its SYS/HLR/LLR/TEST entries in the first +commit on the branch, before any implementation. UUIDs are +generated at seed time (`python3 -c "import uuid; print(uuid.uuid4())"`). +Each trace entry's `traces_to` array points at the parent UID one +layer up. + +Each trace seed bumps the matching counter in `cert/floors.toml` +(`trace_sys`, `trace_hlr`, `trace_llr`, `trace_test`). After the +implementation commit lands its tests, also bump the affected +`per_crate..test_count` row to match the new measurement. +The `floors_equal_current_no_slack` test enforces equality, not +inequality — drift either way fails CI. + +Exception — bidirectional contracts spanning two PRs: when a single +SYS-level claim covers both directions of a contract (e.g., forward- +enrichment in one PR + reverse-verification in a follow-up), the +*second* PR seeds the chain for both halves. The first PR ships +under an implicit trace obligation; the second PR's chain-seed +discharges it for both. This is rare — only legitimate when the two +halves form one logical deliverable and splitting the chain would +force referencing UUIDs that don't yet exist. + +## Floors are ratchet-only + +`cert/floors.toml` is a one-way gate: `current >= committed_floor` +on every dimension. The `current_measurements_satisfy_committed_floors` +test enforces this; the companion `floors_equal_current_no_slack` +test enforces `current == committed_floor` (no slack — a slack +floor lets a later PR delete things along that dimension without +firing the gate). + +Lowering a floor requires either: + +- Rare and rejustified: a `Lower-Floor: ` line + in the PR body or commit message, OR +- A schema break: bump `schema_version` in the file header. + +Default expectation: floors only go up. + +## What lands together, what stays separate + +- **One issue per PR.** Break large refactors into independently + revertible steps. A PR that fixes two unrelated bugs is harder to + review and harder to revert. +- **No mixed reformatting.** `cargo fmt` results land as their own + commit, never co-mingled with logic changes. +- **No silent dependency adds.** Each new workspace dep gets a + one-line note in the commit body (why it's needed, what it + costs). +- **No unsafe.** `unsafe_code` is `forbid`-level workspace-wide. + +## Style snapshots + +The full style guide lives in `CLAUDE.md` at the repo root. The +high-impact rules: + +- **Max 500 lines per `.rs` file.** Locked tests count too — split + to a sibling module before the limit, not after. +- **No `mod.rs`.** Use `foo.rs` + `foo/` directory pattern. +- **No `eprintln!` / `println!` for diagnostics** — use `tracing` + (`info`, `warn`, `error`, `debug`). +- **No `unwrap` / `expect` / `panic!` in library code.** Tests + may opt out via `#[allow(clippy::expect_used, clippy::panic)]`. +- **WHY-only comments.** No PR-number breadcrumbs, no absolute + line counts, no temporal phrasing (`migrated from`, `previously`). + These are mechanically enforced by `rot_prone_markers_locked`. +- **No editor-duplicate filenames** (`* 2.rs`, `* 2.toml`, …). + Mechanically enforced by `editor_duplicates_locked`. + +## Reporting + +Open an issue at . +Security-relevant findings: please prefer a private channel +first — the project has no formal security disclosure policy yet +but will respond to good-faith reports. diff --git a/cert/floors.toml b/cert/floors.toml index 9c37383..3adee2e 100644 --- a/cert/floors.toml +++ b/cert/floors.toml @@ -78,7 +78,7 @@ known_surfaces = 21 [per_crate.evidence-core] # `#[test]` attribute count inside crates/evidence-core/**/*.rs. -test_count = 358 +test_count = 361 [per_crate.cargo-evidence] test_count = 136 diff --git a/crates/cargo-evidence/src/cli/README.md b/crates/cargo-evidence/src/cli/README.md new file mode 100644 index 0000000..ddf3fcb --- /dev/null +++ b/crates/cargo-evidence/src/cli/README.md @@ -0,0 +1,113 @@ +# `cli/` — taxonomy of cargo-evidence subcommands + +Every `cargo evidence ` lives here as one module. This README +is the map: where to look when triaging a verb, where to add a new +one, and which lifecycle phase each piece belongs to. + +If a verb's home is unobvious, the answer is "the lifecycle phase it +operates on." That phase determines the directory. + +--- + +## Top-level shape + +``` +cli/ +├── args.rs # clap derive types: CargoCli, EvidenceArgs, Commands +├── output.rs # OutputFormat resolution + JSONL emit primitives +│ +├── generate.rs # `cargo evidence generate` (also the implicit default) +├── generate/ # phase pipeline behind generate +│ +├── verify.rs # `cargo evidence verify ` +├── verify/ # bundle-load + integrity-check helpers +│ +├── check.rs # `cargo evidence check` — agent-facing pass/gap +├── diff.rs # `cargo evidence diff ` +├── doctor.rs # `cargo evidence doctor` — workspace audit +├── doctor/ # per-check helpers (checks.rs, qualification.rs) +├── floors.rs # `cargo evidence floors` — ratchet-gate query +├── init.rs # `cargo evidence init` +├── rules.rs # `cargo evidence rules` — diagnostic-code manifest +├── schema.rs # `cargo evidence schema {show, validate}` +└── trace.rs # `cargo evidence trace --validate ...` +``` + +## Lifecycle taxonomy + +Each verb belongs to exactly one phase of the project lifecycle. +Adding a verb? Pick the phase first; the file goes in the matching +group. + +### Bundle-producing (writes to `out_dir`) + +- **`generate`** — assemble a fresh evidence bundle from the current + workspace state. The default verb when no subcommand is given. + The `generate/` subdirectory holds the phase pipeline (`phases.rs`, + `coverage_phase/`, `envelope.rs`, `policy.rs`, `test_outcomes.rs`) + — extracted from `generate.rs` to keep that file under the + workspace 500-line limit. + +### Bundle-consuming (reads an on-disk bundle) + +- **`verify`** — run the integrity + policy gates against a finished + bundle. The `verify/` subdirectory holds the load-and-check + helpers (`incomplete_bundle.rs`, `skipped_notices.rs`, + `terminals.rs`). +- **`diff`** — compare two bundles structurally. Pure inspection, + reports differences without judging them; exit code stays `0` + even when bundles differ. + +### Source-tree inspection (no bundle, no `out_dir` write) + +- **`check`** — one-shot pass/gap validation. Agent-facing wrapper + that's `auto`-mode in source mode and bundle mode behind one + verb. The MCP server's `evidence_check` is a thin wrapper over + this. +- **`doctor`** — audit a workspace's rigor adoption (boundary, + trace, floors, CI integration, merge-style policy, override + docs). The `doctor/` subdirectory holds the per-check + implementations (`checks.rs`, `qualification.rs`). +- **`floors`** — query the ratcheting-floors gate state. +- **`trace`** — validate the SYS/HLR/LLR/TEST chain in + `cert/trace/`. Also offers `--backfill-uuids` and a few diagnostic + switches. + +### Self-describing / scaffolding + +- **`init`** — bootstrap `cert/boundary.toml` + `cert/floors.toml` + + `cert/trace/` for a project that hasn't adopted the tool yet. +- **`rules`** — emit the manifest of every diagnostic code the tool + can produce. Used by agents pinning autofix flows. +- **`schema`** — `show` / `validate` for the JSON schemas under + `schemas/`. Useful when an integrator wants to generate types + from the wire format. + +## Conventions inside each verb module + +- **`cmd_(...)` is the canonical entry point.** Called from + `dispatch()` in `main.rs`, returns `anyhow::Result` (the + process exit code). All argument parsing happens in clap before + the call; `cmd_` receives typed arguments. +- **Output goes through `cli::output`.** Direct `println!` / + `eprintln!` is reserved for the binary entry-point banner + (`main.rs`'s `--help` intercept and the top-level error sink). + Everything else uses `emit_jsonl`, `emit_json`, or the human- + formatter. +- **Profile / DAL gates live in the verb's policy submodule.** For + `generate`, that's `generate/policy.rs`. For `doctor`, it's + `doctor/qualification.rs`. The pattern: the verb module orchestrates, + the policy submodule holds the dev-vs-cert-vs-record decisions. + +## Adding a new verb + +1. Pick the lifecycle phase. The verb's home directory follows. +2. Add a `Commands` variant in `args.rs` with `#[command(about = ...)]`. +3. Add a `cmd_(...)` function in the new module. +4. Wire `dispatch()` in `main.rs` to call it. +5. Update this README's lifecycle taxonomy with the new entry. +6. Update the `EXPECTED_SUBCOMMANDS` list in + `tests/help_listing.rs` to include the new verb (the locked + test fires otherwise). +7. Trace seed: SYS / HLR / LLR / TEST entries under `cert/trace/`. +8. Floors bump for `trace_*` and `per_crate.cargo-evidence.test_count`. diff --git a/crates/evidence-core/tests/editor_duplicates_locked.rs b/crates/evidence-core/tests/editor_duplicates_locked.rs new file mode 100644 index 0000000..6664542 --- /dev/null +++ b/crates/evidence-core/tests/editor_duplicates_locked.rs @@ -0,0 +1,167 @@ +//! Mechanical guard against editor-duplicate filename artifacts +//! (LLR-076). +//! +//! Walks the workspace and fires on any path whose basename matches +//! the regex: +//! +//! ```text +//! ^.+ ([0-9]{1,2})\.(rs|toml|yml|yaml|md|json|lock)$ +//! ``` +//! +//! These are the artifacts a misbehaving `cp old new` or save-as +//! dialog leaves behind — `helpers 2.rs`, `audit 2.yml`, `tests +//! 2.toml`, etc. They compile, hash, and ratchet floor counts +//! silently; only a manual sweep retroactively catches them. A +//! pre-1.0 cert-track project wants this enforcement mechanical. +//! +//! Test failure renders one line per offender (`:` — line is always 1 because the offense is the +//! filename itself, not its content). + +#![allow( + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + reason = "test setup failures should panic immediately" +)] + +use std::path::{Path, PathBuf}; + +use regex::Regex; + +#[path = "walker_helpers.rs"] +mod traversal; + +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("crates/") + .parent() + .expect("workspace root") + .to_path_buf() +} + +/// Filenames that legitimately match the editor-duplicate regex. +/// Each entry is a glob-free workspace-relative path suffix; the +/// match is `ends_with(suffix)` on a forward-slash-normalized +/// rendering of the absolute path. Initially empty. Add only with +/// written justification beside the const. +const RESERVED_DUPLICATE_PATHS: &[&str] = &[]; + +/// The pinned regex. Anchored at the basename: stem + ASCII space + +/// 1-2 digits + extension. Every recognized cert-relevant extension +/// gets the same enforcement; an editor that produces a different +/// suffix shape (`~`, `.bak`, `.orig`) is out of scope and would +/// land as a sibling regex rather than a relaxation of this one. +fn duplicate_regex() -> Regex { + Regex::new(r"^.+ ([0-9]{1,2})\.(rs|toml|yml|yaml|md|json|lock)$").expect("valid regex") +} + +/// Collect every editor-duplicate-shaped basename in the workspace. +/// Walks `workspace_root`, prunes the conventional skip directories, +/// applies the duplicate regex to each file's basename, drops +/// reserved entries, returns the remaining list as workspace- +/// relative paths (forward-slash separators). +fn scan_for_duplicates(root: &Path) -> Vec { + let re = duplicate_regex(); + traversal::walk(root) + .filter_entry(|e| { + // Skip the conventional build / VCS / vendor dirs plus + // editor-and-tool metadata trees that aren't part of + // the project's source. The `.claude/` directory in + // particular collects scheduled-task lock files whose + // names match the duplicate pattern by accident + // (`scheduled_tasks 2.lock`); they are not source + // artifacts and not under audit. + !traversal::is_dir_named( + e, + &[ + "target", + ".git", + "node_modules", + "fixtures", + ".direnv", + ".claude", + ".idea", + ".vscode", + ], + ) + }) + .filter_map(Result::ok) + .filter(|e| e.file_type().is_file()) + .filter(|e| { + e.path() + .file_name() + .and_then(|n| n.to_str()) + .is_some_and(|name| re.is_match(name)) + }) + .map(|e| { + e.path() + .strip_prefix(root) + .unwrap_or(e.path()) + .to_string_lossy() + .replace('\\', "/") + }) + .filter(|rel| { + !RESERVED_DUPLICATE_PATHS + .iter() + .any(|exempt| rel.ends_with(exempt)) + }) + .collect() +} + +/// Load-bearing regression: the current tree is clean. +#[test] +fn current_tree_is_clean() { + let hits = scan_for_duplicates(&workspace_root()); + assert!( + hits.is_empty(), + "found {} editor-duplicate filename(s) in the workspace. \ + Each one is a `cp old new` / save-as artifact that compiles \ + silently, inflates floor counts, and pollutes audit \ + hashes. Delete with `\\rm` (the leading backslash bypasses \ + shell aliases) or rename to a real filename.\n\n{}", + hits.len(), + hits.iter() + .map(|p| format!(" {}", p)) + .collect::>() + .join("\n"), + ); +} + +/// Positive dogfood: a fixture with one duplicate-shaped filename +/// fires the gate. +#[test] +fn fires_on_synthetic_duplicate() { + let tmp = tempfile::TempDir::new().expect("tempdir"); + let src = tmp.path().join("crates").join("fake").join("src"); + std::fs::create_dir_all(&src).expect("mkdir"); + std::fs::write(src.join("helpers 2.rs"), "// editor duplicate\n").expect("write fixture"); + let hits = scan_for_duplicates(tmp.path()); + assert!( + !hits.is_empty(), + "expected gate to fire on `helpers 2.rs`; hits were empty" + ); + assert!( + hits.iter().any(|p| p.ends_with("helpers 2.rs")), + "expected `helpers 2.rs` in the hit list; got {:?}", + hits + ); +} + +/// Negative dogfood: a filename whose digits do NOT follow a leading +/// space is legitimate and must not fire the gate. +#[test] +fn passes_on_legitimate_digits_filename() { + let tmp = tempfile::TempDir::new().expect("tempdir"); + let src = tmp.path().join("crates").join("fake").join("src"); + std::fs::create_dir_all(&src).expect("mkdir"); + std::fs::write(src.join("mcdc_2024.rs"), "// legitimate filename\n").expect("write fixture"); + std::fs::write(src.join("v1_data.toml"), "[meta]\n").expect("write fixture"); + let hits = scan_for_duplicates(tmp.path()); + assert!( + hits.is_empty(), + "expected legitimate digit-bearing filenames to pass; got hits {:?}", + hits + ); +} From 31360e2a334726fd9333089de0ac25eaa05a344c Mon Sep 17 00:00:00 2001 From: sokoly Date: Fri, 1 May 2026 20:08:53 -0400 Subject: [PATCH 3/4] =?UTF-8?q?review:=20address=20PR=20#118=20findings=20?= =?UTF-8?q?=E2=80=94=20UUID=20workflow,=20sharper=20dogfood,=20surface=20c?= =?UTF-8?q?laim,=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Blocker 1**: CONTRIBUTING.md's UUID workflow now points at the established `cargo evidence trace --backfill-uuids` policy and cross-references `cert/trace/README.md`'s rationale. The python3 one-liner the prior draft suggested was the exact hand-crafting form the existing policy bans (collision risk, weakens the "UUIDs are opaque" contract, tempts reading meaning into digits). Two contradictory authority docs on the same workflow is a quality-of-process regression — fixed. - **Blocker 2**: Sharpened the negative dogfood. The previous test used `mcdc_2024.rs`, which fails the duplicate regex for two independent reasons (no leading space + 4 digits exceeds the 2-digit cap). A regression that drops only the leading-space anchor would still pass. Added `mcdc_24.rs` (2 digits, no leading space) so each anchor is isolated by its own fixture. - **Minor 3**: HLR-069 now claims `surfaces = ["editor-duplicate gate"]`, matching the convention HLR-044 / HLR-046 / HLR-047 established for sibling hygiene gates. New entry in `KNOWN_SURFACES`. `cert/floors.toml` known_surfaces 21→22. - **Minor 5**: 0.1.4 hygiene-track entry added to CHANGELOG. Bundles the changes already in main from PRs #115 / #116 / #117 plus the changes landing in this PR. Per-PR cadence rather than batched release-prep PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 89 +++++++++++++++++++ CONTRIBUTING.md | 25 +++++- cert/floors.toml | 2 +- cert/trace/hlr.toml | 1 + crates/evidence-core/src/trace/surfaces.rs | 1 + .../tests/editor_duplicates_locked.rs | 19 +++- 6 files changed, 129 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2136b4c..3c2f38a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,95 @@ All three workspace crates (`evidence-core`, `cargo-evidence`, `evidence-mcp`) share a single version; release entries cover all three unless noted. +## [Unreleased] — 0.1.4 hygiene track + +### Added + +- **`success: bool` field on every MCP wire shape** + (`JsonlToolResponse`, `RulesToolResponse`, `DiffToolResponse`). + Derived deterministically from `exit_code` + `terminal`/`error` + so MCP hosts have one canonical pass/fail field rather than + pattern-matching the colliding `exit_code = 2` against the + `_OK`-suffix string. Existing fields are unchanged; older agents + ignore the addition. (HLR-067 / LLR-074 / TEST-081, PR #117) +- **`cargo-evidence --help` now lists every subcommand** when the + binary is invoked directly (`cargo-evidence --help`, not + `cargo evidence --help`). Reuses clap's render tree on + `EvidenceArgs`, so new subcommands appear automatically. The + previous four-line redirect stub is retired. An + `EXPECTED_SUBCOMMANDS` list in the test guards drift. + (HLR-068 / LLR-075 / TEST-082, PR #117) +- **`editor_duplicates_locked` integration test** — + mechanical guard against ` N.` editor-duplicate filenames + (`helpers 2.rs`, `tests 2.toml`, `audit 2.yml`, etc.) anywhere + in the workspace tree. Three fixtures: clean-tree regression, + positive dogfood (synthetic `helpers 2.rs`), negative dogfood + with two distinct classes (`mcdc_2024.rs` four digits + + `mcdc_24.rs` two digits, no leading space) so a regression in + either the digit-count cap or the leading-space anchor fails + independently. (SYS-029 / HLR-069 / LLR-076 / TEST-083, PR #118) +- **DAL-A MC/DC fail-loud** at cert / record profile. When any + in-scope crate is at DAL-A and `cert/boundary.toml` lacks a + `[dal.auxiliary_mcdc_tool]` table, generate emits + `BOUNDARY_DAL_A_MISSING_AUXILIARY_MCDC` and refuses to assemble + a bundle. Dev profile keeps the warn-and-continue behavior so + iterative work isn't blocked. Closes the silent-underclaim + sharp-edge an auditor would catch but a careless DER could + miss. (HLR-066 / LLR-073 / TEST-080, PR #115) +- **`AuxiliaryMcdcTool` schema hook** under + `[dal.auxiliary_mcdc_tool]` carrying `name` (required), + optional `qualification_id`, optional bundle-relative + `report` path. Lets a DAL-A project declare LDRA TBvision / + VectorCAST / Rapita RVS evidence by reference today, without + waiting for stable Rust MC/DC support. (PR #115) +- **`CONTRIBUTING.md`** — PR loop, trace-first convention, local + CI gates, floors-only-up rule, style snapshots. Points at + `cert/trace/README.md` for the canonical UUID-generation + workflow. (PR #118) +- **`CODEOWNERS`** — single-maintainer default with cert-track + paths called out separately so future role splits are + mechanical. (PR #118) +- **`crates/cargo-evidence/src/cli/README.md`** — lifecycle + taxonomy of the 24 CLI files (bundle-producing / + bundle-consuming / source-tree-inspection / self-describing) + with an adding-a-new-verb checklist. (PR #118) +- **`editor-duplicate gate` surface** in `KNOWN_SURFACES`, + claimed by HLR-069. (PR #118) + +### Changed + +- **Trace discovery is now a single-source-of-truth function**: + `evidence_core::trace::default_trace_roots(workspace_root)` is + the only path consulted by `cargo evidence trace --validate`, + `cargo evidence check`, `cargo evidence floors`, and + `evidence_core::floors::count_trace_per_layer`. Replaces three + separate callsites that each implemented discovery + inconsistently and would silently under-count when the project + used a non-canonical trace location. Discovery order: `cert/ + trace/` (canonical) → `cert/boundary.toml`'s `scope.trace_roots`. + No `tool/trace/` fallback; the project itself migrated its + self-trace from `tool/trace/` to `cert/trace/` to match the + convention. (PR #116) +- **README content_hash section** explicitly distinguishes + "integrity re-check" from "DO-178C verification independence", + and forward-references the existing **Tool Qualification + Level** honesty section so a casual reader can't conflate the + two. (PR #117) + +### Floors + +| Dimension | 0.1.3 → unreleased | +|---|---| +| trace_sys | 28 → 29 | +| trace_hlr | 65 → 69 | +| trace_llr | 72 → 76 | +| trace_test | 77 → 81 | +| diagnostic_codes | 150 → 151 | +| known_surfaces | 21 → 22 | +| per_crate.evidence-core.test_count | 351 → 361 | +| per_crate.evidence-mcp.test_count | 37 → 45 | +| per_crate.cargo-evidence.test_count | 133 → 136 | + ## [0.1.3] — 2026-04-30 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d46c9f5..86a32be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,10 +49,27 @@ self-cert regressions before they reach CI. Don't skip them. ## Trace-first convention Default: every PR seeds its SYS/HLR/LLR/TEST entries in the first -commit on the branch, before any implementation. UUIDs are -generated at seed time (`python3 -c "import uuid; print(uuid.uuid4())"`). -Each trace entry's `traces_to` array points at the parent UID one -layer up. +commit on the branch, before any implementation. UUIDs are **never +hand-crafted** (even with valid v4 syntax) and **never generated +externally** (e.g., a one-liner Python script): the tool's own +`cargo evidence trace --backfill-uuids` is the single authoritative +generator. The full rationale lives in `cert/trace/README.md`'s +"UUID policy" section. + +Workflow for a new entry: + +1. Append the entry to the appropriate trace file + (`cert/trace/sys.toml` / `hlr.toml` / `llr.toml` / `tests.toml`) + **without** a `uid` field. Set `traces_to` to point at the + parent layer's UID — those already exist in the file. +2. Run `cargo evidence trace --backfill-uuids` from the workspace + root. Discovery picks `cert/trace/` automatically; no + `--trace-roots` flag needed. +3. Commit the populated TOML. + +Re-runs are no-ops; the `trace-self-validate` CI job asserts +backfill reports "all entries already have UUIDs", catching an +uncommitted backfill step before it reaches main. Each trace seed bumps the matching counter in `cert/floors.toml` (`trace_sys`, `trace_hlr`, `trace_llr`, `trace_test`). After the diff --git a/cert/floors.toml b/cert/floors.toml index 3adee2e..e5ef5ae 100644 --- a/cert/floors.toml +++ b/cert/floors.toml @@ -63,7 +63,7 @@ trace_test = 81 # coverage is enforced by `require_hlr_surface_bijection`; this floor # guards against silently shrinking the catalog itself (which would # relax the bijection without firing the check). -known_surfaces = 21 +known_surfaces = 22 # -------------------------------------------------------------------- # Per-crate-true dimensions: one table per in-scope crate. diff --git a/cert/trace/hlr.toml b/cert/trace/hlr.toml index 6af7aca..a6c1964 100644 --- a/cert/trace/hlr.toml +++ b/cert/trace/hlr.toml @@ -1696,3 +1696,4 @@ default state is zero exemptions. """ verification_methods = ["test"] traces_to = ["4b63a601-9770-4c3a-bfe4-e1d70e050c65"] +surfaces = ["editor-duplicate gate"] diff --git a/crates/evidence-core/src/trace/surfaces.rs b/crates/evidence-core/src/trace/surfaces.rs index 6b6ebb3..83d5697 100644 --- a/crates/evidence-core/src/trace/surfaces.rs +++ b/crates/evidence-core/src/trace/surfaces.rs @@ -50,6 +50,7 @@ pub const KNOWN_SURFACES: &[&str] = &[ "comment hygiene gate", "coverage measurement", "diagnostic code namespace (regex + reserved suffixes)", + "editor-duplicate gate", "jsonl stream per Schema Rule 2", "per-test outcome capture", "per-test ↔ LLR back-link", diff --git a/crates/evidence-core/tests/editor_duplicates_locked.rs b/crates/evidence-core/tests/editor_duplicates_locked.rs index 6664542..b0a0dac 100644 --- a/crates/evidence-core/tests/editor_duplicates_locked.rs +++ b/crates/evidence-core/tests/editor_duplicates_locked.rs @@ -149,14 +149,27 @@ fn fires_on_synthetic_duplicate() { ); } -/// Negative dogfood: a filename whose digits do NOT follow a leading -/// space is legitimate and must not fire the gate. +/// Negative dogfood: filenames whose digits do NOT follow a leading +/// space are legitimate and must not fire the gate. Two distinct +/// fixtures cover two regression classes independently: +/// +/// - `mcdc_2024.rs` — digits run beyond the regex's 2-digit cap, +/// so a regression that drops the leading-space anchor would +/// still pass on this filename via the digit-count guard alone. +/// - `mcdc_24.rs` — digits fit within the 2-digit cap, so this +/// fixture isolates the leading-space anchor: the gate must +/// fire only when there's a literal space before the digits. +/// +/// Both must pass for the regex to be sound on independent +/// regression vectors. #[test] fn passes_on_legitimate_digits_filename() { let tmp = tempfile::TempDir::new().expect("tempdir"); let src = tmp.path().join("crates").join("fake").join("src"); std::fs::create_dir_all(&src).expect("mkdir"); - std::fs::write(src.join("mcdc_2024.rs"), "// legitimate filename\n").expect("write fixture"); + std::fs::write(src.join("mcdc_2024.rs"), "// 4-digit filename\n").expect("write fixture"); + std::fs::write(src.join("mcdc_24.rs"), "// 2-digit, no leading space\n") + .expect("write fixture"); std::fs::write(src.join("v1_data.toml"), "[meta]\n").expect("write fixture"); let hits = scan_for_duplicates(tmp.path()); assert!( From 5980b24a9011b2992fd0af7b85ffe1b451b4e8f9 Mon Sep 17 00:00:00 2001 From: sokoly Date: Fri, 1 May 2026 20:15:26 -0400 Subject: [PATCH 4/4] fix(changelog): drop PR-number breadcrumbs from 0.1.4 entries `rot_prone_markers_locked` walks `**/*.md` (excluding `cert/trace/README.md`) and bans the `PR\s+#\d+` regex. The 0.1.4 changelog entries I just added carried 11 of those breadcrumbs, firing the gate on Ubuntu Check + every dependent job. Trace IDs (HLR-067, LLR-074, TEST-081, etc.) are the stable anchors and survive history rewrites; PR numbers don't (a project transfer or squash rebase renumbers them). The changelog now lists trace IDs only, matching the project's own "WHY-only, no rot-prone breadcrumbs" rule. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c2f38a..54acf6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,24 +18,23 @@ three unless noted. Derived deterministically from `exit_code` + `terminal`/`error` so MCP hosts have one canonical pass/fail field rather than pattern-matching the colliding `exit_code = 2` against the - `_OK`-suffix string. Existing fields are unchanged; older agents - ignore the addition. (HLR-067 / LLR-074 / TEST-081, PR #117) + `_OK`-suffix string. Existing fields are unchanged; older + agents ignore the addition. (HLR-067 / LLR-074 / TEST-081) - **`cargo-evidence --help` now lists every subcommand** when the binary is invoked directly (`cargo-evidence --help`, not `cargo evidence --help`). Reuses clap's render tree on `EvidenceArgs`, so new subcommands appear automatically. The - previous four-line redirect stub is retired. An - `EXPECTED_SUBCOMMANDS` list in the test guards drift. - (HLR-068 / LLR-075 / TEST-082, PR #117) -- **`editor_duplicates_locked` integration test** — - mechanical guard against ` N.` editor-duplicate filenames + redirect-stub form is retired. An `EXPECTED_SUBCOMMANDS` list + in the test guards drift. (HLR-068 / LLR-075 / TEST-082) +- **`editor_duplicates_locked` integration test** — mechanical + guard against ` N.` editor-duplicate filenames (`helpers 2.rs`, `tests 2.toml`, `audit 2.yml`, etc.) anywhere in the workspace tree. Three fixtures: clean-tree regression, positive dogfood (synthetic `helpers 2.rs`), negative dogfood with two distinct classes (`mcdc_2024.rs` four digits + `mcdc_24.rs` two digits, no leading space) so a regression in either the digit-count cap or the leading-space anchor fails - independently. (SYS-029 / HLR-069 / LLR-076 / TEST-083, PR #118) + independently. (SYS-029 / HLR-069 / LLR-076 / TEST-083) - **DAL-A MC/DC fail-loud** at cert / record profile. When any in-scope crate is at DAL-A and `cert/boundary.toml` lacks a `[dal.auxiliary_mcdc_tool]` table, generate emits @@ -43,26 +42,26 @@ three unless noted. a bundle. Dev profile keeps the warn-and-continue behavior so iterative work isn't blocked. Closes the silent-underclaim sharp-edge an auditor would catch but a careless DER could - miss. (HLR-066 / LLR-073 / TEST-080, PR #115) + miss. (HLR-066 / LLR-073 / TEST-080) - **`AuxiliaryMcdcTool` schema hook** under `[dal.auxiliary_mcdc_tool]` carrying `name` (required), optional `qualification_id`, optional bundle-relative `report` path. Lets a DAL-A project declare LDRA TBvision / VectorCAST / Rapita RVS evidence by reference today, without - waiting for stable Rust MC/DC support. (PR #115) + waiting for stable Rust MC/DC support. - **`CONTRIBUTING.md`** — PR loop, trace-first convention, local CI gates, floors-only-up rule, style snapshots. Points at `cert/trace/README.md` for the canonical UUID-generation - workflow. (PR #118) + workflow. - **`CODEOWNERS`** — single-maintainer default with cert-track paths called out separately so future role splits are - mechanical. (PR #118) + mechanical. - **`crates/cargo-evidence/src/cli/README.md`** — lifecycle taxonomy of the 24 CLI files (bundle-producing / bundle-consuming / source-tree-inspection / self-describing) - with an adding-a-new-verb checklist. (PR #118) + with an adding-a-new-verb checklist. - **`editor-duplicate gate` surface** in `KNOWN_SURFACES`, - claimed by HLR-069. (PR #118) + claimed by HLR-069. ### Changed @@ -75,14 +74,13 @@ three unless noted. inconsistently and would silently under-count when the project used a non-canonical trace location. Discovery order: `cert/ trace/` (canonical) → `cert/boundary.toml`'s `scope.trace_roots`. - No `tool/trace/` fallback; the project itself migrated its - self-trace from `tool/trace/` to `cert/trace/` to match the - convention. (PR #116) + No `tool/trace/` fallback; the project's self-trace lives at + `cert/trace/` to match the convention. - **README content_hash section** explicitly distinguishes "integrity re-check" from "DO-178C verification independence", and forward-references the existing **Tool Qualification Level** honesty section so a casual reader can't conflate the - two. (PR #117) + two. ### Floors