docs(spec,steering): lift access-control-prover to capability level; engine/target split, pattern discipline, complexity caps, breadth map

This commit is contained in:
Marius Ciepluch
2026-10-29 12:27:44 +01:00
parent f09dc66592
commit 152f405bcf
6 changed files with 355 additions and 59 deletions
+27
View File
@@ -0,0 +1,27 @@
# Breadth Map (2026-10-29)
One screen: every feature's lane, its state today, and its next move. Aligns `.kiro/steering/roadmap.md`
(goals, gates) with the tree (what exists). Update a row when its state changes, not on every commit.
| lane | state today | next move | owner |
|---|---|---|---|
| Engine: obligation checker + dominance | Stable; target-agnostic; checker owns verdicts | All-kind caller-hop re-judgement (task 8.2, opt-in keyword) | access-control-prover |
| Engine: resolution | Designed (class table + receiver/signature-qualified calls); code not started | Task 7.2 `obligations/resolution.py` | access-control-prover |
| Engine: paths / CFG / trace | Stable; `PathRecord` shape kept by design | No change; resolution plugs in before them | access-control-prover |
| Prover integration | `hunt/prover.py` proves changes; caller join is name-keyed | Receiver-qualified target join + down-depth budget (task 11.1) | access-control-prover |
| Runner | `prover_arm.py` metrics, coverage, zero-byte refusal; gate 1 = 65.2% recall, 15.0 FA/100 | Stale-anchor refusal (task 12.2); re-measure after 7-12 (task 13) | access-control-prover |
| Judge (zero-shot over traces) | `judge_pass.py`; gate 2 measured: safe but inert (keeps 15/15 TPs, drops 5/69 FAs) | No tuning; revisit only if trace facts change the FA mix | roadmap (measured 2026-10-28) |
| Recogniser registry | Designed (Strategy+Registry, `mapping/` package); code not started | Task 9.1 registry + registration-sites strategy; 9.2 migrates Stapler as the proof | access-control-prover |
| mapping.py monolith | All framework families inline (1,200+ lines); WordPress/Python registration blocks included | Migrate family by family after Stapler: jetty, symfony, livewire, remix, tornado, then PHP/Python registration tables | breadth (this map) |
| Obligation facts (ruleset TOMLs) | Closed rows, hand-authored; header discipline enforced by loader | Extraction into acquirable structures via fact-init (task 10.1) | access-control-prover |
| Fact initialization job | Designed (`tools/fact_init.py`: extract + propose -> gate -> promote); code not started | Task 10.1 build; 10.2 first run for pool families as data rows | access-control-prover |
| Fact-proposal lane (standing) | Roadmap-level: LLM proposes, engine verifies, repetition promotes | Stays deferred to injection-spec track (roadmap gate 5) | roadmap |
| Injection-family object | Brief only (`engine-change-scoped-analysis`); taint flow + banks MVP exist as evidence | Spec after post-fix pilot holds recall | roadmap |
| Complexity gate | Caps in `code-standards.md`, review-time only | Wire ruff C901 / xenon into CI; threshold per steering | breadth (this map) |
| Idiomatic naming | Rule active for new/amended code; legacy debt un inventoried | Record rename inventory during family migrations; no drive-by renames | breadth (this map) |
| Training pool | pool-train 23 repos / 483 changes frozen 2026-10-14 | Pre-2019 negatives re-audit before any future run (3/12 sampled carry real missing-check bugs) | valid-detection-benchmark |
| Pre-push safety net | Implementation 67/79; wiring unchanged by this work | Unaffected until prover recall/FA hold at pilot | pre-push-safety-net |
Migration order for mapping.py families is deliberate: Stapler first (task 9.2 proves the registry path on
the family the pool exercises most), then one family per migration, each deleting its legacy block from
`mapping.py` in the same change.
+73
View File
@@ -0,0 +1,73 @@
# Code Standards (2026-10-29)
Applies to new code and amended code from this date. Existing modules are grandfathered
until their migration is scheduled in the breadth map; no mass renames.
## Specification level (the horizontal-capability rule)
Specifications describe the way and the concept, not per-target inventories.
- A spec defines **capabilities of the acquisition mechanism**: how recognition
knowledge for any target is represented, proposed, verified, and promoted. It never
enumerates a target's vocabulary (`ServletHolder`, `doNotifyCommit`,
`voteOnAttribute`, ...) as work items or acceptance criteria.
- Per-target knowledge is **data** (fact rows, strategy registrations) produced through
the acquisition pipeline -- an LLM can produce it per target; the engine verifies it.
- Concrete repositories and frameworks appear in specs only as **benchmark evidence**:
named cases the acquired data must satisfy, never lists to implement.
- Litmus: if a requirement's acceptance criteria would not survive swapping the target
repository family, the requirement is written at the wrong level. Lift it.
## Engine / target split (the anti-monolith rule)
The abstract-interpretation engine is agnostic. Target knowledge (framework names,
recognition vocabulary, WordPress/Stapler/Symfony/Jetty/Laravel specifics) never lives
inside engine modules.
- **Engine modules** (`semantic/obligations/check.py`, `dominance.py`, `cfg_dominance.py`,
`paths.py`, `resolution.py`, `operations.py`, `trace.py`): no framework or product names.
If a framework identifier appears in an engine module, the split has been violated.
- **Target knowledge lives in exactly two visible places:**
1. **Data**: `ruleset/obligations/*.toml` and future fact files -- operation rows,
discharger rows, handler-name families. Adding a framework's vocabulary is a data
change, reviewable in one place.
2. **Strategy modules**: one module per recognition concern under `mapping/` (registry
pattern, see below), each file naming its target in its filename and registering
itself in one table.
- **The system adjusts by growing data and strategies, never by editing the engine.** The
fact-proposal lane (LLM proposes, engine verifies, repetition promotes to rows) is the
automated path for the same rule.
## Pattern discipline (GoF)
- **Strategy + Registry** for entry recognisers: `mapping/` gains a recogniser registry;
each framework family is one strategy module. New recognisers land there; the legacy
blocks inside `mapping.py` migrate family by family (tracked in the breadth map).
- **Interpreter (data-driven)** for obligation facts: TOML rows are the language; the
checker interprets them. New target vocabulary = new rows.
- **Adapter** at engine seams: `hunt/prover.py`'s callers adapter binds the IR to the
caller index; witnesses flow into the checker through one interface
(`check_obligations`).
- **Factory** for witnesses stays inside `obligations/enforcement.py` for now, but
enforcement is a target-aware seam (it reads Symfony/Laravel/Tornado constructs): treat
it as adapter territory; engine modules must not import it.
## Complexity caps
- Cyclomatic (McCabe) complexity: **<= 10 per function**.
- Logical lines of code: **<= 50 per function, <= 400 per new module**.
- A task is not done while a function it touched exceeds the caps; split the function.
- Automated gate (ruff C901 / xenon in CI) is not wired yet; caps are enforced at review
time. Wiring the gate is tracked in the breadth map.
## Idiomatic naming
- Names state what a value semantically is, in the pipeline's vocabulary: `entry_point`,
`trace`, `obligation`, `witness`, `discharge`, `finding`, `judgment`.
- Collections name their elements: `entry_point_records`, `judgments_by_key`,
`findings_by_push`. Mappings use `_by_<key>`; sequences are plural. No `eps`, `recs`,
`d2`, `tmp2`.
- Booleans are predicates: `is_public`, `has_dominating_guard`, `dominates_entry`. Avoid
negated names (`not_unflagged`).
- Row/record destructuring uses the record's field names, not positional `a, b`.
- Renaming debt in existing modules is recorded in the breadth map, not fixed drive-by.