Hold every verdict write to a reviewed list of user actions

FR-CULL-13 says evidence never writes a rating, flag, label or trash
membership, and nothing enforced it. tools/traceability/src/verdicts.rs
parses the shipped code with syn and enumerates every write: calls to
the catalog setters and trash recorders, SQL that assigns those columns,
sidecar Amendment::Judgement, and fields named rating/flag/label. Each
site must be in ALLOWED with a reason, as Input (inside a Slint on_*
closure, checked structurally), Relay (its callers are checked in
turn), Carried (a verdict made elsewhere: sidecar and XMP pulls, sync
merge, catalog mirrored to file, duplicates consolidation) or
NotAVerdict. Unlisted sites and stale entries both fail
`cargo test -p traceability`; `traces verdicts` prints the list.

syn and proc-macro2 were already in the lockfile as proc-macro
dependencies; this adds the edges, no new crate and no version change.
This commit is contained in:
2026-09-27 07:20:38 -04:00
parent 6e68f1fb3d
commit 2cde287440
6 changed files with 1894 additions and 0 deletions
Generated
+2
View File
@@ -7112,9 +7112,11 @@ name = "traceability"
version = "0.18.1"
dependencies = [
"anyhow",
"proc-macro2",
"pulldown-cmark",
"serde",
"serde_json",
"syn 2.0.119",
]
[[package]]
+6
View File
@@ -134,6 +134,12 @@ serde_json = "1"
# Slint's Markdown parser, so this adds a dependency edge and no crate; only
# the HTML writer is needed, not the command-line front end.
pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] }
# The verdict-writer check (tools/traceability, FR-CULL-13) reads Rust as Rust:
# a text scan cannot tell a call from a comment, a test module from shipped
# code, or which callback closure a call sits in. Both already in the tree as
# every proc macro's parser; `span-locations` gives a problem its line.
syn = { version = "2", default-features = false, features = ["full", "parsing", "visit", "printing"] }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
base64 = "0.23"
# Display-server clients, for FR-DSP-8's per-display profile acquisition.
+2
View File
@@ -19,3 +19,5 @@ anyhow.workspace = true
serde = { workspace = true }
serde_json.workspace = true
pulldown-cmark.workspace = true
syn.workspace = true
proc-macro2.workspace = true
+1
View File
@@ -42,6 +42,7 @@ pub mod chord;
pub mod gestures;
pub mod keymap;
pub mod manual;
pub mod verdicts;
/// Requirement ID prefixes that participate in coverage.
///
+36
View File
@@ -8,6 +8,7 @@
//! traces gestures-check # gate: non-zero exit if either has drifted
//! traces manual # write docs/manual/index.html from its README
//! traces manual-check # gate: non-zero exit if the page has drifted
//! traces verdicts # list every verdict write; non-zero exit on an unlisted one
//! ```
//!
//! The gesture half scans a different tag out of the same files — see
@@ -79,6 +80,9 @@ fn main() -> Result<()> {
if mode.starts_with("manual") {
return run_manual(&base, mode == "manual-check");
}
if mode == "verdicts" {
return run_verdicts(&base);
}
let req_path = base.join("docs/dev/requirements.md");
let markdown = std::fs::read_to_string(&req_path)
@@ -248,6 +252,38 @@ fn run_gestures(base: &Path, check: bool) -> Result<()> {
Ok(())
}
/// List every verdict write with the reason it is allowed, and fail on any
/// that has none — the same check `cargo test -p traceability` runs, in a
/// form a reviewer can read ([`traceability::verdicts`]).
fn run_verdicts(base: &Path) -> Result<()> {
let files = verdicts::sources(base);
let report = verdicts::check(&files, verdicts::ALLOWED);
println!("files scanned {}", files.len());
println!("writes found {}", report.sites.len());
for s in &report.sites {
let kind = verdicts::ALLOWED
.iter()
.find(|a| a.file == s.file && a.within == s.within && a.writes == s.writes)
.map(|a| format!("{:?}", a.kind))
.unwrap_or_else(|| "UNLISTED".into());
println!(
" {kind:<11} {}:{} {} {} {}",
s.file, s.line, s.within, s.writes, s.detail
);
}
if !report.problems.is_empty() {
for p in &report.problems {
println!(" {p}");
}
bail!(
"{} verdict write problem(s) (FR-CULL-13)",
report.problems.len()
);
}
println!("\nverdict gate: PASS");
Ok(())
}
/// Render the manual to its page, or check the committed page is that render.
fn run_manual(base: &Path, check: bool) -> Result<()> {
let source = std::fs::read_to_string(base.join(MANUAL_SOURCE))
File diff suppressed because it is too large Load Diff