| name | validate-refs |
| description | Resolve P1 references per-class (Cerberus-aggregate; no halt-on-first) — invoke tools/validate-refs.sh to validate file_path / commit_sha / bd_id / adr_anchor / memory_binding citations against their named targets via the 5 per-class adapters at tools/role_schema/adapters/refs/. Returns full inventory of {resolved, violations, warnings}; exit 1 on any block-severity (STALE/NOT_FOUND/AMBIGUOUS for non-warn classes), exit 0 on all RESOLVED OR only warn-severity (bd_id STALE; memory ACTIVE_PENDING_CODIFICATION) per ADR-017 §2.4 + AC |
Validate-refs
When to invoke
Per ADR-017 §2.1 Layer 3 + plan §Phase 3 Task 3.5: invoke tools/validate-refs.sh whenever you need to verify a set of P1 references (paths/SHAs/IDs/anchors/bindings) against their named targets, independent of a dispatch contract. The bash primitive thin-wraps python3 -m role_schema.cli validate-refs.
Typical situations:
- Mid-dispatch reference re-verification: re-check a citation's status without re-running full
dispatch-validate (which also runs P4 structural + P3 role-canonical).
- Outside-dispatch reference resolution: validate a candidate citation set during contract authoring (e.g., before launching a dispatch).
- Per-class adversarial probe: focus the resolver on a single class for debugging (e.g., why is
bd-XYZ reading STALE?).
Sibling primitive: tools/dispatch-validate.sh composes the same 5 P1 resolvers via _collect_references, but additionally runs P4 structural validation + P3 role-canonical assembly + dispatch-packet walking. validate-refs.sh exposes ONLY the P1 reference layer.
What it does
- Parses
--refs <comma-list> + --classes <comma-list> (parallel arrays per Task 3.5 DO Stage 1 Q4-a; positional pairing).
- Validates inputs (Q4-a + Q7-a strictness):
--refs and --classes must be present + non-empty.
--refs length must equal --classes length (positional pairing).
- Each class must be one of
file_path / commit_sha / bd_id / adr_anchor / memory_binding.
- Any invocation error exits 2.
- For each (target, kind) pair in positional order, dispatches to the appropriate per-class adapter at
tools/role_schema/adapters/refs/<kind>.py:resolve(target).
- Aggregates results in Cerberus-aggregate (Q5-a; ADR-017 §2.4 + external-research §7.6 — no halt-on-first): emits FULL inventory of {resolved, violations, warnings} regardless of any single failure.
- Maps each non-RESOLVED status to a diagnostic via the cli.py
_STATUS_DIAGNOSTIC table:
file_path / adr_anchor / memory_binding / commit_sha / bd_id NOT_FOUND → halt-anchor-resolution-failure (block).
bd_id STALE → halt-bd-id-stale (warn; AC #3).
memory_binding ACTIVE_PENDING_CODIFICATION → halt-memory-binding-active-pending-codification (warn; reserved per Q9-a — basic resolver enumerates RESOLVED/NOT_FOUND only).
- Emits JSON diagnostic to stdout.
Contract with primitive (validate-refs.sh)
- Primitive:
tools/validate-refs.sh
- Flags / inputs:
--refs <comma-list> (required; reference targets — paths/SHAs/IDs/anchors/bindings)
--classes <comma-list> (required; reference classes in positional pairing with --refs; same length required)
--json (machine-readable output; default behavior)
--pretty (pretty-printed JSON via indent=2)
-h / --help (usage)
Valid reference classes (per ResolvedReference.kind Literal at tools/role_schema/ports/reference.py:54)
file_path — path-existence check under repo root (Path.exists()).
commit_sha — git cat-file -e <sha> existence check.
bd_id — bd show <id> open/closed discrimination; closed → STALE warn.
adr_anchor — locate ADR file + verify section heading (heading-form OR inline-§-form).
memory_binding — file presence at ~/.claude/projects/.../memory/ (with optional memory/ prefix strip).
Success stdout shape (Cerberus-aggregate; mirrors dispatch_validate Step 4)
{
"valid": true,
"resolved": [
{
"kind": "file_path",
"target": "tools/role_schema/cli.py",
"status": "RESOLVED",
"evidence": {"path_exists": true, "abs_path": "<absolute>"}
}
],
"violations": [],
"warnings": []
}
Mixed-status (Cerberus-aggregate) shape
{
"valid": false,
"resolved": [
{"kind": "file_path", "target": "tools/role_schema/cli.py", "status": "RESOLVED", "evidence": {...}},
{"kind": "bd_id", "target": "bd-1lghe", "status": "STALE", "evidence": {...}},
{"kind": "file_path", "target": "missing/file.txt", "status": "NOT_FOUND", "evidence": {...}}
],
"violations": [
{"halt_class": "halt-anchor-resolution-failure", "severity": "block", "detail": "file_path 'missing/file.txt' status=NOT_FOUND"}
],
"warnings": [
{"halt_class": "halt-bd-id-stale", "severity": "warn", "detail": "bd_id 'bd-1lghe' status=STALE"}
]
}
Note: every input ref appears in resolved[] regardless of status — no halt-on-first.
Failure stdout shape (invocation error)
Emitted on stderr (exit 2):
ERROR: --refs (n=2) and --classes (n=1) must be same length (positional pairing per Q4-a)
Exit semantics:
0 — valid:true (all RESOLVED OR only warn-severity warnings populated).
1 — valid:false (one or more block-severity violations).
2 — invocation error (missing arg, length mismatch, unknown class).
Example invocations
tools/validate-refs.sh \
--refs "tools/role_schema/cli.py,tools/role_schema/ports/reference.py,amnesia-o4fp.1" \
--classes "file_path,file_path,bd_id" \
--pretty
tools/validate-refs.sh --refs "ADR-017 §2.4" --classes "adr_anchor" --pretty
tools/validate-refs.sh --refs "8ef71e76bdb05ea95c1f28aa8d9cfd5e627c1a13" --classes "commit_sha"
Halt conditions (validate-refs.sh; tool-local + propagated from cli.py _STATUS_DIAGNOSTIC)
Block-severity (exit 1):
halt-anchor-resolution-failure (block): file_path / adr_anchor / memory_binding / commit_sha / bd_id NOT_FOUND or AMBIGUOUS; file_path / adr_anchor / memory_binding STALE.
Warn-severity (exit 0; warnings[] non-empty):
halt-bd-id-stale (warn): bd_id STALE (closed bd issue).
halt-memory-binding-active-pending-codification (warn): memory_binding ACTIVE_PENDING_CODIFICATION (per Q9-a — reserved for future enhancement; basic resolver enumerates RESOLVED / NOT_FOUND only).
Invocation errors (exit 2; emitted on stderr):
--refs missing or empty.
--classes missing or empty.
--refs and --classes length mismatch.
- Unknown class name.
Per-class enumerable statuses (per Q9-a)
| Class | RESOLVED | STALE | NOT_FOUND | AMBIGUOUS | ACTIVE_PENDING_CODIFICATION |
|---|
file_path | yes | no (status enum allows but adapter doesn't emit) | yes | no | NO |
commit_sha | yes | no | yes | no (mapped to NOT_FOUND) | NO |
bd_id | yes | yes (warn) | yes | no | NO |
adr_anchor | yes | no | yes | no | NO |
memory_binding | yes | no | yes | no | reserved (Q9-a; warn when emitted) |
Per Q9-a disposition: only memory_binding may emit ACTIVE_PENDING_CODIFICATION; other 4 classes enumerate {RESOLVED, STALE, NOT_FOUND, AMBIGUOUS} only. ADR-017 §7.3 R3 trait posture + reference.py:46-47 docstring scope this status to memory bindings whose codification target is off-main (forward-referenced AGENTS.md Directives 9-15 per ADR-017 L245).
Related
- ADR-017 §2.1 Layer 3 (Invocation; canonical bash primitive registry).
- ADR-017 §2.4 (Halt-class taxonomy + Cerberus-aggregate semantic).
- ADR-017 §5.1 (Perf budget — aggregate ≤500ms p95; Phase 5 microbenchmark CI gate per plan L601).
- ADR-017 §7.3 R3 (Voice-discipline trait-binding posture — ACTIVE_PENDING_CODIFICATION semantic).
tools/role_schema/ports/reference.py (P1 port: ResolvedReference BaseModel).
tools/role_schema/adapters/refs/{file_path,commit_sha,bd_id,adr_anchor,memory_binding}.py (5 per-class resolvers; one module per Literal kind value).
tools/role_schema/cli.py:validate_refs (Python implementation; 6th cli.py subcommand beyond plan §Task 1.8's enumerated 3 — legitimate Phase 3 CLI surface extension per Task 3.5 DO Stage 1 Q11-a docstring update).
tools/dispatch-validate.sh (sibling primitive — composes the same 5 P1 resolvers via _collect_references plus P4 structural + P3 role-canonical).
tools/role_schema/cli.py:_collect_references (P1 walker — packet-driven composition of the same 5 adapters; used by dispatch_validate).
- External research §7.6 — Cerberus full-document-validation semantic (no halt-on-first).