Run Scanner
Execute the audit scanner at $ARGUMENTS.
Protocol
Orient — Read these files (do NOT skip):
CLAUDE.md(system overview — nine-gate Quality Bar and Gate 9 Source-of-Truth table)navigation/features.yaml(feature index)navigation/architecture.md(integration-pattern + canonical-repo map; every repo name encountered during the scan is classified against this file)- The relevant
navigation/domains/*.mdfor the scanner's target domain
Load scanner definition at
$ARGUMENTS. Extract:- Target repo and scope
- Method (step-by-step)
- Criteria (what counts as a finding)
- Output format
- Chunking strategy (if applicable)
ontology_feed:block (if present) — determines run mode (see step 2.5)
2.5. Determine run mode (rev 13):
- If
ontology_feed:is absent ORontology_feed.enabled: false→ Mode A (standalone). Continue with steps 3-10 unchanged. This is backward-compatible — every existing scanner runs as before. - If
ontology_feed.enabled: true→ Mode B (ontology-fed). Execute mode B sub-protocol below before continuing to step 3. Mode is LOCKED for the run — never switched mid-session.
Mode B sub-protocol (rev 13, per APPROACH.md §20 + scanners/README.md "Ontology-fed mode")
2.5.1. Substrate-presence check: verify lineage/{substrate_repo}/ exists with manifest.yaml, nodes.jsonl, feature-flows/index.yaml. If ANY is missing → ABORT with INSUFFICIENT-ONTOLOGY-COVERAGE: substrate not found at lineage/{substrate_repo}/. NO silent fallback to mode A — explicit mode-A re-run (with the ontology_feed: block disabled or absent) is the only path to standalone behaviour.
2.5.2. Stamp ontology commit: read lineage/{substrate_repo}/manifest.yaml:current_substrate_commit (or equivalent — last_scan_commit). Record as ontology_commit_consulted: on every emission in this run. If current_substrate_commit < HEAD - staleness_threshold_commits (default 50) OR last-modified >48h, emit WARNING: stale-ontology — trust downgraded and set ontology_corroborated: stale_warning on every emission for this run. (Per-scanner override: ontology_feed.staleness_action: abort blocks the run instead.)
2.5.3. Load coverage check: enumerate intended F-NNN entries per the scanner's ontology_feed.feature_scope_filter: (e.g. pillar_id: [P-07] or target_repo_overlap). If the filtered set is empty AND the scanner declares feature_scope_filter: → ABORT with ZERO-FEATURES-IN-SCOPE. If the filtered set covers <50% of the maintainer-expected count for this scanner's scope → emit coverage_gap_for_scan: <missing entities> to the scan-feed log and continue.
2.5.4. Load clue sources in the order declared by ontology_feed.clue_sources: (always feature-flows/detail/F-*.yaml first per Rule 21):
- Glob
lineage/{substrate_repo}/feature-flows/detail/F-*.yaml; filter byfeature_scope_filter:; sort byfeature_id. This is the iteration set for steps 5.B. - Read
lineage/{substrate_repo}/concepts/index.yaml+ relevant detail shards (only when scanner enablesconcepts.yamlconsultation). - Read
lineage/{substrate_repo}/shoebox/detail/SHB-*.mdwithCategory: open | clustering(only when scanner enables shoebox consultation). - Read
lineage/{substrate_repo}/doc-gaps/index.md+ per-finding details — DEDUP/PRIORITY HINT ONLY. A feature absent from doc-gaps is NOT presumed documented.
Check coverage manifest at
state/coverage/{scanner-id-dashed}.yaml:- If manifest exists: read it, pick next batch of
not-scannedorchanged-since-scanitems - If no manifest exists: run enumeration first (follow
/enumerateprotocol inline), then pick first batch - Batch size: 10-15 items per session (adjust based on item complexity)
- If manifest exists: read it, pick next batch of
Load existing findings — Before scanning, read ALL existing findings files in
findings/:- Scan every
findings/*/directory (not just the current scanner's directory — gaps cross scanner boundaries) - Build a mental index of: finding ID, location, short title, severity
- During scanning, if you discover a gap that matches an existing finding (same location, same issue), do NOT create a duplicate — instead note an enrichment (see step 5)
- Match broadly: same file + same general issue = match, even if wording differs or the scanner that found it was different
- Scan every
Execute the scan on the selected batch:
- Follow the scanner's method systematically for EACH item in the batch
- Apply criteria to each item
- Every finding must cite a Source of Truth by Gate 9 class (Repo / Integration / Config / Builder / Spec / Term / Lifecycle / Dep / Handler / Cross-repo / Backlog / Ontology (rev 13, additive)) — full table in
pillars/{active}/gates.mdGate 9 + executable procedure inplaybooks/claim-inventory.md. "The doc says X but the code/SoT says Y" is a finding; "the doc might be wrong" is not. If you cannot cite an SoT, the gap is speculation — either find the SoT or drop the finding. - For mode B (rev 13): every finding sourced from an ontology clue cites BOTH the ontology and the underlying primary-source class — e.g.
Source-of-truth: Ontology[F-001:hop-1] → Repo[DataEntityDetails.tsx:56-64]. A finding citing ONLYOntology[...](without a primary-source class) is rejected by Gate 9 — same shape as Gate 9 already rejects "the doc might be wrong" without an SoT. TheOntologyclass is additive (12th class after the existing 11); it does NOT replace any existing class. - For SDK-backed integrations, run
playbooks/unset-parameter-audit.md(Gate 5) — every unset builder parameter with an unsafe SDK default is a finding (retrospectives/LSN-002is the canonical case). - For every outbound URL referenced in the doc under scan (
github.com/opendatadiscovery/*,docs.opendatadiscovery.org/*, external docs), resolve the URL againstnavigation/architecture.mdor (if missing) WebFetch /gh repo view. A broken or mis-targeted URL is a finding with SoT classRepoorIntegration(retrospectives/LSN-003is the canonical case). - Record findings as you go, each with its SoT citation inline
- Do NOT modify any files in target repos (read-only scan)
5.B. Mode B per-feature investigation (rev 13 — runs INSTEAD of step 5 when
ontology_feed.enabled: true)The scanner's PRIMARY investigation target is
lineage/{substrate_repo}/feature-flows/detail/F-*.yaml, not the scanner's traditional axes. For each in-scope F-NNN (the iteration set loaded in step 2.5.4):- Read F-NNN end-to-end. Extract:
feature_name,pillar_id,pillar_anchored_feature_name,description,contributing_nodes[],chain[].evidence,observed_vs_expected.facets[],status,seeded_from(if any). - Derive expected doc location. From
pillar_id+pillar_anchored_feature_name+system-mission.md's pillar-to-doc-path map. Check whether the page exists atdocumentation/docs/{expected_doc_path}and (if scanner is the canonical-accuracy variant) WebFetch the live URL. - Verify against code (4-tier clue ladder):
file_exists(sidecar saysX.java:139) → Read the cited file ±5 lines. Verified iff cited symbol present at cited line ±5.assertion_about_code(sidecar says "method returns 200 OK") → Read the cited region + verify mechanically (regex / direct match). Verified iff textual claim and read evidence agree.cross_layer_behaviour(chain-hop multiplicity, end-to-end DB-delta) → ifprobe_verifications:populated, run the cited probe; else file aprobe-neededfinding.doc_drift(sidecar'sdocs_link_semanticclaim) → fresh WebFetch in this session. Verified iff live status + key excerpt match sidecar within 30 days.
- Compare
descriptionfield against the live doc. If the feature'sdescriptionparagraph and the live doc materially disagree → emit adriftfinding. - Emit findings (per finding, cite Source-of-truth dual-class):
missing-pageif the doc doesn't exist.driftif description and live doc diverge.missing-caveatperobserved_vs_expected.facets[]entry not mentioned in doc.ontology-driftper hop whoseevidenceline moved or vanished (the code shifted since the sidecar was authored).
- Write back (mandatory; per scanners/README.md "Ontology-fed mode" + APPROACH.md §20.5):
- Append a
scanner_reviews:entry tofeature-flows/detail/F-{NNN}.yaml. APPEND-ONLY (never overwrite). Idempotency key:(scanner_id, scan_run_date). - If the finding pins a per-node defect (e.g.
@Valueconsumer with unsafe default) → also annotate the relevant sidecar underlineage/{substrate_repo}/understanding/*.md(max 5 sidecars per run; above that, the cluster belongs on the F-NNN flow). - If the same gap already exists as a DOC-GAP-NNN in
doc-gaps.md: append acorroborated_by_scanner:block to its per-finding detail file — NO new finding emitted (dedup). - If the F-NNN's description disagrees with the chain (intent-vs-implementation drift) AND the disagreement is structural → append a NEW shoebox thread at
lineage/{substrate_repo}/shoebox/detail/SHB-NNN-{slug}.mdwithCategory: open(the correction surface; feature-flow-builder picks it up on next pass).
- Append a
- Per-scan-run consultation budget enforced (hard caps from
ontology_feed.consultation_budget:):graph-retriever≤5: spawn for ad-hoc "does ontology cover {topic}?" queries beyond enumerated axes.feature-reflector≤3: spawn when a feature-flow's product framing disagrees with its chain AND the scanner can't independently judge.odd-sme≤2: spawn when pillar-mapping or industry-vocabulary alignment is ambiguous.- Above budget → backlog escalation entry (
escalation: pending-sme-review), never silent drop.
- Coherence check before emit: grep
lineage/{substrate_repo}/{refactoring-scopes,doc-gaps,test-map,implicit-adrs}.mdfor any existing finding matching the same(file:line)evidence. Duplicates extend via back-link, never create parallel entries. - Verdict tagging: every emission tagged
STATIC-INFERREDorPROBE-VERIFIED(rev-13 Rule 21 D7). A scan-run emitting onlySTATIC-INFERRED OKrows getsverification_class: descriptive-onlyand does not count toward "feature audited" status. ontology-confirmedvsontology-extendeddistinction: each finding marks whether it verified what the feature-flow already named (ontology-confirmed) or surfaced new behaviour beyond it (ontology-extended).
Mode B's findings file shape is the same as mode A; the
Source-of-truth:field carries the dual-class citation.Write findings — Create the output file:
- Path:
findings/{scanner-id-dashed}/YYYY-MM-DD[-batch-N].md - Format: follow
scanners/README.mdoutput format exactly - Include summary counts at the top
- Note which specific items were covered in this run
- Dedup rules:
- New gap with no prior match → new finding ID (F-NNN), written normally
- Gap matches an existing finding from the SAME scanner → skip (already covered)
- Gap matches an existing finding from a DIFFERENT scanner → create an enrichment entry (see format below)
- Enrichment format (append to the findings file):
### F-NNN ← enriches F-XXX ({original-scanner-id}) - **Original**: F-XXX in `findings/{original-scanner-dir}/{file}.md` - **New evidence**: {what this scanner found that adds to the original} - **Severity adjustment**: {unchanged | escalate to X | de-escalate to X} — {reason} - After writing, update the original finding file: append a
- **Cross-ref**: enriched by F-NNN in \findings/{this-scanner-dir}/{file}.md`` line to the original finding
- Path:
Update coverage manifest:
- For each item scanned: set
status: scanned, recordscanned_dateandscanned_commit - Set
findings_refto the findings file path - Recalculate
scanned_itemsandcoverage_pct
- For each item scanned: set
Update navigation (MANDATORY):
- Every file path discovered during scanning → add to relevant
navigation/domains/*.md - Every repo name or integration pattern discovered (a new collector, a push-client, a platform module the scanner touched) → update
navigation/architecture.mdso future Gate 9 verifications can resolve the repo in O(1) without a fresh WebFetch - This is a core output, not optional bookkeeping
- Every file path discovered during scanning → add to relevant
Update progress — Edit
state/PROGRESS.md:- If scanner is now 100% covered, mark as completed
9.5. Mode B only — write scanner-feed log (rev 13):
- Always emit (even if zero ontology clues were consumed — absence is informative).
- Path:
lineage/{substrate_repo}/scanner-feed/{YYYY-MM-DD}-{scan_run_id}.yaml. - Shape (per
adrs/drafts/research/scanner-ontology-fusion/INTEROP.md§2.5):artefact: scanner-feed scanner_id: <scanner id from frontmatter> scan_run_id: SR-{ISO8601-compact} scan_run_date: 2026-05-27 ontology_commit_consulted: <sha> mode: B # A | B | mixed clues_consumed: # ordered by consumption time - source: feature-flow id: F-NNN fields_read: [...] verified_against_code: true findings_produced: [F-NNN, F-MMM] - source: doc-gap id: DOC-GAP-NNN fields_read: [...] findings_produced: [] dedup_action: wrote_back_corroboration - source: concept | shoebox | graph-search ... agent_consultations: feature_reflector_calls: <int> odd_sme_calls: <int> graph_retriever_calls: <int> write_backs: feature_flows: [F-NNN, ...] sidecars: [<slug>, ...] doc_gaps: [DOC-GAP-NNN, ...] shoebox: [SHB-NNN, ...] warnings: - "<staleness / coverage gap / budget exceeded entries>" - Verification-class verdict (per Rule 21 D13): if
clues_consumed[]contains zerosource: feature-flowentries within scope → setverification_class: corroboration-onlyand emit a warning. The scan-run does NOT count toward "feature audited" status until the next mode-B run completes the per-feature iteration.
- Report:
- Items scanned this session: N
- Findings this session: M
- Coverage progress: X% (Y/Z items total)
- Remaining items: list next batch to scan
- Suggested: "Run
/scan $ARGUMENTSagain to continue (N items remaining)"
Rules
- Always check coverage manifest before scanning — never re-scan already covered items
- If manifest shows 100% coverage and no
changed-since-scanitems, report "fully scanned" and suggest re-enumeration if it's been >7 days - If the scanner path doesn't exist, list available scanners from
scanners/and ask which to run - Prefer false positives over missed gaps (triager will filter later)
- If you find a bug in target code, note it in findings but do NOT fix it. If the defect is clearly actionable upstream (concrete file:line, operator-visible failure mode, suggested fix obvious), the triager who picks up this scan will draft it as an
issues/{repo}/{PREFIX}-NNN.mdupstream issue (seeissues/README.mdand/log-issue); record enough detail in the finding (file:line, severity, repro shape) that the triager doesn't have to re-read the code. - If an item can't be scanned (file missing, access issue), mark status as
errorwith note