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:
Generated
+2
@@ -7112,9 +7112,11 @@ name = "traceability"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"pulldown-cmark",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user