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
+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.