Skip to content

Commit 92d1741

Browse files
committed
docs(sql-orm-many-to-many): slice 2 dispatch briefs, trace, learnings (TML-2786)
Filter slice orchestrator artifacts (filter-code dispatch took 2 rounds incl. a truncation recovery; integration-tests dispatch one round). Review log gitignored. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
1 parent e65a9db commit 92d1741

5 files changed

Lines changed: 141 additions & 0 deletions

File tree

projects/sql-orm-many-to-many/learnings.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ Running the whole sql-orm-client integration suite at once (`cd test/integration
1717
## Dispatch truncation recovery (no subagent resume)
1818

1919
A substantial dispatch can exhaust the implementer's budget mid-work and return a truncated report with **uncommitted WIP** (happened on the slice-1 read path). Recovery: inspect `git status`/`git diff`, then dispatch a fresh continuation implementer pointed at the WIP with a focused completion brief (it commits the WIP + completion as one commit). Keep dispatches tight and tell implementers to implement-then-test-then-gate rather than over-explore (over-exploration is what burned the budget).
20+
21+
**Recurrence (2×):** both the slice-1 read-path dispatch and the slice-2 filter dispatch (the "junction-correlation code + unit tests" judgment dispatches) truncated around 70–135k implementer tokens. The combination of (read corpus) + (design the SQL shape) + (write + iterate tests) reliably exceeds one sonnet budget here. The continue-from-WIP recovery works every time, but for future projects of this shape, consider splitting "implement the builder/accessor branch" and "write its unit tests" into two dispatches, or routing these to a higher-budget tier.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Brief: S2-D1 — filter EXISTS walks the junction
2+
3+
## Task
4+
5+
Teach the relation-filter accessor to walk the junction for M:N relations. `db.orm.User.filter((u) => u.tags.some/every/none((t) => …))` must emit an EXISTS / NOT EXISTS subquery that goes through the `UserTag` junction. Today `buildJoinWhere` (`packages/3-extensions/sql-orm-client/src/model-accessor.ts`) reads only `relation.on.localFields/targetFields` — for an M:N relation that emits a wrong-shape EXISTS that skips the junction.
6+
7+
When the resolved relation carries `through` (slice 0 added it to `resolveModelRelations`'s output), build the M:N shape in `buildExistsExpr`/`buildJoinWhere`:
8+
- **`some(pred)`**`EXISTS (SELECT 1 FROM target JOIN junction ON junction.childColumns = target.targetColumns WHERE junction.parentColumns = parent.anchor AND <pred>)`.
9+
- **`none(pred)`**`NOT EXISTS (… AND <pred>)`.
10+
- **`every(pred)`**`NOT EXISTS (… AND NOT(<pred>))` — mirror the existing FK `every` shape, just through the junction.
11+
12+
The parent correlation is on the **junction** side; the target is reached via the junction join; composite keys AND-ed across all pairs. The child predicate is unchanged.
13+
14+
**First confirm** the relation reaching `buildJoinWhere` carries `through` — it should, via `resolveModelRelations` (slice 0). If the filter path uses a relation shape that drops `through`, surface it onto that path (one field; mirror how slice 1 surfaced `through` onto `IncludeExpr`).
15+
16+
**Write unit tests first** asserting the compiled EXISTS AST for `some`/`every`/`none` on an M:N relation joins through the junction (composite-key AND-ed), and that FK relation filters are unchanged.
17+
18+
## Scope
19+
20+
**In:** the M:N branch in `buildExistsExpr`/`buildJoinWhere` (`model-accessor.ts`) for some/every/none; surfacing `through` onto the filter relation if dropped; unit tests for the EXISTS AST.
21+
22+
**Out:** integration tests (S2-D2); include reads (slice 1); nested writes (slice 3); the `through` shape (slice 0). Don't regress FK filters.
23+
24+
## Completed when
25+
26+
- [ ] `some`/`every`/`none` on an M:N relation compile to a correctly-shaped EXISTS/NOT EXISTS through the junction (composite-key AND-ed); unit test asserts the AST.
27+
- [ ] FK relation filters unchanged (existing model-accessor unit tests pass).
28+
- [ ] Gate: `pnpm --filter @prisma-next/sql-orm-client typecheck` + `test` green.
29+
30+
## Standing instruction
31+
32+
Stay focused on the junction EXISTS. The judgment site is the junction hop in `buildJoinWhere` and the `every` = `NOT EXISTS(… NOT(pred))` shape; mirror the FK path. No bare `as` casts (use `castAs`/`blindCast` if unavoidable — a sibling slice was bounced for bare casts twice; don't add new ones).
33+
34+
## References
35+
36+
- Slice spec: `projects/sql-orm-many-to-many/slices/02-filter-exists-through-junction/spec.md`.
37+
- `model-accessor.ts`: `createRelationFilterAccessor` (~190), `buildExistsExpr` (~222), `buildJoinWhere` (~331) — the FK path to extend.
38+
- Slice 0 `ResolvedRelation.through` in `collection-contract.ts`.
39+
40+
## Operational metadata
41+
42+
- **Model tier:** sonnet — bounded judgment (the junction EXISTS + every/none shapes).
43+
- **Branch:** `tml-2786-slice-2-filter`. Explicit staging + `-s` sign-off. **Do not push.**
44+
- **Time-box:** ~75 min — implement `some` first + its test, then `none`/`every`, then gate; don't over-explore.
45+
- **Halt + surface to me:** if `buildJoinWhere`'s EXISTS construction can't host the junction join without a structural change beyond the FK path's shape (surface the obstacle); if `through` is genuinely unavailable on the filter relation and surfacing it touches an out-of-scope consumer.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Brief: S2-D1 R2 — finish the filter EXISTS (continue from WIP)
2+
3+
## Situation
4+
5+
The R1 implementer ran out of budget mid-work and **did not commit**. Uncommitted WIP is in the tree (`git status` + `git diff`): `src/model-accessor.ts` (+96) and `test/model-accessor.test.ts` (+182). It was mid-fix on the **parent-anchor correlation** — it had just realised the junction→parent side must resolve from `relation.on.localFields` (the parent's anchor columns), not from `through.parentColumns`, and was about to thread `contract` + `parentModelName` into a `buildManyToManyExistsExpr` helper to resolve them.
6+
7+
## Task
8+
9+
**Read the uncommitted diff first.** Then finish:
10+
11+
1. Complete the M:N EXISTS for `some`/`every`/`none` in `model-accessor.ts`. The correlation has two distinct sides — get both right (this is exactly what slice 1's read path established, mirror it for consistency):
12+
- **junction → parent:** `junction.{through.parentColumns} = parent.{on.localFields resolved to columns}` (e.g. `user_tags.user_id = users.id`). Resolve the parent anchor columns via `resolveFieldToColumn(contract, parentModelName, localField)` — thread `contract`/`parentModelName` into the helper as the WIP was starting to do.
13+
- **junction → target:** `junction.{through.childColumns} = target.{through.targetColumns}` (e.g. `user_tags.tag_id = tags.id`).
14+
- Shapes: `some` = `EXISTS(SELECT 1 FROM target JOIN junction ON <j→t> WHERE <j→parent> AND <pred>)`; `none` = `NOT EXISTS(… AND <pred>)`; `every` = `NOT EXISTS(… AND NOT(<pred>))`. Composite-key AND-ed across all pairs.
15+
2. Reconcile R1's WIP unit tests so they pass and assert the AST (junction join + both correlation sides; some/every/none). Fix any incoherent WIP test.
16+
3. Don't regress FK relation filters.
17+
18+
## Completed when
19+
20+
- [ ] `some`/`every`/`none` on M:N compile to the correct junction EXISTS/NOT EXISTS (both correlation sides correct, composite-key AND-ed); unit tests pass.
21+
- [ ] FK filter tests pass.
22+
- [ ] `pnpm --filter @prisma-next/sql-orm-client typecheck` + `test` green.
23+
- [ ] Committed as **one coherent commit** (WIP + completion), explicit staging + `-s` sign-off, **no push**. No bare `as` casts.
24+
25+
## Standing instruction
26+
27+
Finish the goal; keep R1's coherent WIP. Implement → get the targeted test green → run the package gate; don't re-explore.
28+
29+
## References
30+
31+
- R1 brief: `./01-filter-code.md`. Slice spec: `../spec.md`.
32+
- **Slice 1 read path** (`query-plan-select.ts` `buildManyToManyJunctionArtifacts`, commit `e587b433c`) resolved the same two-sided junction correlation — mirror its parent-anchor resolution for consistency.
33+
34+
## Operational metadata
35+
36+
- **Model tier:** sonnet.
37+
- **Branch:** `tml-2786-slice-2-filter` (WIP already on it). Explicit staging + `-s`; **no push**. Don't commit under `projects/`.
38+
- **Time-box:** ~50 min.
39+
- **Halt + surface to me:** if R1's WIP is incoherent in a way you can't reconcile (describe it); if the junction EXISTS needs a structural change beyond the FK EXISTS shape.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Brief: S2-D2 — M:N filter integration tests (operator standard)
2+
3+
## Task
4+
5+
Prove M:N relation filters work end-to-end against the database, following the project's **integration-test standard**. D1 made `.some`/`.every`/`.none` emit a junction EXISTS; slice 1's fixture has `User.tags` (→ `Tag` via `UserTag`) and `seedTags`/`seedUserTags` helpers. Add integration tests under `test/integration/test/sql-orm-client/` (PGlite, `withCollectionRuntime`). Seed users/tags/junction rows, then assert `db.orm.User.filter((u) => u.tags.some/every/none(...))` returns the right users.
6+
7+
**Cases (all required):**
8+
- **`some`** — users having ≥1 tag matching a predicate (e.g. `t.name.eq('x')`).
9+
- **`none`** — users with no matching tag.
10+
- **`every`** — users all of whose tags match (include a user with a non-matching tag to prove they're excluded; and verify the vacuous case — a user with **no** tags satisfies `every`).
11+
- **empty-match edge** — a predicate no tag matches → `some` returns none, `none`/`every` behave correctly.
12+
13+
**Standard (all three):** (1) whole-row `toEqual` on the **filtered result set** (assert exactly which users come back, full row shape); (2) explicit `.select(...)` in **most** tests; (3) **≥1** test uses implicit/default selection (no `.select`, asserts full default row shape of the returned users).
14+
15+
## Scope
16+
17+
**In:** new integration test file under `test/integration/test/sql-orm-client/`; reuse slice 1's seed helpers (extend if a filter test needs more seed data).
18+
19+
**Out:** filter code (D1); include reads (slice 1); writes (slice 3); production changes (if a test reveals a filter bug, surface it — don't patch production here). Don't modify the fixture contract.
20+
21+
## Completed when
22+
23+
- [ ] Integration tests pass on PGlite covering `some`, `none`, `every` (incl. the vacuous no-tags case) and an empty-match edge, asserting the exact filtered user set as **whole rows** (`toEqual`).
24+
- [ ] Most tests use explicit `.select`; **≥1** uses implicit/default selection.
25+
- [ ] Gate: `cd test/integration && pnpm test test/sql-orm-client/<your-file>` green (the in-sandbox path for this suite).
26+
27+
## Standing instruction
28+
29+
Match the existing integration corpus style. If a test surfaces a real filter bug (wrong users returned), **surface it to me** with the failing assertion — that's a `must-fix` against D1, not something to patch in the test.
30+
31+
## References
32+
33+
- Slice spec: `projects/sql-orm-many-to-many/slices/02-filter-exists-through-junction/spec.md` (§ done conditions — the standard).
34+
- Slice 1's `mn-include.test.ts` + `runtime-helpers.ts` (`seedTags`/`seedUserTags`) — the harness + seed pattern to reuse.
35+
- Existing filter integration tests (if any) for `.some/.every/.none` on FK relations — mirror their structure.
36+
37+
## Operational metadata
38+
39+
- **Model tier:** sonnet.
40+
- **Branch:** `tml-2786-slice-2-filter`. Explicit staging + `-s` sign-off. **Do not push.**
41+
- **Time-box:** ~60 min — core `some`/`none`/`every` whole-row tests first, then the implicit-selection + empty-match cases; don't over-explore.
42+
- **Halt + surface to me:** if the integration harness can't run in-sandbox (PGlite spin-up failure unrelated to your tests — describe it, don't fake green); if a filter returns the wrong user set (D1 bug).

projects/sql-orm-many-to-many/trace.jsonl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,16 @@
4848
{"event_id":"10edc1f4-e070-4dc6-b0ac-bf8eaf2565a7","schema_version":"1","ts":"2026-06-01T19:14:44.090Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"dispatch-end","dispatch_id":"88c9c264-8edc-4584-b440-7d51eeae37e4","result":"completed","wall_clock_ms":1299697}
4949
{"event_id":"c766ebb8-dfa9-4f3a-b033-3ce51a3594cc","schema_version":"1","ts":"2026-06-01T19:18:04.849Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"spec-authored","spec_path":"projects/sql-orm-many-to-many/slices/02-filter-exists-through-junction/spec.md","spec_kind":"slice","byte_length":4110,"edge_cases_count":3,"open_questions_count":1,"dod_items_count":3}
5050
{"event_id":"c358128a-8d02-451d-b2d6-ee791793231e","schema_version":"1","ts":"2026-06-01T19:18:05.325Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"plan-authored","plan_path":"projects/sql-orm-many-to-many/slices/02-filter-exists-through-junction/plan.md","plan_kind":"slice","byte_length":2068,"dispatch_count":2,"slice_count":null,"dispatch_size_distribution":{"S":0,"M":2,"L":0,"XL":0},"open_items_count":0}
51+
{"event_id":"1f4269f6-b643-49cc-b578-486bde519cb2","schema_version":"1","ts":"2026-06-01T19:18:55.231Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"dispatch-start","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","dispatch_name":"S2-D1 filter EXISTS through junction","subagent_type":"general-purpose","model":"sonnet","parent_dispatch_id":null}
52+
{"event_id":"0204eb47-f7db-4946-baaf-9ac8e44efa7f","schema_version":"1","ts":"2026-06-01T19:18:55.645Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-start","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"c1a6d082-e1ff-4946-85b5-ffdc629907f7","round_number":1}
53+
{"event_id":"8acf055e-4669-437a-b8d0-c9116fb33f7a","schema_version":"1","ts":"2026-06-01T19:18:56.042Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"brief-issued","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"c1a6d082-e1ff-4946-85b5-ffdc629907f7","brief_byte_length":3636,"brief_content_hash":"4315f8e52c313f3c635deb90d4cfabd34a09475ac2e1f5ddb8c2e283397c93af","brief_disposition":"initial"}
54+
{"event_id":"d3da2730-50fa-4a39-a20c-0104eac93e3e","schema_version":"1","ts":"2026-06-01T19:24:24.824Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-end","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"c1a6d082-e1ff-4946-85b5-ffdc629907f7","verdict":"another-round-needed","findings_filed":0,"wall_clock_ms":328774}
55+
{"event_id":"f2f19a37-be1f-4344-9bf4-dae9f9325c35","schema_version":"1","ts":"2026-06-01T19:24:25.231Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-start","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"42b0293b-0378-47f7-a45b-8ee88eabba48","round_number":2}
56+
{"event_id":"01685cd6-220a-4ffc-a954-f6f50c0c8e53","schema_version":"1","ts":"2026-06-01T19:24:25.606Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"brief-issued","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"42b0293b-0378-47f7-a45b-8ee88eabba48","brief_byte_length":3019,"brief_content_hash":"78194f31856d295021e8c57a90d3e795311b3aae0c276348a87154e3d31a301f","brief_disposition":"amended"}
57+
{"event_id":"8d180a85-decb-4192-a567-ee2ecb295f69","schema_version":"1","ts":"2026-06-01T19:32:16.305Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-end","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","round_id":"42b0293b-0378-47f7-a45b-8ee88eabba48","verdict":"satisfied","findings_filed":0,"wall_clock_ms":470687}
58+
{"event_id":"7a0516c4-2017-4f7c-abf9-a56cb3de8ce8","schema_version":"1","ts":"2026-06-01T19:32:16.709Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"dispatch-end","dispatch_id":"e9a8e215-dd56-4083-8da7-7a662426285d","result":"completed","wall_clock_ms":800687}
59+
{"event_id":"78b3ccd1-b852-4db1-8345-7473066dd2c6","schema_version":"1","ts":"2026-06-01T19:32:50.761Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"dispatch-start","dispatch_id":"cf687dda-ed46-4038-aa28-ad94ba17dd8f","dispatch_name":"S2-D2 M:N filter integration tests","subagent_type":"general-purpose","model":"sonnet","parent_dispatch_id":null}
60+
{"event_id":"1ba55c50-3aac-4fa3-83c5-33068f6a3412","schema_version":"1","ts":"2026-06-01T19:32:51.145Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-start","dispatch_id":"cf687dda-ed46-4038-aa28-ad94ba17dd8f","round_id":"1f3a01e1-6941-4453-808b-b833c553c03e","round_number":1}
61+
{"event_id":"2d3e4bc6-9ee3-4c31-a31a-acbdb6aa2281","schema_version":"1","ts":"2026-06-01T19:32:51.521Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"brief-issued","dispatch_id":"cf687dda-ed46-4038-aa28-ad94ba17dd8f","round_id":"1f3a01e1-6941-4453-808b-b833c553c03e","brief_byte_length":3285,"brief_content_hash":"2221c822875da35648cb846f12094465a23c2565d55c8e10f6690482e3bce2e7","brief_disposition":"initial"}
62+
{"event_id":"5e42b22f-745d-4b15-8352-f874f1873552","schema_version":"1","ts":"2026-06-01T19:41:15.103Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"round-end","dispatch_id":"cf687dda-ed46-4038-aa28-ad94ba17dd8f","round_id":"1f3a01e1-6941-4453-808b-b833c553c03e","verdict":"satisfied","findings_filed":0,"wall_clock_ms":503436}
63+
{"event_id":"b61b7318-eee7-458f-ad44-0873e860b3c4","schema_version":"1","ts":"2026-06-01T19:41:15.494Z","project_run_id":"sql-orm-many-to-many","orchestrator_agent_id":null,"event_type":"dispatch-end","dispatch_id":"cf687dda-ed46-4038-aa28-ad94ba17dd8f","result":"completed","wall_clock_ms":503820}

0 commit comments

Comments
 (0)