T08
The necessary fix belongs in the shared vendor_mirror_canonical() matcher, with regression coverage through every existing command consumer. This is a static assessment; nothing was executed to establish runtime behavior.
The governing policy is explicit in .gzkit/skills/gz-obpi-specify/SKILL.md, “What is mechanical today”: creation declarations exempt missing paths, but “a vendor-mirror path fails regardless.” governance/brief_path_validity.py expresses the same invariant and defines the five refused roots and canonical replacements. The request extends that existing refusal to an equivalent spelling; no policy decision about creation exemptions is unresolved.
Observed implementation explains the bypass. extract_allowed_paths() preserves the first backtick token, including ./. vendor_mirror_canonical() only removes trailing slashes before prefix matching. Consequently ./.claude/rules/tests.md misses the refusal. Filesystem existence accepts that spelling; if missing, _normalize_for_creates() strips its literal ./, allowing a matching creation declaration to suppress the remaining error. Without existence or a declaration, the checker reports a missing path instead of mirror refusal and canonical advice.
Normalize one literal leading ./ inside vendor_mirror_canonical() before both prefix matching and suffix slicing. Keep the original Allowed Paths token for diagnostics. Normalizing only the comparison would mis-slice the canonical suffix. Do not use lstrip('./'): it removes meaningful dotfile characters, a failure already documented and tested for creation extraction. Preserve root/trailing-slash handling, glob suffixes, component boundaries, and mirror-first ordering. Avoid changing the general extractor: other consumers use its raw paths, and this correction does not require changing them.
The producer/consumer trace requires these coupled proofs:
- Brief and plan creation extractors share
_extract_creates_paths_from_text(). They recognize inline**CREATE**, “Creates these files” headings, and the scaffold phrase emitted byspecify_cmd._build_discovery_prerequisites(). Test all three with a prefixed mirror: declaration must never suppress refusal. Preserve legitimate missing canonical-path exemptions with mixed prefixed/unprefixed declarations. check_brief_path_validity_for_brief()combines brief extraction and checking.obpi_cmdinvokes it through_validate_brief_path_existence()in both single and batch authored validation; errors cause exit 1. Preserve lenient validation without--authored. Test actual command wiring, not only its wrapper.plan_audit_cmd._gather_brief_path_gaps()unions brief and plan creation sets before calling the shared checker._emit_result()writes FAIL, gaps, and gap count to the receipt and emits text/JSON before exit 1. Integration tests must assert mirror-specific advice and failed receipt/output for declarations originating in either document.adr_promote._check_scaffold_obpis()incorporates the same wrapper’s errors. Test prefixed mirrors, including declared creation, in its structure-error collection. The outer command applies promotion before this check and permits--force; do not describe this as an unconditional pre-write refusal or change that behavior in this fix.
Add explicit expected canonical results for all five roots, each with and without ./, using files, roots, trailing slashes and globs. Cover existing and absent mirrors, canonical .gzkit paths, .claude/plans, and similarly named non-mirror directories. Existing unit and integration tests cover unprefixed refusal and creation precedence but do not establish prefixed parity. These tests would catch broken normalization, incorrect suffix slicing, reordered exemptions, or disconnected consumers.
Update the authored-validation and plan-audit manpages to explain this validity rule and creation precedence. If clarifying the primary specify skill, propagate its canonical change through the established package/vendor sync route; sync_pkg_surfaces() reads canonical skills and produces package copies. No mirror edits are needed directly.
Independent leads: the sync manpage still names GitHub delivery while current sync implementations route skills to Claude/Codex; rule rendering also transforms filenames, whereas refusal advice preserves suffixes. Preserve existing advice parity here and record those broader mapping questions separately. The plan scope helper always returns true, despite the manpage’s scope-check claim; that is another independent lead.
Unverified: no tests, checks, workflows, or edits ran. Historical ownership and omitted doctrine cannot be established. Some broad search output was truncated. The completed trace is not proof of completeness.
Read inventory (paths relative to /tmp/gzkit-remedy-snapshot; some reads were excerpts): .gzkit/skills/gz-obpi-specify/SKILL.md; data/vendor-manifest.json; src/gzkit/{governance/brief_path_validity.py,commands/{plan_audit_cmd,obpi_cmd,adr_promote,specify_cmd}.py,sync_surfaces.py,sync_skills.py,rules/__init__.py}; tests/{governance/{test_brief_path_validity,test_brief_path_validity_wiring}.py,test_plan_audit_cmd.py}; docs/user/manpages/{obpi-validate,plan-audit,agent-sync-control-surfaces}.md; /tmp/gzkit-remedy-inputs/T08.md. Searches covered supplied src/tests/docs/rules/skills. Attempted but unavailable: three docs/governance/{trust-doctrine,state-doctrine,advisory-rules-audit}.md, .gzkit/personas/main-session.md, src/gzkit/rules.py. Actual shell/read calls: 13, including task read and this output write.