Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
37 KiB
Implementation Plan
-
1. Foundation: closed facts and obligation shapes
-
1.1 Load obligated operations and dischargers as closed facts
data/facts/obligations.tomlwith[[operation]](id, kind, language, calls, resource_arg, requires, sensitivity) and[[discharger]](id, kind, language, calls, decorators, identity_sources, constraint_params) for python, javascript, typescript;load_obligation_facts(dir)withfor_language;OPERATION_KINDS,DISCHARGER_KINDS,PROVENANCE_KINDSclosed.- Observable: the loader returns Python operation facts for ORM query and raw execute calls and discharger facts for
login_required, token validators anduser/owner_idconstraint params; a file naming kind"maybe_guard"or an unknown field raisesObligationFactsErrornaming the entry; no code path detects an obligation without a matching fact. - Requirements: 1.1, 1.2, 1.3, 1.4, 1.5
- Boundary: ObligationFacts
-
1.2 Derive obligation shapes from trusted pairs
ObligationShape(language, operation_kind, discharger_kind, provenance, resource_class, mechanism, family="obligation") with text-freekey(),to_dict(),from_dict();derive_obligationfinds the obligated operation in the labeled function on the vulnerable side and classifies the discharger the fixed side added together with the provenance of the value it binds (authenticated context, request input, constant) viavariants.source_kind_of_name-style tracing over the identity sources.- Observable: the
vampi-books-get-by-titlepair yieldsprotected_read/identity_constraint/authenticated_context/owned; a permissive-default pair yieldssecurity_setting/non_permissive_value/constant; a pair whose fixed side adds no discharger returns None with reasonno_discharger_added;key()contains no path, line or literal and is identical for renamed identifiers. - Requirements: 2.1, 2.3, 2.5
- Boundary: ObligationShapes
- Depends: 1.1
-
1.3 Export obligation shapes through the existing store and exporter
seed.pair_lessonsderives obligation shapes for rows carryingobligation; onlyseeded|reviewed, non-known-limit, parsing pairs seed; rows written withappend_from_pair(family in the shape dict,guard= discharger kind), deduped by key with accumulated pairs;obligation_mechanisms(records)selector; sink-shapesearch_treeignores obligation rows.- Observable:
ousast mechanisms export --slice vibe-pyreports the absence pairs that seeded and skips advisory/title/known_limit/parse-failed rows with reasons; two pairs with the same shape produce one row listing both;search_treeover a store holding only obligation rows searches 0 mechanisms. - Requirements: 2.1, 2.2, 2.4
- Depends: 1.2
-
2. Core: operations, dischargers, siblings, policy, dominance, checker
-
2.1 Find operations and discharge witnesses in a parsed file (P)
find_operations(ir, facts, ranges)returns operation sites with kind, fact id, enclosing function and resource token;find_discharges(ir, facts, flow_facts, ranges, entry)returns witnesses with kind, provenance and scope (decorator, router, statement, hop) using the entry point's decorators and the handler's binds.- Observable: a Flask fixture with three handlers yields three
protected_readoperations on resourcebook; the handler with@login_requiredyields apath_guarddecorator witness; the handler that bindsuserfromrequest.useryields anidentity_constraintwitness withauthenticated_context, and one binding fromrequest.argsyieldsrequest_input. - Requirements: 3.1, 5.4
- Boundary: Operations
- Depends: 1.1
-
2.2 Group sibling handlers and find consistency anomalies (P)
sibling_sets(entries, operations, discharges)keyed by (router or module, resource token);consistency_anomalies(sets, min_siblings)flags a handler that reaches an operation kind without a discharge its siblings perform; under-populated sets are counted, never reported; a public route among authenticated siblings is a missingpath_guardwith the access classification as evidence.- Observable: three
/bookshandlers where two constrain by owner produce one anomaly naming the two siblings; withmin_siblings = 4the same set produces none and is counted under-populated; anomalies never reference or alter existing findings. - Requirements: 3.1, 3.2, 3.3, 3.4, 3.5
- Boundary: Siblings
- Depends: 2.1
-
2.3 Load the declared policy with a closed schema (P)
load_declared_policy(path)reads version, identity_source,[[resource]](name, sensitivity, identity_field),[[route]](path, access, roles); unknown field or value raisesPolicyErrornaming it;version_hash; absent file returns None.- Observable: a valid file loads with its hash; a file with
access = "sometimes"or an extra key is rejected naming the field; the scan wrapper recordspolicy_invalidand continues. - Requirements: 4.1, 4.2
- Boundary: DeclaredPolicy
-
2.4 Dominance protocol with an order-based default (P)
Dominanceprotocoldominates(witness, operation, path);OrderDominance: decorator and router witnesses dominate the handler; a statement witness dominates when it precedes the operation with an early exit between them or is a guard call; along aPathRecorda witness on an earlier hop dominates.- Observable: a guard call before the operation with a
returnin between dominates; the same call after the operation does not; a witness on hop 1 of a stubPathRecorddominates an operation on hop 3; the checker accepts any object implementing the protocol. - Requirements: 5.1, 5.2
- Boundary: Dominance
-
2.5 The obligation checker: path-aware and function-local
check_obligations(irs, entries, facts, flow_facts, policy, paths, dominance, store_shapes, min_siblings)→ObligationResult; reached = a path ends at the operation or (no paths) the operation's function is an entry point; label precedence declared > consistency >function_local; a declared public route suppressespath_guardobligations for that route only;known_fixfrom a matching store shape; identity constraints bound from request input are reported with that provenance;findings_to_staticemitsobligation:<kind>:<path>:<line>atsuspicionwith tagsobligation:*,discharger:*,obligation_evidence:*.- Observable: the three-handler fixture without paths yields one finding labeled
consistency_violationnaming the missingidentity_constraintand both siblings with afunction_local-mode degradation recorded once; with a declared protected resource the label becomesdeclared_policy_violationnaming the clause; a declared public route removes the guard finding but keeps the identity one; with a stub path whose earlier hop discharges, no finding; evidence level is alwayssuspicion. - Requirements: 3.6, 4.3, 4.4, 5.1, 5.2, 5.3, 5.4, 6.1
- Depends: 2.2, 2.3, 2.4
-
2.6 Recognize registration-based entry points (P)
- The entry-point mapper names a handler when its route is registered by a call or by convention, not only by a decorator on it: Python
add_url_rule(path, view_func=fn),api.add_resource(Cls, path)and HTTP-verb methods of aResource/MethodView/APIView/ViewSetsubclass; JavaScript and TypeScriptrouter.<verb>(path, ...middleware, handler)naming the handler identifier with the middleware identifiers as access evidence, and exportedGET/POST/PUT/PATCH/DELETEfunctions in aroute.ts/route.jsfile (Next.js App Router). Access classification, evidence and conditions follow the existing rules. - Observable: on the vendored
vampi-books-get-by-title,threatbyte-api-v1-getandsupplieradvisor-mvp-route-a69e8fexcerpts the mapper names the labeled function as a route entry (today it names none, so the obligation checker reports nothing on any real absence pair); arequireAuthmiddleware argument classifies the routeauthenticated; existing decorator routes, the map gate and the map artifact are unchanged. - Requirements: 3.1, 5.3
- Boundary: mapping
- Depends: 2.5
- The entry-point mapper names a handler when its route is registered by a call or by convention, not only by a decorator on it: Python
-
3. Integration: scan, reports, ranking, intent
-
3.1 Run the checker in MAP with config and manifest counters
[obligations] enabled=true, min_siblings=3, policy_path; MAP runs_check_obligationsafter variant search in standard and deep only, withPathRecords when the reachability artifact is present andOrderDominanceotherwise; findings appended; manifestobligations = {sibling_sets, under_populated, operations, findings_by_label, policy_version, degradations}; obligation findings never enter the sandbox candidate set.- Observable: a standard scan of the fixture writes one
obligation:finding and the manifest block withpolicy_version: null; quick has neither; with a policy file the block carries its hash; the deep scan's verdicts contain no obligation finding. - Requirements: 4.1, 5.3, 5.5, 6.5
- Depends: 2.5
-
3.2 Reports and ranking name the obligation (P)
- Markdown lines
Obligation,Missing discharger,Evidence,Known fix,Intentper finding and a## Obligationssection (sibling sets, policy version); SARIFobligation_*properties;rankweight = sensitivity x label weight (declared > consistency > function_local), capped strictly below the lowest sandbox-proven finding. - Observable: the fixture's report shows the missing
identity_constraint, the two siblings and the known fix learned from the pairs; SARIF carries the properties withevidence_level = "suspicion"; in a run with one sandbox-proven finding the obligation finding ranks below it; reports without obligations are unchanged. - Requirements: 6.1, 6.2, 6.4
- Boundary: reports, rank
- Depends: 2.5
- Markdown lines
-
3.3 Hunter adjudicates intent without raising evidence (P)
- When the hunter client resolves, ask one closed question per route-bearing finding ("is this route meant to be public?") with redacted handler text and sibling names; record
intentand the rationale; absent client →intent_adjudication_unavailableonce; the label and evidence level never change. - Observable: with a scripted client answering
public, the finding's rationale carries the adjudication and its label is unchanged; without a client the degradation appears once and findings are identical. - Requirements: 6.3
- Boundary: IntentAdjudication
- Depends: 2.5
- When the hunter client resolves, ask one closed question per route-bearing finding ("is this route meant to be public?") with redacted handler text and sibling names; record
-
4. Corpus: absence pairs with context, labels, leave-one-out
-
4.1 Harvest mode
handler_contextand theobligationlabel (P)- Harvest library mode keeping the labeled function with its decorators and the registration statements naming it on both sides;
ExpectedFinding.obligationadditive; the pair scorer counts a finding taggedobligation:<kind>inside the labeled function as a detection for obligation-labeled rows; vocabulary gainsunconstrained_protected_read,unconstrained_protected_write,unguarded_privileged_action; loader rejects unknown obligation kinds. - Observable: harvesting a fixture handler keeps
@app.routeand@login_requiredin the excerpt; a catalog row withobligation = "protected_read"loads and is detected by an obligation finding in its function; a row withobligation = "guess"is rejected; existing catalogs load unchanged. - Requirements: 7.1, 7.2, 7.3
- Boundary: harvest, pairs
- Harvest library mode keeping the labeled function with its decorators and the registration statements naming it on both sides;
-
4.2 Leave-one-out per obligation kind
loo.score_pair_with_storerunscheck_obligationson both sides for obligation rows with the held-out store's obligation shapes;per_obligation_kindmetrics;found_bynames the shape record that suppliedknown_fix;pairs --loopayload and roadmap table gain the obligation kinds.- Observable: a three-pair toy slice (two teach
protected_read/identity_constraint, one teachessecurity_setting) reports the identity pairs found by each other and the setting pair unfound; the real vibe-py and agent-vfc absence pairs report a baseline per kind; the roadmap table row is present and no gate reads it. - Requirements: 7.4, 7.5
- Depends: 1.3, 2.5, 4.1
-
4.3 Relabel and re-harvest the corpus absence pairs
- vibe-py and agent-vfc rows whose mechanism is an absence kind gain
obligation; the agent-vfc absence rows are re-harvested inhandler_contextmode (pointer rows through the cache); catalogs regenerated idempotently; nothing unlicensed vendored. - Observable:
mechanisms exportseeds at least one obligation shape from vibe-py; the vendored catalogs still load with unchanged vendored counts; gates unchanged. - Requirements: 7.2, 7.5
- Depends: 4.1
- vibe-py and agent-vfc rows whose mechanism is an absence kind gain
-
5. Integration: the obligations lever
-
5.1 Validator dispatch on shape family and pending policy clauses
validate_mechanismdispatches onshape["family"]: obligation rows check operation kind, discharger kind, provenance and resource class against the closed sets;PolicyClauseEdit(kind, fields, evidence, rationale)validated against the policy schema and journaled underpending_policy_clauses, never applied.- Observable: an obligation row with
discharger_kind = "if user"is rejected; a valid row admits; a clause edit lands in the journal as pending and a following scan reads no policy from it. - Requirements: 4.5, 8.1, 8.2, 8.4, 8.5
- Boundary: Lever
- Depends: 1.3
-
5.2 Propose obligation admissions and retractions under the existing gate
propose_mechanism_editstreats obligation rows like sink rows (a trial store recovers a missed holdout absence pair without leaking → admit; an admitted row firing on a fixed twin → retract);evaluate_mechanism_profilesruns the checker for obligation rows; rejection reverts the store byte for byte; journal carries pair or sibling provenance and metrics before and after.- Observable: a scripted admission that recovers a holdout absence pair is accepted and persisted; one that fires on a fixed twin is rejected with
mechanism_leakand the store unchanged;ousast improvepicks obligation candidates up from the same candidates file. - Requirements: 8.3, 8.5
- Depends: 5.1, 4.2
-
6. Validation
-
6.1 Prove offline tests and unchanged gates
- Every component unit-tested on local fixtures; semantic-marked tests skip without the extra; detection, map and local pair gates unchanged; extra-free suite green; no test needs network, model or sandbox.
- Observable: both pytest matrices green; gate stdout byte-identical to the pre-spec commit; a network-blocked run of the obligation tests passes.
- Requirements: 9.1, 9.2, 9.3
- Depends: 3.1, 3.2, 3.3, 4.3, 5.2
Implementation Notes
- Requirements approved 2026-09-05, design approved 2026-09-05 (maintainer, in session).
- Task 1.1 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_facts.py,ModuleNotFoundError: semantic.obligations; then green):semantic/obligations/facts.pywith closedOPERATION_KINDS,DISCHARGER_KINDS,PROVENANCE_KINDS,OperationFact/DischargerFact/ObligationFacts.for_language,load_obligation_facts(dir)rejecting unknown kinds and fields by name; facts inruleset/obligations/{python,javascript,typescript}.toml(design deviation: a sibling directory of the flow facts rather thandata/facts/, becauseload_factsglobs every*.tomlin its own directory). Data decision: protected reads and writes are discharged only by an identity constraint or an ownership check; a path guard proves the caller is logged in, not that this caller may touch this record, sopath_guarddischarges privileged actions only. - Task 1.2 (2026-09-05, RED first: 5 failing tests in
tests/test_obligation_shapes.py; then green):ObligationShape(familyobligation, text-free key with a family prefix),derive_obligation,explain_skip. Design refinements forced by the corpus: Real-Vuln twins are either the identical flag-toggled function or an unrelated trap function, so the discharger lesson is any discharged operation of the target's kind — fixed side first, then the vulnerable function's own safe branch (the vampi pair teaches fromfilter_by(user=user, ...)withuserbound fromtoken_validator(...)['sub']);FileIRcall sites carry positional arguments only, so keyword constraints are read from the call's own source line (derivation only, nothing textual enters the shape); the CST walker names a chainedfilter_by(...).update(x)after its first segment, so when the labeled kind matches no operation any obligated operation in the labeled function stands in and the shape keeps the labeled kind; an identity constraint bound from request input or a constant discharges nothing (that is theidentity_from_request_bodybug itself). - Facts tightening while starting 2.1 (2026-09-05, after group 1 went to review): bare
get/find/filter/allin the read facts matchedrequest.args.get(...)and produced a phantom resource-less operation; the entries are receiver-qualified (query.get,objects.get,query.filter,objects.all) in all three languages. Group-1 tests unchanged and green; disclosed to the group-1 reviewer. - Review round 1 (2026-09-05) APPROVED 1.2; REJECTED 1.1 (no test for the discharger-kind rejection arm of Req 1.4) and 1.3 (the sink-shape fallback in
pair_lessonswas untested and the threatbyte note was wrong). Remediated RED-first:test_loader_rejects_...gains the discharger-kind arm;test_absence_row_without_an_obligation_lesson_still_teaches_its_sink_shapepins the fallback; the note above now states the measured behaviour. Suggestions applied: permissive values and request-source patterns are discharger-fact data (permissive_values,request_sources) instead of code-level lists; the skip reason names the dischargers the operation accepts (no_discharger_added:identity_constraint|ownership_check); a trap-twin fixture exercises the vulnerable-side lesson; the deadsource_kind_of_namere-export is gone;_skip_detailpasses the loaded flow facts; the package exports the shape API; design File Structure Plan amended toruleset/obligations/<language>.toml. - Task 2.1 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_operations.py,ModuleNotFoundError: obligations.operations; then green):operations.pywithOperation,Discharge,find_operations(ir, facts, text=),find_discharges(ir, facts, flow_facts, text=, entries=),covers. Design deviations: both functions take the file text (decorators are not calls inFileIR, keyword constraints live on the call line); chained call sites (x.filter_by(...).first()recorded twice) are deduped per function, line and fact;coversis the kind-and-provenance fit only (ordering is the Dominance protocol's job). Resource token: the receiver's first segment (Book->book), the fact'sresource_arg, or a table name in a SQL literal; receivers likeself,db,cursor,requestname nothing. - Task 2.2 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_siblings.py; then green):siblings.pygroups entry-point handlers by (module path without suffix, resource token);dischargingcounts only valid witnesses (an identity constraint must bind the authenticated context);consistency_anomalies(sets, min_siblings)reports handlers lacking a kind at leastmin_siblingssiblings practice, with the siblings and, for path guards, the route access classifications as evidence; sets below the minimum are returned as under-populated. Consistency is about practice, so a path-guard anomaly is raised even though a protected read's own obligation is identity or ownership (Req 3.4). - Task 2.3 (2026-09-05, RED first: 2 failing tests in
tests/test_obligation_policy.py; then green):policy.pyload_declared_policy(path) -> DeclaredPolicy | None, closed top-level, resource and route fields,accessin public|authenticated|role, sensitivity in the closed set,identity_sourcerestricted to the discharger facts' identity sources ortoken; unknown fields and values rejected by name;version_hashover the comment-stripped text;route_accessmatches exact paths or a trailing*glob. - Task 2.4 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_dominance.py; then green):dominance.pyruntime-checkableDominanceprotocol andOrderDominance(texts): decorator and router witnesses dominate the handler; an identity constraint or a non-permissive value dominates the operation it sits on; a guard call before the operation dominates by contract; another statement witness dominates only with an early exit (return,raise,abort(,throw, ...) between it and the operation; along a path record a witness on an earlier hop dominates. - Task 2.5 (2026-09-05, RED first: 4 failing tests in
tests/test_obligation_check.py,ModuleNotFoundError: obligations.check; then green):check.pycheck_obligations(irs, entries, facts, flow_facts, policy, paths, dominance, store_shapes, min_siblings, extra_witnesses=)->ObligationResult; reached = a path record's last hop is the operation's node, or (no paths) the operation's function is an entry point, with oneobligations_function_localdegradation; an operation is discharged when any of the dischargers it accepts dominates (OR semantics); the missing kind is the one the siblings practice when an anomaly names it, else the first accepted kind; consistency anomalies on kinds outside the operation's own requirement (a path guard) are reported too, except that a route declared public waives only the guard; label precedence: a declared clause covers the obligation it speaks about (a protected resource covers identity and ownership, a route declared authenticated or role covers the path guard) > consistency >function_local;known_fixfrom a matching store shape; the route path is read from the handler's route decorator literal;findings_to_staticemitsobligation:<kind>:<path>:<line>:<missing>atsuspicionwith tagsobligation:*,discharger:*,obligation_evidence:*,resource:*,mechanism:*. - Task 3.1 (2026-09-05, RED first: 3 of 4 tests in
tests/test_obligation_scan.pyfailing — noobligation:finding, no manifest block, sandbox test's finding absent; then green):[obligations] enabled=true, min_siblings=3, policy_path=".openultrasast/obligations.toml";_check_obligationsruns in MAP after variant search (standard/deep only) withOrderDominanceover the parsed targets, the declared policy loaded from the target (apolicy_invaliddegradation continues the scan), obligation shapes from the target's mechanism store, andpaths=()until reachability lands; findings are appended and ranked; the manifest carriesobligations = {sibling_sets, under_populated, operations, findings_by_label, policy_version}; checker degradations join the run's; obligation findings never enter the sandbox candidate set (they are neither overlay promotions nor variant findings). Test note: the scan readsopenultrasast.tomlfrom the working directory, so the tests pass--config <target>/openultrasast.toml. - Task 3.2 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_reports.py— writers rejectedobligations=,rank_obligationsmissing; then green): the markdown writer printsObligation,Missing discharger(with provenance),Evidence(label and siblings or clause),Known fix(guard learned from pairs) andIntentper finding plus a## Obligationssection; SARIF gainsobligation_kind/resource/missing/provenance/label/evidence/known_fix/intent;rank.rank_obligations(top-levelrank.py, per the design's file plan) weighs sensitivity x label (declared 0.9 > consistency 0.6 > function_local 0.3) and caps strictly below the lowest sandbox-proven finding; other findings keep their priority;_search_variants-style,_check_obligationsreturns the per-finding mapping the writers render.rank.pyimportsStaticFindingunderTYPE_CHECKINGonly (findings imports rank). - Task 3.3 (2026-09-05, RED first: 3 failing tests in
tests/test_obligation_intent.py,ModuleNotFoundError: obligations.intent; then green):intent.pyadjudicate_intent(result, client=, model=, texts=)asks the hunter one closed question per route-bearing finding (route, handler, operation, missing discharger, siblings; the handler's own lines only) and parses a JSON answer intointentpublic | protected | unknown with a one-sentence rationale; malformed or off-vocabulary answers areunknown; without a client or model oneintent_adjudication_unavailabledegradation is recorded (only when a route-bearing finding exists to adjudicate; a run without obligation findings records nothing) and findings are identical; labels, evidence and evidence level never change.ObligationFinding.intent_rationaleadded (additive, disclosed to the groups 1–2 reviewer). The CLI resolves the client throughtool_hunter.resolve_hunter_client()and[models].hunter; the report mapping carries intent and rationale. - Task 1.3 (2026-09-05, RED first: 4 failing tests in
tests/test_obligation_export.py—obligation_mechanismsmissing, absence pairs skipped asno_labeled_sink_call_site; then green):seed.pair_lessonsderives obligation shapes for rows whose mechanism is in the closedABSENCE_MECHANISMSset (or carries the futureobligationlabel) and falls back to a sink shape when an absence row teaches no obligation (a weak literal at a sink still teaches); skip reasons come fromexplain_skip(no_obligated_operation,no_undischarged_operation,no_discharger_added);append_from_pairaccepts both shape families (guardcarries the discharger kind);variant_search.search_treeignores obligation rows;obligation_mechanisms()selects them. Measured on vendored vibe-py:vampi-books-get-by-titleandvampi-users-update-passwordseedprotected_read/identity_constraint/authenticated_context; the two threatbyte pairs teach no obligation (their twins are login handlers) and fall back to the sink shape they taught before (execute, guardnone), so the flow store is unchanged; the three permissive-default pairs skipno_obligated_operation(no security-setting call in the facts matchesmain/init/session_middleware, a facts-growth item, not a code gap). Flow shapes unchanged (10 with the extra). - Review round 2 (2026-09-05) APPROVED 2.1, 2.3, 2.4; REJECTED 2.2 (sibling key was the file basename, so
api/v1/books.pyandadmin/legacy/books.pymerged; amin_discharging=1default let one sibling set the norm against Req 3.2; the tasks.md two-sibling observable was untested) and 2.5 (with path records and neither anomaly nor clause the checker still emittedconsistency_violationwith empty evidence; theobligations_function_localdegradation named no file; two weak assertions). Remediated RED-first (2026-09-05; 5 new or tightened tests failed for the stated reasons, then green):sibling_setskeys on the module path without suffix (_module_of), pinned bytest_same_named_files_in_different_modules_are_not_siblings;consistency_anomalies(sets, min_siblings)has one threshold: at leastmin_siblingssiblings must discharge the kind (Req 3.2) and a set with fewer members is under-populated (Req 3.3); the shared fixture gainsconstrained_too(identity fromcurrent_user.id) andconstrainedbecomes@login_required, so the leaky handler's anomaly names both constraining siblings andmin_siblings=3stays silent with two;_findingreturnsNonein path-aware mode without a clause or anomaly (design gating 3,test_with_path_records_an_unjustified_obligation_yields_no_finding),function_localis the degraded mode's label only; the degradation carrieslanguageandfiles;dominance.callsis asserted== 0without a valid witness and> 0with the hop witness; the static id assertion is exact. Suggestions applied:valid_witnesslives inoperations.pyand is used bycovers,siblingsandcheck; decorator witnesses match the parsed decorator name only; the duplicatedmaybe_guardblock is gone;[[route]]unknown-field test added;orm-writeandsecurity-settingfacts are receiver-qualified (session.add,query.update,app.run,app.listen) andabortis no longer a path-guard call; the package exportscheck_obligations,ObligationFinding,ObligationResult,findings_to_static. Both matrices green (569 passed / 4 skipped; extra-free 554 / 19), ruff, format and mypy clean, gate stdout byte-identical to HEAD. - Task 4.1 (2026-09-05, RED first:
test_handler_context_mode_keeps_decorators_and_registration(KeyError: extract_handler_context),test_obligation_labeled_row_loads_and_is_detected_by_an_obligation_finding(vocabulary lacked the three ids),test_catalog_rejects_an_obligation_kind_outside_the_closed_set(no rejection); then green):harvest.pymodehandler_context=extract_handler_context(source, function, language): the function with its decorators (viaextract_function) followed by every statement outside it that names the function as an identifier in the masked text (comments and strings never count;def/function/subdeclarations excluded), one line per registration statement (a multi-lineurlpatterns = [...]contributes only the line naming the handler; recorded limitation);validate_reciperequiresfunctionfor the mode;extract_pair(_with_lines)dispatch; README lists the mode.mechanisms.tomlversion 2 addsunconstrained_protected_read,unconstrained_protected_write,unguarded_privileged_actionwith definitions naming the operation kind.ExpectedFinding.obligation: str | None = None(additive, default keeps every manifest and catalog loading unchanged);pairs._parse_expectedacceptsobligationonly from the closedOPERATION_KINDSand rejects others by name. Scorer:_overlay_scan(..., obligations=)computes obligation statics through_obligation_scan(function-localcheck_obligations, no policy, no store,ObligationsConfig().min_siblings) only when a row carries the label, so rows without it run byte-identical code; the one additive rule_obligation_matches_expectedcounts a finding taggedobligation:<kind>inside the labeled function; obligation findings on the fixed side are leaks underfix_policy = silent;detection_kindsgainsobligation; the inventory fallback is not taken when obligation statics exist. Both matrices green (572 passed / 4 skipped), ruff and mypy clean, gate stdout byte-identical to HEAD (no gate row carries the label). - Review round 3 (2026-09-05) APPROVED 2.2; REJECTED 2.5, 3.1, 3.2, 3.3. Remediated RED-first (6 tests failed for the stated reasons, then green): (2.5) the declared-public waiver now covers the operation's own requirement, not only sibling anomalies (Req 4.4): the checker drops
path_guardfromrequireswhen the route is declared public and falls through to the next accepted kind, pinned bytest_a_declared_public_route_never_reports_its_missing_path_guard_but_keeps_the_resource_obligationon aprivileged_actionhandler; the tautological static-id assertion is now exact for two findings; a malformed store row is anobligations_store_row_invaliddegradation naming the record ids instead of a silent skip (design Error Handling). (3.1) the manifest block carriesdegradationsandsets({module, resource, handlers}) as the design requires; the scan test asserts the block'sobligations_function_localentry names the language and file. (3.2) the## Obligationssection printsSibling sets evaluated: N (under-populated: M), one line per sibling set and the policy version, fed by a newobligations_summary=parameter carrying the manifest payload; the proven-finding cap is keyed on verdict ids (rank_obligations(findings, proven_ids=)) because proof rungs live onCandidateVerdictand never onStaticFinding, and the scan re-ranks after REGRESS when the sandbox proved anything. (3.3) the intent prompt is passed throughredaction.redact_secretsbefore it reaches the client (design Security Considerations); a test pins that theleakyprompt carries its own decorator and body and none of the other three handlers, and that a runtime-assembled key literal in the handler reaches the model redacted. Design amended: the static id contract carries the:<missing>suffix, the manifest listssets, and the rank cap states the verdict-id keying. Both matrices green (578 passed / 4 skipped; extra-free 563 / 19), ruff and mypy clean, gate stdout byte-identical to HEAD. - Task 4.2 (2026-09-05, RED first: 2 failing tests in
tests/test_loo.py—LooOutcomehad noobligations, obligation rows produced no hits; then green):score_pair_with_storerunscheck_obligationson both materialized sides for rows carrying an obligation label, with the held-out store's obligation shapes asstore_shapes; a row is detected only when a finding inside the labeled function carries both the labeledobligation:<kind>tag and amechanism:tag naming a store record, so the checker's own facts-only finding is never counted as the corpus teaching; obligation findings on the fixed side are leaks;LooOutcome.obligationsandLooResult.per_obligation_kindjoin the existing groupings in the payload. A three-pair toy slice measures 2/2 onprotected_read(each pair found by the other) and 0/1 onsecurity_setting(nobody else teaches the kind). - Blocking measurement before 4.3 (2026-09-05): the checker reports nothing on any real vendored absence pair.
analyze_entry_pointson thevampi-books-get-by-title,threatbyte-api-v1-get/delete,vampi-users-update-passwordanddvpwa-app-initexcerpts returns only the two file-level tag records (auth boundary file tag,network entry file tag) withfunction_name = None, because_python_entry_pointsrecognizes a route only from a decorator on the function and these projects register routes withadd_url_ruleor throughflask_restx.Resourcemethods. Function-local mode gates on "the operation's function is an entry point", so no operation is ever reached. The research log's claim that the mapper "needs no change" holds for decorator frameworks only. Task 2.6 added to repair it; 4.3 waits on it, because labeling a corpus for a detector that cannot fire would measure the harness, not the detector. - Task 2.6 (2026-09-05, RED first:
test_entry_points_name_handlers_registered_by_call_or_conventionfailed with an empty route set; then green):mapping._python_registered_routesnames handlers registered byadd_url_rule(view_func=),add_resource/register_blueprint/include_router/add_viewand the HTTP-verb methods of aResource/MethodView/APIView/ViewSetsubclass;_js_registrationnames the handler identifier on arouter.<verb>(path, ...middleware, handler)line and classifies the route from the middleware identifiers;_js_route_modulenames the exported HTTP-verb functions of aroute.<ext>module. Record shape, access rules and the map gate are unchanged. - Measurement after 2.6 (2026-09-05, the honest picture before 4.3). Operations are found on the Python absence pairs (
vampi-books-get-by-titlesees threeprotected_readsites,threatbyte-api-v1-deletethree), so the facts work; what is missing is the context around them:- No registration in the excerpt. Every vendored Python absence excerpt is the function alone, so even the repaired mapper names no entry point and the checker reports nothing. Appending the real
app.add_url_rule('/books/v1/<book_title>', view_func=get_by_title)line to the vampi excerpt makes the whole chain fire end to end (obligation:protected_read:...:identity_constraint, labelfunction_local). This is exactly what task 4.3'shandler_contextre-harvest supplies, and it is the binding constraint for the Python rows. - TypeScript has no grammar.
parse_filereturnslanguage_unsupportedfor TypeScript in this environment (tree_sitter_typescriptis not installed), so every TypeScript absence row (supplieradvisor, capgo, zoonk, new-erp, and 18 agent-vfc rows) is unscorable for obligations regardless of labels. Carry-forward ofoverlay-ir-completeness, not of this spec. - Five vibe-py twins are byte-identical.
dsvw-dsvw-do-get,python-insecure-app-main-try-hack-me,pythonssti-main-read-root,vampi-books-get-by-titleandvulnpy-deserialization-do-pickle-loadhave identical vulnerable and fixed excerpts once the provenance header is stripped. No detector can be both loud on the vulnerable side and silent on the fixed one, so these five are permanently counted as failures and silently cap the measured ceiling at 30/35. The other slices have none. This is a corpus defect and belongs to the pair corpus, not to this spec. - The unit fixtures are easier than the corpus. The fixture binds identity in one hop (
user_id = request.user.id); the real vampi fix binds it in two (resp = token_validator(...)thenuser = User.query.filter_by(username=resp['sub']).first()thenfilter_by(user=user)). Green unit tests therefore do not evidence corpus behaviour, which is why 4.2 and 4.3 measure and 6.1 reports.
- No registration in the excerpt. Every vendored Python absence excerpt is the function alone, so even the repaired mapper names no entry point and the checker reports nothing. Appending the real
- Path-aware evaluation (5.1) is wired through the
Dominanceprotocol and an optionalPathRecordinput; untilreachability-flow-modellands, every run is function-local by construction and tests use a stubPathRecord. No task here waits on the upstream specs. - Sibling grouping keys on (router or module, resource token). If the maintainer prefers data-model-only grouping, only task 2.2 and its tests change.