T10

The repair belongs in vendor_mirror_canonical, shared by the existing brief-path checks. Static inspection establishes the cause; no behavior was executed.

extract_allowed_paths preserves ./. The classifier adds a trailing slash but compares against unprefixed mirror roots. Consequently ./.claude/rules/tests.md escapes classification: an existing file passes; a missing file produces an existence error without canonical advice; a missing file declared created passes because _normalize_for_creates already removes one literal ./. This contradicts the module’s unconditional mirror-refusal invariant and gz-obpi-specify’s statement that “a vendor-mirror path fails regardless.”

Normalize one literal leading ./ inside the classifier before both prefix comparison AND tail slicing. Using the original string for slicing after normalizing only the comparison would corrupt canonical advice. Preserve the original entry in the diagnostic and preserve dots in .claude/.gzkit; do not use lstrip("./"). Keep separator boundaries, directory-root behavior, trailing-slash handling, glob tails, and the five existing mappings. Keep mirror refusal before existence and creation exemptions. No extractor-wide rewrite or filesystem resolution is needed.

The authority/producer/consumer trace yields these coupled obligations:

  • .gzkit/rules/skill-surface-sync.md makes .gzkit/skills and .gzkit/rules canonical. sync_skill_mirror copies source bytes into mirror targets, confirming why edits there are overwritten. The classifier produces canonical advice from its explicit mapping. .gzkit.json supplies today’s vendor configuration; the classifier deliberately also retains historical GitHub audit subjects. Do not replace that map with enabled-vendor filtering as part of this spelling repair.
  • Brief authors supply Allowed Paths and creation markers. specify_cmd._build_discovery_prerequisites also produces “intentionally created in this OBPI”; the shared creation extractor recognizes that wording, **CREATE**, and creation headings. Preserve all three producers’ semantics. Test mirror precedence through actual extraction, not just a hand-built creation set.
  • obpi_cmd uses the shared wrapper in both single and batch validation, only with --authored. Preserve lenient mode. plan_audit_cmd._gather_brief_path_gaps unions brief and plan creation declarations before calling the checker. Its emitter writes FAIL, gaps, and exit 1; pipeline_markers.load_plan_audit_receipt consumes that verdict. Verify this complete result path.
  • adr_promote._check_scaffold_obpis includes shared path errors in structural errors. Promotion runs this after applying promotion, and --force overrides the block. “Pre-flight” wording in the shared module therefore overstates promotion’s timing. Preserve current timing/override semantics for equivalent spellings; changing transactional behavior is independent work.

Verification should begin with failing unittest regressions covering all five mappings, prefixed/unprefixed pairs, exact canonical tails, roots with/without trailing slash, and globs. Assert refusal for existing, missing, and creation-declared mirrors. Cover brief inline markers, creation headings, scaffold wording, and plan-only declarations. Positive controls must preserve canonical/source creation exemptions, missing-path errors, .claude/plans, and near-prefix nonmirrors such as .claude/rules-extra.

Extend shared tests, wiring tests, and plan command tests. Existing wiring tests invoke helpers, so they do not prove command dispatch: add authored single/batch command coverage and promotion error propagation using otherwise valid fixtures. Check plan JSON/receipt FAIL, canonical advice, exit status, and receipt-loader interpretation. These assertions must fail if classification, tail slicing, precedence, or caller wiring breaks.

Update the plan-audit and obpi-validate manpages to explain the existing path checks plus ./ equivalence; explain promotion’s actual behavior in its manpage. Clarify the authoring skill’s creation exception and mirror precedence. Skill changes require version/date updates and generated package/vendor delivery through sync, with parity and skill audits. Documentation validation and applicable quality/BDD gates remain proposed verification, not evidence from this assessment.

Independent leads recorded here: _path_within_allowed always returns True despite the plan manpage’s scope-containment claim; broader configurable-vendor coverage differs from the fixed classifier. Neither is necessary to this repair. Repeated ./, traversal, symlinks, and additional mirror roots remain outside this decision. Missing governance doctrine documents prevent a complete doctrine trace; runtime results and repository-wide completeness remain unverified.

Read inventory (paths relative to /tmp/gzkit-remedy-snapshot; selected ranges where applicable): AGENTS.md, .gzkit.json, data/vendor-manifest.json; .gzkit/skills/{gz-obpi-specify,gz-agent-sync}/SKILL.md; .gzkit/rules/{skill-surface-sync,tool-skill-runbook-alignment,cli}.md; src/gzkit/governance/brief_path_validity.py; src/gzkit/commands/{plan_audit_cmd,obpi_cmd,adr_promote,specify_cmd}.py; src/gzkit/{sync_skills,pipeline_markers}.py; tests/governance/test_brief_path_validity{,_wiring}.py; tests/test_plan_audit_cmd.py; docs/user/manpages/{plan-audit,obpi-validate,adr-promote}.md. Also read /tmp/gzkit-remedy-inputs/T10.md; scoped searches inspected snippets under corpus src/tests/docs/data/.gzkit. Attempted unavailable reads: doctrine trust/state/advisory-rules-audit, canonical-source rule, main-session persona. Actual calls: 13 including task read and this output write; no checks executed.