Files
Marius CiepluchandClaude Fable 5.1 d2010d0693 docs: Pathfinder map of src, duplication report, unified proposal, and the breadth ledger
PATHFINDER-2026-10-13/: feature inventory, ten per-unit flowcharts with
file:line evidence, cross-feature and within-unit duplication reports, a
proposal that keeps every package on one of the four active lines, and
copy-ready plan prompts in order. .kiro/breadth.toml: every spec and
capability with a status and the deletion that closes it. No code changed.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-13 09:55:21 +02:00

223 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Duplication report (2026-10-13)
Every claim was checked against the source by the phase-2 agents, not taken from the flowcharts. Paths are
relative to `src/openultrasast/` unless they start with `benchmarks/`. "First" dates are from
`git log --diff-filter=A` and give rough order only.
Verdict in one line: of ten candidate concerns, seven are real duplication (D1 to D5, D7, D10), two are one
owner missing (D8, D9), one is a misplaced type (D6). The three that matter most: D1 (LLM call and cost),
D4+D5 (enclosing function, callers, declaration tables), D3 (the quick-rule pipeline).
## Part A: concerns duplicated across features
### D1. Chat client, resolver and cost metering (accidental, highest value)
Where: `ChatClient` Protocol `.complete` (tool_hunter.py:163); `RankerClient.complete_json` (rank.py:31, tests
only). Clients: `ScriptedChatClient` (tool_hunter.py:176); `OpenRouterHunterClient` (203) drops `json_object`,
calls `complete_chat`, records no usage (222-223); `DeepSeekChatClient` (model/endpoint.py:91) uses
`complete_chat_raw`, keeps `usage` as a list (98), has its own retry on empty JSON (135-144);
`OpenRouterChatClient` (provider/openrouter.py:56, 69, 84). Wrappers: `MeteredClient` (plane/budget.py:68,
`usage` is a dict 95); `FillMeter(MeteredClient)` (learn/fill.py:44) reserves before each call; `Caller`
(learn/program.py:369) caches and replays, diffs `client.usage` as a dict (404-406); `_Recording`
(plane/tasks/verify.py:66); `_CountingClient` (model/scan.py:1628). Two resolvers: `resolve_hunter_client`
(tool_hunter.py:239) ignores config and prices against `DEFAULT_DETECTOR_MODEL` whatever model runs (258);
`resolve_chat_endpoint(config)` (endpoint.py:192) honours `[models]`; their OpenRouter branches build different
wrappers (tool_hunter.py:262 unmetered vs endpoint.py:221 metered). Duplicate env-flag sets tool_hunter.py:36-39
and endpoint.py:39. Four cost paths: `ChatEndpoint.cost` (endpoint.py:70-80; `cost_usd` 147; push/assistance.py:
103-110 inline); `cost_of` (budget.py:59, docstring: "the arithmetic of ChatEndpoint.cost") used by
`_absorb_rows` (158-167), `Caller.usd` (program.py:420-423), `verify._sum_usage` (232-244); `SpendBudget`/
`SpendReservation` (budget.py:260-322) with search/worker.py:218-270 repeating the DeepSeek `extra_body`;
`_CountingClient.cost_usd` (scan.py:1639). `_tokens` copied (budget.py:170, endpoint.py:262). Fallout: the
list-vs-dict `usage` mismatch needs the shim in benchmarks/valid/hunter_arm.py:56-65 ("made every live
classification fail while stubbed tests passed"); the CLI hunt passes the bare client (cli.py:976) so the
shipped scan's hunter spend is never metered.
Why: tool_hunter 09-03, endpoint 09-08, budget 09-29, program 10-01, fill 10-03, worker 10-05; each consumer
added the wrapper it needed (tool_hunter.py:240-244, 257; budget.py:263-266 "existing MeteredClient callers
retain their historical post-call metering").
Legitimate parts: the worker's hard reservation (fail-closed, `max_attempts=1` worker.py:188-189); `Caller`'s
replay cache; `ScriptedChatClient` as a test double. Accidental: two resolvers, two adapters, four cost
formulas, two usage shapes.
Unification: one `resolve_chat_client(config|None) -> (client, endpoint)`; one client class (today's
`DeepSeekChatClient`, renamed) with `usage: list[row]`; one `Meter` with `reserve(bound)`/`settle(row)` so
`MeteredClient`, `FillMeter` and the worker's `_call` are configurations (post-call vs reserve-first); one
`cost(row, prices)`; `Caller` stays as the cache layer; `_CountingClient`, `_Recording` become counters read
off the meter. Lost: calling OpenRouter unmetered; the hunter prices by its configured model (recorded dollars change).
### D2. Sandboxed execution for a verdict (runtime split legitimate, verdict split not)
Shared `SandboxJob`/`SandboxResult` (sandbox/runner.py:29-45; search/_sandbox.py:24; regress/candidate.py:15).
Runners: `DockerCliRunner.run` (sandbox/runner.py:67, 107-137) implements `SandboxRunner` (24);
`search/_sandbox.run` (105) is bwrap+seccomp with extra keyword-only args and does not satisfy the Protocol.
Probes: `resolve_sandbox_probe` (`docker info`, sandbox/probe.py:22-49) vs `_sandbox.isolation_check` (68).
Verdicts: regress `triggerable/not_triggerable/already_covered/inconclusive` + 14 reasons (regress/verdict.py:
10-26); search `demonstrated/inconclusive/no_oracle/could_not_run/could_not_build`, no negative
(search/verify.py:350-701); learn `vulnerable/not_vulnerable/unsure` (program.py:43, classification). Gap:
neither path produces `Rung.EXECUTION_CONFIRMED` (model/ladder.py:33; consumers push_scoring.py:84, mcp.py:98,
learn/schema.py:77), so the evidence ladder never receives execution evidence.
Why: regress/sandbox 09-03; _sandbox.py 10-05 for AX pods without Docker (docstring :1-8).
Unification: make `_sandbox.run` a `SandboxRunner` (options into `SandboxJob`) selected by
`resolve_sandbox_runner`; one outcome record `{outcome ∈ confirmed|refuted|inconclusive|unavailable, reason}`
that emits `Rung.EXECUTION_CONFIRMED`; keep two oracle strategies. Lost: nothing.
### D3. The quick-rule pipeline, re-implemented by each caller (accidental)
Each site runs preprocess → `analyze_entry_points` → `attach_reachability_hints` → `rank_targets` →
`quick_scan_findings` itself:
| Site | Ruleset | Shadow findings | Emit floors |
|---|---|---|---|
| cli.py:643-687 | `load_ruleset` + target ledger (634-638) | filtered (691) | quick/deep only; the standard pool skips them (670 vs 683-684) |
| gate.py:81-84 | None → `PATTERN_RULES` | not filtered here | no |
| map_gate.py:52-54 | `PATTERN_RULES` | not filtered | no |
| pairs.py:794-801, 815 | param | filtered | no |
| plane/tasks/alerts.py:120-122 | `PATTERN_RULES` | kept on purpose (docstring :5) | no |
| improve/evolve.py:311-315 | param | filtered | no |
| push/quick.py:87-92 | `PATTERN_RULES` | filtered; no preprocess, targets from changed files | no |
| hunter_tools.py:232 (`flows`) | None | filtered | no; re-runs preprocess per tool call (307), uncached |
| hunter.py:151 | pool | per task | no |
Plus benchmarks/learn/build_features.py and benchmarks/measurements/2026-09-30-php-quick-rules/measure_php_quick.py.
Legitimate differences: push's changed-files target set; alerts keeping shadow findings. The divergent ledger
and floors mean a gate measures different rules from what `ousast scan` ships.
Unification: one `quick_pipeline(root, targets=None, *, ruleset, policy, floors, include_shadow=False) ->
(targets, entries, rankings, findings)`; cli wraps steps in `run_stage` through a callback. Lost: nothing;
gates pass `ruleset=PATTERN_RULES` explicitly to keep today's numbers.
### D4. Enclosing function, function spans and callers (two naming systems, accidental)
Tree-sitter path: semantic/functions.py `_DECL` (56-69), `RESERVED_FUNCTION_LABELS` (23-51), sentinel
`<module>` (20), `function_at` (136); docstring (1-9): "the single source of that naming. Every consumer must
therefore name ranges the same way, or function-scoped matching silently fails". Used by hunter_tools
`_named_span` (317-323), overlay, pairs, model/candidates. Regex path: plane/tasks/repo_facts.py `DECLARATION`
(48-63), `KEYWORDS` (64-88), sentinel `<global>` (34), `enclosing` (131), `declared_functions` (118),
`call_sites` (154); used by plane/tasks/alerts.`function_at` (108-114); learn/features
`FeatureContext.function_at` (365-370), line-for-line the same as the alerts copy; learn/excerpt
`function_bounds` (302) with its own brace matcher; learn/labels.py:48, learn/roles.py:35, plane/harvest.py:46.
Two caller indexes: `repo_facts.call_sites` (154) written to facts.json (301), read by `verify.callers_of` (128)
accepting two facts shapes (131-141); `learn/features.callers()` (372-395) with a separate `_CALL` regex (49).
`hunter_tools.find_refs` (83) is a plain `\bsymbol\b` grep. `mapping.analyze_entry_points` (123) is the single
source for entry points, recomputed per caller (nine D3 sites plus learn/features.py:415, push/runner.py:123).
Why: functions.py 09-05 for the pair scorer; repo_facts.py 09-29 as a pod task avoiding tree-sitter, so learn
imported it. functions.py already has a regex fallback (120-133). Two sentinels and two keyword sets mean labels
(`<global>`) and scorer ranges (`<module>`) can name the same function differently, the exact failure
functions.py:6-8 warns about.
Unification: one `functions` module with `ranges(path, text, lang)` (tree-sitter, regex fallback),
`enclosing(line)`, `bounds(name)`, `call_sites(names)`, one sentinel, one reserved set; `repo_facts` re-exports
it so the runner still finds the task by name. Lost: Java/Go/Ruby/PHP patterns in DECLARATION must merge into
`_DECL` or labels change; re-label and diff before switching.
### D5. Declaration tables, product-file filter, language detection (accidental)
Product-file filters: `repo_facts.product_files` (96-115, rglob, own `_EXCLUDED_DIRS`/`_NOT_PRODUCT` 36-43,
`LANGUAGES` 33) vs `preprocess.enumerate_source_files` (96-100, 240, `git ls-files`); excluded-dir lists also at
preprocess.py:9 and model/partitions.py:194. Language detection: `preprocess.detect_language`/
`LANGUAGE_BY_EXTENSION` (11, 130) is the one extension map (push/runner.py:219-226, regress/candidate.py:14);
`learn/excerpt.resolve_language` (200) sniffs; alias maps at semantic/facts.py:18, semantic/obligations/facts.py:21,
model/candidates.py:49; language enumerations at learn/schema.py:54, repo_facts.py:33. The tables are imported
by learn/labels, features, roles, excerpt; plane/harvest, generate (`_digest` 72), remember (44); plane/tasks/
alerts, features: this is the Learn↔PlaneTasks cycle.
Unification: move `DECLARATION`/`KEYWORDS`/`product_files`/`_digest` into a neutral `source.py` or D4's module;
one `LANGUAGE_ALIASES` beside `LANGUAGE_BY_EXTENSION`. Lost: nothing; the cycle goes away.
### D6. Contracts and the CPG↔Model cycle (layered by design, one type misplaced)
contracts.py:1-93 generic `Contract`; model/contracts.py 12 classes (19-205); push/contracts.py 9 classes
(18-161) building on `QuestionIdentity`/`ScopeDecision` (14). Layered, not duplicated. The cycle is
`ExecutionBudget` only (model/contracts.py:19-33; cpg/backend.py:37, cpg/session.py:44); reverse direction
private names `_DeadlineExpired`/`_remove_owned_tree` (cpg/backend.py:1554) imported by model/partitions.py:14,
cpg/artifact.py:151 (lazy) and push/cache.py:22 (missed by the flowcharts). Budget types `ScanBudget`
(scan.py:79), `HunterBudget` (hunter.py:19), `SearchBudget` (search/budget.py:8), `SpendBudget` (budget.py:260),
manifest `Budget` (plane/manifests.py:185) cover different quantities: legitimate.
Unification: move `ExecutionBudget` and public `DeadlineExpired`/`remove_owned_tree` into contracts.py or a
`deadline.py`. Lost: nothing.
### D7. Reachability multipliers and severity (accidental constants, legitimate severity split)
Same dict at scoring/project_score.py:15 `REACH_MULT` and calibration.py:255 `_BASE_REACH_MULT` (both
06-17/22). The set `{"reachable","inferred-file-surface"}` at regress/verdict.py:28 and complexity/ledger.py:23;
produced once at findings.py:164. Severity: policy-derived via `SEVERITY_LABEL` (findings.py:39) for quick rules
(132) and overlay (overlay.py:134); hard-coded "medium" for model-layer findings (cli.py:1373); LLM's own or
"medium" for the hunter (tool_hunter.py:35, 619); sensitivity-mapped for obligations (check.py:162).
Unification: calibration imports `REACH_MULT`; one `REACHABLE_STATUSES`; `resolve_severity` whenever a family
maps to a CWE. Lost: nothing.
### D8. Hotspot construction for the hunter (legitimate inputs, no constructor)
`Hotspot` (complexity/map.py:20-28, `band` documented "high | medium | low") built by `_hotspot_from_signals`
(62-102), plane/tasks/verify.py:180-186 with `band="candidate"` (outside the vocabulary), and
benchmarks/valid/hunter_arm.py:233-242 with `band="low"`. Unification: `Hotspot.for_file(path, function=None,
rationale, band="low")` or let `run_tool_hunter` take `(path, function)` targets. Lost: nothing.
### D9. Result and record writers (accidental sequencing, one owner missing)
`findings.json` written at cli.py:708, 788, 801, 814, 877, 1005 after each proposer; merge-by-id dedup repeated
(784-787, 989-1000). Writes into the scanned repo all come from `CALIBRATION_DIR` (cli.py:94):
`false_positive_learnings.json` (635, 1021-1022), `rule_policy.json` (636; improve 1137),
`complexity_ledger.json` (765, 872), `rule_signals.json` (improve/evolve.py:173-174), `improve_journal.json`
(1141-1144); plane/tasks/loop.py:43 re-declares the path. The plane `MemoryStore` (memory.py:267) is a separate
store that knows nothing of these. Unification: accumulate findings per proposer, write once in a `finally`
(and move scoring after the merge, fixing the U1 bug); one `CalibrationLedger(target)` owns the five files.
Lost: partial findings.json on a crash unless the finally write stays.
### D10. Other
- JSON-in-fences parsers: provider/openrouter.py:205 `parse_json_content` (raises; used by program.py:326,
compile.py:264, worker.py:329); tool_hunter.py:581 `_load_json` (returns None); tool_hunter.py:409
`_looks_like_json`. `cpg/backend.extract_payload` (203) is not a duplicate (Joern BEGIN/END markers, partial
bodies). Fix: tool_hunter wraps `parse_json_content`.
- Retry: one helper `call_with_retry` (openrouter.py:34, used 115, 168); the duplication is in policy:
DeepSeek's empty-JSON re-call (endpoint.py:135-144) makes up to 2×3 attempts; `bounded()` (103-113) and the
worker (worker.py:188) force one attempt. One policy on the client.
- `resolve_*` factories (tool_hunter.py:239, endpoint.py:192, sandbox/runner.py:59, sandbox/probe.py:49,
cpg/backend.py:1813): legitimate pattern; only the first two overlap (D1).
- Two evidence vocabularies on one record: `StaticFinding.evidence_level` (findings.py:139, overlay.py:115,
tool_hunter.py:621, check.py:164) and `rung` (findings.py:34-35: "the older overlay pipeline's word"). Fold into `rung`.
- Excerpt helpers `hunter_tools.read_definition` (174) and `learn/excerpt.function_bounds` (302): covered by D4.
Confidence: high on D1, D3, D4, D6, D7, D9; medium-high on D2, D5, D8. Unverified: whether
`evaluate_benchmark` filters shadow findings for the gates; `MeteredEmbeddingClient`/`UnmeteredUsage`
(budget.py) as a fifth metering path; callers of `resolve_chat_endpoint` beyond push/assistance.py:84 and
learn/cli.py:254.
## Part B: repetition inside the big units (22 items, about 650 to 800 lines; four are correctness risks)
Correctness risks first: an unbounded Joern path drops partial answers on timeout (W4.3); plane uploads follow
redirects while search uploads refuse them (W8.2); the advisory engine findings are deduplicated differently in
the background summary and the terminal report (W3.3); evaluate builds folds differently from experiments (W7.2).
### U3 push/
- **W3.1 Git command prefix twice.** `["git","-c","protocol.allow=never","-c","core.hooksPath=…","-C",repo,*args]` at snapshot.py:94 (`_git`, subprocess.run, 10 s) and :570 (`_bounded_git`, Popen+selector); `_git` already routes through `_bounded_git` when a budget exists (83-91) and `_repository_path` (74-79) fakes a 10 s budget. Fix: one argv constant, then always `_bounded_git`. ~20 lines.
- **W3.2 Two forked-child helpers.** `_prepare` (runner.py:56-115: os.pipe, selector, pickle, 32 MiB cap) vs `_save`/`_write_artifact` (report.py:120-199: multiprocessing.Pipe); identical finally blocks (runner.py:107-115, report.py:190-199). Fix: `run_forked(fn, *, until, output_cap)`. ~35 lines.
- **W3.3 Advisory engine filter twice, different dedup keys.** `background._summary` (background.py:113-145) dedups on (family, path, line); `report._advisory_engine_lines` (report.py:241-265) on `defect_id`. Counts can differ for one push. Fix: `is_advisory(disposition)` + one key. ~15 lines.
- **W3.4 Two "scan complete" checks that disagree.** `report._completed_scan` (108-117) vs `policy.compare_evidence.complete` (policy.py:502-512; allows tier_zero deferrals); push/contracts.py:64 a third partial copy. Fix: one `scan_complete(scan, *, allow_tier_zero)`. ~15 lines.
- **W3.5 Budget arithmetic written out 66 times** across 14 files (push 47, model 13, cpg 16); `ExecutionBudget` (model/contracts.py:19-32) has no methods. Existing copies: snapshot.py:635, 642; scan.py:1435; backend.py:1377, 1399; report.py:160; partitions.py:117, 183, 188, 205, 253, 267; regions.py:313; scan.py:203. Fix: `expired()`, `remaining()`, `cleanup_end`, `check()` on `ExecutionBudget`, landing with the D6 move. ~40 lines.
- Unreachable: none at def level. vetoes.py (533 lines) only under `--experimental-record-vetoes` in replay; callers runner.py:49, benchmarks/push/m1a_record_vetoes.py:32, transfer_replay.py:37, tests. `GraphLease` Protocol (cpg/artifact.py:70) only tests/test_push_contracts.py.
### U4 model/ + cpg/
- **W4.1 Arbiter chosen by spec type three times**: `scan._grouped` (scan.py:1491-1506), `pipeline._arbitrate_all` (93-98), `pipeline._arbitrate` (117-121); identical `request_params(spec, *, function, file)` in taint.py:22, dominance.py:30, config_value.py:22; three row normalisers (taint._flows 178, dominance._operations 144, config_value._settings 125) and three `_location` builders (taint.py:297, dominance.py:124, config_value.py:96). Fix: `ARBITERS = {TaintSpec: Arbiter(...)}` table + shared `_rows`, `_site`. ~45 lines. Divergence: `_grouped` passes `hook_callbacks`, `field_parameter_sources` (1498-1505) but `taint.verdicts` (taint.py:124-127) does not, so the single-query fallback (scan.py:519) asks a different question from the batch.
- **W4.2 Three build routes in `_build_impl`** (backend.py:573-664): "frontend → None ⇒ dispose; `_apply_overlays`" twice (630-638, 648-653); unparsed warning twice (618-619, 654-655); `CpgResult(...)` twice (622-630, 656-664). Fix: `_frontend_or_none()`, `_result(paths, unparsed, dispose)`. ~30 lines. Bug candidate: the joern-parse retry (648) calls `_build_with_frontend` without `exclude`, so vendored trees are included on retry.
- **W4.3 `_run` vs `_run_bounded`** (backend.py:1514-1555 vs 1414-1484): same Popen/`_jvm_env`/communicate/group kill; the unbounded `_run` returns None on timeout and never calls `_partial_stdout`, so the partial-answer harvest at `_batch_once` (1247) works only under a budget. Fix: one `_run(command, timeout, cwd)` with deadline `min(timeout, budget.remaining())`. ~35 lines.
- **W4.4 `query` (1285-1316) vs `_batch_once` (1193-1281)**: same script check, session-or-`joern --script`, `_run`, `_engine_failure`, `extract_payload`, `_time_available`. Fix: `_invoke(cpg_path, query, params, *, harvest)`. ~25 lines.
- **W4.5 Shard merges** `query_across` (933) / `query_batch_across` (951). ~8 lines.
- Unreachable, new: `pipeline._arbitrate` config and taint branches (119-121; only caller 96 is inside `isinstance(spec, DominanceSpec)`), so `config_value.verdict` (90) and `taint.verdict` (72) are test-only; `taxonomy.hierarchical` (76) no caller at all; `taxonomy.related` (80) tests only. Confirmed: model/report.py `SliceRow`, `overfitting_gap`, `render_findings` tests only; model/audit.py no src importer.
### U7 learn/
- **W7.1 Repository-cluster bootstrap four times**: `evaluate.cluster_bootstrap` (318-337), `experiments._group_bootstrap_precision` (1107-1122), `experiments.paired_bootstrap` (673-706), `calibrate.check` (198-213); percentile rule inconsistent (`int(q*(n-1))` thrice vs `round` in calibrate._percentile 192). Fix: `cluster_resample(groups, rng)` + `percentile_ci(values, alpha)`. ~45 lines, one quantile rule.
- **W7.2 Compile split and folds three times**: learn/cli.py:312-313, compile.py:367-370, experiments.program_folds (458-463); cli and compile ignore a configured `k`, only compile calls `assert_disjoint`, experiments reads `k`. Fix: `folds.evaluation_folds(memory, cfg) -> (split, folds, digest)`. ~10 lines, closes an evaluate/experiment mismatch.
- **W7.3 JSONL readers five times**: `examples.read_jsonl` (333), plane/tasks/features._jsonl (188), remember._jsonl (156) byte-identical; loop.py:227 inline; typed `experiments.read_units` (440), `memory.read_memory` (956). Fix: one `read_jsonl`. ~15 lines.
- **W7.4 `run` (899-996) vs `run_rule` (1035-1096)**: `_checked_units` copied at 921-930 vs 1021-1032; summary `put_row` duplicated 989-995 vs 1088-1094; the row header repeated at 393, 521, 862-866, 990, 1093, 1305-1309. Fix: call `_checked_units`; `_experiment_row(manifest, pin, task, **extra)`. ~35 lines.
- **W7.5 SourceGuard vs improve.memory.Guard: not duplicates** (path allowlist vs row exclusion).
- Unreachable: `roles.load_roles` (539) no caller; `features.language_of` (65) no caller (new); `folds.leave_one_*` (129, 138) only tests/test_learn_retrieve.py:169-170; `features.build_for_scan` (467) only tests/test_learn_roles.py:180; `plane/tasks/features.recorded_case` (202) tests only. `program.prepare_ensemble` is live (program.py:521).
### U8 + U9 plane/, search/, benchmarks/ax
- **W8.1 Safe tar extraction five times**: `delivery.collect` (delivery.py:134-140) and `egress.Receiver.accept` (egress.py:278-284) byte-identical; `runner._export` (runner.py:500-504, `filter="data"`); `executor.unpack_checkout` (executor.py:658-690, size/count/.git caps); `verify_task.extract_checkout` (161-215, gzip cap). The plane copies have no size cap and no `filter="data"`. Fix: one `safe_extract(data, dest, *, max_bytes, max_files, allow_dirs)`. ~50 lines.
- **W8.2 Presigned PUT/GET with different redirect handling**: plane `runner.deliver`/`fetch` (719-790) follow redirects; `search/executor.URLTransport` (508-535) no proxy + NoRedirect; `verify_task.upload` (262-270) its own `_NoRedirect`. Fix: one `ObjectURL` client (no proxy, no redirect, bounded read, backoff). ~60 lines.
- **W8.3 Presigning: one store API, two layouts**: `Delivery.body` (delivery.py:95-111, `runs/<run>/<task>/`) vs batch.py:289-304, 716-723 (`engine-queue/...`). Part of W8.4.
- **W8.4 Two task-dispatch stacks**: plane (reconciler `Ax` 185-228 → Router POST → plane/runner 1,037 lines → tar PUT → `Delivery.collect`) vs search (batch.py `lane.ax` 241-262 → `SearchExecutorTask` 675-800 → `search/executor.serve` 622 → part PUTs). Four CLI wrappers: `reconciler.Ax._run` (188; no timeout, no KUBECONFIG, no `--atespace`), `batch.ax` (241-262), `egress.Egress._run` (174), `batch.check_cluster_health` (221); three error tails. First step: one `Cli(executable, base_args, env, timeout).run(*args)` + `error_tail(proc)` in plane/ (~40 lines). Merging the stacks is a phase-3 decision.
- **W8.5 Redaction three ways**: `router.redact` (router.py:50, exact values), `redaction.redact_secrets` (redaction.py:38, patterns, `***REDACTED***`), `search/task_storage.diagnostic` (100-120, own `sk-`/`gh*_`/`Bearer` regexes, `[redacted]`); `runner.safe_url` (713) URL-only. runner.py:621 imports `router.redact` (not a copy). Fix: `redaction.py` takes `values=`, `urls=True`. ~20 lines.
- Unreachable: receiver delivery mode (reconciler.py:409-412, egress.py:233-324, runner.py:829-840; `OUSAST_DELIVERY` set by no script, ops/ax/up.sh:121-124 still creates the Service); `delivery.put_input` (167) tests only; `coordinator.BatchExecutor` (426) tests only; `memory.purge` (836) + `delete_version` (833) tests only; `memory.drop_rows` (399) used by benchmarks/learn/build_features.py.
### U1 + U2 cli.py and core
- **W1.1 Argument plumbing**: `_main` (cli.py:426-470) unpacks the namespace for `_scan` (4), `_benchmark` (3), `_improve` (14, 432-447), `_pairs` (6), `_repos` (4). Fix: `set_defaults(func=…)`, handlers take `args`. ~45 lines.
- **W1.2 "Is a file" guards** at 1077-1078, 1123-1124, 1128, 1184-1185. Fix: `type=existing_file`. ~10 lines.
- **W1.3 `_benchmark` vs `mcp._benchmark`** (mcp.py:160-183): the MCP copy skips `runtime_seconds`, `baseline_findings`, `write_benchmark_artifacts`, so MCP benchmark runs leave no artifacts. Fix: `benchmark.run_benchmark(manifest, config, mode)` used by both; inline `_load_scan_findings` (1499). ~20 lines.
- **W1.4 Degradation recording 13 times** (cli.py:647, 698, 773, 817, 843, 996, 1312, 1330, 1343-1344, 1411, 1427, 1445-1446), mixing raw dicts and `stages.skip_as_degradation` (stages.py:32). Fix: `runtime.degrade(stage, reason, detail)`. ~15 lines.
- **W1.5 `write_*` wrappers of one json.dumps** (24 sites in 19 files: findings.py:113, verification.py:121, mapping.py:240, 245, config.py:256, rank.py:72, calibration.py:185, 190, overlay.py:126, preprocess.py:90, regress/candidate.py:222, complexity/map.py:57; inline cli.py:726, 746, 1713; `_write_json` in reconciler.py:76 and repo_facts.py:211). Fix: `run.write_json`, `write_records`. ~35 lines.
- **W1.6 Run-dir creators** run.py:18-26 vs benchmark.py:185-190 (~6 lines). **W1.7 findings.json six writes** (~5 lines).
- Unreachable, each only one test file: `calibration.compute_ranking_metrics`, `findings.build_quick_hunter_prompt`, `verification.validate_evidence_transition`, `fusion.should_fuse`, `harness.ProcessorSpec` (+ `_run_processor`, `_contract_violation` with no reference at all), `rank.rank_targets_with_model`, mapping's five task/record functions.
Confidence: high for W3.2-W3.4, W4.1-W4.4, W7.1-W7.4, W8.1, W8.2, W8.5, W1.1-W1.5 and the unreachable
list (name greps across src, tests, benchmarks, ops; dispatch-table or getattr calls would be missed); medium
for W8.4 and the line estimates. The `hook_callbacks` divergence, the missing `exclude` on retry, the redirect
handling and the plane tar limits come from reading, not from running anything.