Skip to content

[finding] applyMigrationEntries reports every SQLite entry as applied after a table rebuild, including ops the rebuild is documented to ignore #11722

Description

@huangyiirene

Observation while measuring the reconciler for #11535 / #11720. Latent, not reachable today — filed so it is not discovered by the op that makes it reachable.

The shape

SqlDriver.applyMigrationEntries splits by dialect. The non-SQLite arm asks per entry and believes the answer:

for (const d of ents) {
  const ok = await this.applyDriftOpInPlace(d.op);
  (ok ? applied : skipped).push(d);
}

The SQLite arm does not ask at all:

if (this.isSqlite) {
  await this.rebuildSqliteTablePatched(table, ents);
  applied.push(...ents);        // every entry, unconditionally
}

But rebuildSqliteTablePatched handles four op types (relax_not_null, tighten_not_null, drop_column, drop_column_default) and silently ignores anything else — its own docstring says so for the varchar ops: "varchar widen/narrow are no-ops on SQLite (dynamic typing) and ignored." An ignored op is still pushed into applied.

So on SQLite, "applied" means "a rebuild ran", not "this op happened". Every consumer reads it as the latter: reconcileAndWarnDrift logs auto-reconciled <op> on <table>.<col>, and the artifact boot gate prints ↪ migrated <op>. A finding reported as migrated but still present comes back on the next boot, reports as drift again, and is "migrated" again — a loop with no failing signal anywhere in it.

Why it is not reachable today

The only column ops the differ emits on SQLite are the four the rebuild handles. The varchar ops are excluded by enforcesVarcharLength (SQLite is not in it), and #11720's new manual_column_type_change is excluded by multiValueColumnTypeIsLoadBearing for an unrelated, measured reason (SQLite does not corrupt the value). So the gap is currently unreachable by construction — from two independent directions, neither of which is aware it is holding this closed.

Why it is worth a note anyway

The next column op that is not SQLite-rebuildable opens it, and the failure mode is a false green rather than an error: nothing throws, nothing is skipped, and the log says the work was done. The asymmetry is also invisible at the call site — the two arms of the same function disagree about what applied means, and only one of them can be wrong.

A minimal fix is for rebuildSqliteTablePatched to return the set of entries it actually honoured (it already partitions them into four Sets to do its work) and for the caller to push the remainder into skipped. That is sql-driver.ts, outside #11720's declared surface.

Activity

  1. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage round: finding → pm:queue + domain:engine stands, Task — latent but the fix is small and local: the SQLite arm must push into applied only the four op types rebuildSqliteTablePatched actually handles, and route the ignored remainder to skipped so consumers stop logging "auto-reconciled" for ops that never happened. Serial note: same driver-sql migration seam as the queued #11535 — fold-or-serial is a required answer when either dispatches.


    Generated by Claude Code

  2. added theissue type on Aug 25, 2026
  3. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    Claim: PM loop (domain:engine seat)
    Session: session_01W6HFzyH98W1YaQXhJUJt6o
    Branch: claude/issue-11722-sqlite-rebuild-applied-honesty
    Worktree: objectstack-issue-11722
    Domain: domain:engine
    Container & model: M, mode:subagent, model: opus
    Clause-②: no — applied / skipped is a reported partition consumed by log lines, not an accept/reject door, and no public surface widens. If the fix finds itself changing what an op does rather than what is reported, stop and report.

    Serial constraints cleared — three cards are live on this file

    Measured by symbol on origin/main (packages/drivers/driver-sql/src/sql-driver.ts, 15,248 lines):

    The one thing that makes this card hard

    The card is explicit that the defect is latent — unreachable today, held closed from two independent directions that are unaware they are holding it (enforcesVarcharLength excludes SQLite; multiValueColumnTypeIsLoadBearing excludes #11720's manual_column_type_change for an unrelated measured reason).

    ⇒ A test that passes on main proves nothing here. You must construct the reachability — drive an op through applyMigrationEntries on SQLite that rebuildSqliteTablePatched does not honour — and show it lands in applied before the fix and in skipped after. Predict that direction in writing before you run it. If you cannot construct a reachable case at all, that is a real finding and a fork back to me, not something to paper over with a unit test on the partition helper alone.

    The reporter names the minimal fix: rebuildSqliteTablePatched returns the entries it actually honoured (it already partitions them into four Sets to do its work) and the caller pushes the remainder into skipped. Read that as a starting point you must verify, not a spec.


    Generated by Claude Code

  4. self-assigned this
    on Aug 25, 2026
  5. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator
    {
      "issue": 11722,
      "status": "done",
      "branch": "claude/issue-11722-sqlite-rebuild-applied-honesty",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12127",
      "premise_still_valid": true,
      "summary": "Verified the premise on origin/main first: the SQLite arm of applyMigrationEntries really did `applied.push(...ents)` unconditionally at :10196, and rebuildSqliteTablePatched really does honour only four op types. rebuildSqliteTablePatched now returns the entries it acted on, built in the SAME pass that fills the four column sets it already partitioned into (an `else continue` before `honoured.push(e)`), so the report cannot drift from the work; the caller reports those applied and routes the remainder to skipped, logging it in the identical sentence the in-place arm uses for an op its dialect cannot do. Clause 2 stays NO: nothing changes what an op DOES. In particular the rebuild still runs for the whole table even when it honours nothing, because it re-materializes defaults and the declared index set from metadata (11321/4560/3696) and suppressing it would be a behaviour change, not a reporting one; that is now stated in the docblock. Held bands untouched: 8600-9200 (12015 / PR 12109) needed nothing because the :9176 call site discards the return value, and 13275-13900 (11875) was never opened. The two {@link rebuildSqliteTablePatched} docblocks at :14031 and :14076 were checked and read true unchanged (they cite the default re-materialization, not the return value); schema-drift.ts:163's 'skipped, never applied' prose read true as written but was silent about the one dialect where it was false, and now names SQLite.",
      "tests": "NON-VACUITY, the hard part of this card: the defect is latent (enforcesVarcharLength and multiValueColumnTypeIsLoadBearing both exclude sqlite), so reachability was CONSTRUCTED rather than waited for -- entries are handed straight to the public applyMigrationEntries seam that `os migrate apply` (cli/src/commands/migrate/apply.ts:199) and the artifact boot gate (cli/src/utils/artifact-boot-migration.ts:143) both call, substituting only the differ's dialect guard; driver, dialect and database real throughout. New suite packages/drivers/driver-sql/src/sql-driver-11722-sqlite-rebuild-applied-honesty.test.ts, 5 cases: manual_column_type_change skipped + physical column proven unchanged; widen_varchar (safe) skipped; a MIXED batch where the rebuild really relaxes NOT NULL while the ignored sibling is skipped; a ratchet feeding one entry per column op type and pinning the honoured set as exactly the four, with a positive control on the physical table; and the consumer-level case pinning that reconcileAndWarnDrift no longer logs 'auto-reconciled'. || ABLATION -- direction predicted IN WRITING BEFORE THE RUN (scratchpad PREDICTION.md): restoring the caller's arm to main's exact two lines turns the suite RED on the skipped-membership assertions; not 'fewer diagnostics', not a reversal. Mutation proved ON DISK by anchored grep -cF counts of the text actually changed, BEFORE any result was read: fixed-arm marker 1 -> 0 and main-arm marker 0 -> 1 (the python exit code was printed and explicitly labelled NOT the proof). REBUILD: none was owed and that is shown, not assumed -- packages/drivers/driver-sql/dist was ABSENT at ablation time (ls output in the run) and the suite imports './sql-driver.js' intra-package, which vitest resolves to the TS source, so no dist could stand between the mutation and the reading. OBSERVED: 5 of 5 RED, the predicted direction, the consumer case failing on 'expected true to be false' (the auto-reconciled line). RESTORE verified not trusted: trap ... EXIT INT TERM running `git checkout HEAD -- {absolute path}`, then git hash-object of the working file 18c76de812602304edb9614457060573e0ad399b == git rev-parse HEAD:packages/drivers/driver-sql/src/sql-driver.ts, and git status --porcelain empty. || GATE UNION derived not recalled at the FINAL commit fd1eeeb162 via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths passed; provenance line confirmed 'objectstack-ai/objectstack at commit fd1eeeb162', change set 4 paths vs merge base a933ed720): 14 path-matched + 6 convention-triggered families, ALL RUN, ALL GREEN, every exit code captured before any pipe (`cmd > log 2>&1; EXIT=$?`). Their own verdict lines: check:driver-conformance 'OK -- 45 covered cell(s), 0 in the DEBT ledger, 0 exempt.'; check:type-check-debt --self-test '47 semantic + 65 observation + 29 re-measure + 28 built-closure + 19 auto-lowering case(s) hold' and --re-measure 'OK -- 32 ledger entr(ies) re-measured in 227.7s, 1897 raw tsc error(s) total, none above its recorded number.' (run against a BUILT workspace closure: turbo build, 70/70 successful); check:engine-double-contract '390 (file, verb) row(s) held by the RETAINED ledger'; check:where-matcher '299 matcher(s) discovered, 299 answer the combinator battery correctly or refuse it loudly ... none new'; check:cross-package-test-inputs 'OK: 16 package(s) read outside themselves, all declared'; check:type-check-coverage 'test layer: 19 package(s) still hide their own tests from tsc'; check:query-options-erasure 'ratchet holds ... none new'; check:published-files '69 publishable package(s) of 78'; check:test-source-alias 'OK -- 72 packages with tests scanned'; check:type-source-resolution 'OK -- 93 tsc program(s) across 77 packages scanned'; check:slot-lookup 'ratchet holds ... none new'; check-drift-comment '56 cases pass across 5 fixture diff(s)'; changeset family (adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, objectui-changeset, release-rehearsal-clone --self-test) all exit 0; check:nul-bytes exit 0 plus a manual control-byte grep over all four changed files (clean). || REPO-WIDE LINT RUN IN FULL, no narrowing claimed: `pnpm lint` (node eslint.js . --no-inline-config) exit 0. || PACKAGES: @objectstack/driver-sql typecheck exit 0; @objectstack/driver-sql test exit 0 -- 'Test Files 132 passed | 8 skipped (140) / Tests 2016 passed | 121 skipped (2137)'. Both inheritors and the CLI consumer green (turbo, 59/59 tasks): driver-sqlite-wasm 25 files, driver-turso 39 files, cli 178 files. || Every build/test went through scripts/pm/os-verify-lock.sh; longest hold 8m46s (the three-package consumer batch, which turbo expanded to 59 tasks) -- flagging it since that is a long hold on a shared lock. Total lock waiting this round: about 8.5 minutes across six acquisitions, all spent inside the turn.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, needs you to file or clear it -- the dedup read was unavailable from this seat. Observation: on SQLite, applyMigrationEntries runs a FULL table rebuild (create tmp, copy every row, drop, rename, re-materialize defaults and the whole declared index set) even when the batch contains ZERO entries the rebuild can honour. Latent exactly like 11722 itself and deliberately left alone by this PR because removing it would change what the reconciler DOES, not what it reports -- which is precisely the clause-2 line I was told to stop at. It is now parked explicitly in rebuildSqliteTablePatched's docblock so nobody 'optimizes' it without a decision. Would be a `finding` label, no pm:queue, unassigned, in objectstack.",
        "SEAT-LEVEL CHANNEL FACT, worth your attention because it silently disables the file-before-you-report discipline: the repo-scoped REST channel that .claude/skills/pm-dispatch/references/rest-channel.md records as verified 2026-08-23 REFUSES from this dev seat today. `GET /repos/objectstack-ai/objectstack` returns 403 and `GET /repos/objectstack-ai/objectstack/issues?state=open` returns {'message': 'GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.'}, with and without the Authorization header, using the container's own GITHUB_TOKEN. `GET /rate_limit` on the same token succeeds and reads core 15000/15000, graphql 10000/10000, search 30/30 -- so this is not exhaustion and not a dead token, it is the repo-scoped path being refused. MCP github tools work fine. Consequence for me: the prescribed dedup route (REST list endpoint + local grep) lost its REST half, and the dispatch forbids MCP list_issues/search_issues, so I did the local grep only (nothing in-tree tracks the finding above) and am handing you the filing rather than creating a possible duplicate blind. Flagging the conflict rather than quietly picking a side, per the standing clause."
      ]
    }

    Generated by Claude Code

  6. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 11722,
      "status": "done",
      "branch": "claude/issue-11722-sqlite-rebuild-applied-honesty",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12127",
      "premise_still_valid": true,
      "summary": "Verified the premise on origin/main first: the SQLite arm of applyMigrationEntries really did `applied.push(...ents)` unconditionally at :10196, and rebuildSqliteTablePatched really does honour only four op types. rebuildSqliteTablePatched now returns the entries it acted on, built in the SAME pass that fills the four column sets it already partitioned into (an `else continue` before `honoured.push(e)`), so the report cannot drift from the work; the caller reports those applied and routes the remainder to skipped, logging it in the identical sentence the in-place arm uses for an op its dialect cannot do. Clause 2 stays NO: nothing changes what an op DOES. In particular the rebuild still runs for the whole table even when it honours nothing, because it re-materializes defaults and the declared index set from metadata (11321/4560/3696) and suppressing it would be a behaviour change, not a reporting one; that is now stated in the docblock. Held bands untouched: 8600-9200 (12015 / PR 12109) needed nothing because the :9176 call site discards the return value, and 13275-13900 (11875) was never opened. The two {@link rebuildSqliteTablePatched} docblocks at :14031 and :14076 were checked and read true unchanged (they cite the default re-materialization, not the return value); schema-drift.ts:163's 'skipped, never applied' prose read true as written but was silent about the one dialect where it was false, and now names SQLite.",
      "tests": "NON-VACUITY, the hard part of this card: the defect is latent (enforcesVarcharLength and multiValueColumnTypeIsLoadBearing both exclude sqlite), so reachability was CONSTRUCTED rather than waited for -- entries are handed straight to the public applyMigrationEntries seam that `os migrate apply` (cli/src/commands/migrate/apply.ts:199) and the artifact boot gate (cli/src/utils/artifact-boot-migration.ts:143) both call, substituting only the differ's dialect guard; driver, dialect and database real throughout. New suite packages/drivers/driver-sql/src/sql-driver-11722-sqlite-rebuild-applied-honesty.test.ts, 5 cases: manual_column_type_change skipped + physical column proven unchanged; widen_varchar (safe) skipped; a MIXED batch where the rebuild really relaxes NOT NULL while the ignored sibling is skipped; a ratchet feeding one entry per column op type and pinning the honoured set as exactly the four, with a positive control on the physical table; and the consumer-level case pinning that reconcileAndWarnDrift no longer logs 'auto-reconciled'. || ABLATION -- direction predicted IN WRITING BEFORE THE RUN (scratchpad PREDICTION.md): restoring the caller's arm to main's exact two lines turns the suite RED on the skipped-membership assertions; not 'fewer diagnostics', not a reversal. Mutation proved ON DISK by anchored grep -cF counts of the text actually changed, BEFORE any result was read: fixed-arm marker 1 -> 0 and main-arm marker 0 -> 1 (the python exit code was printed and explicitly labelled NOT the proof). REBUILD: none was owed and that is shown, not assumed -- packages/drivers/driver-sql/dist was ABSENT at ablation time (ls output in the run) and the suite imports './sql-driver.js' intra-package, which vitest resolves to the TS source, so no dist could stand between the mutation and the reading. OBSERVED: 5 of 5 RED, the predicted direction, the consumer case failing on 'expected true to be false' (the auto-reconciled line). RESTORE verified not trusted: trap ... EXIT INT TERM running `git checkout HEAD -- {absolute path}`, then git hash-object of the working file 18c76de812602304edb9614457060573e0ad399b == git rev-parse HEAD:packages/drivers/driver-sql/src/sql-driver.ts, and git status --porcelain empty. || GATE UNION derived not recalled at the FINAL commit fd1eeeb162 via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths passed; provenance line confirmed 'objectstack-ai/objectstack at commit fd1eeeb162', change set 4 paths vs merge base a933ed720): 14 path-matched + 6 convention-triggered families, ALL RUN, ALL GREEN, every exit code captured before any pipe (`cmd > log 2>&1; EXIT=$?`). Their own verdict lines: check:driver-conformance 'OK -- 45 covered cell(s), 0 in the DEBT ledger, 0 exempt.'; check:type-check-debt --self-test '47 semantic + 65 observation + 29 re-measure + 28 built-closure + 19 auto-lowering case(s) hold' and --re-measure 'OK -- 32 ledger entr(ies) re-measured in 227.7s, 1897 raw tsc error(s) total, none above its recorded number.' (run against a BUILT workspace closure: turbo build, 70/70 successful); check:engine-double-contract '390 (file, verb) row(s) held by the RETAINED ledger'; check:where-matcher '299 matcher(s) discovered, 299 answer the combinator battery correctly or refuse it loudly ... none new'; check:cross-package-test-inputs 'OK: 16 package(s) read outside themselves, all declared'; check:type-check-coverage 'test layer: 19 package(s) still hide their own tests from tsc'; check:query-options-erasure 'ratchet holds ... none new'; check:published-files '69 publishable package(s) of 78'; check:test-source-alias 'OK -- 72 packages with tests scanned'; check:type-source-resolution 'OK -- 93 tsc program(s) across 77 packages scanned'; check:slot-lookup 'ratchet holds ... none new'; check-drift-comment '56 cases pass across 5 fixture diff(s)'; changeset family (adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, objectui-changeset, release-rehearsal-clone --self-test) all exit 0; check:nul-bytes exit 0 plus a manual control-byte grep over all four changed files (clean). || REPO-WIDE LINT RUN IN FULL, no narrowing claimed: `pnpm lint` (node eslint.js . --no-inline-config) exit 0. || PACKAGES: @objectstack/driver-sql typecheck exit 0; @objectstack/driver-sql test exit 0 -- 'Test Files 132 passed | 8 skipped (140) / Tests 2016 passed | 121 skipped (2137)'. Both inheritors and the CLI consumer green (turbo, 59/59 tasks): driver-sqlite-wasm 25 files, driver-turso 39 files, cli 178 files. || Every build/test went through scripts/pm/os-verify-lock.sh; longest hold 8m46s (the three-package consumer batch, which turbo expanded to 59 tasks) -- flagging it since that is a long hold on a shared lock. Total lock waiting this round: about 8.5 minutes across six acquisitions, all spent inside the turn.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, needs you to file or clear it -- the dedup read was unavailable from this seat. Observation: on SQLite, applyMigrationEntries runs a FULL table rebuild (create tmp, copy every row, drop, rename, re-materialize defaults and the whole declared index set) even when the batch contains ZERO entries the rebuild can honour. Latent exactly like 11722 itself and deliberately left alone by this PR because removing it would change what the reconciler DOES, not what it reports -- which is precisely the clause-2 line I was told to stop at. It is now parked explicitly in rebuildSqliteTablePatched's docblock so nobody 'optimizes' it without a decision. Would be a `finding` label, no pm:queue, unassigned, in objectstack.",
        "SEAT-LEVEL CHANNEL FACT, worth your attention because it silently disables the file-before-you-report discipline: the repo-scoped REST channel that .claude/skills/pm-dispatch/references/rest-channel.md records as verified 2026-08-23 REFUSES from this dev seat today. `GET /repos/objectstack-ai/objectstack` returns 403 and `GET /repos/objectstack-ai/objectstack/issues?state=open` returns {'message': 'GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.'}, with and without the Authorization header, using the container's own GITHUB_TOKEN. `GET /rate_limit` on the same token succeeds and reads core 15000/15000, graphql 10000/10000, search 30/30 -- so this is not exhaustion and not a dead token, it is the repo-scoped path being refused. MCP github tools work fine. Consequence for me: the prescribed dedup route (REST list endpoint + local grep) lost its REST half, and the dispatch forbids MCP list_issues/search_issues, so I did the local grep only (nothing in-tree tracks the finding above) and am handing you the filing rather than creating a possible duplicate blind. Flagging the conflict rather than quietly picking a side, per the standing clause.",
        "SANITIZER, measured on THIS issue just now: the prescribed HTML-comment marker did NOT survive. Comment 5409022449 was posted with the marker as its literal first line and read back with that line GONE -- body starts straight at the json fence. This comment is the re-post with the literal-text marker the contract prescribes as the fallback, so 5409022449 and this one are the same report; 5409022449 is invisible to a marker scan and can be ignored. There is no comment-edit channel from this seat (no MCP update-comment tool, and REST PATCH is refused per the finding above), which is why it is a re-post rather than an edit."
      ]
    }

    Generated by Claude Code

  7. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    ACCEPT — PR #12127.

    The fix is structurally incapable of the defect, not merely corrected

    This is the part that decides the review. The card's minimal fix ("return the set it honoured") could have been implemented as a second list of op types next to the four branches — which would have re-created the same drift one level up, in a place nothing checks. It wasn't:

    for (const e of ents) {
      if (e.op.type === 'relax_not_null') relax.add(e.op.column);
      else if (e.op.type === 'tighten_not_null') tighten.add(e.op.column);
      else if (e.op.type === 'drop_column') drop.add(e.op.column);
      else if (e.op.type === 'drop_column_default') dropDefault.add(e.op.column);
      else continue;          // ← anything else never reaches the next line
      honoured.push(e);
    }

    honoured is filled by the same pass that fills the four sets, so an entry is honoured iff one of the branches took it. And the consequence that matters: a column op added later lands in the continue branch by default rather than being silently absorbed into applied — which is precisely the failure this card was filed about, now prevented by construction rather than by remembering. The docblock says so in as many words.

    The caller arm reads correctly, and it reports the remainder in the identical sentence the in-place arm already uses for an op its dialect cannot do, so the two arms of applyMigrationEntries now agree about what applied means.

    ⭐ The naive fix was declined for a measured reason

    The obvious "improvement" — skip the rebuild when nothing is honourable — would have been a behaviour change wearing a reporting change's clothes. The rebuild re-materializes defaults and the whole declared index set from metadata (#11321 / #4560 / #3696), so suppressing it drops work that has nothing to do with the entries. The dev kept the rebuild running, stated why in the docblock, and parked the optimization explicitly so nobody takes it without a decision. That is exactly the clause ② line I asked it to stop at, held correctly.

    Non-vacuity — the hard requirement of this card, met

    The defect is latent, held closed from two independent directions, so a test passing on main would have proven nothing. Reachability was constructed: entries handed straight to the public applyMigrationEntries seam that os migrate apply (cli/src/commands/migrate/apply.ts:199) and the artifact boot gate (cli/src/utils/artifact-boot-migration.ts:143) both call, substituting only the differ's dialect guard — driver, dialect and database real throughout. That is the narrowest possible substitution and it keeps the seam under test the real one.

    Five cases including a mixed batch where the rebuild genuinely relaxes NOT NULL while an ignored sibling is skipped — the case that separates "reports honestly" from "reports nothing".

    Ablation: direction predicted in writing first, mutation proven on disk by anchored grep -cF counts before any result was read (with the python exit code explicitly labelled not the proof), dist shown absent so no artifact could stand between mutation and reading, 5 of 5 red in the predicted direction, restore verified by git hash-object == git rev-parse HEAD:<path> with clean porcelain.

    Fences held — measured

    All seven sql-driver.ts hunks are at 10155–10499, inside the declared band. 8600–9200 (#12015 / PR #12109) untouched — correctly, because the :9176 call site discards the return value, exactly as I predicted at dispatch. 13275–13900 (#11875) never opened.

    The two {@link rebuildSqliteTablePatched} docblocks at :14031 / :14076 were checked and read true unchanged (they cite the default re-materialization, not the return value). schema-drift.ts:163's "skipped, never applied" prose read true as written but was silent about the one dialect where it was false, and now names SQLite — a good catch, since that sentence was the reason the gap looked closed.

    Carried out of this card

    Flipping to ready and enqueueing once CI converges.


    Generated by Claude Code

  8. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    Correction to the ACCEPT above: the carried-out finding is #12132, not #12128 — I named the number before filing. Nothing else in that comment changes.


    Generated by Claude Code

  9. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    LANDED — PR #12127 merged as f5a7f9c884.

    Verified by content on origin/main: honoured.push(e) → 1 (0 before).

    applied now means the same thing on all three dialects — this op happened — and the SQLite arm reports the remainder as skipped in the same greppable sentence the in-place arm already used.

    The shape that makes this durable: honoured is filled by the same pass that fills the four column sets, with else continue sitting before honoured.push(e). So an entry is honoured iff a branch took it, and a column op added later lands in the ignored branch by default rather than being silently absorbed into applied. The card's own failure mode is now structurally prevented rather than corrected — a second list of op types next to the four branches would have re-created it one level up, in a place nothing checks.

    The naive optimization was declined for a measured reason and parked in the docblock: the rebuild still runs for the whole table even when it honours nothing, because it re-materializes defaults (#11321 / #4560) and the full declared index set (#3696). Suppressing it would be a behaviour change wearing a reporting change's clothes.

    The latent-defect problem was met by constructing the reachability — entries handed straight to the public applyMigrationEntries seam that os migrate apply and the artifact boot gate both call, substituting only the differ's dialect guard, driver and database real throughout. All five cases red on the pre-fix tree.

    Carried out: #12132 — on SQLite the rebuild runs in full even when zero entries are honourable. Filed from this seat because the dev's dedup channel was 403'd (#12123); I ran the dedup with a firing positive control.

    Closing as completed; pm:dispatched stripped in the same stroke.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions