Skip to content

test(orchestration): open real shipped-schema DBs, not the current one restamped - #19817

Open
Jinwoo-H wants to merge 3 commits into
mainfrom
test/orchestration-shipped-schema-fixtures
Open

Jinwoo-H wants to merge 3 commits into
mainfrom
test/orchestration-shipped-schema-fixtures

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1324 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1324
Prod 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3

What the user notices

Before: a user upgrading from v1.4.180/190/198 opens Orca and the orchestration DB migrates.
If a migration forgot an ALTER TABLE, or its guard was wrong for the version the user is actually
on, nothing in CI noticed: user_version is still stamped 40, so the app believes it is current.
The user finds out when a query hits the missing column — orca orchestration check or a federated
worker's control mail fails at runtime, on their machine, with a no such column error and no path
back. That is the mechanism behind the #19542 class: every layer asserted against state it had
built itself, so it could not see a state a real release had written.

After: eight SQLite files that v1.4.180, v1.4.190, v1.4.198 and v1.4.199 actually wrote are
checked in, and a test opens each with current code on every run of the unit suite. A migration
that forgets a column, narrows a CHECK, drops a row, loses a Run/Task/Dispatch id, flips a read
flag, guards itself wrongly for the incremental upgrade path, or is not idempotent on the second
open fails at PR time instead of on a user's machine. Nothing about the running product changes;
this PR is test surface only.

Two variants per tag, because a release wrote two states

The same release produced two on-disk states that migrate down different paths:

Variant File Start version current code resolves
unbound-mail <tag>.sqlite 6 at v1.4.180/190/198 — a direct message between two plain terminals sits under run_legacy_local, so the skew probe distrusts the stamp and replays the whole chain
no-unbound-mail <tag>-no-unbound-mail.sqlite the stored stamp (25 / 29 / 30 / 40) — only the tail migrations run

The second is the path most upgrading users are on, and the only one that can catch a migration
whose guard is right when replayed from the bottom and wrong when entered at the stored version.
Proof 3 below is exactly that bug.

The delete question

Nothing was deleted, and I looked hard at the obvious candidate.

orchestration-all-start-versions-migration.test.ts is the weak test this replaces in spirit, and
the brief allowed replacing it. I kept it: it walks every integer stamp from 0 to
SCHEMA_VERSION in about half a second, which eight binary fixtures can never afford. It is weak in
exactly one dimension — it builds the current schema and only rewrites user_version, so a
forgotten column is already present. The new test is strong in exactly that dimension and narrow in
versions. They are complements, and a comment in the old file now says so and names its counterpart
rather than leaving a future reader to rediscover the overlap.

I did not add a second map/ledger beside anything: the whole change is a fixture directory, one
generator, and one test. No product code is touched.

Proof

1. The gate is green on the branch, twice

$ ./node_modules/.bin/vitest run --config config/vitest.config.ts src/main/runtime/orchestration/db/shipped-schema-fixtures.test.ts
 RUN  v4.1.11 /Users/jinwoo/stably/orca/.claude/worktrees/agent-a686469d3a4391277
 Test Files  1 passed (1)
      Tests  33 passed (33)
   Start at  19:37:59
   Duration  864ms (transform 235ms, setup 31ms, import 279ms, tests 449ms, environment 0ms)

$ ./node_modules/.bin/vitest run --config config/vitest.config.ts src/main/runtime/orchestration/db/shipped-schema-fixtures.test.ts
 RUN  v4.1.11 /Users/jinwoo/stably/orca/.claude/worktrees/agent-a686469d3a4391277
 Test Files  1 passed (1)
      Tests  33 passed (33)
   Start at  19:38:04
   Duration  749ms (transform 192ms, setup 31ms, import 223ms, tests 374ms, environment 0ms)

2. A migration that forgets a column: caught ONLY here

Scratch state (local, reverted with git checkout -- <file>): add a column to
create-core-tables-sql.ts so fresh installs get it, and write no migration — the exact bug the
restamp test cannot see.

   pointer_pty_id TEXT,
-  pointer_process_incarnation TEXT
+  pointer_process_incarnation TEXT,
+  scratch_proof_only_column TEXT
 );
$ ./node_modules/.bin/vitest run --config config/vitest.config.ts src/main/runtime/orchestration
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.180 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.180 (unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.190 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.190 (unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.198 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.198 (unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.199 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.199 (unbound-mail) to the schema a fresh install creates

 Test Files  1 failed | 115 passed | 2 skipped (118)
      Tests  8 failed | 924 passed | 10 skipped (942)

924 other orchestration tests stay green, including
orchestration-all-start-versions-migration.test.ts. That class of regression currently has zero
coverage anywhere else in the repo.

3. A migration guard that is wrong only on the incremental path: caught ONLY by the new variants

Scratch state: change migrateV39's guard from current >= 39 to current >= 25. Replaying from
the v6 floor still runs it; entering at a stored 25/29/30 skips it, and the
worker_terminal_archives CHECK is never widened.

$ ./node_modules/.bin/vitest run --config config/vitest.config.ts src/main/runtime/orchestration
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.180 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.190 (no-unbound-mail) to the schema a fresh install creates
 FAIL  …/shipped-schema-fixtures.test.ts > migrates v1.4.198 (no-unbound-mail) to the schema a fresh install creates
AssertionError: expected { …(104) } to deeply equal { …(104) }

- Expected
+ Received

    "checks worker_terminal_archives": [
-     "CHECK(kind IN ('transcript_pin', 'terminal_tail', 'structured_journal'))",
+     "CHECK(kind IN ('transcript_pin', 'terminal_tail'))",
    ],

 Test Files  1 failed | 115 passed | 2 skipped (118)
      Tests  3 failed | 929 passed | 10 skipped (942)

Exactly the three old no-unbound-mail fixtures. All four unbound-mail fixtures stay green,
because they replay from 6 and never take that branch — which is why the amendment was needed.
Before this run the fingerprint compared table_info only, and that edit was invisible to the
entire suite (932 passed); the fingerprint now scans each table's DDL for CHECK clauses,
because table_info does not report them.

4. The brief's migrate-v40 edit: red here, and honestly, red elsewhere too

Scratch state: delete the ALTER TABLE … ADD COLUMN home_run_id from migrate-v40.ts.

 FAIL  …/shipped-schema-fixtures.test.ts > (9 cases across the three pre-v40 tags)
 FAIL  …/schema/federated-home-run-migration.test.ts > backfills a pre-upgrade attachment with a stub home Run that keeps its mailbox
 FAIL  …/nested-worker-depth-migration.test.ts > (3 cases)
Error: no such column: home_run_id

$ ./node_modules/.bin/vitest run … orchestration-all-start-versions-migration.test.ts
 Test Files  1 passed (1)
      Tests  1 passed (1)

The all-start-versions test stays green, as the brief predicted. But two existing tests
(federated-home-run-migration, nested-worker-depth-migration) also go red, because they
hand-fabricate a pre-v40 remote_dispatch_attachments with ALTER TABLE … DROP COLUMN. So the v40
edit is not unique evidence for this PR; proofs 2 and 3 are. Reporting both rather than only the
flattering one.

Wire / SSH / folder workspace

  • Wire: unaffected. Nothing a client and host exchange changes; no RPC param, stream opcode, or
    published field is touched. The fixtures do contain a remote_dispatch_attachments row written by
    a pre-v40 host, which is the storage side of a federated attachment, and the test asserts v40
    still backfills its home_run_id — the on-disk half of the fix(orchestration): accept a missing Run id from older federation coordinators #19689 failure. The wire half is PR 3.
  • SSH: unaffected, and not simulated. The orchestration DB is owned by the execution host and
    opened locally by whichever runtime owns it; an SSH host runs the same OrchestrationDb on its own
    filesystem, so this gate covers an SSH host's DB exactly as it covers a local one. Nothing here
    reports on, stops, or lists remote work, so no liveness vocabulary is involved.
  • Folder workspace: unaffected. The orchestration DB lives in the Orca profile, not in a
    worktree or repo, so nothing here assumes a git workspace. The generator uses git worktree only
    to check out release tags of the Orca repo itself when regenerating fixtures — that is a
    maintainer action, never something a user's workspace shape reaches.
  • Cross-platform: the test copies fixtures to os.tmpdir() with path.join and reads
    user_version from the file header rather than opening the committed file (a WAL open leaves
    -wal/-shm beside it even read-only, on every platform). .gitattributes marks
    tests/fixtures/orchestration-db/*.sqlite binary so a Windows checkout cannot eol-mangle them.
    The generator is Node-only (import.meta.dirname, esbuild's JS API, spawnSync('git', …) with
    windowsHide) with no shell and no macOS assumption.

Testing

Gate Result
node config/scripts/run-typecheck-projects-in-parallel.mjs (pnpm tc) exit 0
./node_modules/.bin/tsc --noEmit -p config/tsconfig.node.json --composite false exit 0, run directly because the parallel wrapper has hidden CI-only failures before
fixture test 33 passed, run twice (output above)
src/main/runtime/orchestration (whole layer) 116 files passed, 932 passed / 10 skipped
pnpm run check:code-quality:changed 0 new finding(s) across 4 changed file(s) … passed since aac38d698ff7
pnpm run check:react-doctor:changed 0 issues
oxlint on the changed files clean
oxfmt --check on the changed files All matched files use the correct format
pnpm run check:max-lines-ratchet OK — 7 grandfathered suppression(s), no new bypasses
pnpm run check:ts-nocheck-ratchet OK — 171 grandfathered file(s), no new suppressions
git diff --check clean
git status clean; no stray -wal/-shm beside the fixtures after a test run
git diff --cached --stat fixtures show as Bin 0 -> N bytes

Vitest was run directly as ./node_modules/.bin/vitest run --config config/vitest.config.ts …
rather than through pnpm test, which re-verifies the lockfile and triggers a node-pty rebuild in
the node_modules shared by every PR worktree.

No lint disables, no @ts-ignore/@ts-expect-error. Largest new file is the test at 377 lines
(cap 800); the two .mjs are 154 and 144 lines.

No Playwright spec is added, so the e2e build steps do not apply.

…e restamped

orchestration-all-start-versions-migration.test.ts creates the CURRENT schema and rewrites
user_version backwards, so a migration that forgets its ALTER TABLE passes: the column was
already there. The hand-written old-schema tests only fabricate the columns their own case
cares about, so they miss it too. #19542 was a different mechanism, but the same shape --
every layer asserted against state it had built itself.

These four fixtures were written by v1.4.180, v1.4.190, v1.4.198 and v1.4.199 running their
own OrchestrationDb in a throwaway worktree, so a forgotten column is genuinely absent. The
test asserts a migrated file reaches the same schema a fresh install creates, that every Run,
Task, Dispatch, message and federated attachment still resolves, that the legacy graph is
adopted exactly once, and that a second open changes nothing.

Keeps the all-start-versions test: it covers every integer stamp, which binary fixtures cannot
afford. The two are complements, and a comment there now says so.
…ored-version start path

The first eight cases all entered migration at the v6 floor. Unbound direct mail sits under
run_legacy_local at every tag up to v1.4.198, so hasConsistentLegacyAdoption distrusts the
stamp and the whole chain replays -- which means the incremental path, where the stored 25/29/30
is trusted and only the tail migrations run, had no coverage at all. That is the path most
upgrading users are on, and the one a wrong migration guard breaks.

Each tag now also has a fixture without that message, and a case asserting the start version
resolved from the raw file BEFORE OrchestrationDb opens it: the v6 floor when a legacy graph is
present, the stored stamp when it is not. Changing migrateV39's guard to `current >= 25` -- a bug
only the incremental path can see -- turns exactly the three old no-unbound-mail fixtures red and
leaves 929 tests green, including all four unbound-mail ones.

Also fixes a hole that edit exposed in the schema fingerprint: table_info does not report CHECK
constraints, so a skipped CHECK-widening rebuild was invisible. The fingerprint now scans each
table's DDL for CHECK clauses with balanced parentheses.
@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

📝 Walkthrough

Walkthrough

Adds eight release-generated orchestration SQLite fixtures from four tags, with two historical mail variants per tag. Adds a generator and population module that record fixture metadata and expected rows. Adds migration tests for schema fingerprints, data access, legacy graph adoption, version resolution, reopening, and fixture immutability. Marks fixture files as binary and documents their generation and migration behavior.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 6e01c

The change is test-only and current fixtures remain valid, but aligning the duplicate adoption-count assertions would prevent confusing failures when fixtures evolve.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides detailed change context, rationale, testing evidence, and compatibility analysis. However, it omits several required template sections, including ELI5, Why, Linked Issue with … Add the missing template sections. Complete the required Linked Issue entry, state N/A with a reason for Visual Proof if applicable, document AI disclosure when required, complete the Review, Agent skill upstream boundary, Notes, and Checkl…
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 4 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: testing migrations against real shipped-schema orchestration databases.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides detailed change context, rationale, testing evidence, and compatibility analysis. However, it omits several required template sections, including ELI5, Why, Linked Issue with a completed issue number, Visual Proof or explicit N/A, Review, Agent skill upstream boundary, Notes, and the Checklist.

Resolution

Add the missing template sections. Complete the required Linked Issue entry, state N/A with a reason for Visual Proof if applicable, document AI disclosure when required, complete the Review, Agent skill upstream boundary, Notes, and Checklist sections, and include a concise ELI5 and Why section.

Full details: Docstring Coverage

Explanation

Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 4 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/runtime/orchestration/db/shipped-schema-fixtures.test.ts (1)

368-371: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the two legacy_adoptions expectations.

Although all current fixtures set legacyAdoptionsCount to 0, line 317 includes this value while line 371 ignores it. If a fixture records a nonzero value, the assertions can disagree. Use the same expression in both places.

♻️ Proposed alignment
-        expect(adoptions, `${tag} legacy_adoptions rows`).toBe(carriesLegacyGraph ? 1 : 0)
+        expect(adoptions, `${tag} legacy_adoptions rows`).toBe(
+          expected.legacyAdoptionsCount + (carriesLegacyGraph ? 1 : 0)
+        )

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: de2a66cf-43d0-4e89-979a-79e6a64e873b

📥 Commits

Reviewing files that changed from the base of the PR and between aac38d6 and 6e01cbe.

📒 Files selected for processing (15)
  • .gitattributes
  • src/main/runtime/orchestration/db/shipped-schema-fixtures.test.ts
  • src/main/runtime/orchestration/orchestration-all-start-versions-migration.test.ts
  • tests/fixtures/orchestration-db/README.md
  • tests/fixtures/orchestration-db/generate-fixtures.mjs
  • tests/fixtures/orchestration-db/manifest.json
  • tests/fixtures/orchestration-db/populate-fixture.mjs
  • tests/fixtures/orchestration-db/v1.4.180-no-unbound-mail.sqlite
  • tests/fixtures/orchestration-db/v1.4.180.sqlite
  • tests/fixtures/orchestration-db/v1.4.190-no-unbound-mail.sqlite
  • tests/fixtures/orchestration-db/v1.4.190.sqlite
  • tests/fixtures/orchestration-db/v1.4.198-no-unbound-mail.sqlite
  • tests/fixtures/orchestration-db/v1.4.198.sqlite
  • tests/fixtures/orchestration-db/v1.4.199-no-unbound-mail.sqlite
  • tests/fixtures/orchestration-db/v1.4.199.sqlite

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ One minor observation — nothing blocking. This is a genuinely valuable complement to orchestration-all-start-versions-migration.test.ts, and the core mechanism is sound.

Reviewed changes

  • Fixture test (shipped-schema-fixtures.test.ts) — opens 8 real SQLite files written by v1.4.180/190/198/199 and asserts the migrated schema fingerprint equals a fresh install's (sorted columns, CHECK clauses, indexes/triggers/views), alongside row addressability and legacy-adoption idempotency on reopen.
  • Fixture corpus + manifest (tests/fixtures/orchestration-db/) — 8 .sqlite files, a manifest.json pinning each file's sha256 and expected ids/counts, and two Node generators that reproduce them from each tag's own OrchestrationDb.
  • .gitattributes — marks the fixture *.sqlite files as binary.

The key assertion expect(schemaFingerprint(db.db)).toEqual(freshSchemaFingerprint()) is an exact, sorted comparison that genuinely fails on a forgotten column or a CHECK narrow — I ran the file locally (33 passed) and the mechanism is confirmed by the PR's own red/red proof. This covers the class of migration regressions the current restamp test cannot.

ℹ️ Row-dropping migrations are still unobserved

The PR description lists "drops a row" among what this test catches, but no assertion actually reads the row counts. manifest.json records expected.tableRowCounts for every fixture and the Fixture type declares it, yet the test never asserts against it — and databaseShape only compares first-open vs reopen (idempotency), so a migration that deletes rows in a table no addressability probe touches (e.g. mutation_receipts, deliveries) passes silently. Either assert the stable subset of post-migration counts against the manifest, or drop the field and soften the claim.

Technical details
# Unused row-count data leaves "drops a row" uncovered

## Affected sites
- `src/main/runtime/orchestration/db/shipped-schema-fixtures.test.ts:42` — `tableRowCounts: Record<string, number>` declared, never read by any assertion.
- `tests/fixtures/orchestration-db/manifest.json` — `expected.tableRowCounts` populated for all 8 fixtures.
- `tests/fixtures/orchestration-db/populate-fixture.mjs` — collects the counts via `tableRowCounts(db)`.

## Required outcome
- A migration that deletes rows in a table not covered by the addressability probes should fail the test, OR the "drops a row" claim and the unused `tableRowCounts` field should be removed.

## Suggested approach (optional)
- Assert post-migration counts only for tables whose count is unaffected by the replayed migrations (e.g. `messages`, `tasks`, `dispatch_contexts`, `remote_dispatch_attachments`, `mutation_receipts`); exclude known-shifting tables (`runs`, `legacy_adoptions`, `run_coordinator_handles` seeding).

## Open questions for the human (optional)
- Is per-table row-count fidelity worth the maintenance cost of regenerating counts whenever a future migration legitimately mutates rows?

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant