# Resolve Task 17 D1 transaction-contract defect

## Objective

Starting from checkpoint `e030685f`, correct the final Task 17 contract defect spec-first. D1 must no longer expose, claim, cast, or emulate an interactive callback transaction. PostgreSQL and other genuinely transactional adapters keep the callback-minted `Transaction<S>` capability and exact identity semantics. `createD1Client` instead exposes its ordinary public query seam plus a minimal typed atomic batch seam backed by exactly one real Cloudflare `binding.batch(...)` call. Prove genuine D1 rollback and genuine PostgreSQL callback rollback, keep the package contracts truthful in emitted DTS/public exports, obtain an independent `gpt-5.6-sol/low` P1/P2-none review, and commit the scoped repair.

## Fixed constraints

- Before editing, verify `HEAD` is exactly `e030685f` and the only expected new file is this planner artifact. Do not reset or rewrite the existing Task 17 commits.
- Use Node 22 for every shell command. At the beginning of every shell invocation export `PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH"`; verify `node --version` reports v22, and keep that environment for git, pnpm, Node, and review-support commands.
- Follow the spec-first order in `AGENTS.md`: amend the authoritative specs and plan before changing implementation.
- Never modify `apps/mod-cms`, `../mod-cms`, distro work, Posts-list work, generated `dist`, or unrelated files. Do not start Task 2/7 migrations or build a service/repository/coordinator framework.
- Do not implement `BEGIN`/`COMMIT` as sequential D1 statements, callback-over-client shims, casts to a transactional type, or any other synthetic transaction. Cloudflare D1 has no interactive transaction callback API.
- Keep exact combined parent identity for real callback transactions: one combined-schema PostgreSQL callback object is assignable to narrower content/fields transaction parameters, keeps one identity, and is passed unchanged. Independently minted or inactive callbacks still reject before effects.
- D1 composition must remain declarative and single-shot: future content, fields, audit, and outbox statements are accumulated before execution and submitted in one real binding batch. Package-level helpers must not execute partial batches or sequential side effects.
- Treat warnings/notices from owned build output as blocking. Fix causes; do not suppress, filter, redirect, allowlist, or weaken tree-shaking.

## 1. Baseline and authoritative reading

1. Record `git status --short --branch`, `git rev-parse HEAD`, and the scoped diff baseline. Preserve the new `specs/dcce95b4_d1-batch-contract.md` plan copy as expected process state.
2. Read in order:
   - `docs/specs/2026-08-08-content-types-complete-design.md`
   - `docs/specs/2026-08-08-fields-complete-design.md`
   - `docs/specs/2026-08-08-mod-cms-content-model-completion-design.md`
   - `docs/plans/2026-08-08-mod-cms-content-model-completion.md`
   - `docs/plans/2026-08-08-mod-cms-content-model-completion.jsonl`
   - `docs/standards/coding-standard.md` §2, §4, §5A, §6 and `docs/standards/authz.md`
3. Inspect the current DB transaction implementation, D1 adapter/types/tests, package exports and generated type topology; then inspect the content/fields Task 17 schema/store barrels, conformance tests, and PostgreSQL harnesses. Confirm the exact defect: `createD1Client` is typed as transactional despite D1 lacking that API, and both conformance files replace `transaction` with `callback(client)` and apply the internal identity wrapper, which cannot roll back.

## 2. Amend all authoritative contracts first

Update these five authoritative files before implementation:

- `docs/specs/2026-08-08-content-types-complete-design.md`
- `docs/specs/2026-08-08-fields-complete-design.md`
- `docs/specs/2026-08-08-mod-cms-content-model-completion-design.md`
- `docs/plans/2026-08-08-mod-cms-content-model-completion.md`
- `docs/plans/2026-08-08-mod-cms-content-model-completion.jsonl`

Make one consistent capability model throughout:

1. **PostgreSQL/genuinely interactive adapters:** retain callback-only `Transaction<S>`, callback-scoped `TransactionIdentity`, exact object identity, inactive-capability rejection, nested/independent identity rejection, and rollback on callback failure. Do not weaken the combined-schema/narrow-schema contract.
2. **D1:** define a D1-specific atomic batch capability over the actual binding batch path. The D1 client has no public `transaction` member and no `TransactionIdentity`. Its normal query/read seam remains available. Atomic mutations use typed prepared batch items/results and one batch call.
3. **Composition:** state explicitly that a future parent content+fields+audit+outbox mutation contributes all D1 statements to one host-owned batch invocation; no participant may execute early, issue its own nested batch, or advertise atomicity through compensation/sequential effects. Freeze only the statement/result/error surface needed for this composition, not future mutation services.
4. **Equivalent behavior, not identical APIs:** replace blanket “D1 and Postgres use the same transaction/callback boundary” language with equivalent observable atomicity through adapter capabilities. Callback identity assertions and `expectedTransactionIdentity` apply only to callback-transaction paths; the D1 path proves atomic all-or-nothing batch behavior instead.
5. Reconcile the concrete contradictory sections, including content hooks/outbox and import atomicity; fields schema invariant 9, Values API transaction paragraph, parent-linked block/revision clauses, import/migration atomicity; integration Wave A, combined save/restore, revisions, import/publication/reversal, and D1/Postgres acceptance. Preserve every authorization, idempotency, hidden-staging, audit/outbox, and no-partial-write hard floor.
6. Amend plan Gates, Task 1, Task 17, relevant downstream composition/acceptance wording, and matching JSONL task descriptions/acceptance so they no longer instruct a builder to mint a D1 callback transaction. Task 17 must explicitly require public D1 query use, real D1 batch rollback, and real PostgreSQL callback identity/rollback. Keep Markdown and JSONL semantically identical.
7. Run a targeted text consistency scan over these five files after editing. No authoritative sentence may still claim D1 has an interactive callback, that identical API shape is required, or that D1 carries transaction identity.

## 3. Pin the truthful DB public surface with RED tests/types

Change only the minimal DB files needed, expected primarily:

- `packages/db/src/sqlite/d1.ts`
- `packages/db/src/sqlite/types.ts`
- `packages/db/src/sqlite/index.ts`
- `packages/db/src/sqlite/d1.test.ts`
- `packages/db/src/adapters.test-d.ts` and/or a focused D1 type-contract fixture
- `packages/db/package.json` and `pnpm-lock.yaml` only if Miniflare must become a direct DB test dependency

Before implementation, add discriminating coverage that fails on the checkpoint:

1. Compile-time assertions from the public `@platform-modules/db/sqlite/d1` surface: the result of `createD1Client` has `execute`/query and atomic `batch`, has no `transaction` key, is not assignable to `TransactionalDatabase`/`Transaction`, and preserves tuple/result row inference for heterogeneous prepared statements. Verify the public barrel and emitted DTS, not an internal-only type.
2. Runtime assertions that the D1 client has no public/runtime `transaction` member and still executes SQL reads through `execute()` using D1 `.all()` row normalization.
3. A real Miniflare D1 rollback test: create a table, prepare a valid write followed by a guaranteed failing statement in the same public batch, assert the public structural batch error, then read through the public D1 query seam and prove the valid write did not commit. A mock that merely rejects is insufficient.
4. A successful heterogeneous batch test proving real SQL executes once through the binding batch path and returns correctly typed/normalized structural results in input order. Add a narrow spy/mock test only to pin “one binding.batch call”; it supplements rather than replaces the real rollback fixture.
5. Keep existing PostgreSQL transaction identity tests green; do not move identity helpers into the D1 surface.

## 4. Implement the minimal D1 atomic batch adapter

1. Remove `withTransactionIdentity` from `packages/db/src/sqlite/d1.ts` and stop returning/casting `SQLiteTransactionalDatabase`. Introduce/rename the D1 client type so its public keys truthfully describe D1: query/fluent methods plus `execute` and atomic `batch`, with no `transaction`.
2. Build the batch seam on Drizzle/Cloudflare’s real prepared-query path and ultimately exactly one `binding.batch(...)` invocation. Reuse the installed library’s public batch item/result types where possible rather than copying private internals or requiring `@cloudflare/workers-types` at runtime.
3. Preserve heterogeneous tuple typing and normalized rows/metadata needed by callers. Expose a small stable structural batch error plus cross-copy structural guard (stable code/detail and only metadata the binding can honestly provide). Do not invent a failed-statement index if D1 cannot report one, leak a provider exception as the only contract, or rely on `instanceof` across packages.
4. Reject structurally invalid/empty batch input before effects if the underlying contract cannot safely execute it. Do not expose the raw binding, a public transaction/identity factory, or an identity-shaped batch wrapper.
5. The composition contract is a single array of prepared statements. Callers may concatenate content/fields/audit/outbox contributions before calling `batch`; no helper introduced here may call `batch` per participant.
6. Update `packages/db/src/sqlite/index.ts` and the package subpath exports/types so the truthful D1 client, batch item/result/error, and guard are reachable from `@platform-modules/db/sqlite/d1`. Do not widen the root barrel with adapter-specific surface unless existing repository export doctrine requires it.
7. Add Miniflare as a direct DB dev dependency only if necessary for the real adapter test, update the lockfile through pnpm, and add no production dependency/framework.

## 5. Repair Task 17 content/fields conformance

Expected files:

- `packages/content/src/conformance.test.ts`
- `packages/content/src/pg-harness.ts` only if needed to construct the real callback adapter cleanly
- `packages/fields/src/conformance.test.ts`
- `packages/fields/src/pg-harness.ts` only if needed
- `packages/content/src/schema.ts`, `store.ts`, `index.ts`, `packages/fields/src/schema.ts`, `store.ts`, `index.ts` only where public type/docs assertions must distinguish callback transactions from D1 batching

For both package suites:

1. Import `createD1Client` and D1 public types from `@platform-modules/db/sqlite/d1`. Delete the `Object.assign(client, { transaction: callback => callback(client) })` shim and every conformance import of `../../db/src/transaction.js` / `withTransactionIdentity` used to fake D1.
2. D1 flat-corpus reads must call the existing final public store with the public D1 `execute` query seam directly; reads do not need and must not pretend to be inside a transaction.
3. Add a package-specific D1 atomicity probe through the public batch seam: valid write first, guaranteed failing SQL second, batch rejects structurally, and a subsequent public query/store read proves no write committed. Ensure the test would fail if statements were run sequentially or if the first write committed.
4. Keep the identified real Miniflare D1 and embedded real-PostgreSQL fixtures mandatory and visibly exercised. Preserve parity checks for arbitrary legacy types/statuses, dates, exact numeric lanes, booleans, JSON/reference metadata, ordering, and immutability.
5. PostgreSQL conformance must use its genuine Drizzle callback transaction, not `callback(db)`. If the embedded harness must apply the DB adapter’s internal identity decorator, do it once at harness adapter construction around the existing real `transaction` implementation; never replace the transaction method with a non-transactional callback. Conformance itself should consume the resulting callback seam without internal D1 workarounds.
6. In each PostgreSQL suite prove: same-callback identity succeeds; a separately minted valid callback identity rejects before effects; a valid write followed by a thrown/failing statement rolls back; and escaped/completed callback capability is inactive. Read after rollback to prove zero partial commit.
7. Preserve `assertContentTransactionIdentity` / `assertFieldsTransactionIdentity` as callback-transaction contracts. Clarify aliases/comments or public exports only as needed so DTS does not imply those apply to D1. Do not add a D1 identity analogue.
8. Do not change Task 17 flat readers into a future repository/service abstraction and do not begin final mutation implementation.

## 6. Focused Node 22 verification

Use one Node 22 environment per shell and judge every command by exit status. During TDD run only the smallest affected test; after the diff stabilizes run these final commands separately, without duplicate ritual:

```bash
export PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH"
pnpm --filter @platform-modules/db test
pnpm --filter @platform-modules/db typecheck
pnpm --filter @platform-modules/db build
pnpm --filter @platform-modules/fields exec vitest run --config vitest.config.ts src/definition-version.test.ts src/index.test.ts src/conformance.test.ts
pnpm --filter @platform-modules/content exec vitest run --config vitest.config.ts src/index.test.ts src/conformance.test.ts
pnpm --filter @platform-modules/content exec vitest run --config vitest.config.ts src/feed.test.ts src/search-sqlite.test.ts src/search.test.ts src/store.test.ts src/revisions.test.ts
pnpm --filter @platform-modules/content typecheck
pnpm --filter @platform-modules/fields typecheck
pnpm --filter @platform-modules/fields-react typecheck
pnpm --filter @platform-modules/content build
pnpm --filter @platform-modules/fields build
git diff --check
```

If a changed public type affects `fields-react` tests/build rather than typecheck alone, also run its focused test and build. Inspect complete successful DB/content/fields build output for warnings; zero exit with owned warnings is not completion. Verify generated DTS by building and inspecting/import-testing the D1 subpath, but do not commit `dist`.

## 7. Independent review and repair loop

1. After all focused gates are clean, launch an independent reviewer explicitly using `gpt-5.6-sol` at `low` thinking. Give it the full diff from `e030685f` including uncommitted spec, plan, code, tests, lockfile, and this plan artifact.
2. Require actionable P1/P2/P3 findings and explicit checks for:
   - no fake/cast/emulated D1 transaction or sequential BEGIN/COMMIT;
   - D1 client has no runtime/type-level transaction member;
   - one real binding batch call and failing-second-statement rollback proof;
   - typed structural D1 results/errors and truthful public barrel/DTS;
   - real PostgreSQL callback rollback and exact/combined identity preservation;
   - content/fields using public D1 query/batch seams with no internal fake identity import;
   - all three authoritative specs plus Markdown/JSONL plan consistency;
   - no speculative framework and no forbidden/unrelated files.
3. Wait for the review. Fix every P1/P2. Rerun affected narrow tests and the final focused quality set, then repeat independent review on the new full diff whenever a P1/P2 repair changes code/spec/tests. Completion requires a final report explicitly stating P1/P2 none.

## 8. Scope audit and commit

1. Inspect `git diff --name-only e030685f`, `git diff --stat`, and `git status --short`. Reject any change under forbidden paths or outside the named docs, DB adapter/tests, Task 17 content/fields contracts/conformance/harnesses, necessary package metadata/lockfile, and this planner artifact.
2. Ensure no generated `dist`, logs, PostgreSQL/Miniflare state, warning captures, or review scratch files are tracked.
3. Confirm the final specs describe equivalent atomic behavior by capability, D1 has no callback/identity claim, Postgres retains exact combined identity, and the JSONL remains valid one-object-per-line JSON.
4. Commit the complete scoped repair (including the planner artifact) with an imperative subject such as `Make D1 atomicity capability truthful` using normal hooks. Do not bypass hooks.
5. Verify the commit exists and `git status --porcelain` is empty. Handoff the commit SHA, command exit statuses, warning-free build confirmation, real D1 rollback evidence, real PostgreSQL rollback/identity evidence, emitted D1 DTS/public-export result, and final independent review P1/P2-none verdict.

## Definition of done

The checkpoint is repaired only when authoritative specs and both plan forms are capability-truthful; `createD1Client` exposes reads plus one typed real atomic batch path and no transaction member; a failing D1 statement after a valid write proves rollback; content and fields conformance use public D1 query/batch seams; PostgreSQL keeps real callback identity and rollback, including exact combined parent identity; public barrels/DTS match runtime; all focused Node 22 checks are warning-free; independent `gpt-5.6-sol/low` review reports P1/P2 none; the scoped change is committed; and the worktree is clean.
