mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 17:58:18 +00:00
docs(spec,steering): lift access-control-prover to capability level; engine/target split, pattern discipline, complexity caps, breadth map
This commit is contained in:
@@ -101,29 +101,36 @@ flowchart LR
|
||||
## File Structure Plan
|
||||
```
|
||||
src/openultrasast/
|
||||
├── mapping.py # + recognisers: _stapler_entries, _jetty_entries, _symfony_entries,
|
||||
│ # _livewire_entries, _remix_exports (extend), _tornado_entries,
|
||||
│ # _jetty_registration_entries [2026-10-29]
|
||||
├── mapping/ # recogniser registry [2026-10-29]: one strategy module per
|
||||
│ │ # recognition concern, each registering itself in one table
|
||||
│ ├── registry.py # the registry the language dispatch consults
|
||||
│ ├── registration_sites.py # call-site handler registration strategy (data-driven vocabulary)
|
||||
│ └── stapler.py, jetty.py, symfony.py, livewire.py, remix.py, tornado.py
|
||||
│ # strategy modules migrating from mapping.py's legacy blocks
|
||||
├── mapping.py # dispatch consults the registry during migration; blocks retire
|
||||
│ # family by family (breadth map tracks the order)
|
||||
├── semantic/
|
||||
│ ├── cst.py # + capture call receiver text in _call [2026-10-29]
|
||||
│ ├── ir.py # + CallSite.receiver: str | None [2026-10-29]
|
||||
│ ├── cst.py # + capture call receiver text in _call [2026-10-29]
|
||||
│ ├── ir.py # + CallSite.receiver: str | None [2026-10-29]
|
||||
│ └── obligations/
|
||||
│ ├── resolution.py # ClassRecord, class_table, qualify — receiver/signature resolution [2026-10-29]
|
||||
│ ├── paths.py # PathRecord, compose_paths(callers, entries, targets, depth, fan_out)
|
||||
│ └── enforcement.py # witnesses(root, texts, entries, facts) -> list[Discharge]
|
||||
├── hunt/prover.py # prove_change(root, change, ctx) -> ProverResult(findings, coverage)
|
||||
└── ruleset/obligations/{java,php,python,typescript}.toml # + operation and discharger rows, checked headers
|
||||
benchmarks/valid/prover_arm.py # run / summarize; records per change; coverage table; anchor refusal
|
||||
tests/test_mapping_frameworks.py, tests/test_obligation_resolution.py, tests/test_obligation_paths.py,
|
||||
tests/test_obligation_enforcement.py, tests/test_hunt_prover.py, tests/test_prover_arm.py
|
||||
│ ├── resolution.py # ClassRecord, class_table, qualify — receiver/signature resolution [2026-10-29]
|
||||
│ ├── paths.py # PathRecord, compose_paths(callers, entries, targets, depth, fan_out)
|
||||
│ └── enforcement.py # witnesses(root, texts, entries, facts) -> list[Discharge]
|
||||
├── hunt/prover.py # prove_change(root, change, ctx) -> ProverResult(findings, coverage)
|
||||
└── ruleset/obligations/{java,php,python,typescript}.toml # + operation and discharger rows, checked headers
|
||||
src/openultrasast/tools/fact_init.py # the one-time initialization job: extract + propose -> verify -> promote [2026-10-29]
|
||||
benchmarks/valid/prover_arm.py # run / summarize; records per change; coverage table; anchor refusal
|
||||
tests/test_mapping_registry.py, tests/test_mapping_registration_sites.py, tests/test_obligation_resolution.py,
|
||||
tests/test_obligation_paths.py, tests/test_obligation_enforcement.py, tests/test_hunt_prover.py,
|
||||
tests/test_prover_arm.py, tests/test_fact_init.py
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
- `src/openultrasast/mapping.py`: new recogniser blocks, registered where `analyze_entry_points` dispatches by
|
||||
language and path; `EntryPointRecord` unchanged.
|
||||
- `src/openultrasast/mapping.py`: dispatch consults the `mapping/` registry (Strategy pattern); legacy blocks
|
||||
migrate into strategy modules family by family; `EntryPointRecord` unchanged.
|
||||
- `src/openultrasast/hunt/producers.py`: `Context` exposes `entry_points` and `ir(path)` already; add nothing
|
||||
unless a cache is missing (then a `cached_property`, same pattern).
|
||||
- `src/openultrasast/ruleset/obligations/*.toml`: rows only.
|
||||
- `src/openultrasast/ruleset/obligations/*.toml`: rows only, admitted through the fact gate.
|
||||
|
||||
## System Flows
|
||||
```mermaid
|
||||
@@ -172,6 +179,43 @@ counts and no finding. A declared-public route suppresses `path_guard` only.
|
||||
| 9.1-9.6 | trace serialisation in prove_change (obligations.trace) |
|
||||
| 10.1-10.7 | cfg.FunctionCfg, CfgDominance |
|
||||
|
||||
## Pattern Application and Fact Acquisition `[2026-10-29]`
|
||||
|
||||
The implementation follows the steering pattern discipline (`code-standards.md`): the engine stays
|
||||
target-agnostic; per-target vocabulary is data. Concretely for this spec:
|
||||
|
||||
- **Strategy + Registry** for entry recognisers: the recognisers this spec adds (registration sites, and the
|
||||
migration of existing framework blocks) live as strategy modules in a `mapping/` registry -- one module per
|
||||
recognition concern, each naming its target family in the filename, registering itself in one table. The
|
||||
dispatch in `mapping.py` consults the registry; it does not name frameworks.
|
||||
- **Interpreter** for obligation facts: operation rows, discharger rows, handler-name families and
|
||||
registration-site vocabulary are TOML data interpreted by the engine; adding a target's vocabulary is a data
|
||||
change reviewable in one place.
|
||||
- **Adapter** at the engine seams: `hunt/prover.py`'s callers adapter binds the IR to the caller index
|
||||
(receiver-qualified through `resolution`); `obligations/enforcement.py` adapts framework constructs into
|
||||
`Discharge` witnesses and is target-aware territory -- engine modules never import it.
|
||||
|
||||
### Fact acquisition (the one-time initialization pass)
|
||||
|
||||
Per-target vocabulary is produced by a one-time initialization job, not by hand-specced enumerations. The job
|
||||
has two modes and one gate:
|
||||
|
||||
1. **Extraction mode**: walks the repository's existing recogniser code and framework knowledge (the legacy
|
||||
`mapping.py` blocks, the current TOML rows) and extracts the target vocabulary already embedded there into
|
||||
the acquirable structures (row families, strategy registrations).
|
||||
2. **Proposal mode**: an LLM pass (or the maintainer) proposes the vocabulary a new target needs -- entry
|
||||
shapes, registration sites, handler-name families, policy-decision constructs -- against the target's
|
||||
repository.
|
||||
3. **The gate**: every extracted or proposed row passes the same header discipline the facts files already
|
||||
enforce (source, version, region) and engine verification admits each row (the row must match what the
|
||||
code says); only then is it promoted to a closed row. Nothing skips the gate.
|
||||
|
||||
The initialization job is a pipeline tool in this spec's scope (it produces the data the engine consumes);
|
||||
the standing fact-proposal lane (LLM proposes continuously, engine verifies, repetition promotes) remains the
|
||||
roadmap's growth path and is out of boundary here. First runs: extraction over the current embedded
|
||||
vocabulary, proposal for the training pool's target families, with the pool repositories as evidence.
|
||||
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### mapping recognisers
|
||||
@@ -191,15 +235,18 @@ methods not starting with `_`, plus `Route::` registrations naming a controller
|
||||
action|loader` under `app/routes`. Tornado: classes extending `RequestHandler` or `WebSocketHandler`, request
|
||||
methods and `open`.
|
||||
|
||||
Programmatic registration `[2026-10-29]` (1.9): a separate recogniser follows the call-site registration
|
||||
pattern the Python route registrations and the WordPress registration table already use. It scans Java text
|
||||
for registration sites -- `new ServletContextHandler(`, `new ServletHolder(`, `addServlet`,
|
||||
`addServletWithMapping`, `addHandler`, `addServletMapping` -- resolves the registered class literal through
|
||||
`resolution.class_table`, and emits the registered class's request methods (`doGet`, `doPost`, `service`,
|
||||
`handle`) as `route` entries at the method's declaration line in the registered class's file, with the
|
||||
registration site (`path:line`) and the registered class name as evidence and `registered_by` set. A
|
||||
registration whose target class the repository does not define yields coverage (`registration_unresolved`)
|
||||
and no entry. Framework tag: `jetty`.
|
||||
Registration sites `[2026-10-29]` (1.9): the call-site registration concept the Python route
|
||||
registrations and the WordPress registration table already use, stated once, generically: when the
|
||||
repository's own code registers a handler at a call site (a constructor or registration method whose argument
|
||||
names the handler class), the registered class's request methods become `route` entries at the method's
|
||||
declaration line in the registered class's file, with the registration site (`path:line`) and the registered
|
||||
class name as evidence and `registered_by` set. The registration-site vocabulary -- which constructors and
|
||||
methods count as registrations, and which methods of the registered class are request methods -- is acquirable
|
||||
data under the facts discipline (R2 AC7), carried by a registration strategy module in the recogniser
|
||||
registry, never hardcoded in the engine. Class-literal resolution goes through `resolution.class_table`. A
|
||||
registration whose target class the repository does not define yields coverage (`registration_unresolved`) and
|
||||
no entry. Jetty's programmatic servlet registration is the first data case (seatunnel evidence), not the
|
||||
definition.
|
||||
|
||||
### obligations.paths
|
||||
```python
|
||||
@@ -287,11 +334,13 @@ Jenkins fixes are form-validation and list-filling web methods whose body calls
|
||||
method is the operation. Jenkins documents the rule (form validation methods must check a permission).
|
||||
|
||||
Handler-named rows for the webhook, build-trigger and data-exposure shapes `[2026-10-29]` (2.9): the
|
||||
`handlers` patterns extend to the verb families seen in the gate-1 misses -- `^doNotifyCommit$`,
|
||||
`^doChange(Submit)?$`, `^doEvent$`, `^doBuild(Submit)?$` (webhook and build-trigger endpoints that act with
|
||||
repository or job privileges) and `^doJson$`, `^doPython$` (protected-data exposure) -- as operation rows on
|
||||
`stapler`-recognised entries, at the declaration line. The families are rows in the facts files, not code: a
|
||||
new verb family is a row addition, and a row without `handlers` never matches a declaration name.
|
||||
`handlers` patterns extend to verb families whose handler name itself carries the obligation -- endpoints
|
||||
that act with repository or job privileges when called (webhook and build-trigger shapes) and endpoints that
|
||||
expose protected data (data-exposure shapes) -- as operation rows on framework-recognised entries, at the
|
||||
declaration line. The families are rows in the facts files, not code: a new verb family is a row addition
|
||||
acquired through the one-time initialization pass for the target (or proposed by the fact-proposal lane), and
|
||||
a row without `handlers` never matches a declaration name. The gate-1 misses (Stapler webhook and
|
||||
chart-data endpoints) are the first evidence set, not the definition.
|
||||
|
||||
### trace: the ordered serialisation (owned here)
|
||||
```python
|
||||
@@ -345,15 +394,17 @@ Reads Laravel route files (`routes/*.php`) for `->middleware([...])` and control
|
||||
`access_control`; Tornado `@authenticated`; emits `Discharge(kind="path_guard", scope="router"|"decorator", path,
|
||||
function, line=None, evidence=...)` for each covered handler. Names recognised live in the facts files' headers.
|
||||
|
||||
Resource binding and voters `[2026-10-29]` (2.10): every witness this module mints carries the resource it
|
||||
binds (`Discharge` gains an optional `resource: str | None`, default `None`): the route path for router
|
||||
middleware, the attribute or subject expression for `IsGranted`/`denyAccessUnlessGranted`/`isGranted`, the
|
||||
middleware name for `auth`-class guards (no resource -- `None`). A voter decision is a `voter` witness: when a
|
||||
Symfony action (or a function it calls) calls `isGranted`/`denyAccessUnlessGranted` with an attribute, or a
|
||||
voter class in the repository implements `voteOnAttribute` for that attribute, the witness is scoped to that
|
||||
action's operations with the attribute as its resource. Discharge matching is the checker's
|
||||
`resource_bound` keyword (above); the resource token computation is shared with `Operation.resource` so the two
|
||||
sides compare like with like.
|
||||
Resource binding and policy witnesses `[2026-10-29]` (2.10): every witness this module mints carries the
|
||||
resource it binds (`Discharge` gains an optional `resource: str | None`, default `None`): the route path for
|
||||
router middleware, the attribute or subject expression for a policy-layer guard, the middleware name for
|
||||
`auth`-class guards (no resource -- `None`). A policy-layer witness covers an action's operations when a
|
||||
policy decision (voter, capability check, or equivalent) reachable from the action -- in the action body or a
|
||||
function it calls -- names the action's attribute or subject; the witness is scoped to that action's
|
||||
operations with the attribute or subject as its resource. Which constructs count as policy decisions is
|
||||
acquirable data in the facts files (the discharger rows already carry the vocabulary); the witness shapes are
|
||||
engine behaviour. Discharge matching is the checker's `resource_bound` keyword (above); the resource token
|
||||
computation is shared with `Operation.resource` so the two sides compare like with like. Symfony's voter
|
||||
attributes are the first data case (kimai evidence), not the definition.
|
||||
|
||||
### hunt.prover
|
||||
```python
|
||||
|
||||
@@ -30,6 +30,13 @@ programmatic-registration entry facts (Requirement 1 acceptance criterion 9), an
|
||||
criteria (Requirement 5 acceptance criterion 6, Requirement 7 acceptance criterion 3). Acceptance criteria
|
||||
added or changed by this amendment carry the tag `[2026-10-29]`.
|
||||
|
||||
Per the specification-level steering rule, this amendment states **acquisition capabilities**, not per-target
|
||||
inventories: the target vocabulary implied below (registration sites, handler-name families, policy-witness
|
||||
shapes) is data the fact pipeline acquires per target -- a one-time initialization job per target can produce
|
||||
it, and can extract the target knowledge already embedded in the repository's recogniser code into the same
|
||||
structures -- with the engine's verification discipline admitting each row. The named repositories appear only
|
||||
as benchmark evidence the acquired data must satisfy.
|
||||
|
||||
## Boundary Context
|
||||
- **In scope**: entry-point recognition for the pool's frameworks; reachability paths from entry points to
|
||||
touched functions through the existing caller index; framework enforcement points that discharge an obligation
|
||||
@@ -80,11 +87,12 @@ the pool uses, so that reachability starts where an attacker starts.
|
||||
7. The mapper shall record every recogniser's evidence strings so a finding can name why a method is an entry.
|
||||
8. If a file's framework is not recognised, then the mapper shall record nothing for it and the coverage
|
||||
statement (Requirement 7) shall count the file as unchecked; silence is never a pass.
|
||||
9. `[2026-10-29]` When a Java program in the repository registers a servlet or handler programmatically (Jetty
|
||||
`ServletContextHandler`, `ServletHolder`, an add-servlet or add-handler registration call in the repository's
|
||||
own code), the mapper shall record the registered class's request methods as `route` entries with the
|
||||
registration site and the registered class as evidence, so that a server assembled in code, not annotations,
|
||||
still yields entry points (the seatunnel miss of gate 3).
|
||||
9. `[2026-10-29]` When the repository's own code registers a handler at a call site rather than declaring it
|
||||
through annotations or configuration, the mapper shall record the registered class's request methods as
|
||||
`route` entries with the registration site and the registered class as evidence, so that a server assembled
|
||||
in code still yields entry points. The registration-site vocabulary for a target is acquirable data under
|
||||
Requirement 2 acceptance criterion 7's discipline, not engine code; Jetty's programmatic servlet
|
||||
registrations are the first case (the seatunnel miss of gate 3).
|
||||
|
||||
### Requirement 2: Enforcement points outside the function
|
||||
**Objective:** As a maintainer, I want checks that frameworks apply outside the handler to count as discharges,
|
||||
@@ -115,21 +123,24 @@ so that a guarded route is not reported because its handler body shows no check.
|
||||
to functions the mapper recognised as entries of that framework, at the method's declaration line.
|
||||
7. The prover shall write every enforcement fact as closed data in the obligation facts files, with the header
|
||||
naming the clones and references checked, and shall never add a name that does not occur in the checked code
|
||||
or the framework's documentation.
|
||||
9. `[2026-10-29]` The handler-named operations shall cover the pool's webhook and data-exposure handlers: the
|
||||
facts shall name, by handler-name pattern and by the privileged calls the handler makes, the build-trigger
|
||||
and webhook-submit operations (the pool's `doNotifyCommit`, `doChange`, `doChangeSubmit`, `doEvent`,
|
||||
`doBuild`, `doBuildSubmit` shapes) and the protected-data exposure operations (the pool's `doJson`,
|
||||
`doPython` shapes), applied at the method's declaration line to entries the mapper recognised (the
|
||||
git-plugin, p4, analysis-core and global-build-stats misses of gate 3 all reached the entry and checked no
|
||||
operation).
|
||||
or the framework's documentation. A proposed fact (from a maintainer or an LLM proposal pass) is promoted to
|
||||
a closed row only after the same header discipline is satisfied `[2026-10-29]`; per-target vocabulary enters
|
||||
as data through this gate, never as engine code.
|
||||
9. `[2026-10-29]` The facts shall be able to name, by handler-name pattern and by the privileged calls the
|
||||
handler makes, webhook and build-trigger obligations and protected-data exposure obligations, applied at the
|
||||
method's declaration line to entries the mapper recognised. The name families for a target are acquirable
|
||||
data under acceptance criterion 7's discipline -- a one-time initialization pass per target can produce them
|
||||
(and can extract the vocabulary already embedded in the repository's recogniser code into those structures)
|
||||
-- and the pool's Stapler webhook and data-exposure handlers are the first cases (the git-plugin, p4,
|
||||
analysis-core and global-build-stats misses of gate 3 all reached the entry and checked no operation).
|
||||
10. `[2026-10-29]` A guard shall discharge an operation only when it binds that operation's resource: when the
|
||||
guard constrains a different path, a different object, or a permission level the operation does not require,
|
||||
the prover shall treat the operation as undischarged (the crx and kimai misses of gate 3: a generic
|
||||
`path_guard` or `ownership_check` dominated a credentials form-fill and invoice actions whose resource it
|
||||
never touches). Where a Symfony action or a function it calls is guarded by a voter decision on the action's
|
||||
attribute (`isGranted`, `denyAccessUnlessGranted`, a voter's `voteOnAttribute`), the prover shall supply a
|
||||
`voter` witness scoped to that action's operations.
|
||||
never touches). Where the target's policy layer decides access by an attribute or subject of the request --
|
||||
a voter, policy, or capability decision reachable from the action or a function it calls -- the prover shall
|
||||
supply a policy witness scoped to that action's operations with the attribute or subject as its resource;
|
||||
Symfony's voter decisions are the first case.
|
||||
|
||||
### Requirement 3: Reachability paths through the caller index
|
||||
**Objective:** As a maintainer, I want a path from an entry point to each touched function, so that an
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"feature_name": "access-control-prover",
|
||||
"created_at": "2026-10-14T13:48:18Z",
|
||||
"updated_at": "2026-10-29T13:05:00Z",
|
||||
"updated_at": "2026-10-29T15:40:00Z",
|
||||
"language": "en",
|
||||
"phase": "design-generated",
|
||||
"phase": "requirements-generated",
|
||||
"approvals": {
|
||||
"requirements": {"generated": true, "approved": true},
|
||||
"requirements": {"generated": true, "approved": false},
|
||||
"design": {"generated": true, "approved": false},
|
||||
"tasks": {"generated": true, "approved": false}
|
||||
},
|
||||
"ready_for_implementation": false,
|
||||
"depends_on": ["authorization-obligations", "valid-detection-benchmark", "hunter-learning-loop"],
|
||||
"approval_note": "2026-10-14 maintainer directive in session: spec the prover and queue implementation with Codex (fast-track; the three gates were passed on that instruction, review the documents before the second delivery). 2026-10-29: requirements amended after the 2026-10-28 gates 1-5 measurements (trace resolution, programmatic entry facts, failure visibility); requirements await re-approval, then design and tasks must be revisited against the amendment before implementation resumes. 2026-10-29 (later): maintainer instruction in session ('can you address the issues?') treated as requirements approval and as the directive to regenerate the design; design.md amended in merge mode against the amendment (resolution component, resource-bound and voter witnesses, programmatic Jetty registration, stale-anchor refusal, error reasons), tasks.md still to be regenerated after design approval."
|
||||
"approval_note": "2026-10-14 maintainer directive in session: spec the prover and queue implementation with Codex (fast-track; the three gates were passed on that instruction, review the documents before the second delivery). 2026-10-29: requirements amended after the 2026-10-28 gates 1-5 measurements (trace resolution, programmatic entry facts, failure visibility); requirements await re-approval, then design and tasks must be revisited against the amendment before implementation resumes. 2026-10-29 (later): maintainer instruction in session ('can you address the issues?') treated as requirements approval and as the directive to regenerate the design; design.md amended in merge mode against the amendment (resolution component, resource-bound and voter witnesses, programmatic Jetty registration, stale-anchor refusal, error reasons), tasks.md still to be regenerated after design approval. 2026-10-29 (latest): maintainer directives lifted the specification to the capability level -- per-target vocabulary is acquired data (one-time initialization job with extraction and proposal modes behind the facts gate), never enumerated in acceptance criteria; engine/target split, GoF pattern discipline, complexity caps, and idiomatic naming recorded in .kiro/steering/code-standards.md; breadth map added at .kiro/steering/breadth-map.md. requirements.md, design.md and tasks.md were re-lifted accordingly; all three phases re-opened for approval in order."
|
||||
}
|
||||
|
||||
@@ -116,6 +116,140 @@
|
||||
instrument failures; on the 5 repos shared with the 2026-10-14 hunt arms, prover 4/5 vs
|
||||
DSPy 2/5 and hunter 2/5 (union 4/5), including trigger.dev which neither arm found.
|
||||
|
||||
- [ ] 7. Resolution foundation `[2026-10-29]`
|
||||
- [ ] 7.1 `CallSite` receiver capture: `receiver: str | None` field on `semantic/ir.py`'s `CallSite`, captured
|
||||
in `semantic/cst.py` call extraction (receiver expression text for `$obj->method` and `x.y.z` shapes; `None`
|
||||
for bare calls); every existing consumer unchanged
|
||||
- Done when IR fixtures show receiver text carried through call extraction for PHP/TS shapes, `CallSite` still
|
||||
constructs without it for existing callers, and all existing IR/cst tests pass unchanged
|
||||
- _Requirements: 3.9_
|
||||
- _Boundary: semantic.ir, semantic.cst_
|
||||
- [ ] 7.2 (P) `obligations.resolution`: `ClassRecord`, `class_table(texts, language)` cached per change on the
|
||||
hunter `Context`, `qualify(receiver, enclosing_class, name, arity, table)` returning `Class.name`,
|
||||
`Class.name/arity`, or the bare name; base-class attachment rule (attaches to extending classes only)
|
||||
- Done when `tests/test_obligation_resolution.py` covers: two same-name methods on different classes give
|
||||
distinct keys; same name different arity gives distinct keys; an unresolvable receiver gives the bare name
|
||||
with `receiver_unresolved` counted; an abstract-base method attaches to a subclass call and not to an
|
||||
unrelated class
|
||||
- _Requirements: 3.9_
|
||||
- _Boundary: obligations.resolution, hunt.producers (Context cache field only)_
|
||||
|
||||
- [ ] 8. Guard binding and policy witnesses `[2026-10-29]`
|
||||
- [ ] 8.1 (P) Resource-bound witnesses: optional `resource` field on `Discharge` (additive, default `None`);
|
||||
enforcement mints tokens (route path for router middleware, attribute/subject for a policy-layer guard,
|
||||
`None` for auth-class guards); policy-layer witness scoped to the action with the attribute or subject as
|
||||
resource (a policy decision -- voter, capability check, or equivalent -- reachable from the action or a
|
||||
function it calls). Which constructs count as policy decisions comes from the facts rows (task 10), never
|
||||
engine code
|
||||
- Done when `tests/test_obligation_enforcement.py` shows a resource-mismatch fixture (guard binds another
|
||||
object's resource, the operation stays undischarged under `resource_bound`) and a policy-witness fixture
|
||||
yielding a witness scoped to the action with the attribute as resource
|
||||
- _Requirements: 2.10_
|
||||
- _Boundary: obligations.enforcement, obligations.operations (dataclass field only)_
|
||||
- [ ] 8.2 Checker opt-in `resource_bound` and all-kind caller-hop re-judgement in `check.py`'s `_on_caller_hop`:
|
||||
a witness with `resource` set suppresses only operations whose resource token matches; every discharger kind
|
||||
is re-judged on earlier hops at the hop's call line; `guards_before_call` participates in the re-judgement
|
||||
- Done when the existing checker tests pass unchanged and new tests show: keyword off keeps today's behaviour
|
||||
(including the `assume_entries_protected`-gated hop re-judgement); keyword on, a mismatched-resource witness
|
||||
does not suppress; a guard one call deeper than the handler discharges; a guard dominating before a hop
|
||||
call yields no finding
|
||||
- _Requirements: 2.10, 3.8, 3.10_
|
||||
- _Boundary: obligations.check (opt-in only)_
|
||||
- _Depends: 8.1_
|
||||
|
||||
- [ ] 9. Recogniser registry `[2026-10-29]`
|
||||
- [ ] 9.1 `mapping/` package with a recogniser registry (Strategy pattern): registry table consulted by the
|
||||
language dispatch in `mapping.py`; the registration-sites strategy lands first -- call-site handler
|
||||
registration resolved through `resolution.class_table`, entries at the registered class's request-method
|
||||
declarations with `registered_by` evidence, registration-site vocabulary carried as fact data (task 10), not
|
||||
engine code; the recogniser returns unresolved registrations alongside the entries so the prover can count
|
||||
them
|
||||
- Done when `tests/test_mapping_registration_sites.py` shows a registration-site fixture (vocabulary from
|
||||
data rows) yielding entries at the registered class's file with registration evidence, an unresolved
|
||||
registration returned as such (no entry), `mapping.py`'s dispatch consults the registry, and the existing
|
||||
mapping tests pass unchanged
|
||||
- _Requirements: 1.9_
|
||||
- _Boundary: mapping_
|
||||
- _Depends: 7.2_
|
||||
- [ ] 9.2 Migrate the first legacy recogniser family (Stapler) from `mapping.py`'s inline blocks into a
|
||||
`mapping/` strategy module, proving the migration path; remaining families migrate per the breadth map, not
|
||||
this task
|
||||
- Done when Stapler entries come from `mapping/stapler.py` through the registry with the existing Stapler
|
||||
tests unchanged, and `mapping.py` contains no Stapler-specific regex
|
||||
- _Requirements: 1.1_
|
||||
- _Boundary: mapping_
|
||||
|
||||
- [ ] 10. Fact initialization job `[2026-10-29]`
|
||||
- [ ] 10.1 (P) `tools/fact_init.py`: the one-time initialization pass per target. Extraction mode walks the
|
||||
legacy `mapping.py` blocks and current TOML rows and extracts embedded target vocabulary into the acquirable
|
||||
structures. Proposal mode takes an LLM or maintainer proposal for a target's vocabulary (entry shapes,
|
||||
registration sites, handler-name families, policy-decision constructs). The gate: every row passes the facts
|
||||
header discipline (source, version, region) and engine verification admits it (the row must match what the
|
||||
target's code says); only then promoted to a closed row
|
||||
- Done when `tests/test_fact_init.py` shows: extraction mode recovers the vocabulary of one legacy family
|
||||
into rows; a proposal matching a fixture repository is promoted; a proposal the fixture contradicts is
|
||||
rejected with the reason; nothing lands in the TOML files without passing the gate
|
||||
- _Requirements: 2.7_
|
||||
- _Boundary: tools.fact_init, ruleset/obligations (rows it produces)_
|
||||
- [ ] 10.2 First initialization run for the training pool's target families: produce the handler-name rows for
|
||||
webhook/build-trigger and data-exposure shapes, the registration-site vocabulary, and the policy-decision
|
||||
constructs, as data rows admitted through the gate (names verified against the pool clones; header updated)
|
||||
- Done when the pool's gate-1 miss shapes (Stapler webhook/data-exposure handlers, programmatic registration,
|
||||
policy voters) are all carried as rows with headers, no engine module names a target, and
|
||||
`tests/test_obligation_facts.py` loads each family
|
||||
- _Requirements: 2.7, 2.9, 2.10, 1.9_
|
||||
- _Boundary: ruleset/obligations_
|
||||
- _Depends: 10.1, 9.1, 8.1_
|
||||
|
||||
- [ ] 11. Prover resolution integration `[2026-10-29]`
|
||||
- [ ] 11.1 Target enumeration joins callees by qualified key with a `down_depth` budget (default 2, sharing the
|
||||
fan-out bound) so operations one call deeper are reached; the `callers` adapter keeps only callers whose
|
||||
receiver binds to the target's class or subclass; unresolvable receivers fall back to the bare name and are
|
||||
counted `receiver_unresolved`; unresolved registrations from mapping are counted `registration_unresolved` in
|
||||
`Coverage`
|
||||
- Done when `tests/test_hunt_prover.py` shows: an operation one call deeper than the handler is reached and
|
||||
checked; a same-name method on another class yields a separate verdict (no cross-contamination); coverage
|
||||
carries `receiver_unresolved` when a receiver does not resolve and `registration_unresolved` when a
|
||||
registration target is outside the repository
|
||||
- _Requirements: 3.7, 3.9_
|
||||
- _Boundary: hunt.prover_
|
||||
- _Depends: 7.1, 7.2, 9.1_
|
||||
|
||||
- [ ] 12. Arm and coverage `[2026-10-29]`
|
||||
- [ ] 12.1 (P) Recognition or resolution exceptions inside `prove_change` are recorded under
|
||||
`error:<exception class name>` reason keys in coverage, never folded into `framework_unrecognised` or any
|
||||
other quiet reason
|
||||
- Done when a fixture raising inside a recognition or resolution pass counts under `error:<class>` and
|
||||
`framework_unrecognised` stays empty for that fixture
|
||||
- _Requirements: 7.3_
|
||||
- _Boundary: hunt.prover_
|
||||
- [ ] 12.2 (P) Stale-anchor refusal in `prover_arm._prove`: every returned finding's `path` is validated
|
||||
against the files actually read; an absent path marks the record `instrument_failure` with reason
|
||||
`stale_anchor` and findings cleared; `summarize` refuses such runs listing the record ids
|
||||
- Done when `tests/test_prover_arm.py` shows a fixture finding on an absent path makes `_prove` mark the
|
||||
record and makes `summarize` refuse, mirroring the zero-byte refusal
|
||||
- _Requirements: 5.6_
|
||||
- _Boundary: prover_arm_
|
||||
|
||||
- [ ] 13. Measurement after the amendment (maintainer, not Codex) `[2026-10-29]`
|
||||
- [ ] 13.1 Re-run the arm on `benchmarks/valid/train/pool-train.toml` after tasks 7-12 land; write
|
||||
`benchmarks/measurements/<date>-access-control-prover-v2/` with NOTES, summary, coverage table; compare
|
||||
against the 2026-10-28 gate 1 run (recall 65.2%, FA 15.0/100) on the same pool
|
||||
- _Requirements: 5.2, 5.3, 7.2_
|
||||
|
||||
### Task constraints `[2026-10-29]`
|
||||
|
||||
From `.kiro/steering/code-standards.md`, binding on every task above:
|
||||
|
||||
- Complexity caps: McCabe <= 10 per function; <= 50 LLOC per function; <= 400 LLOC per new module. A task is
|
||||
not done while a function it touched exceeds a cap; split the function.
|
||||
- Pattern discipline: tasks 9.1-9.2 implement Strategy + Registry; task 10.1 implements the acquisition
|
||||
pipeline behind the Interpreter discipline (rows are the language); task 11.1 is the Adapter at the
|
||||
engine seam. New recognisers land in `mapping/` strategy modules, never as `mapping.py` blocks.
|
||||
- Idiomatic naming: names state what a value semantically is in the pipeline vocabulary; collections name
|
||||
their elements (`entry_point_records`, `judgments_by_key`); booleans are predicates; no abbreviations like
|
||||
`eps`, `recs`, `d2`.
|
||||
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user