Traceability: vendor the shared gate, annotate the source
CI / fmt, clippy, test (push) Failing after 1m21s
CI / static musl binary (push) Has been skipped
CI / advisories and licences (push) Successful in 24s

Adds jray-project as a submodule at scripts/vendor/jray-project, so this repo
runs the same extractor as every other component rather than its own copy, and
gains the system spec that defines the PR/SR requirements its register traces
up to.

scripts/traceability-gate.sh is a thin wrapper holding only what is specific to
this repo: UR/DR prefixes, .rs sources, and REPO_ROOT — which the shared gate
cannot infer once vendored, since its default resolves to the submodule itself.
Each override fails silently in a way that looks like "no work done" rather
than "misconfigured", so the wrapper documents why each is needed.

Annotates 35 units with TRACES tags, on the code that decides rather than every
helper it calls. Coverage is 23/32 (71.9%) with no orphan tags. The nine
untraced are genuinely unimplemented: UR-007 is plugin-side, UR-008 is
federation, and UR-015..018 are the pending SR-003 schema bump.

The gate caught a real error in the first pass: several tags separated IDs of
different types with commas. A comma joins IDs within one type; a pipe
separates types. Fixed, and the diagnostics are now clean.

MIN_COVERAGE stays 0 deliberately. The gate still fails on orphan tags, a >100%
ratio, a register parsing to nothing, or an empty source scan — raise the
threshold as a ratchet once the remaining work lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 18:27:16 +02:00
co-authored by Claude Opus 5
parent a848750a65
commit a1e789a6fe
24 changed files with 464 additions and 2 deletions
+2
View File
@@ -58,6 +58,7 @@ pub struct BatchResponse {
pub results: Vec<ExistsResponse>,
}
/// TRACES: UR-001 | SR-001
pub async fn exists(
State(state): State<AppState>,
peer: crate::state::PeerIp,
@@ -70,6 +71,7 @@ pub async fn exists(
Ok(with_quota_headers(Json(body).into_response(), quota))
}
/// TRACES: UR-001, UR-007 | SR-001 | PR-005
pub async fn exists_batch(
State(state): State<AppState>,
peer: crate::state::PeerIp,
+2
View File
@@ -125,6 +125,7 @@ async fn fetch_best(
/// bundle with 9 of 13 episodes is a valid, useful response, not an error (§2).
/// Episode-level cut matching is done client-side against the returned bundle,
/// since a client pulling a whole series already knows its own runtimes.
/// TRACES: UR-006 | PR-006
pub async fn get_series(
State(state): State<AppState>,
peer: crate::state::PeerIp,
@@ -263,6 +264,7 @@ fn title_of(conn: &rusqlite::Connection, title_id: &str) -> anyhow::Result<repo:
/// Names come from `people` — populated from TMDB by the server — so `name` is
/// server-authoritative on download and a name a contributor invented does not
/// round-trip (§2, §5a).
/// TRACES: UR-010, UR-013 | DR-002 | SR-001, SR-002
pub fn reconstruct(
conn: &rusqlite::Connection,
row: &ManifestRow,
+1
View File
@@ -97,6 +97,7 @@ where
/// serde_json would reject non-UTF-8 anyway; the value added here is a diagnosable
/// error rather than a misleading one. A UTF-16 body otherwise fails with "key
/// must be a string", which points an operator at the wrong problem entirely.
/// TRACES: DR-010, DR-013 | SR-003
fn require_utf8(bytes: &[u8]) -> Result<&str, ApiError> {
// A BOM is not valid JSON (RFC 8259 §8.1: "implementations MUST NOT add a
// byte order mark"), and it is the clearest signal of an encoding mistake, so
+1
View File
@@ -53,6 +53,7 @@ pub struct ReportAccepted {
pub report_id: String,
}
/// TRACES: UR-005 | SR-004
pub async fn post_report(
State(state): State<AppState>,
peer: crate::state::PeerIp,
+3
View File
@@ -26,6 +26,7 @@ pub struct UploadAccepted {
}
/// `POST /manifests` — UR-2.
/// TRACES: UR-002, UR-003 | SR-004 | PR-006
pub async fn post_manifest(
State(state): State<AppState>,
headers: HeaderMap,
@@ -97,6 +98,7 @@ pub struct BundleAccepted {
///
/// **One rate-limit unit**, so contributing a season is not punished relative to
/// contributing a film (§2, §5).
/// TRACES: UR-006 | PR-006
pub async fn post_bundle(
State(state): State<AppState>,
headers: HeaderMap,
@@ -218,6 +220,7 @@ pub struct TokenIssued {
/// only as a hash, so the server cannot enumerate who holds tokens. Discarding a
/// token and requesting another is trivially easy — and that is fine, because the
/// token is not the defence; the content checks are.
/// TRACES: UR-005 | SR-004
pub async fn post_token(
State(state): State<AppState>,
peer: crate::state::PeerIp,