diff --git a/.agent-loop/LOOP_STATE.md b/.agent-loop/LOOP_STATE.md index ec07e3c6..e3c774dc 100644 --- a/.agent-loop/LOOP_STATE.md +++ b/.agent-loop/LOOP_STATE.md @@ -2,24 +2,81 @@ ## Current State -- Active initiative: `WS-AUTH-001` - Workstream Authorization Service +- Active initiative: `WS-QUAL-001` - Backend Coverage Floor - Active planning chunk: none -- Active implementation chunk: `WS-AUTH-001-02` - Verified Issuer Token And - JWKS Boundary -- Branch: `codex/ws-auth-001-02-verified-issuer-token` -- Worktree: `/home/abiorh/flow/workstream-auth-001-02` -- Status: implementation and repair review complete; all required internal - tracks pass on reviewed code SHA `47dd5a7`; ready PR #107 is published. -- Prior `WS-AUTH-001-01` reviewed implementation SHA: `be0b836` -- Prior `WS-AUTH-001-01` final merged branch head: `b5217e1` -- Latest integrated `main` merge commit: `6dccb8e` -- Current gate: external checks and explicit human review on PR #107. Do not - merge or start the next chunk automatically. -- Next chunk: none; do not start `WS-AUTH-001-03` automatically. -- Parallel initiative: `WS-QUAL-001-01B1A-R2` merged through PR #105 as - `8a4182e`, and its post-merge memory merged through PR #106 as `6dccb8e`. - Later coverage chunks remain inactive and must not be edited or started from - this worktree. +- Active implementation chunk: `WS-QUAL-001-01B1B-R10` TypeVar child order +- Branch: `codex/ws-qual-001-01b1b-semantic-delta-guards` +- Status: B1B stopped at 223/300 lines after its second binding/AST repair + cycle. Review at `10dff4f` found lexical-shadow false positives and a + weakened local-lookalike `skipTest` expectation. B1B-R1 was proposed as the + bounded lexical-binding replacement. R1 passed plan review but stopped at its + first size checkpoint: the shared resolver alone measured 282 lines before + its required matrix, so its 300-line cap could not preserve proof. R2 is + stopped at implementation review `d4cef1d`: the 348/350 candidate omitted + standard Python scope and control-flow cases, and proof could not fit its + two-line reserve. R3 is proposed to use stdlib `symtable` for lexical facts + with bounded AST value flow under the normal 500-line L1 cap. Cycle-zero + review at `10ca508` found path joins, chained ambiguity, binding targets, and + future-annotation gaps. R3 stopped before cycle-one edits because the 32-line + reserve lacked credible proof. R4 stopped at `06a6d61` after review found AST + replay corrupting symbol-table consumption, target/unpack bypasses, and + optional comprehension effects. R5 stopped at `5f59f40`: review found + non-transitive loop summaries, evaluation-order/iterable-target bypasses, and + incorrect `except*` flow. R6 stopped at `68174d1`: review found incomplete + comprehension/set/dict element provenance, structural generator consumption, + and class-global bindings. R7 passed all ten plan tracks at `f0134aa`. + Cycle-zero review at `26a4e6e` found lazy-generator, unpacked provenance, + conditional provenance, empty-dict reachability, and class-global transfer + gaps. Cycle-one review at `a8e1e78` found incomplete structural consumption, + empty-comprehension provenance, and class-control/import boundary gaps. The + final permitted repair is bound at `5fcd9bb`; review found transparent-wrapper + provenance, sequential/qualified/async consumer, relative-import, and nested + class-expression gaps. R7 is stopped under its two-cycle rule. R8 now proposes + a bounded syntactic policy with no runtime execution model. All required plan + tracks passed at `9d72e42`. The implementation is bound at `3acf572`; 117 + focused tests and the exact 412/700 delta gate passed. Cycle-zero review found + bounded lexical namespace/scope gaps; the cycle-one repair is bound at + `1a13bea`. Cycle-one review found five bounded syntax traversal gaps. The + final permitted repair is bound at `e2ac216`; final QA found Python 3.11 + comprehension symtable incompatibility. R8 is stopped. R9 proposes only the + dual-version scope-pairing closure. Implementation is bound at `5a971d8`; + initial dual-version proof passed at `5a971d8`. Review found nested 3.12 inline + and Python 3.13 public scope-name gaps. The one permitted repair is bound at + `a5395c1`; identical 165-test matrices pass on Python 3.11, 3.12, and 3.13 at + 553/600 raw lines. Final review found Python 3.13 bound/default children need + one shared public ordinal. R9 is stopped; all R10 plan tracks passed at + `c42a67a`. The reviewed external repair passes identical 172-test matrices + on Python 3.11, 3.12, and 3.13 at 584/620 raw lines. All nine required + reviewer tracks and the circuit breaker pass at `7e192b8`; ready PR #108 is + under external and human review. +- Prior WS-ART reviewed planning SHA: `f7fbc33` +- Prior WS-ART final evidence-bound planning branch head: `c069064` +- WS-ART planning merge commit: `8644a43` +- Prior WS-AUTH-001-01 reviewed implementation SHA: `be0b836` +- Prior WS-AUTH-001-01 final merged branch head: `b5217e1` +- Latest integrated `main` merge commit: `060b780190435bc79464ae92fd9235a652f70e00` +- WS-QUAL final reviewed planning SHA: `0d9dd987d546c864fa8de7bae462e5e73a1b5ea9` +- WS-QUAL final evidence-bound branch head: `3da1769882e9f6db4c48ef3dba33da8380e6a613` +- WS-QUAL planning merge commit: `9046d52f31c7c39f06e06c45c43783bb08a5181c` +- WS-QUAL post-merge memory merged through PR #100 as `58d4459`. +- WS-ART-001-01 reviewed implementation SHA: `5574bf59cf1cb86da76749e0cbc529036346fa8a` +- WS-ART-001-01 final evidence-bound branch head: `2b8c2a0` +- WS-ART-001-01 merge commit: `050eb15eab8c57e6bc265477a5e92484d27a893c` +- WS-QUAL-001-01A implementation base SHA: `58d44596f614895964b82bb344e0ed98596eaae8` +- Current 01B split base SHA: `8829a7ec3aa5199aae0aecbe5fda030c42a051cd` +- WS-QUAL-001-01A reviewed implementation SHA: + `d1582ec64b9176c5ead62f695c7a23b48e4c72b9` +- WS-QUAL-001-01A final evidence-bound head: + `8cd7616b497ceb46d8359c25de689192632dfee8` +- WS-QUAL-001-01A merge commit: `2901a3ebe68b7c770ccb1ff06841d79ce0c20d94` +- WS-QUAL-001-01A post-merge memory merge commit: + `8829a7ec3aa5199aae0aecbe5fda030c42a051cd` +- Current gate: external checks and human review on ready PR #108. 01B2 remains + inactive; AUTH-02 merged through PR #107 as `060b780`. +- Next chunk: `WS-QUAL-001-02` remains inactive; do not start it automatically. +- Parallel initiative: `WS-AUTH-001-02` merged through PR #107 as `060b780`. + `WS-AUTH-001-03` is active after explicit user start in isolated worktree + `workstream-auth-001-03`; its plan repair and implementation run independently. - Parallel initiative: `WS-ART-001-01` merged through PR #101 as `050eb15`. `WS-ART-001-02` remains proposed and inactive pending a separate explicit user start. diff --git a/.agent-loop/REVIEW_LOG.md b/.agent-loop/REVIEW_LOG.md index 6d642bbd..cdef8335 100644 --- a/.agent-loop/REVIEW_LOG.md +++ b/.agent-loop/REVIEW_LOG.md @@ -53,6 +53,218 @@ R2 merge result: PR #105 merged to main as `8a4182edb09970131aded73edf3428ac83fe60b9` on 2026-07-12. Post-merge memory is the only active coverage work; 01B1B and 01B2 remain inactive. +R2 memory result: PR #106 merged as `6dccb8e`. The user then explicitly started +01B1B and confirmed coverage may run in parallel with AUTH in isolated +worktrees. Only 01B1B is active on the coverage branch; 01B2 remains inactive. + +B1B implementation review: BLOCKED at `10dff4f`. Engineering accepted the +second repair, but QA/test-delta found valid lexical-shadow false positives and +a weakened local-lookalike `skipTest` expectation. The two-cycle circuit rule +stopped B1B at 223/300; B1B-R1 is proposed for exact lexical binding closure. + +B1B-R1 contract review: PASS at `93e48b4` across senior engineering, QA/test, +security/auth, product/ops, architecture, CI integrity, docs, reuse/dedup, test +delta, and circuit breaker. The user directed coverage and AUTH to continue in +parallel; only B1B-R1 is active on the coverage branch. + +B1B-R1 size checkpoint: STOPPED before implementation commit. The shared +resolver draft measured 282 lines before the required behavior matrix, above +the 270 checkpoint and incompatible with the 300 cap. The draft was discarded; +B1B-R2 is proposed with a measured 350-line cap and unchanged behavior scope. + +B1B-R2 contract review: PASS at `8a5fc4a` across all ten required tracks. Its +measured 223/290/340/350 allocation, 310 checkpoint, lexical behavior, finite +repair rule, and B2/AUTH boundaries passed. R2 implementation is active. + +B1B-R2 implementation review: BLOCKED at `d4cef1d`. Product/docs, security, and +CI passed; engineering, architecture, reuse, QA, test-delta, and circuit failed +on reproduced stdlib scope/control-flow gaps. At 348/350, valid fixes plus tests +could not fit. R3 is proposed around stdlib `symtable`; no in-place repair. + +B1B-R3 contract review: PASS at `245ab58` across all ten required tracks. +Stdlib lexical ownership, ordinal scope pairing, full regression scope, the +348/420/480/500 allocation, and B2/AUTH boundaries passed. R3 is active. + +B1B-R3 cycle-zero implementation review: FAIL at `10ca508` with 468/500 lines. +Cycle one must close independent control paths, chained ambiguity, exact store +targets, future annotations, and Python 3.12 inlined-comprehension semantics. +No repair starts until the clarified contract passes internal review. + +B1B-R3 cycle-one plan review: STOPPED. QA accepted the clarified behavior, but +engineering found no credible way to fit complete repairs plus regressions in +32 lines. No executable repair was made. R4 is proposed with measured room. + +B1B-R4 contract review: PASS at `ac2bcc6` across all ten required tracks. The +468/515/535/550 allocation and complete control/value-flow contract passed; R4 +implementation is active. + +B1B-R4 implementation review: BLOCKED at `06a6d61`. Review reproduced AST +replay/symtable corruption, missing loop-carried effects, target-load and unpack +bypasses, nested inline ownership, and optional comprehension mistakes. At +535/550 the fixes plus proof could not fit; B1B-R5 is proposed. + +B1B-R5 contract review: PASS at `5672971` across all ten required tracks. The +single-consumption/cursor-neutral-summary boundary and 535/600/630/650 +allocation passed; R5 implementation is active. + +B1B-R5 implementation review: BLOCKED at `5f59f40`. Review reproduced +transitive loop/header, iterable target, exception/assignment ordering, +summary-pruning, and `except*` gaps. At 641/650 the repairs and missing proof +could not fit; B1B-R6 is proposed. + +B1B-R6 contract review: PASS at `bfb2d8e` across all ten required tracks. The +fixed-point provenance/evaluation-order contract and 641/720/770/800 allocation +passed; R6 implementation is active. + +B1B-R6 cycle-one implementation review: BLOCKED at `68174d1`. Review reproduced +comprehension/set/dict iterable provenance, structural consumption, nested +reachability, and class-global binding gaps. At 800/800 no valid repair and +proof fit; B1B-R7 is proposed. + +B1B-R7 contract review: PASS at `f0134aa` across all ten required tracks. The +recursive iterable/structural consumption contract and 800/870/920/950 +allocation passed; R7 implementation is active. + +B1B-R7 cycle-zero review: FAIL at `26a4e6e`. Engineering and QA reproduced +lazy generator body effects, starred/dict-unpack and conditional provenance +bypasses, empty-dict reachability, and class-global boundary/sequencing gaps. + +B1B-R7 cycle-one candidate: `a8e1e789f0421c35ecd6f23b9778379fb4b01156`. +The repair preserves lazy unknown-call genexpr bodies, recognizes structural +and known eager consumption, closes recursive unpack/conditional provenance, +walks literal-empty clauses, and makes declared class-global transfer +sequential and class-local. Proof is 254 focused tests, Ruff, `pip check`, diff +hygiene, and exactly 950/950 candidate lines. Fresh internal review is active. + +B1B-R7 cycle-one review: FAIL at `a8e1e78`. Reviewers reproduced binding-blind +eager-call classification, missed comprehension/display/unpack/`yield from` +consumption, nested lazy over-consumption, empty-comprehension provenance, and +class control/import boundary leaks. + +B1B-R7 cycle-two candidate: `5fcd9bb99a733fea9d6b05411ea26c4563375d61`. +One binding-aware consumption path now covers eager builtins, transparent +iterator wrappers, comprehensions, star displays/calls, sequence unpacking, +loops, and `yield from` without consuming nested yielded generators. Empty +comprehensions produce local provenance; class transfer keeps nested control +flow behind declared globals and qualifies framework imports by root. Proof is +278 focused tests, Ruff, and 920/950 candidate lines. Final review is active; +any additional valid finding stops R7 under its two-cycle rule. + +B1B-R7 cycle-two review: STOP at `5fcd9bb`. Reviewers confirmed the prior +findings were repaired but reproduced transparent-wrapper provenance/emptiness, +qualified and async consumers, sequential builtin shadowing, relative-import +classification, class handler/guard and local-walrus effects, method consumers, +and argument-role precision gaps. The compressed two-case-per-line matrix was +also rejected as unreviewable. Under the approved two-cycle rule, do not repair +R7 again or publish it. Replan a replacement with a readable test budget. + +B1B-R8 contract review: PASS at `9d72e42` across all ten required tracks after +clarifying monotone lexical ownership, simple alias closure, exact TestCase +syntax, merged-main restoration, readable matrices, and raw scope/size gates. + +B1B-R8 implementation candidate: +`3acf57281da4638476e70d7ed118e24413c1d20b`. The R2-R7 interpreter delta was +removed and replaced by conservative framework-qualified syntax detection. +Pre-review proof: 117 focused tests, Ruff, `pip check`, self-applied delta +validation, stale wording/auth docs, loop memory, Markdown links, diff hygiene, +and 412/700 raw candidate lines pass. Internal implementation review is active. + +B1B-R8 cycle-zero review: FAIL at `3acf572`. Reviewers reproduced unknown +direct-import false ownership, missed exact `unittest.case` chains, enclosing +shadow fallthrough, conflicting-owner overblocking, Python 3.12 type-parameter +scope mismatches, and nested TestCase receiver shadowing. + +B1B-R8 cycle-one candidate: +`1a13beaaf968a00a19c64e702b33026283cf0d22`. Framework ownership now retains +sets of known paths, outer lexical bindings are barriers, exact unittest case +namespaces are preserved, unknown members remain local, PEP 695 function/class/ +type-alias scopes are paired, and TestCase closures distinguish captured versus +local `self`. Proof: 140 focused tests and every deterministic gate pass at +498/700 raw lines. Internal re-review is active. + +B1B-R8 cycle-one review: FAIL at `1a13bea`. Reviewers reproduced TypeVar-bound +child-scope mismatches, dotted aliased import overownership, nearer nonlocal +barrier fallthrough, missed comprehension/annotated targets, and omitted +vararg/kwarg annotations. + +B1B-R8 cycle-two candidate: +`e2ac216a114bacb4115c7b44efa736e48cd500fb`. Bound/default child scopes are +paired, dotted aliases are exact, nonlocal lookup stops at the nearest lexical +binding, and every executable target and non-postponed argument annotation is +visited. Proof: 157 focused tests and every deterministic gate pass at 537/700 +raw lines. Final internal review is active; another valid finding stops R8. + +B1B-R8 final review: STOP at `e2ac216`. Engineering, security, product, docs, +architecture, reuse, and all deterministic 3.12 gates passed. Final QA +reproduced one supported-version defect: Python 3.11 gives list/set/dict +comprehensions child symtables, so nested clean lexical scopes can mismatch and +fail closed. The two-cycle rule forbids another R8 repair. + +B1B-R9 proposal: introspect matching comprehension child tables so Python 3.11 +enters them and Python 3.12+ uses inlined scope, with isolated dual-version +proof. No syntax-policy, Python-floor, workflow, dependency, B2, or application +change is permitted. Internal contract review is active. + +B1B-R9 contract review: PASS at `4da0880` across all ten required tracks after +making the isolated Python 3.11 backend environment, interpreter assertion, +600-line cap, self-validator, and test-preservation rules executable. + +B1B-R9 implementation candidate: +`5a971d80c38cbf856e9eee5bcd49fac6873c38c2`. Comprehension scope selection now +enters an optional stdlib list/set/dict child when present and otherwise uses +the containing inlined scope; genexpr remains required. The identical 161-test +matrix passes on isolated Python 3.11.15 and project Python 3.12.3. Ruff, pip, +self-validation, scope, wording, memory, links, diff hygiene, and 546/600 raw +lines pass. Internal implementation review is active. + +B1B-R9 cycle-zero review: FAIL at `5a971d8`. Reviewers found one related +compatibility class: nested synthetic inline-comprehension frames masked module +ownership on 3.12+, and Python 3.13 exposes renamed public PEP 695 symtable +types. + +B1B-R9 cycle-one candidate: +`a5395c173741ee584312e5b69e70676092ce9c46`. Synthetic versus real +comprehension frames are distinguished by selected table identity, and child +matching accepts both supported public symtable type families without version +branches. Identical 165-test matrices pass on Python 3.11.15, 3.12.3, and +3.13.3. Every deterministic gate passes at 553/600 raw lines. Final internal +review is active; another valid finding stops R9. + +B1B-R9 final review: STOP at `a5395c1`. The comprehension repair and identical +165-test Python 3.11/3.12/3.13 matrices passed, but Python 3.13 TypeVar bounds +and defaults share identical public child identifiers. Separate field keys can +select the first child twice when nested scope shapes differ. R9's one-repair +rule forbids another edit. + +B1B-R10 proposal: consume public TypeVar children through one shared AST-order +ordinal while retaining distinct legacy families, with mixed bound/default +proof on Python 3.13 and invalid-syntax fail-closed expectations on 3.11/3.12. +No comprehension, policy, config, dependency, workflow, or application change +is permitted. Internal contract review is active. + +B1B-R10 contract review: PASS at `c42a67a` across all required tracks after +requiring successful `analyze_python` traversal before owned outcomes can pass. + +B1B-R10 implementation candidate: +`15d0b80e776f5be12cacc5dbe5226ffe3992dcfd`. Public `type variable` bound and +default children now share one AST-order ordinal; legacy families remain +distinct. Clean, skip, and raises-deletion mixed-shape fixtures cover both +orders across generic functions, classes, and type aliases. Identical 171-test +matrices pass on Python 3.11.15, 3.12.3, and 3.13.3. Every deterministic gate +passes at 577/620 raw lines. All required implementation tracks passed. The +final evidence review found only structured-evidence and stale-memory defects; +those are repaired before PR publication without changing reviewed code. + +B1B-R10 publication: ready PR #108 published at +`https://github.com/Flow-Research/workstream/pull/108`. External checks and +human review are active; B2 remains inactive. + +B1B-R10 external review: CodeRabbit's provenance comment was already repaired. +Three further valid comments require truthful nine-track/circuit-breaker +wording, R7-R9 supersession state, and fail-closed deleted-assertion behavior +when base-source retrieval fails. The bounded repair adds a direct regression +test and must pass deterministic proof plus internal review before publication. + ## WS-QUAL-001-01B Status: user started the chunk after PR #104 merged. Its repaired L1 contract diff --git a/.agent-loop/WORK_QUEUE.md b/.agent-loop/WORK_QUEUE.md index d0cb96da..1042abe0 100644 --- a/.agent-loop/WORK_QUEUE.md +++ b/.agent-loop/WORK_QUEUE.md @@ -4,15 +4,15 @@ | Chunk | Title | Risk | Status | |---|---|---:|---| -| `WS-AUTH-001-02` | Verified Issuer Token And JWKS Boundary | L1 | Implementation and internal repair review complete; PR publication evidence pending | +| `WS-AUTH-001-03` | Legacy Actor Classification Preflight | L1 | Active in isolated AUTH worktree after user start; plan review running | +| `WS-QUAL-001-01B1B-R10` | TypeVar Child Order | L1 | Ready PR #108 published; external checks and human review active | ## Planned Next | Chunk | Title | Risk | Status | |---|---|---:|---| -| `WS-QUAL-001-01B1B` | Semantic Delta Guards | L1 | Inactive until 01B1A-R2 merge/memory and explicit user start | -| `WS-QUAL-001-01B2` | Baseline Evidence And CI Ratchet | L1 | Inactive until 01B1A-R2 and 01B1B merge/memory plus explicit user start | -| `WS-QUAL-001-02` | Project Service Coverage | L1 | Inactive until 01B1A-R2, 01B1B, and 01B2 merge/memory plus explicit user start | +| `WS-QUAL-001-01B2` | Baseline Evidence And CI Ratchet | L1 | Inactive until B1B replacement merge/memory plus explicit user start | +| `WS-QUAL-001-02` | Project Service Coverage | L1 | Inactive until B1B replacement and 01B2 merge/memory plus explicit user start | | `WS-POL-002-04` | Locked Runtime Execution And Routing Hardening | L1 | Inactive pending relevant authorization proof and a separate explicit user start | | `WS-ART-001-02` | Flow Node Adapter And Reconciliation | L1 | Proposed; inactive pending separate explicit user start | @@ -45,6 +45,7 @@ | `WS-POL-002-03` | Server-Owned Policy Approval And Visibility APIs | L1 | Merged through PR #90 as `a7aa474` on 2026-07-11 | | `WS-AUTH-001-PLAN` | Authorization Service Planning | L0 | Merged through PR #91 as `ad6d644` on 2026-07-11 | | `WS-AUTH-001-01` | Adopt Authorization Baseline And Repository Contracts | L1 | Merged through PR #93 as `772af1d` on 2026-07-11 | +| `WS-AUTH-001-02` | Verified Issuer Token And JWKS Boundary | L1 | Merged through PR #107 as `060b780` on 2026-07-13 | | `WS-ART-001-PLAN` | Immutable Artifact Storage And Flow Node Integration Planning | L1 | Merged through PR #97 as `8644a43` on 2026-07-12 | | `WS-QUAL-001-PLAN` | Backend Coverage Floor Planning | L1 | Merged through PR #99 as `9046d52` on 2026-07-12 | | `WS-ART-001-01` | Artifact Domain And Local Adapter | L1 | Merged through PR #101 as `050eb15` on 2026-07-12 | @@ -53,11 +54,9 @@ ## Proposed Next -`WS-AUTH-001-02` is the only active chunk. Do not start `WS-AUTH-001-03` or -`WS-POL-002-04` automatically. - -Coverage R2 merged through PR #105. Do not start 01B1B, 01B2, chunk 02, or -another coverage implementation chunk from this worktree. +R10 is the reviewed replacement for the stopped B1B-R8/R9 attempts. Publish +only R10 for human review. AUTH-03 continues independently in its isolated +worktree. Do not start 01B2, chunk 02, or POL-002-04 automatically. `WS-ART-001-01` is merged. Do not start `WS-ART-001-02` or edit Flow Node until the user gives a separate explicit start signal. @@ -72,3 +71,13 @@ this AUTH queue update. | `WS-QUAL-001-01B1` | 496/500 lines after two semantic-integrity repair cycles | Superseded by the proposed 01B1A/01B1B split; do not resume | | `WS-QUAL-001-01B1A` | 394/400 lines after two parser-normalization repair cycles; final QA/security review found two valid bypass variants | Replan the parser boundary before any further implementation | | `WS-QUAL-001-01B1A-R1` | Additional valid false-positive finding outside its two-fix contract | Superseded by R2 canonical-grammar proposal; do not resume | +| `WS-QUAL-001-01B1B` | 223/300 after two binding/AST repair cycles; final test-delta review found lexical-shadow false positives and a weakened local-skipTest expectation | Superseded through stopped R1-R9 by reviewed B1B-R10; do not resume | +| `WS-QUAL-001-01B1B-R7` | Final review after two repair cycles found wrapper provenance, qualified/async consumer, relative-import, class-expression, and readability gaps | Superseded by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R8` | Final QA found Python 3.11 comprehension symtable incompatibility after two repairs | Superseded by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R9` | Final review found shared Python 3.13 TypeVar bound/default child ordinals | Superseded by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R1` | Shared resolver measured 282 lines before its required behavior matrix, exceeding the 270 checkpoint and making the 300 cap infeasible | Superseded through stopped R2-R9 by reviewed B1B-R10; no implementation draft retained | +| `WS-QUAL-001-01B1B-R2` | 348/350 candidate failed cycle-zero implementation review on standard lexical/control-flow cases with only two lines reserve | Superseded through stopped R3-R9 by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R3` | 468/500 cycle-zero candidate; required control/value-flow repair plus tests did not credibly fit 32 lines | Superseded through stopped R4-R9 by reviewed B1B-R10; no cycle-one code edit made | +| `WS-QUAL-001-01B1B-R4` | 535/550 cycle-zero candidate replayed AST scopes and missed target/unpack/comprehension semantics | Superseded through stopped R5-R9 by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R5` | 641/650 cycle-zero candidate; provenance closure/evaluation order/except-star proof could not fit nine lines | Superseded through stopped R6-R9 by reviewed B1B-R10; do not repair in place | +| `WS-QUAL-001-01B1B-R6` | 800/800 cycle-one candidate; iterable abstraction and class-global proof remained incomplete | Superseded through stopped R7-R9 by reviewed B1B-R10; do not repair in place | diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md index 728f3803..87b8bdbd 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md @@ -9,8 +9,18 @@ | `WS-QUAL-001-01B1A` | Read-only coverage arithmetic and bounded policy parsers | L1 | Blocked at 394/400 after two parser repair cycles | | `WS-QUAL-001-01B1A-R1` | Replacement parser candidate closing two normalization bypasses | L1 | Stopped at `c0fa4a2`; superseded by R2 | | `WS-QUAL-001-01B1A-R2` | Canonical coverage.py exclusion grammar closure | L1 | Merged through PR #105 as `8a4182e` | -| `WS-QUAL-001-01B1B` | Repository-delta and semantic test-integrity guards | L1 | Inactive until 01B1A-R2 merge/memory plus explicit user start | -| `WS-QUAL-001-01B2` | Git provenance, configured baseline evidence, and CI ratchet | L1 | Inactive until 01B1A-R2 and 01B1B merge/memory plus explicit user start | +| `WS-QUAL-001-01B1B` | Repository-delta and semantic test-integrity guards | L1 | Blocked at 223/300 after two binding repair cycles | +| `WS-QUAL-001-01B1B-R1` | Lexical binding closure for semantic guards | L1 | Stopped at first size checkpoint; superseded by R2 | +| `WS-QUAL-001-01B1B-R2` | Measured lexical binding closure | L1 | Blocked at 348/350 after cycle-zero proof-fit failure | +| `WS-QUAL-001-01B1B-R3` | Stdlib symtable lexical closure | L1 | Blocked at 468/500 before cycle-one repair | +| `WS-QUAL-001-01B1B-R4` | Complete symtable control/value flow | L1 | Blocked at 535/550 after cycle-zero review | +| `WS-QUAL-001-01B1B-R5` | Single-pass abstract flow closure | L1 | Blocked at 641/650 after cycle-zero review | +| `WS-QUAL-001-01B1B-R6` | Transitive provenance closure | L1 | Blocked at 800/800 after cycle-one review | +| `WS-QUAL-001-01B1B-R7` | Recursive iterable provenance | L1 | Stopped after final two-cycle review; do not resume | +| `WS-QUAL-001-01B1B-R8` | Conservative syntactic integrity policy | L1 | Stopped after final review found Python 3.11 scope incompatibility | +| `WS-QUAL-001-01B1B-R9` | Python 3.11 comprehension scope compatibility | L1 | Stopped after final Python 3.13 TypeVar child-order finding | +| `WS-QUAL-001-01B1B-R10` | Python 3.13 TypeVar child order | L1 | Ready PR #108 published; external checks and human review active | +| `WS-QUAL-001-01B2` | Git provenance, configured baseline evidence, and CI ratchet | L1 | Inactive until B1B-R10 merge/memory plus explicit user start | | `WS-QUAL-001-02` | Project setup/policy/correction service coverage; floor at least 82% | L1 | Inactive | | `WS-QUAL-001-03` | Project repository/router coverage; floor at least 84% | L1 | Inactive | | `WS-QUAL-001-04` | Task service/repository/router coverage; floor at least 86% | L1 | Inactive | diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/DECISIONS.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/DECISIONS.md index 818abd71..4e17dfde 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/DECISIONS.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/DECISIONS.md @@ -149,3 +149,108 @@ coverage runtime selected by the pinned pytest-cov toolchain and adds positive/negative same-runtime equivalence proof. Coverage.py itself is not exactly pinned; R2 records the resolved version, while later 01B2 owns committed tool-version evidence. This is a reuse correction, not new policy scope. + +## D14: Replace file-global semantic aliases with lexical binding + +Status: proposed after B1B's two-cycle circuit stop; internal contract review +pending under the user's direction to keep coverage and AUTH running in +parallel. + +The B1B candidate correctly handled committed renames and base-AST assertion +spans, but file-global alias sets misclassified locally shadowed pytest names, +and broad `.skipTest` detection caused a test expectation to be weakened. R1 +must resolve actual bindings by lexical scope, preserve arbitrary local +lookalikes, and retain every passing Git/scope/size invariant. This is a +corrective replacement, not B2 or application scope. + +## D15: Use measured lexical-binding allocation + +Status: proposed after R1's first implementation checkpoint; internal contract +review pending. + +R1's shared resolver draft measured 282 total implementation lines before its +required adversarial matrix, so the reviewed 300-line cap could not preserve +proof. The draft was discarded. R2 retains the identical behavior and file +scope with a 350-line cap allocated from measured code: 290 lines through the +lint-clean resolver, 50 lines for the matrix, and 10 reserve. This changes only +the evidence-backed size boundary; it does not admit B2, AUTH, application, +configuration, workflow, evidence, documentation, or coverage-raising scope. + +## D16: Delegate lexical facts to Python's symbol table + +Status: proposed after R2's cycle-zero proof-fit stop; internal contract review +pending. + +R2 proved that a partial AST symbol engine misses lambda, vararg, comprehension, +exception-target, nested-class, and control-flow semantics. R3 uses stdlib +`symtable` as the authority for lexical namespaces and layers only bounded AST +framework-value flow and joins on top. The normal 500-line L1 cap provides room +for the reproduced regression matrix without compression. This remains semantic +test-integrity scope only; B2, AUTH, application, configuration, workflow, +evidence, documentation, and coverage-raising tests remain forbidden. + +## D17: Fund complete control-flow proof instead of compressing cycle one + +Status: proposed after R3's proof-fit stop; internal contract review pending. + +R3's 468/500 candidate left 32 lines, while measured review requires roughly +47 policy lines and 20 regression lines after replacement savings. R4 uses a +550-line cap: existing 468, policy through 515, tests through 535, and 15 +reserve. The added budget closes already-reproduced semantic behavior only; it +does not admit B2, AUTH, app, config, workflow, evidence, docs, or coverage- +raising tests. + +## D18: Separate single syntax consumption from abstract state joins + +Status: proposed after R4's cycle-zero stop; internal contract review pending. + +R4 replayed AST subtrees to simulate paths, which is incompatible with ordinal +symtable child consumption. R5 visits every syntax node once. It joins incoming +abstract states before a handler/case, uses conservative loop-carried binding +summaries instead of replay, and applies mandatory regions afterward. Assignment +target loading/binding and comprehension ownership share one recursive helper. +This is a replacement architecture for existing findings, not new product/B2/ +AUTH/config/workflow/evidence scope. + +## D19: Close provenance transitively with evaluation-order proof + +Status: proposed after R5's cycle-zero stop; internal contract review pending. + +R5 repaired semantic replay but its loop summary was a one-step heuristic. +R6 replaces duplicate RHS walks with one shared cursor-neutral provenance +summary evaluated to closure, including outer-executed nested-scope headers. +It also funds exact iterable-to-target propagation, Python evaluation order, +and distinct sequential `except*` handling. The 800-line cap is restricted to +these reproduced integrity gaps and their tests. + +## D20: Use one recursive iterable-element abstraction + +Status: proposed after R6's hard-cap stop; internal contract review pending. + +R6 closed binding fixed points but substituted raw comprehension elements +without their target environment and special-cased generator consumers. R7 +uses one recursive element-provenance function for literal containers, +set/dict keys, and nested comprehension/genexpr environments. Control summaries +use the same abstraction to decide structural consumption/reachability. The +950-line cap funds only these reproduced semantic gaps and regressions. + +## D21: Enforce syntax integrity instead of predicting execution + +Status: proposed after R7's mandatory two-cycle stop; internal contract review +pending. + +R1-R7 demonstrated that exact reachability, value provenance, iterator +consumption, builtin behavior, and class control flow constitute an unbounded +Python abstract interpreter. That work did not raise backend coverage and kept +the measured application baseline at 79.25 percent. R8 narrows B1B to its actual +security purpose: framework-qualified weakening syntax is forbidden in changed +tests, and syntactic assertion constructs cannot be deleted. It deliberately +does not decide whether syntax executes. False-positive risk is bounded by exact +absolute-import ownership, stdlib lexical scopes, relative-import exclusion, +and local-lookalike tests. Ownership is monotone within a scope and propagates +only through cycle-safe simple Name-to-framework Name/Attribute aliases; no +container, unpacking, comprehension, iteration, call return, wrapper value, +statement order, reachability, or runtime inference is admitted. Execution +contexts cannot hide an already-owned construct and cannot create ownership. +This smaller gate must finish before B2 and the +coverage-raising service chunks can move the repository toward 90 percent. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md index 9c2dcc85..b478ad93 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md @@ -2,15 +2,68 @@ ## Current state -- Phase: `WS-QUAL-001-01B1A-R2` post-merge memory -- Branch: `codex/ws-qual-001-01b1a-r2-post-merge-memory` +- Phase: `WS-QUAL-001-01B1B-R10` external and human review +- Branch: `codex/ws-qual-001-01b1b-semantic-delta-guards` - Authoritative target: 90 percent complete backend application statement coverage - Diagnostic AUTH-02 baseline: 78.26 percent after database isolation repair - Final reviewed planning SHA: `0d9dd987d546c864fa8de7bae462e5e73a1b5ea9` - Final evidence-bound branch head: `3da1769882e9f6db4c48ef3dba33da8380e6a613` - Planning merge commit: `9046d52f31c7c39f06e06c45c43783bb08a5181c` - Internal review: PASS after cleanup, signal, authority, CI, and split repairs -- Active implementation chunk: none +- Active implementation chunk: `WS-QUAL-001-01B1B-R10` +- R8 reviewed contract SHA: `9d72e42cf52f0d2da44dc37216fa38b3542dc2c1` +- R8 start signal: user's 2026-07-13 instruction to fix the blocking coverage + gate after AUTH-02 merged; implementation activated after plan review passed. +- R8 implementation candidate: + `3acf57281da4638476e70d7ed118e24413c1d20b` +- R8 pre-review proof: 117 focused tests, Ruff, `pip check`, self-applied delta + validation, repository documentation gates, and 412/700 raw lines pass. +- R8 cycle-zero review: bounded direct-import, enclosing-shadow, + `unittest.case`, conflicting-owner, PEP 695, and TestCase receiver gaps found. +- R8 cycle-one candidate: + `1a13beaaf968a00a19c64e702b33026283cf0d22` +- R8 cycle-one proof: 140 focused tests and every deterministic gate pass at + 498/700 raw lines. +- R8 cycle-one review: TypeVar-bound scopes, dotted aliases, nearer nonlocal + barriers, executable targets, and vararg annotations remained incomplete. +- R8 cycle-two candidate: + `e2ac216a114bacb4115c7b44efa736e48cd500fb` +- R8 cycle-two proof: 157 focused tests and every deterministic gate pass at + 537/700 raw lines. +- R8 final result: stopped because Python 3.11 uses child symbol tables for + list/set/dict comprehensions while the implementation assumed 3.12 inlining. +- R9 direction: introspected dual-version comprehension scope selection with + explicit Python 3.11 and 3.12 proof; implementation inactive pending review. +- R9 reviewed contract SHA: `4da088000d6f059104a08318104fd211a5e9f77f` +- R9 start signal: user's instruction to fix and finish the blocking coverage + gate; implementation activated after every plan track passed. +- R9 implementation candidate: + `5a971d80c38cbf856e9eee5bcd49fac6873c38c2` +- R9 proof: identical 161-test matrices pass on Python 3.11.15 and 3.12.3; + every deterministic gate passes at 546/600 raw lines. +- R9 cycle-zero review: nested 3.12 inline scope and Python 3.13 public + symtable-type compatibility remained incomplete. +- R9 cycle-one candidate: + `a5395c173741ee584312e5b69e70676092ce9c46` +- R9 cycle-one proof: identical 165-test matrices pass on Python 3.11.15, + 3.12.3, and 3.13.3; every deterministic gate passes at 553/600 raw lines. +- R9 final result: stopped because Python 3.13 bound/default TypeVar children + share public identifiers and require one ordered ordinal. +- R10 direction: shared public TypeVar ordinal with mixed-shape Python 3.13 + proof; the contract subsequently passed review and implementation activated. +- R10 reviewed contract SHA: `c42a67add8e68e646d64eb2bc5b4e2d86502d150` +- R10 start signal: user's instruction to keep AUTH and coverage moving; + implementation activated after all plan tracks passed. +- R10 implementation candidate: + `15d0b80e776f5be12cacc5dbe5226ffe3992dcfd` +- R10 external-repair proof: identical 172-test matrices pass on Python 3.11, + 3.12, and 3.13; Ruff and dependency integrity pass at 584/620 raw lines. +- R10 original internal review: nine required reviewer tracks and the circuit + breaker passed at `15d0b80e776f5be12cacc5dbe5226ffe3992dcfd`. +- R10 external repair: code/test change is `4bcf910`; all nine required reviewer + tracks and the circuit breaker pass at `7e192b8` after evidence-provenance + repair. +- R10 ready PR: `https://github.com/Flow-Research/workstream/pull/108` - 01A implementation PR: `https://github.com/Flow-Research/workstream/pull/103` (merged) - 01A final reviewed implementation SHA: `d1582ec64b9176c5ead62f695c7a23b48e4c72b9` - 01A final evidence-bound branch head: `8cd7616b497ceb46d8359c25de689192632dfee8` @@ -56,9 +109,90 @@ - R2 implementation PR: `https://github.com/Flow-Research/workstream/pull/105` - R2 merge commit: `8a4182edb09970131aded73edf3428ac83fe60b9` - R2 post-merge memory PR: `https://github.com/Flow-Research/workstream/pull/106` +- R2 post-merge memory merge commit: + `6dccb8e632a6244ca575094be0e3338d49b15856` +- B1B start signal: explicit user direction on 2026-07-13 to run coverage and + AUTH in parallel using isolated worktrees +- B1B reviewed candidate: `10dff4fbbbefaec17e42cd31ca24593ee59209e2` +- B1B result: circuit stop at 223/300 after the second binding repair cycle; + lexical shadowing remained false-positive and a local-lookalike expectation + was weakened to match broader behavior +- B1B-R1 reviewed contract SHA: + `93e48b45171272f5715d00b2158ed0279e64e5e1` +- B1B-R1 start signal: explicit user direction to continue coverage in + parallel with AUTH on 2026-07-13 +- B1B-R1 result: stopped at its first size checkpoint; the shared resolver + measured 282 lines before its required matrix, so the 300-line cap could not + preserve the contract's proof. The draft was discarded without commit. +- B1B-R2 reviewed contract SHA: + `8a5fc4a801ed63b17075720cf248156d7164da7a` +- B1B-R2 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R2 reviewed candidate: `d4cef1d6042de351419d9bf07209735007cc17a7` +- B1B-R2 result: stopped at 348/350 during cycle-zero review. Stdlib lexical + cases, control-flow joins, TestCase provenance, `pytestmark`, and local + `exec` shadowing remained incomplete; the two-line reserve could not fit + genuine repairs and regressions. +- B1B-R3 reviewed contract SHA: + `245ab58b371788c02ecfda2c11c5acf859b8c318` +- B1B-R3 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R3 cycle-zero candidate: `10ca5086c52435f41f33e25cae0f94f454e30c8e` +- B1B-R3 cycle-zero result: 468/500; cycle-one findings cover independent + try/match/loop paths, chained ambiguity, actual binding targets, augmented + assignment/match captures, future annotations, and inlined comprehension + contract precision +- B1B-R3 final result: stopped before cycle-one implementation because the + complete repair and adversarial proof did not credibly fit the 32-line + reserve; no post-`10ca508` executable edit was made +- B1B-R4 reviewed contract SHA: + `ac2bcc6` +- B1B-R4 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R4 reviewed candidate: `06a6d61a0246a3975e58c0ddea2319ad74b37ba3` +- B1B-R4 result: stopped at 535/550 during cycle-zero review. Replayed syntax + corrupted ordinal symtable consumption; loop fixed points, target loads, + unpack provenance, inline ownership, and optional comprehension effects + remained incomplete. +- B1B-R5 reviewed contract SHA: `5672971` +- B1B-R5 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R5 reviewed candidate: `5f59f40` +- B1B-R5 result: stopped at 641/650 during cycle-zero review. Transitive loop + provenance, outer-executed headers, iterable target provenance, evaluation + ordering, summary pruning, and `except*` sequential behavior remained open. +- B1B-R6 reviewed contract SHA: `bfb2d8e` +- B1B-R6 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R6 reviewed candidate: `68174d1` +- B1B-R6 result: stopped at 800/800 after cycle-one review. Comprehension- + produced/set/dict element provenance, structural generator consumption, + nested reachability, and class-global import/AugAssign remained open. +- B1B-R7 reviewed contract SHA: `f0134aa` +- B1B-R7 start signal: continuing the user's explicit parallel coverage/AUTH + direction after internal contract approval on 2026-07-13 +- B1B-R7 cycle-zero candidate: `26a4e6e` +- B1B-R7 cycle-zero result: lazy unknown-call genexpr bodies, unpacked and + conditional provenance, empty-dict reachability, and class-global transfer + remained incomplete. +- B1B-R7 cycle-one candidate: + `a8e1e789f0421c35ecd6f23b9778379fb4b01156` +- B1B-R7 cycle-one proof: 254 focused behavior tests, Ruff, `pip check`, diff + hygiene, and exact 950/950 candidate lines passed. +- B1B-R7 cycle-one review: structural consumption, empty-comprehension + provenance, and class-control/import boundaries remained incomplete. +- B1B-R7 cycle-two candidate: + `5fcd9bb99a733fea9d6b05411ea26c4563375d61` +- B1B-R7 cycle-two proof: 278 focused behavior tests and Ruff passed at + 920/950 candidate lines. +- B1B-R7 final result: stopped after cycle-two review found valid adjacent + transparent-wrapper, qualified/async consumer, sequential shadowing, + relative-import, class-expression, method-consumer, and readability gaps. + No R7 PR may be opened; a replacement requires a reviewed plan and start. ## Stop condition -R2 is merged. Review and merge memory PR #106, then stop. AUTH-02 may -proceed independently off-main in its separate worktree. Do not start 01B1B, -01B2, or chunk 02 without their separate checkpoints. +R10 implementation and required internal reviews are complete; publish only +the reviewed R10 PR. AUTH-02 merged through PR #107 and AUTH-03 is independently +active in its isolated worktree after explicit user start. Do not start 01B2 or +chunk 02 before R10 merge, post-merge memory, and the required start gate. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R1-lexical-binding-closure.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R1-lexical-binding-closure.md new file mode 100644 index 00000000..97370ce4 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R1-lexical-binding-closure.md @@ -0,0 +1,74 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R1 Lexical Binding Closure + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: stopped at the first size checkpoint. The resolver-only draft measured +282 lines before the required matrix, so the 300-line cap could not preserve +proof. Superseded by B1B-R2; do not resume. + +## Goal And Scope + +Complete the semantic delta guards using lexical binding rather than +file-global alias sets. Preserve all passing B1B repository, assertion-span, +rename, scope, size, and cwd behavior. + +Risk: L1 test/CI integrity. SLA: P1. Allowed implementation files are +`backend/scripts/coverage_policy.py`, `backend/tests/test_coverage_contract.py`, +and WS-QUAL/global process evidence. Parser redesign, B2, docs, live config, +workflow/evidence mutation, `backend/app/**`, DB/API/product/AUTH behavior, +skips/xfails, exclusions, and coverage-raising tests remain forbidden. + +The complete replacement candidate is capped at 300 non-memory lines from +merge base. The existing candidate is 223 lines. Forecast: replace the two +file-global analyzers with one shared resolver for at most 30 net lines, add +the binding/TestCase matrices for at most 40 net lines, and retain 7 lines of +reserve. Checkpoint at or before 270 lines, before adding the remaining matrix; +stop above 300 or if the forecast cannot preserve the required proof. + +R1 resets the stopped B1B repair count because its reviewed contract replaces +the binding model. It permits at most two implementation-review repair cycles. +Any valid same-class finding remaining after cycle two requires another stop +and replan; any valid finding outside this corrective scope stops immediately. + +## Acceptance Criteria + +- Implement one shared static binding resolver used by both weakening and + deleted-assertion analysis. It covers module, function, class, and nested + scopes; parameters; imports and aliases; simple/annotated/augmented + assignment and other name-store targets; and reassignment in statement + order. Function-local names are determined for the whole function; + `global`/`nonlocal` route to their declared scope, and method bodies do not + inherit the class namespace as a closure. +- Bindings are classified as framework module/member, proven TestCase class or + instance, local, or ambiguous. Conditional paths merge classifications; + star imports, dynamic binding, or unresolved control-flow combinations that + can retain a framework classification fail closed. A definite parameter or + local reassignment is local before/after the relevant call as Python scope + and statement order require. +- A local or parameter binding that shadows `pytest`, a module alias, or an + imported `raises`/skip name must be treated as local, not framework behavior. +- Detect genuine pytest skip/xfail/importorskip and unittest skip decorators + and exceptions. `self.skipTest` and `super().skipTest` are proven only inside + a class whose base resolves to `unittest.TestCase` or its direct alias; + another receiver is proven only when its binding comes from direct or + aliased `TestCase` construction. Arbitrary local `.skipTest()` methods remain + accepted. +- Base-AST deleted assertion detection must apply the same lexical resolution + to direct/imported/module-aliased `pytest.raises` while preserving multiline + native/self assertions and inert/local-lookalike acceptance. +- Preserve real committed rename rejection, root helper reuse, cwd restoration, + exact global/WS-QUAL memory scope, and the implementation cap. +- Restore the local-lookalike test to its original non-weak expectation; tests + may not be rewritten to accept broader guard behavior. + +## Verification And Review + +Run the complete B1B verification command set plus adversarial nested-scope, +parameter-shadow, before/after assignment-shadow, conditional ambiguity, +`global`/`nonlocal`, and recognized/unrecognized class/TestCase matrices. +Required reviewers: senior engineering, QA/test, security/auth, product/ops, +architecture, CI integrity, reuse/dedup, and test delta. Human focus: lexical +false positives/negatives, no weakened expectations, and no B2/AUTH spill. + +Stop after R1. Do not start 01B2 or chunk 02. AUTH-02 may continue independently +in its already-authorized worktree. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R10-typevar-child-order.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R10-typevar-child-order.md new file mode 100644 index 00000000..169571e5 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R10-typevar-child-order.md @@ -0,0 +1,61 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R10 TypeVar Child Order + +Parent: `WS-QUAL-001` Backend Coverage Floor + +## Goal + +Close R9's single final-review defect: Python 3.13 exposes TypeVar bound and +default scopes as identically typed/named public children, so they must be +consumed in AST field order through one shared ordinal. + +Risk: L1. SLA: fast. The user's instruction to keep AUTH and coverage moving is +the start signal after internal contract review passes. + +## Scope And Limit + +Allowed: `backend/scripts/coverage_policy.py`, +`backend/tests/test_coverage_contract.py`, and WS-QUAL/global loop memory. +All config, dependency, workflow, application, AUTH, B2, coverage-raising, and +Python-floor changes are forbidden. + +Base is merged-main `060b780190435bc79464ae92fd9235a652f70e00`. +Complete policy/test raw added-plus-deleted delta must remain `<=620`; R9 is +553. Tests remain readable, preserved, and one new case per line. R10 allows +one repair cycle; any unrelated finding stops it. + +## Acceptance Criteria + +- When public `type variable` children exist, select only that public family and + consume bound then default through one shared ordinal for the parameter/name/ + line, matching AST field order. +- When public children do not exist, retain the distinct Python 3.12 legacy + `TypeVar bound`/`TypeVar default` families. +- Mixed bound/default nested lambda/genexpr shapes pair correctly for generic + functions, classes, and type aliases; owned weakening syntax remains detected. +- Python 3.13 fixtures test clean and owned mixed bound/default forms. The same + source is invalid-syntax fail-closed on Python 3.11/3.12. +- On Python 3.13 every mixed-shape fixture must first call `analyze_python` + successfully, so fail-closed `SyntaxError` cannot satisfy a positive. Cover + bound-lambda/default-genexpr and bound-genexpr/default-lambda orders across + generic functions, classes, and type aliases. Clean forms remain clean; + framework-owned skip forms set `weak=True`; framework-owned `pytest.raises` + forms produce a concrete assertion range and block deletion. +- Preserve all 165 R9 cases/assertions and every syntax-policy behavior. Do not + add execution/value/provenance logic or change comprehension selection. + +## Verification And Review + +Run the complete R9 gates with self-validation/raw cap changed to 620, and run +the identical focused matrix in isolated `backend[dev]` environments on Python +3.11, 3.12, and 3.13 with explicit interpreter assertions. + +Required reviewers: senior engineering, QA/test, security/auth, product/ops, +architecture, CI integrity, docs, reuse/dedup, test delta, and circuit breaker. +Human focus: shared public ordinal, legacy separation, mixed-shape proof, +unchanged policy behavior, cross-version evidence, and bounded scope. + +## Stop Conditions + +Stop above 620 lines, after one failed repair, if any supported-version proof +cannot run, or if config/dependency/workflow changes are required. Do not start +B2, chunk 02, AUTH-04, or any later coverage chunk automatically. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R2-measured-lexical-binding-closure.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R2-measured-lexical-binding-closure.md new file mode 100644 index 00000000..60b7dbc7 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R2-measured-lexical-binding-closure.md @@ -0,0 +1,52 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R2 Measured Lexical Binding Closure + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: blocked at 348/350 during cycle-zero implementation review. Standard +lexical/control-flow proof could not fit the two-line reserve. Superseded by +B1B-R3; retain as review history and do not repair in place. + +## Scope And Budget + +Complete B1B's semantic guards with the lexical model already reviewed for R1. +Allowed files are `backend/scripts/coverage_policy.py`, +`backend/tests/test_coverage_contract.py`, and WS-QUAL/global memory. Parser or +B2 work, docs, configuration, workflows, evidence, `backend/app/**`, DB/API, +product/AUTH behavior, skips/xfails, exclusions, and coverage-raising tests are +forbidden. + +The candidate is capped at 350 non-memory lines from merge base: existing B1B +223, lint-clean shared resolver through 290, adversarial matrix through 340, +and 10 reserve. Checkpoint at or before 310 before the remaining matrix. Stop +above 350 or if proof cannot fit. R2 permits at most two review repair cycles; +a same-class finding after cycle two or any outside-scope finding stops/replans. + +## Binding Contract + +- One shared AST resolver serves skip/xfail weakening and deleted-assertion + analysis across module, function, class, and nested scopes. +- Parameters and whole-function locals shadow outer bindings. Imports, simple + and other name-store assignments, reassignment order, `global`, `nonlocal`, + and class-method non-closure follow Python lexical behavior. Conditional or + dynamic ambiguity that can retain a framework binding fails closed. +- Resolve direct and aliased pytest/unittest modules and members. Detect genuine + skip/xfail/importorskip, unittest decorators/exceptions, and deleted + `pytest.raises` calls, including multiline calls. +- `self.skipTest` and `super().skipTest` require a resolved TestCase subclass; + another receiver requires direct or aliased TestCase construction. Arbitrary + local `.skipTest()` and shadowed pytest/raises names remain accepted. +- Preserve committed test-rename rejection, base-AST line mapping, native/self + assertion deletion, inert strings/comments, root-helper reuse, cwd restore, + global/WS-QUAL memory scope, and exact size enforcement. Restore the weakened + local-lookalike expectation; no test may be rewritten for broader behavior. + +## Verification And Review + +Run the complete B1B commands plus nested, parameter, before/after assignment, +conditional, `global`/`nonlocal`, class non-closure, TestCase, and local +lookalike matrices. Required reviewers: senior engineering, QA/test, +security/auth, product/ops, architecture, CI integrity, reuse/dedup, and test +delta. Human focus: exact binding behavior, genuine tests, size, and no B2/AUTH +spill. + +Stop after R2. Do not start 01B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R3-symtable-lexical-binding-closure.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R3-symtable-lexical-binding-closure.md new file mode 100644 index 00000000..6e8a2e6a --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R3-symtable-lexical-binding-closure.md @@ -0,0 +1,68 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R3 Symtable Lexical Binding Closure + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: blocked at 468/500 after cycle-zero review. Cycle-one repair lacked +credible proof within 32 lines, so no repair was attempted. Superseded by R4. + +## Scope And Allocation + +Complete semantic test-delta guards using stdlib `symtable` for lexical facts +and bounded AST value flow for pytest/unittest provenance. Allowed files are +`backend/scripts/coverage_policy.py`, `backend/tests/test_coverage_contract.py`, +and WS-QUAL/global memory. B2/parser work, docs, config, workflows, evidence, +`backend/app/**`, DB/API/product/AUTH behavior, skips/xfails, exclusions, and +coverage-raising tests are forbidden. + +Use the normal 500-line L1 cap from merge base: existing reviewed candidate 348, +symtable resolver and control joins through 420, reproduced/adversarial matrix +through 480, and 20 reserve. Checkpoint at or before 440 before the remaining +matrix. Stop above 500 or if proof cannot fit. R3 permits two implementation +review repair cycles; any same-class finding after cycle two or any outside- +scope finding stops/replans. + +## Acceptance Criteria + +- `symtable` is authoritative for module/function/lambda/comprehension/class + namespaces, parameters including varargs/kwargs, whole-function locals, + globals/nonlocals, exception/loop/with targets, and class non-closure. +- Within each parent scope, pair AST scope-creating nodes with + `SymbolTable.get_children()` in source traversal ordinal. Validate expected + table type, normalized name, and line, consume each child exactly once, and + fail closed on missing, mismatched, or extra children; do not use a + collision-prone `(name, line)` lookup. +- On the locked Python 3.12 runtime, list/set/dict comprehensions are compiler- + inlined and emit no child table. Isolate only their AST binding targets while + leaving outer iterable evaluation in the parent scope. Generator expressions + must consume a validated `genexpr` child. Any unexpected emitted child or + missing/mismatched generator child fails closed. +- AST value flow classifies framework modules/members, TestCase class/instance, + local, and ambiguity. Imports, assignment/annotation/named expressions, + decorators, `pytestmark` scalar/list forms, and If/loop/try/match joins are + handled; paths retaining framework provenance fail closed. +- Try handlers, match cases, loop body/else, and equivalent alternatives are + evaluated from independent incoming states before joining. Attribute and + subscript assignment bases are loads, not binding targets; augmented writes + and match captures follow their actual symbol-table ownership. +- Detect genuine skip/xfail/importorskip and unittest behavior. `skipTest` + requires a resolved TestCase receiver or ambiguous path retaining one; + arbitrary locals remain accepted. Builtin `exec` fails closed, while a + definite local/parameter named `exec` remains accepted. +- Deleted native/self/pytest-raises assertions use the same resolver, including + multiline calls and every scope/shadow/control case. Preserve inert strings, + committed rename rejection, base-line mapping, root helpers, cwd restoration, + exact memory scope, and size enforcement. No weakened tests. +- With `from __future__ import annotations`, non-executable annotations are not + traversed as runtime expressions or paired to nonexistent child tables. + +## Verification And Review + +Run all B1B checks and regressions for lambda, varargs/kwargs, comprehensions, +exception targets, nested classes, annotations, loops/try/match, `pytestmark`, +shadowed `exec`, TestCase ambiguity, and deleted raises. Include two same-line +lambdas/comprehensions and repeated same-name definitions to prove ordinal +scope-table pairing. Required reviewers: +senior engineering, QA/test, security/auth, product/ops, architecture, CI +integrity, reuse/dedup, and test delta. + +Stop after R3. Do not start B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R4-complete-symtable-control-flow.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R4-complete-symtable-control-flow.md new file mode 100644 index 00000000..fa690d69 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R4-complete-symtable-control-flow.md @@ -0,0 +1,53 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R4 Complete Symtable Control Flow + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: blocked at 535/550 during cycle-zero implementation review. Superseded +by B1B-R5; retain as review history and do not repair in place. + +## Scope And Allocation + +Finish the reviewed symtable resolver's reproduced control/value-flow gaps. +Allowed files are `backend/scripts/coverage_policy.py`, +`backend/tests/test_coverage_contract.py`, and WS-QUAL/global memory. B2/parser, +docs, config, workflow, evidence, `backend/app/**`, DB/API/product/AUTH, skips, +xfails, exclusions, and coverage-raising tests remain forbidden. + +Cap the complete candidate at 550 lines from merge base: existing 468, policy +repair through 515, regression matrix through 535, and 15 reserve. Checkpoint +at or before 520 before remaining tests. Stop above 550 or if proof cannot fit. +R4 permits two repair cycles; a same-class finding after cycle two or any +outside-scope finding stops/replans. + +## Acceptance Criteria + +- Retain stdlib `symtable` lexical authority and deterministic ordinal child + consumption. Python 3.12 list/set/dict comprehensions use isolated generator- + clause target frames because the compiler emits no child; genexpr must consume + a validated child. Walrus targets bind the containing module/function per + symtable, including nested inlined-comprehension regressions. +- Evaluate try body progress, each handler, else, and loop/match alternatives as + independent executable paths. A handler conservatively includes provenance + written before a possible exception. Join alternatives before applying the + mandatory `finally` region. Any framework-retaining path fails closed. +- Bind only true Name/Starred/tuple/list assignment targets; attribute/subscript + bases remain loads. Cover augmented writes, annotations, named expressions, + with/exception targets, and match captures at their symtable-owned scope. +- Preserve chained ambiguity through `.mark`, `.TestCase`, construction, and + `.skipTest`. With future annotations, do not traverse postponed annotation + expressions or expect child tables. Builtin `exec` remains blocked while a + definite shadow remains accepted. +- The same resolver protects deleted raises across all cases. Preserve every + passing rename, base-line, native/self assertion, cwd, root-helper, memory, + and size invariant. Tests remain genuine and may not be weakened. + +## Verification And Review + +Run all B1B/R3 checks plus regressions for multi-handler try with progressive +body provenance/finally, multi-case match, loop else, exact assignment targets, +AugAssign, match capture, chained mark/TestCase ambiguity, future annotations, +and nested inline-comprehension walrus ownership. Required reviewers: senior +engineering, QA/test, security/auth, product/ops, architecture, CI integrity, +reuse/dedup, and test delta. + +Stop after R4. Do not start B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R5-single-pass-abstract-flow.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R5-single-pass-abstract-flow.md new file mode 100644 index 00000000..83b432c0 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R5-single-pass-abstract-flow.md @@ -0,0 +1,63 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R5 Single-Pass Abstract Flow + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: blocked at 641/650 during cycle-zero implementation review. Superseded +by B1B-R6; retain as review history and do not repair in place. + +## Scope And Allocation + +Replace path replay with single-consumption abstract flow while preserving the +reviewed symtable lexical resolver. Allowed files are +`backend/scripts/coverage_policy.py`, `backend/tests/test_coverage_contract.py`, +and WS-QUAL/global memory. B2/parser, docs, config, workflow, evidence, +`backend/app/**`, DB/API/product/AUTH, skips/xfails, exclusions, and coverage- +raising tests remain forbidden. + +Cap the complete candidate at 650 lines from merge base: existing 535, policy +replacement through 600, regression matrix through 630, and 20 reserve. +Checkpoint at or before 610 before remaining tests. Stop above 650 or if proof +cannot fit. R5 permits two repair cycles; a same-class finding after cycle two +or any outside-scope finding stops/replans. + +## Acceptance Criteria + +- The semantic visitor consumes every AST and emitted symtable child exactly + once. Alternative paths receive joined abstract incoming provenance; they + never re-run a subtree or advance its child cursor twice. Try handlers receive + conservative progressive body provenance, match cases retain failed-guard + effects, and `finally` is applied once after joined alternatives. +- A separate cursor-neutral loop summary may inspect binding statements before + the single semantic body visit. It is pure, scope-pruned, never enters nested + function/lambda/class/comprehension scopes, never advances a child cursor, + and never classifies calls. Definite or conditional later framework bindings + enter the loop header as possible/ambiguous provenance, so next-iteration + calls fail closed. Join zero/body/else outcomes without semantic replay. +- One recursive assignment helper pairs tuple/list RHS elements through nested + unpacking, binds only Name/Starred/tuple/list targets, and visits executable + Attribute/Subscript bases and indexes. Reuse it for normal, annotated, + augmented, loop, with, exception, and comprehension targets. +- Inline-comprehension generator-clause targets are local; walrus effects bind + the containing real lexical scope and reset across nested lambdas/functions. + Comprehension body effects are optional, including zero iterations and lazy + genexpr execution, and join with the incoming state. +- Preserve chained ambiguity, future-annotation suppression, pytestmark, + builtin/shadowed exec, TestCase proof, and the same deleted-raises resolver. + Function-local variable annotations are non-executable and are not traversed + even without the future import; module/class annotations remain executable + unless postponed. + Preserve rename, base-line/native/self assertion, cwd, root-helper, memory, + and size invariants. Restore the empty-comprehension expectation to genuine + framework-skip detection; no weakened tests. + +## Verification And Review + +Run all B1B/R4 checks plus nested def/lambda/genexpr in every control region, +loop-carried call-before-import, failed match guard effects, target base/index +loads, nested unpacked module/raises provenance, nested inline lambda walrus, +zero/lazy comprehension effects, function-local versus module/class annotation +execution, and deleted-raises counterparts. Required +reviewers: senior engineering, QA/test, security/auth, product/ops, +architecture, CI integrity, reuse/dedup, and test delta. + +Stop after R5. Do not start B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R6-provenance-closure.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R6-provenance-closure.md new file mode 100644 index 00000000..b65e63da --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R6-provenance-closure.md @@ -0,0 +1,57 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R6 Provenance Closure + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: blocked at 800/800 after cycle-one implementation review. Superseded by +B1B-R7; retain as review history and do not repair in place. + +## Scope And Allocation + +Close the reviewed single-pass resolver with transitive provenance and exact +evaluation order. Allowed files are `backend/scripts/coverage_policy.py`, +`backend/tests/test_coverage_contract.py`, and WS-QUAL/global memory. B2/parser, +docs, config, workflow, evidence, `backend/app/**`, DB/API/product/AUTH, +skips/xfails, exclusions, and coverage-raising tests remain forbidden. + +Cap the complete candidate at 800 lines from merge base: existing 641, policy +closure through 720, regression matrix through 770, and 30 reserve. Checkpoint +at or before 740 before remaining tests. Stop above 800 or if proof cannot fit. +R6 permits two repair cycles; a same-class finding after cycle two or any +outside-scope finding stops/replans. + +## Acceptance Criteria + +- Replace duplicate loop RHS heuristics with one shared cursor-neutral, + scope-pruned provenance summary. Iterate bindings to a fixed point so alias + chains, TestCase construction, direct/imported raises, conditional bindings, + and later assignments all enter the loop header as possible provenance. +- The summary prunes nested bodies but inspects outer-evaluated function + defaults/decorators, class decorators/bases/keywords, and required iterable + expressions. It ignores function-local annotations and lazy/unconsumed + comprehension/genexpr bodies; containing-scope walrus effects are included + only where execution can occur before the next-iteration use. +- Propagate iterable element provenance through for/comprehension targets, + including nested unpacking. Shared target traversal visits executable + Attribute/Subscript bases/indexes and never binds their load Names. Deleted + raises uses identical provenance. +- Preserve Python 3.12 evaluation order: AnnAssign RHS, target base/index, then + executable annotation; AugAssign target base/index before RHS; exception type + before `as` binding. Function-local/postponed annotations remain suppressed. +- Normal try handlers retain progressive mutually exclusive flow. TryStar is a + separate sequential handler-carried flow because multiple `except*` handlers + can execute. Every syntax/symtable child remains consumed once. +- Preserve all passing control joins, optional comprehension writes, chained + ambiguity, pytestmark/exec/TestCase, rename, base-line/native/self assertion, + cwd, root-helper, memory, and size behavior. No weakened tests. + +## Verification And Review + +Run all B1B/R5 checks plus transitive alias/TestCase chains, outer-evaluated +defaults/decorators/bases, annotation/lazy-summary negatives, iterable target +module/raises provenance, target-evaluation walrus ordering, exception-type +ordering, sequential mixed `except*`, nested lexical nodes in every control +region, and deleted-raises counterparts. Required reviewers: senior +engineering, QA/test, security/auth, product/ops, architecture, CI integrity, +reuse/dedup, and test delta. + +Stop after R6. Do not start B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R7-iterable-provenance.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R7-iterable-provenance.md new file mode 100644 index 00000000..e6d8b4d5 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R7-iterable-provenance.md @@ -0,0 +1,54 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R7 Iterable Provenance + +Parent: `WS-QUAL-001` Backend Coverage Floor + +Status: all ten internal plan-review tracks passed at `f0134aa`; implementation +started under the user's parallel coverage/AUTH direction on 2026-07-13. + +## Scope And Allocation + +Replace ad hoc iterable substitution and consumption allowlists with one +recursive provenance abstraction. Allowed files are +`backend/scripts/coverage_policy.py`, `backend/tests/test_coverage_contract.py`, +and WS-QUAL/global memory. B2/parser, docs, config, workflow, evidence, +`backend/app/**`, DB/API/product/AUTH, skips/xfails, exclusions, and coverage- +raising tests remain forbidden. + +Cap the complete candidate at 950 lines from merge base: existing 800, policy +replacement through 870, regression matrix through 920, and 30 reserve. +Checkpoint at or before 890 before remaining tests. Stop above 950 or if proof +cannot fit. R7 permits two repair cycles; a same-class finding after cycle two +or any outside-scope finding stops/replans. + +## Acceptance Criteria + +- One recursive `element provenance` abstraction handles tuple/list/set + elements, dict keys, and list/set/dict comprehension or genexpr elements. + Comprehension target bindings are derived recursively from each generator's + iterable and applied when resolving the yielded element, including nested + unpacking and mixed framework/local values. Deleted raises uses the same path. +- Generator consumption is structural: for/comprehension iteration consumes + its iterable, and explicitly eager consumers propagate genexpr bodies without + a brittle closed builtin list. Lazy/unconsumed genexpr bodies remain pruned. +- Reachability walks every generator clause in order. A literal-empty clause + prevents later clauses/body effects; reachable eager clauses propagate outer- + owned walrus effects. No first-generator-only approximation. +- Eager class bodies propagate only declared-global Assign/AnnAssign/NamedExpr, + Import/ImportFrom, and AugAssign effects to loop fixed points. Ordinary class + attributes remain isolated. +- Preserve exact evaluation order, single AST/symtable consumption, progressive + normal handler tests, sequential TryStar, target load/store separation, + future/local annotations, fixed-point aliases/TestCase, optional writes, + pytestmark/exec, rename, deleted/native/self assertions, cwd/root/memory, and + size behavior. No weakened tests. + +## Verification And Review + +Run all B1B/R6 checks plus passthrough and transformed gen/list/set/dict +iterables, set/dict literals and keys, mixed nested elements, nested structural +generator consumers, later-empty comprehension clauses, class-global import/ +from-import/AugAssign, and deleted-raises counterparts. Required reviewers: +senior engineering, QA/test, security/auth, product/ops, architecture, CI +integrity, reuse/dedup, and test delta. + +Stop after R7. Do not start B2 or chunk 02. AUTH continues independently. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R8-syntactic-integrity-policy.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R8-syntactic-integrity-policy.md new file mode 100644 index 00000000..1f0a3258 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R8-syntactic-integrity-policy.md @@ -0,0 +1,116 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R8 Syntactic Integrity Policy + +Parent: `WS-QUAL-001` Backend Coverage Floor + +## Goal And Rationale + +Replace R7's attempted Python execution model with a bounded syntactic policy. +The gate protects changed tests from known pytest/unittest weakening constructs +and deleted assertion constructs; it does not predict runtime reachability, +iterator consumption, or arbitrary Python values. + +Risk: L1. SLA: normal. The user's 2026-07-13 instruction to fix the blocking +coverage gate is the implementation start signal only after this contract passes +all required internal plan-review tracks. + +## Scope + +Allowed files: + +- `backend/scripts/coverage_policy.py` +- `backend/tests/test_coverage_contract.py` +- WS-QUAL and global `.agent-loop/` memory + +Not allowed: `backend/app/**`, coverage-raising tests, skips/xfails/exclusions, +configuration, workflows, evidence baselines, B2, chunk 02, AUTH-03, product +runtime, dependencies, public docs, or spreadsheet exports. + +The implementation base is merged-main SHA +`060b780190435bc79464ae92fd9235a652f70e00`. Its raw added-plus-deleted delta is +capped at 700 lines across the policy and contract-test files, with the same +metric used for the 600-line checkpoint. Tests must keep one case per physical +line for every new or replacement R8 matrix entry, stay reviewable without +generated/encoded fixtures, and must not be compacted to satisfy the cap. +Merged-main formatting remains unchanged. The first implementation step restores +the branch-only R2-R7 policy/test delta to base `060b780`, preserving every base +test, before adding R8. Apply the checkpoint and cap to that resulting candidate +delta. Stop above 700. + +## Acceptance Criteria + +- Same-scope absolute `pytest` and `unittest` imports, aliases, and direct imports + establish monotone framework ownership regardless of statement order, + reassignment, branch, or reachability. Relative imports and imports whose + absolute root is not exactly `pytest` or `unittest` remain local. +- Python lexical scopes use stdlib `symtable`. Parameters and declarations in a + nested scope shadow inherited ownership; explicit global/nonlocal names retain + their resolved owner. A finite, cycle-safe alias closure covers only simple + `Name = framework-owned Name` and `Name = framework-owned Attribute` aliases. + Containers, unpacking, comprehensions, iteration, calls/returns, wrapper + values, and arbitrary attributes never create ownership. +- Once a construct's root is framework-owned, dead branches, empty + comprehensions, lazy/async generators, wrappers, qualified/arbitrary + consumers, and argument position cannot suppress detection. Those contexts do + not themselves create ownership; no statement-order, reachability, class + transfer, consumer, or runtime-value inference is permitted. +- Reject `pytest.skip`, `pytest.xfail`, `pytest.importorskip`, + `pytest.mark.skip/skipif/xfail`, pytestmark assignments resolving to those + marks, `unittest.skip/skipIf/skipUnless/expectedFailure`, `SkipTest`, and + framework-owned `unittest.TestCase.skipTest`. Reject `self.skipTest` and + `super().skipTest` only inside a class whose syntactic base resolves through + the lexical alias closure to framework-owned `unittest.TestCase`. Keep the + same syntax in unrelated classes clean. `case = TestCase(); case.skipTest()` + is outside R8 because call-return provenance is intentionally retired. +- Native `assert`, framework-owned `pytest.raises`/imported `raises`, and + syntactic `self.assert*` calls are recognized in the base file for + deleted-assertion protection, independent of runtime reachability. The + receiver must be exactly the name `self`; unrelated receivers remain clean. +- Strings, comments, annotations under postponed evaluation, relative imports, + local lookalikes, shadowed names, and unrelated methods remain clean. +- Preserve every test present on base SHA `060b780` unchanged and passing, + including repository root/cwd, committed rename, scope/size, metadata, + canonical grammar, invalid-syntax fail-closed, native assertion, and lexical + local-lookalike behavior. +- Retire R2-R7 branch-only reachability, iterator-consumption, sequential-value, + and control-flow expectations. Map every discovered weakening construct into + the R8 conservative syntax matrix, including skip and deleted-assertion + variants; this intentional contract replacement is not test weakening. Do not + remove, skip, xfail, weaken, or compress merged-main tests or assertions. +- The positive matrix covers dead/empty/lazy/async/wrapper/qualified/arbitrary + contexts after ownership, owned-name reassignment invariance, + framework-rooted alias chains/cycles, and resolved global/nonlocal uses. + Negatives cover strings, comments, postponed annotations, relative imports, + local lookalikes, unowned cycles, nested parameter/local shadowing, and + unrelated attributes. Mirror it for native assert, owned/imported raises, and + exact-receiver `self.assert*` deletion. + +## Verification And Review + +Run: + +```text +cd backend && .venv/bin/python -m pytest -q tests/test_coverage_contract.py +cd backend && .venv/bin/ruff check scripts/coverage_policy.py tests/test_coverage_contract.py +cd backend && .venv/bin/pip check +backend/.venv/bin/python scripts/check_stale_workstream_wording.py +backend/.venv/bin/python scripts/check_stale_authorization_docs.py +backend/.venv/bin/python scripts/check_loop_memory_state.py +backend/.venv/bin/python scripts/check_markdown_links.py +git diff --check +git merge-base --is-ancestor 060b780190435bc79464ae92fd9235a652f70e00 HEAD +test -z "$(git diff --name-only 060b780190435bc79464ae92fd9235a652f70e00...HEAD | grep -Ev '^(backend/scripts/coverage_policy.py|backend/tests/test_coverage_contract.py|\.agent-loop/(LOOP_STATE.md|REVIEW_LOG.md|WORK_QUEUE.md|initiatives/WS-QUAL-001-backend-coverage-floor/.*))$')" +test "$(git diff --numstat 060b780190435bc79464ae92fd9235a652f70e00...HEAD -- backend/scripts/coverage_policy.py backend/tests/test_coverage_contract.py | awk '{total += $1 + $2} END {print total + 0}')" -le 700 +cd backend && BASE_SHA=060b780190435bc79464ae92fd9235a652f70e00 .venv/bin/python -c 'import os, sys; sys.path.insert(0, "scripts"); import coverage_policy as policy; policy.validate_delta(os.environ["BASE_SHA"], 700, {"backend/scripts/coverage_policy.py", "backend/tests/test_coverage_contract.py"})' +``` + +Required reviewers: senior engineering, QA/test, security/auth, product/ops, +architecture, CI integrity, docs, reuse/dedup, test delta, and circuit breaker. +Human review focus: the intentionally conservative syntax contract, local +lookalike precision, readable tests, no CI weakening, and bounded scope. + +## Stop Conditions + +Stop if the syntactic contract requires runtime reachability/consumption, +arbitrary call semantics, another abstract interpreter, unreadable tests, more +than two repair cycles, or more than 700 implementation lines. Do not start B2, +chunk 02, AUTH-03, or any other chunk after R8. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R9-python311-scope-compatibility.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R9-python311-scope-compatibility.md new file mode 100644 index 00000000..4f3981df --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-R9-python311-scope-compatibility.md @@ -0,0 +1,69 @@ +# Chunk Contract: WS-QUAL-001-01B1B-R9 Python 3.11 Scope Compatibility + +Parent: `WS-QUAL-001` Backend Coverage Floor + +## Goal + +Close the single R8 final-review defect: syntactic scope pairing must support +both Python 3.11 child comprehension tables and Python 3.12+ inlined +comprehensions. This is a compatibility closure, not another policy redesign. + +Risk: L1. SLA: fast. The user's instruction to fix and finish the blocking +coverage gate is the start signal after this contract passes internal review. + +## Scope And Limit + +Allowed: `backend/scripts/coverage_policy.py`, +`backend/tests/test_coverage_contract.py`, and WS-QUAL/global loop memory. +Everything else is forbidden, including dependencies, Python-floor changes, +workflow/CI changes, B2, chunk 02, AUTH-03, application code, exclusions, and +coverage-raising tests. + +Base SHA is merged main `060b780190435bc79464ae92fd9235a652f70e00`. +The complete policy/test delta must remain at or below 600 raw added-plus-deleted +lines; the R8 candidate is 537. Tests remain readable and one new case per line. +R9 has one repair cycle. Any unrelated finding or size failure stops it. + +## Acceptance Criteria + +- Detect whether a list/set/dict comprehension has a matching stdlib symtable + child. Enter it on Python 3.11; use the containing table when Python 3.12+ + inlines the comprehension. Do not branch only on a hard-coded version number. +- Generator expressions continue entering their required `genexpr` table. +- Nested lambda/genexpr/function scopes pair under the selected comprehension + table, including same-line and multiply nested forms. +- Comprehension targets shadow framework names through nested scopes on both + supported interpreter families; owned names remain detected when unshadowed. +- Python 3.12-only PEP 695 fixtures expect fail-closed invalid syntax on Python + 3.11 and retain their existing semantic expectations on Python 3.12+. +- Preserve every R8 behavior and deterministic gate. No execution, value, + reachability, consumer, or provenance model may be added. +- Preserve every merged-main and R8 case and assertion. Only PEP 695 expected + results may be version-conditional: fail-closed invalid syntax on 3.11 versus + the existing semantic expectation on 3.12+. Do not remove, skip, xfail, + weaken, compress, or reduce the non-PEP matrix/test count on either version. + Every new R9 case occupies one physical line. + +## Verification And Review + +Run the existing 3.12 focused/Ruff/pip/self-validation/documentation/scope/raw +delta gates, plus a Python 3.11 focused run using an isolated uv environment: + +```text +uv run --no-project --isolated --python 3.11 --with-editable 'backend[dev]' python -c 'import sys; assert sys.version_info[:2] == (3, 11)' +uv run --no-project --isolated --python 3.11 --with-editable 'backend[dev]' python -m pytest -q backend/tests/test_coverage_contract.py +test -z "$(git diff --name-only 060b780190435bc79464ae92fd9235a652f70e00...HEAD | grep -Ev '^(backend/scripts/coverage_policy.py|backend/tests/test_coverage_contract.py|\.agent-loop/(LOOP_STATE.md|REVIEW_LOG.md|WORK_QUEUE.md|initiatives/WS-QUAL-001-backend-coverage-floor/.*))$')" +test "$(git diff --numstat 060b780190435bc79464ae92fd9235a652f70e00...HEAD -- backend/scripts/coverage_policy.py backend/tests/test_coverage_contract.py | awk '{total += $1 + $2} END {print total + 0}')" -le 600 +cd backend && BASE_SHA=060b780190435bc79464ae92fd9235a652f70e00 .venv/bin/python -c 'import os, sys; sys.path.insert(0, "scripts"); import coverage_policy as policy; policy.validate_delta(os.environ["BASE_SHA"], 600, {"backend/scripts/coverage_policy.py", "backend/tests/test_coverage_contract.py"})' +``` + +Required reviewers: senior engineering, QA/test, security/auth, product/ops, +architecture, CI integrity, docs, reuse/dedup, test delta, and circuit breaker. +Human focus: introspected symtable compatibility, dual-version proof, unchanged +syntax policy, readable tests, and no Python-floor or workflow change. + +## Stop Conditions + +Stop above 600 lines, after one failed repair, if Python 3.11 proof cannot run, +or if the fix requires config/workflow/dependency changes. Do not start B2, +chunk 02, AUTH-03, or any later chunk automatically. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-semantic-delta-guards.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-semantic-delta-guards.md index 857315ad..dc8b96a3 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-semantic-delta-guards.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B1B-semantic-delta-guards.md @@ -2,8 +2,8 @@ Parent: `WS-QUAL-001` Backend Coverage Floor -Status: proposed and inactive until 01B1A-R2 merges, post-merge memory -completes, and the user gives a separate explicit start signal. +Status: blocked at 223/300 after two binding/AST repair cycles; superseded by +proposed B1B-R1. Retain as circuit-breaker history and do not resume. ## Goal And Reason @@ -57,4 +57,5 @@ architecture, CI integrity, reuse/dedup, and test delta. Human focus: false negatives, false positives, root-path behavior, bounded memory scope, and no parser/01B2 redesign. -Stop after this chunk. Do not start 01B2, chunk 02, or AUTH. +Stop after this chunk. Do not start 01B2 or chunk 02. AUTH-02 may continue +independently in its already-authorized worktree. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B2-baseline-evidence-ci-ratchet.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B2-baseline-evidence-ci-ratchet.md index 8929ad7c..884cc083 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B2-baseline-evidence-ci-ratchet.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/chunks/WS-QUAL-001-01B2-baseline-evidence-ci-ratchet.md @@ -1,7 +1,7 @@ # Chunk Contract: WS-QUAL-001-01B2 Baseline Evidence And CI Ratchet -Status: inactive until 01B1A-R2 and 01B1B merge, both post-merge memory updates -complete, and the user gives a separate explicit start signal. +Status: inactive until B1B-R7 merges, its post-merge memory update is complete, +and the user gives a separate explicit start signal. ## Goal, Risk, And Budget diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-external-review-response.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-external-review-response.md new file mode 100644 index 00000000..6e5c5bb0 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-external-review-response.md @@ -0,0 +1,39 @@ +# External Review Response: WS-QUAL-001-01B1B-R10 + +## Comments Addressed + +- Retained the reviewed implementation SHA separately from the final + evidence-bound lifecycle SHA; CodeRabbit recognized the provenance repair in + commits `91d6302` through `4686d5b`. +- Corrected the trust bundle and status to distinguish nine required reviewer + tracks from the separate circuit-breaker result. +- Replaced obsolete R7-R9 next actions with the reviewed R10 supersession. +- Made deleted-assertion detection fail closed when a diff deletes lines but + `git show` cannot provide base source, with direct positive and clean cases. + +## Comments Deferred + +None. + +## Human Decisions Needed + +None. The fail-closed behavior is required by the approved semantic-integrity +contract and does not expand into CI ratchet or application coverage work. + +## Commands Rerun + +```text +172 focused behavior tests passed on Python 3.11, 3.12, and 3.13 +Ruff passed +pip check passed +implementation delta: 584/620 +``` + +Repository documentation gates, self-validation, and all required internal +reviewer tracks pass at final reviewed head `7e192b8`. + +## Remaining Risks + +- This policy remains local until B2 enables the canonical GitHub CI ratchet. +- Whole-backend coverage remains 79.25 percent until behavior-test chunks + 02-06 raise it toward 90 percent. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-internal-review-evidence.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-internal-review-evidence.md new file mode 100644 index 00000000..47bb5d95 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-internal-review-evidence.md @@ -0,0 +1,62 @@ +# WS-QUAL-001-01B1B-R10 Internal Review Evidence + +Open sub-agent sessions: none + +Valid findings addressed: yes + +Reviewed code SHA: `7e192b8fa0e522c240d66ef81ccf9532c080e30a` + +Reviewed at: 2026-07-13T06:00:46Z + +Reviewer run IDs: engineering=`/root/qual_r7_final_eng`; +QA/security/product/docs/test-delta=`WS-QUAL-001-01B1B-R10-EVIDENCE-SYNC-20260713T060046Z` + +## Binding + +- Base: `060b780190435bc79464ae92fd9235a652f70e00` +- Original implementation: `15d0b80e776f5be12cacc5dbe5226ffe3992dcfd` +- External-repair implementation: `4bcf910ac6b46904a7408360dc1c1d4d7df9ee2e` +- Final reviewed head: `7e192b8fa0e522c240d66ef81ccf9532c080e30a` +- Contract: `WS-QUAL-001-01B1B-R10 TypeVar Child Order` +- Raw policy/test delta: 584/620 added-plus-deleted lines + +## Behavior Proof + +- Python 3.11 isolated `backend[dev]`: 172 passed +- Python 3.12 project environment: 172 passed +- Python 3.13 isolated `backend[dev]`: 172 passed +- Ruff: passed +- `pip check`: passed +- Self-applied `validate_delta(..., 620, exact_allowed_files)`: passed +- Scope, base ancestry, stale wording, authorization docs, loop memory, + Markdown links, and `git diff --check`: passed + +The matrix proves framework-owned weakening syntax, deleted assertion syntax, +lexical aliases/shadows, TestCase receivers, comprehension targets/scopes, +PEP 695/696 TypeVar scopes, Python 3.11 child comprehensions, Python 3.12+ +inlined comprehensions, and Python 3.13 shared public TypeVar child ordering. + +## Reviewer Results + +| Reviewer | Result | Blocking findings | Notes | +|---|---:|---|---| +| Senior engineering | PASS | None | External repair passes at final reviewed head. | +| QA/test | PASS AFTER FIXES | None | Fail-closed regression and 172-test matrix pass. | +| Security/auth | PASS AFTER FIXES | None | No auth behavior changed; ambiguity fails closed. | +| Product/ops | PASS | None | No Workstream product lifecycle or human-role behavior changes. | +| Architecture | PASS | None | External repair remains separate from CI ratchet and coverage raising. | +| CI integrity | PASS | None | No workflow, threshold, exclusion, or dependency is weakened. | +| Docs | PASS AFTER FIXES | None | Implementation, repair, and evidence SHAs are distinct. | +| Reuse/dedup | PASS | None | The two-line repair reuses the existing deletion parser. | +| Test delta | PASS | None | Additive regression has no skip, xfail, or assertion weakening. | + +Circuit breaker passed at 584/620 raw policy-and-test lines. No valid internal +finding or reviewer session remains for the external repair. + +## Scope Integrity + +Implementation changes are limited to the coverage policy and its contract +tests. Remaining branch changes are the durable WS-QUAL/global memory and +replacement-contract history required by the repository loop. No application, +AUTH, dependency, configuration, workflow, exclusion, B2, or coverage-raising +change is included. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-pr-trust-bundle.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-pr-trust-bundle.md new file mode 100644 index 00000000..bcd2b964 --- /dev/null +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-01B1B-R10-pr-trust-bundle.md @@ -0,0 +1,52 @@ +# WS-QUAL-001-01B1B-R10 PR Trust Bundle + +## Intent + +Finish the semantic test-integrity gate without embedding a Python execution +model. The final policy is conservative syntax ownership: known pytest/unittest +weakening constructs are rejected and assertion deletion is protected. + +## Design + +- Exact absolute framework imports and finite simple aliases establish monotone + lexical ownership. +- Runtime reachability, values, containers, consumers, and provenance do not. +- Stdlib symtable supplies lexical scopes across Python 3.11-3.13. +- Capability-based child selection supports Python 3.11 comprehension tables, + Python 3.12+ inlining, and Python 3.13 public TypeVar child ordering. + +## Scope + +- `backend/scripts/coverage_policy.py` +- `backend/tests/test_coverage_contract.py` +- WS-QUAL/global loop memory, contracts, decisions, and review evidence + +The 23-file external-repair branch diff contains 1,967 additions and 39 deletions. +Only 584 raw lines belong to the policy/test implementation; the remainder is +the retained R1-R10 planning, stop, and review history. No CI workflow is enabled +here; B2 owns mandatory GitHub enforcement after this PR merges. + +## Verification + +The identical 172-test matrix passes on Python 3.11, 3.12, and 3.13. +Ruff, dependency integrity, self-validation, exact scope, ancestry, wording, +memory, links, and diff hygiene pass. The original implementation passed all +nine reviewer tracks and the circuit breaker at `15d0b80`. The external-repair +code is `4bcf910`; all nine required reviewer tracks and the circuit breaker +pass at final reviewed head `7e192b8`. + +## Risk And Human Focus + +- The policy is intentionally conservative once syntax is framework-owned. +- Deleted test lines fail closed when base-source retrieval fails. +- Local parameters/declarations, relative imports, unknown framework members, + unrelated attributes, and non-TestCase `self.skipTest` remain clean. +- Human review should confirm the syntax-only boundary, cross-version symtable + selection, readable behavior matrix, and absence of CI/config/application + drift. + +## Follow-Up Gate + +Stop after human review and merge. B2 remains inactive until this PR and its +post-merge memory are merged and the user explicitly starts B2. Coverage stays +at the measured 79.25 percent until B2 and coverage-raising chunks 02-06 run. diff --git a/backend/scripts/coverage_policy.py b/backend/scripts/coverage_policy.py index 7e4723e8..9aac60dc 100644 --- a/backend/scripts/coverage_policy.py +++ b/backend/scripts/coverage_policy.py @@ -4,11 +4,14 @@ from __future__ import annotations import argparse +import ast from decimal import Decimal, InvalidOperation from io import StringIO import json +import os from pathlib import Path import re +import symtable import sys import tokenize import tomllib @@ -17,6 +20,9 @@ from run_isolated_tests import NAME_RE BACKEND = Path(__file__).resolve().parents[1] +REPO = BACKEND.parent +sys.path.insert(0, str(REPO / "scripts")) +from workstream_agent_gate import changed_files, diff_text, maybe_run, numstat # noqa: E402 SHA_RE = re.compile(r"[a-f0-9]{40}") DECIMAL_RE = re.compile(r"\d+\.\d{6}") @@ -27,6 +33,9 @@ "measured_percent configured_floor minimum_milestone python_version coverage_version " "pytest_cov_version database_name alembic_head".split() ) +GLOBAL_MEMORY = {".agent-loop/LOOP_STATE.md", ".agent-loop/REVIEW_LOG.md", ".agent-loop/WORK_QUEUE.md"} +QUAL_MEMORY = ".agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/" +BLOCKED = {"skip", "skipif", "skipIf", "skipUnless", "xfail", "expectedFailure", "importorskip", "SkipTest"} class PolicyError(RuntimeError): @@ -145,6 +154,394 @@ def runner_metadata(path: Path, expected_tree: str, expected_head: str) -> dict: return data +class SyntaxPolicy(ast.NodeVisitor): + """Conservatively identify framework-owned test-integrity syntax.""" + + def __init__(self, source: str, tree: ast.Module) -> None: + self.tables = [symtable.symtable(source, "", "exec")] + self.owners: list[dict[str, str]] = [{}] + self.scope_types = ["module"] + self.shadows = [set()] + self.cases = [False] + self.child_counts: dict[tuple[int, str, str, int], int] = {} + self.future_annotations = any( + isinstance(node, ast.ImportFrom) + and node.module == "__future__" + and any(alias.name == "annotations" for alias in node.names) + for node in tree.body + ) + self.weak = False + self.assertion_ranges: set[tuple[int, int]] = set() + self.seed(tree.body) + + def child(self, node: ast.AST, table_type: str | set[str], name: str, *, required: bool = True) -> symtable.SymbolTable | None: + table_types = {table_type} if isinstance(table_type, str) else table_type + children = [child for child in self.tables[-1].get_children() if child.get_type() in table_types and (child.get_name(), child.get_lineno()) == (name, node.lineno)] + key = (id(self.tables[-1]), "|".join(sorted(table_types)), name, node.lineno) + index = self.child_counts.get(key, 0) + if index < len(children): + self.child_counts[key] = index + 1 + return children[index] + if required: + raise SyntaxError("symbol table mismatch") + return None + + def scope_nodes(self, items: list[ast.stmt]): + def walk(node: ast.AST): + yield node + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda, ast.comprehension)): + for child in ast.iter_child_nodes(node): + yield from walk(child) + for item in items: + yield from walk(item) + + def import_kind(self, root: str, name: str) -> str: + if root == "pytest": + return {"mark": "mark", "raises": "raises"}.get(name, "blocked" if name in BLOCKED else "local") + if root == "unittest": + return "testcase" if name == "TestCase" else "blocked" if name in BLOCKED else "unittest_case" if name == "case" else "local" + return "local" + + def own(self, name: str, kind: str) -> bool: + previous = self.owners[-1].get(name) + merged = "|".join(sorted((set((previous or "").split("|")) | set(kind.split("|"))) - {""})) + self.owners[-1][name] = merged + return merged != previous + + def seed(self, body: list[ast.stmt]) -> None: + nodes = list(self.scope_nodes(body)) + for node in nodes: + if isinstance(node, ast.Import): + for alias in node.names: + root = alias.name.split(".", 1)[0] + if root in {"pytest", "unittest"}: + name = alias.asname or root + kind = "unittest_case" if alias.asname and alias.name == "unittest.case" else root if not alias.asname or alias.name == root else "local" + if kind != "local": + self.own(name, kind) + elif isinstance(node, ast.ImportFrom) and node.level == 0: + root = (node.module or "").split(".", 1)[0] + if root in {"pytest", "unittest"}: + for alias in node.names: + if alias.name == "*": + self.weak = True + else: + self.own(alias.asname or alias.name, self.import_kind(root, alias.name)) + aliases = [ + (node.targets[0].id, node.value) + for node in nodes + if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and isinstance(node.value, (ast.Name, ast.Attribute)) + ] + changed = True + while changed: + changed = False + for name, value in aliases: + kind = self.kind(value) + if kind != "local": + changed |= self.own(name, kind) + + def get(self, name: str) -> str: + if name in self.owners[-1]: + return self.owners[-1][name] + if name in self.shadows[-1]: + return "local" + try: + symbol = None if self.scope_types[-1] == "comprehension" else self.tables[-1].lookup(name) + except KeyError: + symbol = None + if symbol and symbol.is_global(): + return self.owners[0].get(name, "local") + if symbol and symbol.is_nonlocal(): + for index in range(len(self.owners) - 2, 0, -1): + if self.scope_types[index] == "class": + continue + if name in self.owners[index]: + return self.owners[index][name] + if name in self.shadows[index]: + return "local" + try: + outer_symbol = self.tables[index].lookup(name) + except KeyError: + outer_symbol = None + if outer_symbol and (outer_symbol.is_local() or outer_symbol.is_parameter() or outer_symbol.is_imported()): + return "local" + return "local" + if symbol and (symbol.is_local() or symbol.is_parameter() or symbol.is_imported()): + return "local" + for index in range(len(self.owners) - 2, -1, -1): + if self.scope_types[index] == "class": + continue + if name in self.owners[index]: + return self.owners[index][name] + if name in self.shadows[index]: + return "local" + if self.scope_types[index] == "comprehension" and self.tables[index] is self.tables[index - 1]: + continue + try: + outer_symbol = self.tables[index].lookup(name) + except KeyError: + outer_symbol = None + if outer_symbol and (outer_symbol.is_local() or outer_symbol.is_parameter() or outer_symbol.is_imported()): + return "local" + return "local" + + def kind(self, node: ast.AST) -> str: + if isinstance(node, ast.Name): + return self.get(node.id) + if isinstance(node, ast.Attribute): + result: set[str] = set() + for owner in self.kind(node.value).split("|"): + if owner == "pytest" and node.attr == "mark": + result.add("mark") + elif owner == "pytest" and node.attr == "raises": + result.add("raises") + elif owner == "unittest" and node.attr == "case": + result.add("unittest_case") + elif owner in {"unittest", "unittest_case"} and node.attr == "TestCase": + result.add("testcase") + elif owner in {"pytest", "unittest", "unittest_case", "mark"} and node.attr in BLOCKED: + result.add("blocked") + elif owner == "testcase" and node.attr == "skipTest": + result.add("blocked") + if result: + return "|".join(sorted(result)) + return "local" + + def enter(self, node: ast.AST, table_type: str, name: str, body: list[ast.stmt]) -> None: + self.tables.append(self.child(node, table_type, name)) + self.owners.append({}) + self.scope_types.append(table_type) + self.shadows.append(set()) + self.cases.append(False) + self.seed(body) + + def leave(self) -> None: + self.tables.pop() + self.owners.pop() + self.scope_types.pop() + self.shadows.pop() + self.cases.pop() + + def enter_type_params(self, node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias) -> bool: + parameters = getattr(node, "type_params", []) + if not parameters: + return False + name = node.name.id if isinstance(node, ast.TypeAlias) else node.name + self.tables.append(self.child(node, {"type parameter", "type parameters"}, name)) + self.owners.append({}) + self.scope_types.append("function") + self.shadows.append({parameter.name for parameter in parameters}) + self.cases.append(self.cases[-1]) + for parameter in parameters: + public_children = any(child.get_type() == "type variable" and (child.get_name(), child.get_lineno()) == (parameter.name, parameter.lineno) for child in self.tables[-1].get_children()) + for field in ("bound", "default_value"): + value = getattr(parameter, field, None) + if value: + table_types = {"type variable"} if public_children else {"TypeVar bound" if field == "bound" else "TypeVar default"} + self.tables.append(self.child(parameter, table_types, parameter.name)) + self.owners.append({}) + self.scope_types.append("function") + self.shadows.append(set()) + self.cases.append(self.cases[-1]) + self.visit(value) + self.leave() + return True + + def visit_Name(self, node: ast.Name) -> None: + if isinstance(node.ctx, ast.Load) and "blocked" in self.get(node.id).split("|"): + self.weak = True + + def visit_Attribute(self, node: ast.Attribute) -> None: + if "blocked" in self.kind(node).split("|"): + self.weak = True + if node.attr == "skipTest" and self.cases[-1]: + self.weak |= isinstance(node.value, ast.Name) and node.value.id == "self" or isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name) and node.value.func.id == "super" + self.generic_visit(node) + + def visit_Call(self, node: ast.Call) -> None: + if "raises" in self.kind(node.func).split("|"): + self.assertion_ranges.add((node.lineno, node.end_lineno)) + if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == "self" and node.func.attr.startswith("assert"): + self.assertion_ranges.add((node.lineno, node.end_lineno)) + self.generic_visit(node) + + def visit_Assert(self, node: ast.Assert) -> None: + self.assertion_ranges.add((node.lineno, node.end_lineno)) + self.generic_visit(node) + + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: + parent_case, direct_method = self.cases[-1], self.scope_types[-1] == "class" and self.cases[-1] + for value in (*node.decorator_list, *node.args.defaults, *node.args.kw_defaults): + if value: + self.visit(value) + type_scope = self.enter_type_params(node) + if not self.future_annotations: + for argument in (*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs, node.args.vararg, node.args.kwarg): + if argument and argument.annotation: + self.visit(argument.annotation) + if node.returns: + self.visit(node.returns) + self.enter(node, "function", node.name, node.body) + try: + self_symbol = self.tables[-1].lookup("self") + except KeyError: + self_symbol = None + self.cases[-1] = direct_method or parent_case and not (self_symbol and (self_symbol.is_local() or self_symbol.is_parameter())) + for item in node.body: + self.visit(item) + self.leave() + if type_scope: + self.leave() + + visit_AsyncFunctionDef = visit_FunctionDef + + def visit_Lambda(self, node: ast.Lambda) -> None: + for value in (*node.args.defaults, *node.args.kw_defaults): + if value: + self.visit(value) + parent_case = self.cases[-1] + self.enter(node, "function", "lambda", []) + try: + self_symbol = self.tables[-1].lookup("self") + except KeyError: + self_symbol = None + self.cases[-1] = parent_case and not (self_symbol and (self_symbol.is_local() or self_symbol.is_parameter())) + self.visit(node.body) + self.leave() + + def visit_ClassDef(self, node: ast.ClassDef) -> None: + for value in node.decorator_list: + self.visit(value) + type_scope = self.enter_type_params(node) + for value in (*node.bases, *(keyword.value for keyword in node.keywords)): + self.visit(value) + is_case = any("testcase" in self.kind(base).split("|") for base in node.bases) + self.enter(node, "class", node.name, node.body) + self.cases[-1] = is_case + for item in node.body: + self.visit(item) + self.leave() + if type_scope: + self.leave() + + def visit_TypeAlias(self, node: ast.TypeAlias) -> None: + type_scope = self.enter_type_params(node) + name = node.name.id + self.tables.append(self.child(node, "type alias", name)) + self.owners.append({}) + self.scope_types.append("function") + self.shadows.append(set()) + self.cases.append(False) + self.visit(node.value) + self.leave() + if type_scope: + self.leave() + + def visit_AnnAssign(self, node: ast.AnnAssign) -> None: + if node.value: + self.visit(node.value) + self.visit(node.target) + if not self.future_annotations: + self.visit(node.annotation) + + def visit_comprehension_scope(self, node: ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp) -> None: + self.visit(node.generators[0].iter) + names = set().union(*(target_names(generator.target) for generator in node.generators)) + expected = {ast.ListComp: "listcomp", ast.SetComp: "setcomp", ast.DictComp: "dictcomp", ast.GeneratorExp: "genexpr"}[type(node)] + child = self.child(node, "function", expected, required=isinstance(node, ast.GeneratorExp)) + table = child or self.tables[-1] + self.tables.append(table) + self.owners.append({}) + self.scope_types.append("comprehension") + self.shadows.append(names) + self.cases.append(self.cases[-1] and "self" not in names) + for index, generator in enumerate(node.generators): + if index: + self.visit(generator.iter) + self.visit(generator.target) + for condition in generator.ifs: + self.visit(condition) + for value in ((node.key, node.value) if isinstance(node, ast.DictComp) else (node.elt,)): + self.visit(value) + self.leave() + + visit_ListComp = visit_comprehension_scope + visit_SetComp = visit_comprehension_scope + visit_DictComp = visit_comprehension_scope + visit_GeneratorExp = visit_comprehension_scope + + +def target_names(node: ast.AST) -> set[str]: + if isinstance(node, ast.Name): + return {node.id} + if isinstance(node, (ast.Tuple, ast.List)): + return set().union(*(target_names(item) for item in node.elts)) + if isinstance(node, ast.Starred): + return target_names(node.value) + return set() + + +def analyze_python(source: str) -> tuple[ast.Module, SyntaxPolicy]: + tree = ast.parse(source) + policy = SyntaxPolicy(source, tree) + policy.visit(tree) + return tree, policy + + +def weak_python(path: Path) -> bool: + try: + return analyze_python(path.read_text(encoding="utf-8"))[1].weak + except (OSError, SyntaxError): + return True + + +def has_deleted_assertion(diff: str, base_source: str) -> bool: + deleted, old_line = set(), 0 + for line in diff.splitlines(): + match = re.match(r"^@@ -(\d+)(?:,\d+)? \+", line) + if match: + old_line = int(match.group(1)) + elif line.startswith("-") and not line.startswith("---"): + deleted.add(old_line) + old_line += 1 + elif not line.startswith("+"): + old_line += 1 + if deleted and not base_source: + return True + try: + analysis = analyze_python(base_source)[1] + except SyntaxError: + return bool(deleted) + return any(deleted.intersection(range(start, end + 1)) for start, end in analysis.assertion_ranges) + + +def approved_memory(path: str) -> bool: + return path in GLOBAL_MEMORY or path.startswith(QUAL_MEMORY) + + +def has_test_rename(statuses: str) -> bool: + return any(parts[0].startswith("R") and any(path.startswith("backend/tests/") and path.endswith(".py") for path in parts[1:]) for parts in (line.split("\t") for line in statuses.splitlines()) if parts) + + +def validate_delta(base: str, max_lines: int, allowed: set[str]) -> None: + previous = Path.cwd() + try: + os.chdir(REPO) + files = changed_files(base, "HEAD") + _, _, rows = numstat(base, "HEAD") + tests = [path for path in files if path.startswith("backend/tests/") and path.endswith(".py")] + statuses = "\n".join((maybe_run(["git", "diff", "--name-status", "--find-renames", f"{base}...HEAD"]), maybe_run(["git", "diff", "--name-status", "--find-renames", "--cached"]), maybe_run(["git", "diff", "--name-status", "--find-renames"]))) + diffs = {path: diff_text(base, "HEAD", [path]) for path in tests} + sources = {path: maybe_run(["git", "show", f"{base}:{path}"]) for path in tests} + finally: + os.chdir(previous) + require(all(path in allowed or approved_memory(path) for path in files), "scope_violation") + require(not has_test_rename(statuses), "test_rename") + require(sum(add + delete for path, add, delete in rows if not approved_memory(path)) <= max_lines, "implementation_size_exceeded") + require(not any(weak_python(REPO / path) for path in tests), "test_skip_or_xfail") + require(not any(has_deleted_assertion(diffs[path], sources[path]) for path in tests), "deleted_assertion") + + def main() -> int: """Compute a candidate floor without configuring policy or writing evidence.""" parser = argparse.ArgumentParser() diff --git a/backend/tests/test_coverage_contract.py b/backend/tests/test_coverage_contract.py index 128f7b46..6dbbefc3 100644 --- a/backend/tests/test_coverage_contract.py +++ b/backend/tests/test_coverage_contract.py @@ -14,6 +14,7 @@ HEAD = "0016_artifact_domain" SHA = "a" * 40 +PEP695_INVALID = sys.version_info < (3, 12) def write_json(path: Path, value: dict, *, canonical: bool = False) -> Path: @@ -207,6 +208,192 @@ def test_runner_metadata_fails_closed(tmp_path: Path, updates: dict, code: str) policy.runner_metadata(path, SHA, HEAD) +@pytest.mark.parametrize(("source", "weak"), [ + ("import pytest\npytest.skip('disabled')", True), + ("import pytest as pt\npt.xfail('disabled')", True), + ("from pytest import skip as stop\nstop('disabled')", True), + ("import pytest\npytest.importorskip('optional')", True), + ("import pytest\n@pytest.mark.skip\ndef test_x(): pass", True), + ("from pytest import mark\npytestmark = mark.skipif", True), + ("import unittest\n@unittest.skipUnless(True, 'reason')\ndef test_x(): pass", True), + ("from unittest import expectedFailure\n@expectedFailure\ndef test_x(): pass", True), + ("from unittest.case import SkipTest\nraise SkipTest('disabled')", True), + ("import unittest.case as uc\nraise uc.SkipTest('disabled')", True), + ("import unittest\nunittest.TestCase.skipTest", True), + ("import unittest\nclass T(unittest.TestCase):\n def test_x(self): self.skipTest('disabled')", True), + ("from unittest import TestCase as Case\nclass T(Case):\n def test_x(self): super().skipTest('disabled')", True), + ("from pytest import *", True), + ("import pytest\nif False: pytest.skip('dead')", True), + ("import pytest\n[pytest.skip() for _ in ()]", True), + ("import pytest\nvalue = (pytest.skip() for _ in ())", True), + ("import pytest\nasync def f(xs): return [pytest.skip() async for _ in xs]", True), + ("import pytest\nconsume(value=(pytest.skip() for _ in ()))", True), + ("import pytest\npytest = Local()\npytest.skip()", True), + ("import pytest\np = pytest\nq = p\nq.skip()", True), + ("import pytest\na = b\nb = a\nb = pytest\na.skip()", True), + ("import pytest, unittest\ncontrol = pytest.raises\ncontrol = unittest.skip\ncontrol()", True), + ("import pytest\ndef f():\n global pytest\n pytest.skip()", True), + ("def outer():\n import pytest\n def inner(): pytest.skip()", True), + ("def outer():\n import pytest\n def inner():\n nonlocal pytest\n pytest.skip()", True), + ("value = 'pytest.skip and xfail are inert'\n# pytest.skip()", False), + ("from .pytest import skip\nskip()", False), + ("from local import pytest\npytest.skip()", False), + ("import pytest\ndef f(pytest): pytest.skip()", False), + ("import pytest\na = lambda pytest: pytest.skip(); b = lambda: pytest.skip()", True), + ("import pytest\nvalues = (lambda: pytest.skip() for _ in ())", True), + ("import pytest\ndef f():\n pytest.skip()\n pytest = Local()", False), + ("import pytest\n[pytest.skip() for pytest in ()]", False), + ("a = b\nb = a\na.skip()", False), + ("class Local:\n def test_x(self): self.skipTest()", False), + ("from unittest import TestCase\ncase = TestCase()\ncase.skipTest()", False), + ("class Local:\n def skip(self): pass\nlocal = Local()\nlocal.skip()", False), + ("from __future__ import annotations\nimport pytest\nvalue: pytest.mark.skip", False), + ("import pytest\np = (pytest,)\np.skip()", False), + ("import pytest\np = build(pytest)\np.skip()", False), + ("from pytest import fixture\nfixture.skip()", False), + ("from unittest import mock\nmock.skip()", False), + ("import unittest.mock as mock\nmock.skip()", False), + ("import pytest.fixture as fixture\nfixture.skip()", False), + ("import unittest.mock\nunittest.skip()", True), + ("import pytest\ndef outer():\n pytest = Local()\n def inner(): pytest.skip()", False), + ("import pytest\ndef outer(pytest):\n def inner(): pytest.skip()", False), + ("import pytest\ndef outer():\n import local as pytest\n def inner(): pytest.skip()", False), + ("import pytest\nouter = lambda pytest: lambda: pytest.skip()", False), + ("import pytest\n[(lambda: pytest.skip())() for pytest in ()]", False), + ("import pytest\n[[pytest.skip() for _ in ()] for pytest in ()]", False), + ("import unittest\nraise unittest.case.SkipTest('disabled')", True), + ("import unittest\nunittest.case.skip('disabled')", True), + ("import unittest.case\nunittest.case.expectedFailure", True), + ("import unittest\nunittest.case.TestCase.skipTest", True), + ("import pytest as p\nimport unittest as p\nprint(p)\nprint(p.foo)", False), + ("from pytest import raises as f\nfrom unittest import TestCase as f\nprint(f)", False), + ("import pytest as p\nimport unittest as p\np.skip()", True), + ("def f[T](): pass\nclass C[T]: pass\ntype A[T] = list[T]", PEP695_INVALID), + ("import pytest\ndef f[T: pytest.skip()](): pass", True), + ("import pytest\ndef f[pytest](): pytest.skip()", PEP695_INVALID), + ("import pytest\ntype Alias[pytest] = pytest.skip", PEP695_INVALID), + ("def f[T: (lambda: int)](): pass\nclass C[T: (x for x in ())]: pass\ntype A[T: (lambda: int)] = T", PEP695_INVALID), + ("import pytest\ndef f[T: (lambda: pytest.skip())](): pass", True), + ("import unittest\nclass T(unittest.TestCase):\n def outer(self):\n def inner(self): self.skipTest()", False), + ("import unittest\nclass T(unittest.TestCase):\n def outer(self):\n def inner(): self.skipTest()", True), + ("import pytest\ndef a():\n p = pytest\n def b():\n p = Local()\n def c():\n nonlocal p\n p.skip()", False), + ("import pytest\n[x for pytest.skip().field in xs]", True), + ("import pytest\n(x for target[pytest.skip()] in xs)", True), + ("import pytest\ntarget[pytest.skip()]: int = 1", True), + ("import pytest\npytest.skip().field: int = 1", True), + ("import pytest\ndef f(*args: pytest.skip()): pass", True), + ("import pytest\ndef f(**kwargs: pytest.mark.xfail): pass", True), + ("from __future__ import annotations\nimport pytest\ndef f(*args: pytest.skip()): pass", False), + ("import pytest\n{(lambda: pytest.skip())() for pytest in ()}", False), + ("import pytest\n{pytest: (lambda: pytest.skip())() for pytest in ()}", False), + ("[(lambda value: value)(1) for _ in ()]", False), + ("import pytest\n[(lambda: pytest.skip())() for _ in ()]", True), + ("import pytest\n[[pytest.skip() for _ in ()] for _ in ()]", True), + ("import pytest\n{{pytest.skip() for _ in ()} for _ in ()}", True), + ("import pytest\n{key: {pytest.skip(): value for value in ()} for key in ()}", True), +]) +def test_python_weakening_uses_lexical_syntax(tmp_path: Path, source: str, weak: bool) -> None: + path = tmp_path / "test_policy.py" + path.write_text(source, encoding="utf-8") + assert policy.weak_python(path) is weak + + +@pytest.mark.parametrize(("source", "line", "blocked"), [ + ("assert result\n", 1, True), + ("import pytest\npytest.raises(ValueError)\n", 2, True), + ("from pytest import raises as expect\nexpect(ValueError)\n", 2, True), + ("import pytest\nif False: pytest.raises(ValueError)\n", 2, True), + ("import pytest\nvalue = (pytest.raises(ValueError) for _ in ())\n", 2, True), + ("class T:\n def test_x(self): self.assertEqual(1, 1)\n", 2, True), + ("class T:\n def test_x(other): other.assertEqual(1, 1)\n", 2, False), + ("from .pytest import raises\nraises(ValueError)\n", 2, False), + ("from pytest import fixture\nfixture.raises(ValueError)\n", 2, False), + ("import pytest\n[(lambda: pytest.raises(ValueError))() for pytest in ()]\n", 2, False), + ("import pytest\ndef a():\n r = pytest.raises\n def b():\n r = print\n def c():\n nonlocal r\n r(ValueError)\n", 8, False), + ("import pytest\n[x for target[pytest.raises(ValueError)] in xs]\n", 2, True), + ("import pytest\ntarget[pytest.raises(ValueError)]: int = 1\n", 2, True), + ("import pytest\ndef f(*args: pytest.raises(ValueError)): pass\n", 2, True), + ("import pytest\n[[pytest.raises(ValueError) for _ in ()] for _ in ()]\n", 2, True), + ("MESSAGE = 'pytest.raises(ValueError)'\n", 1, False), + ("def invalid(\n", 1, True), +]) +def test_deleted_assertions_use_syntax(source: str, line: int, blocked: bool) -> None: + deleted = source.splitlines()[line - 1] + diff = f"@@ -{line},1 +{line},0 @@\n-{deleted}" + assert policy.has_deleted_assertion(diff, source) is blocked + + +def test_deleted_assertions_fail_closed_without_base_source() -> None: + assert policy.has_deleted_assertion("@@ -1,1 +1,0 @@\n-assert result", "") is True + assert policy.has_deleted_assertion("", "") is False + + +@pytest.mark.parametrize(("clean", "weakening", "assertion"), [ + ("def f[T: (lambda: int) = (x for x in ())](): pass", "import pytest\ndef f[T: (lambda: int) = (pytest.skip() for _ in ())](): pass", "import pytest\ndef f[T: (lambda: int) = (pytest.raises(ValueError) for _ in ())](): pass"), + ("def f[T: (x for x in ()) = (lambda: int)](): pass", "import pytest\ndef f[T: (pytest.skip() for _ in ()) = (lambda: int)](): pass", "import pytest\ndef f[T: (pytest.raises(ValueError) for _ in ()) = (lambda: int)](): pass"), + ("class C[T: (lambda: int) = (x for x in ())]: pass", "import pytest\nclass C[T: (lambda: int) = (pytest.skip() for _ in ())]: pass", "import pytest\nclass C[T: (lambda: int) = (pytest.raises(ValueError) for _ in ())]: pass"), + ("class C[T: (x for x in ()) = (lambda: int)]: pass", "import pytest\nclass C[T: (pytest.skip() for _ in ()) = (lambda: int)]: pass", "import pytest\nclass C[T: (pytest.raises(ValueError) for _ in ()) = (lambda: int)]: pass"), + ("type A[T: (lambda: int) = (x for x in ())] = T", "import pytest\ntype A[T: (lambda: int) = (pytest.skip() for _ in ())] = T", "import pytest\ntype A[T: (lambda: int) = (pytest.raises(ValueError) for _ in ())] = T"), + ("type A[T: (x for x in ()) = (lambda: int)] = T", "import pytest\ntype A[T: (pytest.skip() for _ in ()) = (lambda: int)] = T", "import pytest\ntype A[T: (pytest.raises(ValueError) for _ in ()) = (lambda: int)] = T"), +]) +def test_typevar_bound_default_child_order(clean: str, weakening: str, assertion: str) -> None: + if sys.version_info < (3, 13): + for source in (clean, weakening, assertion): + with pytest.raises(SyntaxError): + policy.analyze_python(source) + return + assert policy.analyze_python(clean)[1].weak is False + assert policy.analyze_python(weakening)[1].weak is True + analysis = policy.analyze_python(assertion)[1] + assert analysis.assertion_ranges + line = next(iter(analysis.assertion_ranges))[0] + deleted = assertion.splitlines()[line - 1] + assert policy.has_deleted_assertion(f"@@ -{line},1 +{line},0 @@\n-{deleted}", assertion) + + +@pytest.mark.parametrize(("files", "rows", "source", "diff", "code"), [ + (["backend/app/a.py"], [], "", "", "scope_violation"), + (["backend/tests/test_ok.py"], [("backend/tests/test_ok.py", 701, 0)], "assert True", "", "implementation_size_exceeded"), + (["backend/tests/test_ok.py"], [("backend/tests/test_ok.py", 1, 0)], "import pytest\npytest.skip()", "", "test_skip_or_xfail"), + (["backend/tests/test_ok.py"], [("backend/tests/test_ok.py", 1, 0)], "assert value", "@@ -1,1 +1,0 @@\n-assert value", "deleted_assertion"), +]) +def test_delta_fails_closed(monkeypatch, tmp_path: Path, files, rows, source, diff, code) -> None: + test = tmp_path / "backend/tests/test_ok.py" + test.parent.mkdir(parents=True) + test.write_text(source, encoding="utf-8") + monkeypatch.setattr(policy, "REPO", tmp_path) + monkeypatch.setattr(policy, "changed_files", lambda *_: files) + monkeypatch.setattr(policy, "numstat", lambda *_: (0, 0, rows)) + monkeypatch.setattr(policy, "diff_text", lambda *_: diff) + monkeypatch.setattr(policy, "maybe_run", lambda command: source if command[:2] == ["git", "show"] else "") + with pytest.raises(policy.PolicyError, match=code): + policy.validate_delta("base", 700, {"backend/tests/test_ok.py"}) + + +def test_delta_accepts_memory_and_restores_cwd(monkeypatch, tmp_path: Path) -> None: + test = tmp_path / "backend/tests/test_ok.py" + test.parent.mkdir(parents=True) + test.write_text("assert value", encoding="utf-8") + files = ["backend/tests/test_ok.py", ".agent-loop/LOOP_STATE.md", f"{policy.QUAL_MEMORY}STATUS.md"] + monkeypatch.setattr(policy, "REPO", tmp_path) + monkeypatch.setattr(policy, "changed_files", lambda *_: files) + monkeypatch.setattr(policy, "numstat", lambda *_: (0, 0, [(files[0], 1, 0), (files[1], 100, 0), (files[2], 100, 0)])) + monkeypatch.setattr(policy, "diff_text", lambda *_: "+assert value") + monkeypatch.setattr(policy, "maybe_run", lambda command: "assert value" if command[:2] == ["git", "show"] else "") + previous = Path.cwd() + policy.validate_delta("base", 1, {files[0]}) + assert Path.cwd() == previous + + +@pytest.mark.parametrize(("statuses", "renamed"), [ + ("R100\tbackend/tests/test_old.py\tbackend/tests/test_new.py", True), + ("R100\tdocs/old.md\tdocs/new.md", False), + ("M\tbackend/tests/test_ok.py", False), +]) +def test_test_rename_detection(statuses: str, renamed: bool) -> None: + assert policy.has_test_rename(statuses) is renamed + + def test_compute_cli_is_read_only_and_stable(tmp_path: Path) -> None: files = { item.relative_to(policy.BACKEND).as_posix():