Skip to content

refactor(orchestration): add actor principal columns and dual-write (1/6) - #19943

Closed
brennanb2025 wants to merge 6 commits into
mainfrom
brennanb2025/principals-01-columns
Closed

brennanb2025 wants to merge 6 commits into
mainfrom
brennanb2025/principals-01-columns

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​614 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​614
Prod 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​280 $\color{#cf222e}{\Huge{\mathbf{−}}}$​30 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​250

Merge status

Ready to merge on its own. Not blocked on any other PR, branch or lane.

What reads these columns: one internal path, dormant in effect. orchestration-principal.ts describes itself as write-only until the resolver PRs, and that is true at the RPC boundary — run-receipt.ts still excludes the column. But it is not true inside this PR: run-coordinator-mail-routing.ts:20-21 selects COALESCE(coordinator_handle, coordinator_principal) to populate the coordinator mail-address cache. It returns nothing different today, because a handle-less coordinator cannot exist until a later PR creates one — but the read path is live, so this is a migration with one internal reader, not a free rename. (Correcting an earlier revision of this description, which claimed nothing reads them.)

What is hooked up to it:

Stacked directly on this #19949 (principals 2/6) — the first reader. Its base is this branch, so it rebases onto main once this lands.
Later in the same line #19648's CLI half, then #16859. Both sit behind #19949, not behind this PR.
Not a dependency The agent-status unification lane. This PR needs nothing from it, and nothing in it touches orchestration identity.

Execution scope is missing, and that is a follow-up in this stack, not a deferral to another lane. A session:<sessionId> principal does not record where that session runs. That is harmless today only because structuredWorkerHostScope refuses any structured session that is not local-and-non-WSL, so there is exactly one host it can be on. The moment structured chat ships on WSL or SSH that guard relaxes and the key becomes ambiguous — in a durable column, silently. So the session arm should carry the four scope fields (executionHostId, wslDistro, workspaceId, workspaceKind), and the cheap window is now, while the only reader is the internal one above.

Note for whoever does that: the fields are on AgentSessionRecord.location, but the resolver's output shape discards them — structuredWorkerHostScope collapses host and distro into {kind:'local'} | null, and workspaceKind appears nowhere in that path. Widening it is real work, not a field copy.

The pane: arm should not ship. It is wrong by construction — a pane outlives the agent in it, so a coordinator bound to pane:<paneKey> has its Run binding inherited by whatever occupies that pane next. And it is unnecessary: PTY agents already coordinate orchestration fine on terminal handles. The bug this stack exists to fix is structured-only.

So the intended end state for this PR is the session arm alone, with PTY keeping coordinator_handle / coordinator_pane_key unchanged. A single reference covering both lanes remains the goal, but it arrives when PTY has a durable agent id — which is separate work, in a separate program.

Rebase, 2026-09-16

Rebased onto main after 438 commits. Four conflicts, two of which were not mechanical:

  • The migration is now v42, not v41. main took the v41 slot for the derived-delivery migration. migrate-v41.ts here is main's file unchanged; the principal columns, trigger recreation and backfill moved to migrate-v42.ts with their guards renumbered, running after v41.
  • SCHEMA_VERSION never conflicted. Only the version-history comment did, so the constant auto-merged to 41 and would have shipped a migration that never runs. Now 42.
  • run-binding.ts needed both sides. This branch adds the principal argument; main renamed fenceOutstandingDelivery to fenceUnacknowledgedMailboxDeliveries. Taking main's side alone would have bound three arguments to a four-placeholder UPDATE — the SQL carrying coordinator_principal = ? auto-merged outside the conflict region — which typecheck cannot see.
  • The migration test retargets migrateV42 from v41. Its real-chain case still seeds a v40 stamp, so it now runs main's v41 before v42, which is the ordering a real upgrade sees.

Post-rebase validation: pnpm tc:node clean; 953 passed across src/main/runtime/orchestration plus the principal unit tests; 777 passed across src/main/runtime/rpc/methods/orchestration; anti-slop audit clean; pnpm-lock.yaml absent from every non-merge commit (main's arrived via the merge parent, byte-identical to origin/main).

Note for reviewers on the changed-code quality gate: merging 438 commits moves its baseline, so it reports findings across ~3900 files. Every finding was checked against the six files this rebase touched — the only hits are two pre-existing consistent-type-assertions lines in the migration test that this diff does not touch.

Summary

Orchestration identifies every actor — Run coordinator, dispatch assignee, worker — by a terminal handle plus a pane key. A native chat session has neither, so today it mints a fake handle and a fake pane key to participate. This is the first of six PRs replacing that key with a principal: pane:<paneKey> or session:<sessionId>, one tagged string per role.

This PR only adds the columns and writes them. Nothing reads them, and there is no behavior change — that is the acceptance bar.

What's here

  • Four nullable columns at schema v42 (was v41 before the rebase; main took that slot): runs.coordinator_principal, dispatch_contexts.assignee_principal, dispatch_contexts.creator_principal, worker_terminal_resources.principal.
  • src/shared/orchestration-principal.ts — format/parse (split at the first colon; pane keys contain one) plus principalFromPaneKey, the single place encoding both rules: NULL pane key yields NULL principal, and a structured worker's pane key classifies to session:<id> rather than pane:<key>.
  • A classifying backfill and an every-open repair that reconciles a stale pane: value, not just a NULL — a rolled-back binary can update a pane key without touching the principal. session: values are never overwritten.
  • Dual-write at all 15 existing writer sites, all routed through principalFromPaneKey / recordedCreatorIdentity so the tab-id prefix string is never re-derived.
  • The coordinator mail-address cache learns session coordinators via COALESCE(coordinator_handle, coordinator_principal) in the two remember-triggers. Dormant until a handle-less coordinator exists (PR 5).

Why classification matters

A structured worker's pane key is structured-agent-session-<sessionId>:<randomUUID> — the session id is in the tab half in plain text. Copying it into a pane: principal would mint the wrong identity: harmless here, but it strands every live structured coordinator's bound Run the moment PR 2 flips reads.

That derivation is legal only in this server-side backfill, over rows the host itself wrote. At request time it is an attack: a caller who learns a session id can fabricate structured-agent-session-<id>:<anything>, and the random leaf is the only real credential. The code carries that asymmetry as a comment; the request-time invariant belongs to PR 2.

Three defects in the original plan, fixed here

  1. Pre-v30 databases would have bricked. createTables runs before migrate, and v40's backfill inserts into runs. A trigger naming NEW.coordinator_principal in the static SQL compiles against a pre-ALTER runs and throws before v41 adds the column — orchestration fails to open for anyone upgrading from an older build. Confirmed with sqlite3. createTables now keeps the handle-only trigger text and migrate-v41 solely owns the final form; a fresh DB still reaches v41 because the skew resolver returns storedVersion, not SCHEMA_VERSION.
  2. A unique index would have stranded mail. An earlier shape gave the cache its own principal column with a (run_id, principal) partial unique index. Two handles legitimately share one pane principal, so INSERT OR IGNORE dropped the cache row for a reminted handle and its mail went undelivered. The COALESCE shape removes the whole class — one address column, keyed by the existing primary key — along with the table rebuild, its CHECK, and both indexes.
  3. A migration test asserted the wrong PR's end state. Restored to the original assertion once the shape was corrected; it passes here.

Validation

  • Orchestration + RPC + shared sweep, post-rebase: 890 files, 9373 passed, 0 failed (127 pre-existing skips).
  • Typecheck: tsc --noEmit clean on node, cli and web, run sequentially.
  • oxlint clean. No max-lines disables — structured-agent-session-projection.ts was at 294/300, so the tab-id pair moved to its own module with re-exports keeping every importer unchanged.
  • pnpm-lock.yaml absent from every commit and from the full branch range.

Notes for review

  • The coordinator cache was first built as a separate principal column with a v41 table rebuild, then simplified to the COALESCE shape below; the branch was rewritten into three coherent commits, so that detour is not in the history. It is recorded here because defect 2 is only reachable under the shape that was dropped.
  • exposeDispatchContext deliberately does not expose principals — no receipt shape changes.
  • isSelfCreatedDispatch and resolveCreatorDepth keep their handle/pane logic, so nesting-depth outcomes are identical to main. The principal-equality switch is owned by a later PR in the stack.

Merge Sim added 3 commits September 10, 2026 16:09
principalFromPaneKey is the single owner of both dual-write rules: NULL pane
key means NULL principal, and a structured worker's minted pane key classifies
to session:<sessionId> via parsePaneKey plus the new inverse
structuredAgentSessionIdFromTabId. The tab-id pair moves to its own shared
module (single source for the prefix) because the projection file sits at the
max-lines cap; the projection re-exports both names.
Migration v41 adds the four nullable principal columns and owns the
coordinator-cache trigger form: the static createTables SQL keeps the
handle-only text because it must stay compilable against a pre-v41 runs
table — a database stamped before the cache existed (< v30) would otherwise
get triggers naming coordinator_principal before the ALTER runs, and the v40
backfill's INSERT INTO runs dies at prepare. The v41 triggers cache
COALESCE(coordinator_handle, coordinator_principal): terminal_handle is a
mailbox-address column (messages.to_handle already carries run:<id>), so a
handle-less session coordinator's row matches every string-equality reader
with zero read-side change, and the cache keeps its shape.

The backfill classifies via principalFromPaneKey (a structured worker's row
gets session:<id>, session values are fenced from repair) and reruns on every
open, v40-style, to repair rows a rolled-back binary writes after
user_version is already 41. coordinator_principal joins the internal receipt
columns so it never ships on the wire.
…iter

Every writer that sets or clears a coordinator/assignee/creator/resource
handle or pane key now writes the classified principal alongside, through
principalFromPaneKey — never a blind copy, so a structured worker's minted
pane key lands as session:<sessionId>. Unbind and legacy adoption clear it.
The open-time cache repopulate widens to COALESCE the same way as the
triggers; a session coordinator's cached address reroutes its mail through
the untouched reader SQL, which the migration test pins.

Nothing reads the new columns; dispatch depth keeps its handle/pane path,
and the new dispatch-depth test pins the equal-principals-imply-self-created
invariant the resolver PR's read switch must preserve.

@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.

ℹ️ No critical issues — one minor comment inline.

Reviewed changes

  • orchestration-principal.ts (new, shared) — format/parse at the first colon plus principalFromPaneKey, the single classification rule, with an explicit security boundary (session-derivation safe server-side only; request-time invariant deferred to PR 2). Correctly scoped.
  • migrate-v41.ts + principal-column-backfill.ts — hasColumn-guarded ALTER ADD COLUMN and a COALESCE-form trigger swap that keeps the static createTables SQL compilable against pre-v41 runs; an every-open reconcile that fences out session: rows and never mints a principal from a bare handle.
  • Dual-write fan-out — coordinator_principal / assignee_principal / creator_principal / principal written at every creator/assignee/resource writer via principalFromPaneKey and recordedCreatorIdentity, with the coordinator mail-address cache learning session coordinators via COALESCE.
  • Projection + receipt isolation — row-column-lists.ts, types.ts, and version-skew columns stay in sync; exposeRun strips coordinator_principal so no receipt shape changes.
  • Tests (613 lines) — migration chain from a seeded v40 file DB, rolled-back-binary repair, session-fence, handle-less coordinator cache seam, and dual-write assertions with exact expected values (not loose >=/truthiness).

The migration reasoning held up under review: a fresh DB still runs migrateV41 because the skew resolver returns storedVersion when the post-v6 schema is complete, and the pre-v30 trigger-compilation hazard is avoided by keeping the handle-only form in static SQL. The one thing that gave me pause is a comment in the backfill that understates the every-open cost — see inline.

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

.prepare(
`SELECT id, ${paneColumn} AS pane_key, ${principalColumn} AS principal FROM ${table}
WHERE (${principalColumn} IS NULL AND ${paneColumn} IS NOT NULL)
OR ${principalColumn} LIKE 'pane:%'`

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.

The comment above overstates the steady-state cheapness: principal LIKE 'pane:%' matches every ordinary pane-keyed row, not zero, so on every open this scans and re-classifies all principal-bearing rows (and dispatch_contexts twice, once per principal column). The v40 backfill this is modeled on (home_run_id = '') genuinely returns zero rows on a healthy DB; this one does not. Worth correcting the comment so future readers aren't misled about the recurring cost, and worth considering whether the stale-pane: detection can be narrowed in SQL rather than recomputed in JS for every row.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

📝 Walkthrough

Walkthrough

The change adds pane- and session-based orchestration principal serialization, parsing, and classification. Schema version 42 adds principal columns, trigger updates, and idempotent backfill logic. Run, dispatch, worker authority, and terminal resource paths now dual-write principal values. Coordinator routing uses principal fallback values. Run receipts exclude internal principal data. Tests cover classification, persistence, migration, routing, and receipt filtering.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 01dad

Upgrade handling for valid v41 databases can unnecessarily replay historical migrations, and a rare empty stored pane key can lose its derived identity. These are bounded migration and data-reconciliation risks that should be corrected before broad rollout.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides detailed technical context and validation results, but it does not follow the required template. It omits the required Linked Issue, Visual Proof, Testing, Review, Agent skill… Restructure the description using all required template headings. Add a valid issue link, state N/A with a reason for Visual Proof if no UI change exists, complete the Testing checkboxes and platform coverage, provide the applicable AI Disc…
Docstring Coverage ⚠️ Warning Docstring coverage is 51.61% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 32 files. 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 identifies the main change: adding orchestration actor principal columns and dual-write support.
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 technical context and validation results, but it does not follow the required template. It omits the required Linked Issue, Visual Proof, Testing, Review, Agent skill upstream boundary, Notes, and Checklist sections, and it contains conflicting statements about whether the principal columns are read.

Resolution

Restructure the description using all required template headings. Add a valid issue link, state N/A with a reason for Visual Proof if no UI change exists, complete the Testing checkboxes and platform coverage, provide the applicable AI Disclosure, Agent skill boundary, Notes, and Checklist entries, and remove or correct the conflicting claim that nothing reads the columns.

  • 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 350cf9d8-d4eb-486c-a049-aa25710f5f35

📥 Commits

Reviewing files that changed from the base of the PR and between a933843 and 64e470a.

📒 Files selected for processing (31)
  • src/main/runtime/orchestration/db/contract-constants.ts
  • src/main/runtime/orchestration/db/dispatch-context/dispatch-capability.ts
  • src/main/runtime/orchestration/db/dispatch-depth.test.ts
  • src/main/runtime/orchestration/db/dispatch-depth.ts
  • src/main/runtime/orchestration/db/dispatch-row-writer.ts
  • src/main/runtime/orchestration/db/orchestration-db.ts
  • src/main/runtime/orchestration/db/principal-dual-write.test.ts
  • src/main/runtime/orchestration/db/row-column-lists.ts
  • src/main/runtime/orchestration/db/runs/run-binding.ts
  • src/main/runtime/orchestration/db/runs/run-coordinator-mail-routing.ts
  • src/main/runtime/orchestration/db/runs/run-create.ts
  • src/main/runtime/orchestration/db/runs/run-lookup.ts
  • src/main/runtime/orchestration/db/schema/adopt-legacy-run.ts
  • src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts
  • src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts
  • src/main/runtime/orchestration/db/schema/migrate-v41.ts
  • src/main/runtime/orchestration/db/schema/migrate.ts
  • src/main/runtime/orchestration/db/schema/principal-column-backfill.ts
  • src/main/runtime/orchestration/db/schema/principal-column-migration.test.ts
  • src/main/runtime/orchestration/db/worker-dispatch/worker-dispatch-authority.ts
  • src/main/runtime/orchestration/db/worker-terminal/failed-start-dispatch-identity.ts
  • src/main/runtime/orchestration/db/worker-terminal/worker-terminal-resource-store.ts
  • src/main/runtime/orchestration/orchestration-schema-version-skew.ts
  • src/main/runtime/orchestration/types.ts
  • src/main/runtime/orchestration/worker-terminal-ownership.ts
  • src/main/runtime/rpc/methods/orchestration/runs/run-receipt.test.ts
  • src/main/runtime/rpc/methods/orchestration/runs/run-receipt.ts
  • src/shared/orchestration-principal.test.ts
  • src/shared/orchestration-principal.ts
  • src/shared/structured-agent-session-projection.ts
  • src/shared/structured-agent-session-tab-id.ts

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

* backfill rule is NOT a general equivalence rule.
*/
export function principalFromPaneKey(paneKey: string | null | undefined): string | null {
if (!paneKey) {

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify that every pane-key column rejects empty strings before relying on the current falsy check.
rg -n -C 4 'CHECK|coordinator_pane_key|assignee_pane_key|creator_pane_key|pane_key' \
  src/main/runtime/orchestration/db/schema src/main/runtime/orchestration/db

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- principal helper ---'
cat -n src/shared/orchestration-principal.ts | sed -n '1,130p'

printf '%s\n' '--- backfill consumer ---'
cat -n src/main/runtime/orchestration/db/schema/principal-column-backfill.ts | sed -n '1,90p'

printf '%s\n' '--- principal references and pane-key writers ---'
rg -n -C 3 'principalFromPaneKey|coordinator_principal|assignee_principal|creator_principal|\.principal|coordinator_pane_key|assignee_pane_key|creator_pane_key' \
  src/main src/shared --glob '*.{ts,tsx}' | head -n 500

Repository: stablyai/orca

Length of output: 50369


🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings

Length of output: 31761


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant schema declarations ---'
for f in \
  src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts \
  src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts \
  src/main/runtime/orchestration/db/schema/migrate-v13-v30.ts \
  src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts
do
  echo "FILE: $f"
  rg -n -C 2 'coordinator_pane_key|assignee_pane_key|creator_pane_key|pane_key|principal' "$f" | head -n 160
done

printf '%s\n' '--- helper documentation and tests ---'
rg -n -C 5 'non-NULL|opaque|pane principal|session principal|principalFromPaneKey|pane:' \
  src test tests docs --glob '*.{ts,tsx,md}' 2>/dev/null | head -n 400

Repository: stablyai/orca

Length of output: 47889


Preserve or reject empty pane keys before principal backfill.

principalFromPaneKey maps '' to NULL, while backfillPrincipalColumns selects all non-NULL pane keys and writes that result to the principal column. The schema does not reject empty pane keys. If empty keys are supported, update both principalFromPaneKey and parseOrchestrationPrincipal so pane: round-trips; otherwise reject them before backfill and writes.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Electron QA evidence (branch brennanb2025/principals-02-resolver, head eb6aee4d5f, which contains #19943):

  • Launched Orca in background mode (ORCA_BACKGROUND_LAUNCH=1) and attached via CDP to the intended worktree (principals-electron-qa).
  • Used an isolated SQLite backup profile, reverted to a v40-shaped schema (removed the four v41 principal columns, restored the pre-v41 coordinator trigger predicates, stamped user_version=40), then launched the branch app. On open, the app completed migration; direct SQLite inspection showed PRAGMA user_version = 41, all four principal columns present, repaired coordinator triggers referencing coordinator_principal, and normal tables readable. The renderer reached the healthy Orca home screen (screenshot below).
  • This was a deliberately constructed v40-shape fixture from a backup, not a database captured from a separately installed pre-v41 binary; that historical-binary upgrade gap is not claimed.

orca-migration-v41.png

…migration to v42

Main took the v41 slot for the derived-delivery migration, so the principal-column
migration moves to v42 and runs after it.

- migrate-v41.ts is main's file unchanged; the principal columns, trigger recreation
  and backfill move to migrate-v42.ts with their version guards renumbered.
- SCHEMA_VERSION is 42. The version-history comment keeps main's per-version lines and
  records v40 and v42, which the two sides had each dropped.
- run-binding keeps the principal argument the four-placeholder UPDATE requires while
  taking main's renamed fenceUnacknowledgedMailboxDeliveries.
- The migration test targets migrateV42 from v41; its real-chain case still seeds a v40
  stamp, so it now exercises main's v41 before v42 as a real upgrade would.

@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.

Important

The v42 renumber left the principal columns listed at version: 41 in the version-skew probe, so a database stamped 41 by main's build reads as skewed and the migration chain replays from the v6 floor. Details inline.

Reviewed changes

  • Renumbered the principal migration to v42 — main's new v41 (derived outstanding deliveries) now precedes it: migrate-v41.ts → migrate-v42.ts, SCHEMA_VERSION 41 → 42, and the chain calls migrateV41 then migrateV42.
  • Merged main's derived-delivery schema — DERIVED_DELIVERY_SCHEMA_SQL is applied at the end of migrate, and callers moved to fenceUnacknowledgedMailboxDeliveries.
  • Updated the migration test to seed a v40 database, so the real chain runs main's v41 before v42 — though it does not cover a database already stamped at 41.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +47 to +50
{ version: 41, table: 'runs', column: 'coordinator_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'assignee_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'creator_principal' },
{ version: 41, table: 'worker_terminal_resources', column: 'principal' }

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.

These four principal columns are declared at version: 41, but the migration that adds them is migrateV42. The completeness probe requires a column whenever storedVersion >= declaredVersion, so a database legitimately stamped 41 (main's current build, which has no principal columns) reads as version-skewed: resolveOrchestrationMigrationStartVersion returns the v6 floor instead of 41 and the entire v6..v42 chain replays, including the historical backfills the VERSIONED_POST_V6_COLUMNS list exists to keep from re-running. The declared version must match the migration that adds the column — exactly as home_run_id is version: 40 because migrateV40 adds it.

Technical details
# Bump the principal columns to version 42 in the version-skew list

## Affected sites
- `src/main/runtime/orchestration/orchestration-schema-version-skew.ts:47-50` — four entries carry `version: 41`; the columns are created by `migrateV42`.
- Tests miss it: `principal-column-migration.test.ts` seeds a v40 database (v40 short-circuits cleanly) and `orchestration-all-start-versions-migration.test.ts` stamps a fully-migrated database whose columns are already present, so neither exercises a v41 stamp that lacks the columns.

## Required outcome
- `resolveOrchestrationMigrationStartVersion(db, 41, 42)` returns 41 for a v41 database without principal columns, so only `migrateV42` runs on upgrade.
- A regression test that drops the four columns from a migrated database, stamps `user_version = 41`, and asserts the resolved start version is 41 (not 6).

## Evidence
- Reproduced locally: build a full-schema database, drop the four principal columns and the two `trg_runs_remember_coordinator_*` triggers, `PRAGMA user_version = 41`, then `resolveOrchestrationMigrationStartVersion(raw, 41, 42)` returned `6` where `41` is expected.
Suggested change
{ version: 41, table: 'runs', column: 'coordinator_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'assignee_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'creator_principal' },
{ version: 41, table: 'worker_terminal_resources', column: 'principal' }
{ version: 42, table: 'runs', column: 'coordinator_principal' },
{ version: 42, table: 'dispatch_contexts', column: 'assignee_principal' },
{ version: 42, table: 'dispatch_contexts', column: 'creator_principal' },
{ version: 42, table: 'worker_terminal_resources', column: 'principal' }

Comment on lines +20 to +23
* Reconciles principal columns from their pane-key columns — the v40 every-open pattern: a binary
* rolled back past v41 keeps writing after user_version is already 41, inserting rows with NULL
* principals and, worse, re-pointing pane keys without touching the principal, leaving stale
* `pane:` values. Runs from migrate-v41 and from every open; idempotent by construction (a second

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.

The renumber left this docblock describing the old version: after a rollback the binary now writes while user_version is already 42, and this reconcile is invoked from migrateV42. Worth updating the v41 / migrate-v41 references so the invariant matches the migration that owns it.

Comment on lines +59 to +61
-- The principal-aware (COALESCE) trigger form is owned by migrate-v41, which every DB passes
-- through: the static SQL below must stay compilable against a pre-v41 runs table, where naming
-- NEW.coordinator_principal would break the first runs INSERT of the migration chain.

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.

This comment still names migrate-v41 / pre-v41, but the COALESCE trigger form moved to migrateV42 in the renumber (main's v41 is the derived-delivery migration). Minor, but it points readers at the wrong migration.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Record the actor principal columns as version 42. · orchestration-schema-version-skew.ts:46-50

src/main/runtime/orchestration/orchestration-schema-version-skew.ts:46-50
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Record the actor principal columns as version 42. migrateV42 adds and backfills these four columns, but hasCompletePostV6Schema currently requires them for user_version = 41. A valid v41 database without them therefore resolves to migration version 6, causing normal migration to replay earlier migrations and backfills before applying v42. Set all four entries to version 42.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 5440068c-5040-4186-ac5b-30af64aa75b9

📥 Commits

Reviewing files that changed from the base of the PR and between 64e470a and 49a7083.

📒 Files selected for processing (9)
  • src/main/runtime/orchestration/db/contract-constants.ts
  • src/main/runtime/orchestration/db/dispatch-context/dispatch-capability.ts
  • src/main/runtime/orchestration/db/runs/run-binding.ts
  • src/main/runtime/orchestration/db/runs/run-lookup.ts
  • src/main/runtime/orchestration/db/schema/migrate-v42.ts
  • src/main/runtime/orchestration/db/schema/migrate.ts
  • src/main/runtime/orchestration/db/schema/principal-column-migration.test.ts
  • src/main/runtime/orchestration/db/worker-dispatch/worker-dispatch-authority.ts
  • src/shared/structured-agent-session-projection.ts

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

…role

The rename to revertToV41Shape tripped the naming audit — "shape" describes
structure rather than what the helper does, which is undo the principal migration.

@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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 9f62b8a9-a87d-497f-bc52-bdd8dd62cf4f

📥 Commits

Reviewing files that changed from the base of the PR and between 7b46cc4 and 01dad51.

📒 Files selected for processing (31)
  • src/main/runtime/orchestration/db/contract-constants.ts
  • src/main/runtime/orchestration/db/dispatch-context/dispatch-capability.ts
  • src/main/runtime/orchestration/db/dispatch-depth.test.ts
  • src/main/runtime/orchestration/db/dispatch-depth.ts
  • src/main/runtime/orchestration/db/dispatch-row-writer.ts
  • src/main/runtime/orchestration/db/orchestration-db.ts
  • src/main/runtime/orchestration/db/principal-dual-write.test.ts
  • src/main/runtime/orchestration/db/row-column-lists.ts
  • src/main/runtime/orchestration/db/runs/run-binding.ts
  • src/main/runtime/orchestration/db/runs/run-coordinator-mail-routing.ts
  • src/main/runtime/orchestration/db/runs/run-create.ts
  • src/main/runtime/orchestration/db/runs/run-lookup.ts
  • src/main/runtime/orchestration/db/schema/adopt-legacy-run.ts
  • src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts
  • src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts
  • src/main/runtime/orchestration/db/schema/migrate-v42.ts
  • src/main/runtime/orchestration/db/schema/migrate.ts
  • src/main/runtime/orchestration/db/schema/principal-column-backfill.ts
  • src/main/runtime/orchestration/db/schema/principal-column-migration.test.ts
  • src/main/runtime/orchestration/db/worker-dispatch/worker-dispatch-authority.ts
  • src/main/runtime/orchestration/db/worker-terminal/failed-start-dispatch-identity.ts
  • src/main/runtime/orchestration/db/worker-terminal/worker-terminal-resource-store.ts
  • src/main/runtime/orchestration/orchestration-schema-version-skew.ts
  • src/main/runtime/orchestration/types.ts
  • src/main/runtime/orchestration/worker-terminal-ownership.ts
  • src/main/runtime/rpc/methods/orchestration/runs/run-receipt.test.ts
  • src/main/runtime/rpc/methods/orchestration/runs/run-receipt.ts
  • src/shared/orchestration-principal.test.ts
  • src/shared/orchestration-principal.ts
  • src/shared/structured-agent-session-projection.ts
  • src/shared/structured-agent-session-tab-id.ts

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

Comment on lines +46 to +50
{ version: 40, table: 'remote_dispatch_attachments', column: 'home_run_id' },
{ version: 41, table: 'runs', column: 'coordinator_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'assignee_principal' },
{ version: 41, table: 'dispatch_contexts', column: 'creator_principal' },
{ version: 41, table: 'worker_terminal_resources', column: 'principal' }

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,130p' src/main/runtime/orchestration/orchestration-schema-version-skew.ts
sed -n '1,100p' src/main/runtime/orchestration/db/schema/migrate-v42.ts
rg -n -C 4 "hasCompletePostV6Schema|VERSIONED_POST_V6_COLUMNS|orchestration-schema-version-skew" src/main --glob '*.ts'

Repository: stablyai/orca

Length of output: 21881


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- skew resolver ---'
cat -n src/main/runtime/orchestration/orchestration-schema-version-skew.ts | sed -n '150,225p'

printf '%s\n' '--- migration runner and version constants ---'
cat -n src/main/runtime/orchestration/db/schema/migrate.ts | sed -n '1,180p'
cat -n src/main/runtime/orchestration/db/contract-constants.ts | sed -n '1,80p'

printf '%s\n' '--- v42 and principal migration references ---'
rg -n -C 5 'migrateV42|applySchemaMigrations|SCHEMA_VERSION|resolveOrchestrationMigrationStartVersion|coordinator_principal|assignee_principal|creator_principal' src/main/runtime/orchestration --glob '*.ts' | head -n 500

printf '%s\n' '--- focused tests ---'
rg -n -C 8 'v41|v42|principal.*column|schema.*skew|incomplete|start version' src/main/runtime/orchestration/*test.ts src/main/runtime/orchestration/db/schema/*test.ts 2>/dev/null | head -n 500

Repository: stablyai/orca

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- migration entrypoints and replay-sensitive operations ---'
rg -n 'export function|function apply|function migrate|INSERT INTO|UPDATE |DELETE FROM|DROP TABLE|DROP COLUMN|ALTER TABLE|CREATE UNIQUE INDEX|CREATE INDEX' \
  src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts \
  src/main/runtime/orchestration/db/schema/migrate-v13-v30.ts \
  src/main/runtime/orchestration/db/schema/migrate-mailbox-pointer-enter-v33.ts \
  src/main/runtime/orchestration/db/schema/migrate-role-mailbox-delivery-v34.ts \
  src/main/runtime/orchestration/db/schema/migrate-v35.ts \
  src/main/runtime/orchestration/db/schema/migrate-v36.ts \
  src/main/runtime/orchestration/db/schema/migrate-v37.ts \
  src/main/runtime/orchestration/db/schema/migrate-v38.ts \
  src/main/runtime/orchestration/db/schema/migrate-v39.ts \
  src/main/runtime/orchestration/db/schema/migrate-v40.ts \
  src/main/runtime/orchestration/db/schema/migrate-v41.ts \
  src/main/runtime/orchestration/db/schema/migrate-v42.ts

printf '%s\n' '--- all-start-versions test ---'
cat -n src/main/runtime/orchestration/orchestration-all-start-versions-migration.test.ts | sed -n '1,110p'

printf '%s\n' '--- migration path assertions near v41/v42 ---'
rg -n -C 12 '41|42|principal|all prior|replay|replay.*chain|start version' \
  src/main/runtime/orchestration/orchestration-all-start-versions-migration.test.ts \
  src/main/runtime/orchestration/orchestration-version-skew-migration.test.ts \
  src/main/runtime/orchestration/*migration.test.ts | head -n 350

Repository: stablyai/orca

Length of output: 28193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- v23 resource migration block ---'
cat -n src/main/runtime/orchestration/db/schema/migrate-v13-v30.ts | sed -n '105,145p'

printf '%s\n' '--- related resource migration tests and assertions ---'
rg -n -C 10 'resource.*backfill|backfill.*resource|worker_terminal_resources|resource ownership|v23' \
  src/main/runtime/orchestration --glob '*.ts' | head -n 300

Repository: stablyai/orca

Length of output: 36993


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 20 'backfillWorkerTerminalResources' src/main/runtime/orchestration --glob '*.ts'

Repository: stablyai/orca

Length of output: 14120


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n src/main/runtime/orchestration/db/worker-terminal/worker-terminal-resource-store.ts | sed -n '14,75p'

Repository: stablyai/orca

Length of output: 2703


Register the principal columns as v42 requirements. migrateV42 creates all four principal columns. With the current version 41 entries, hasCompletePostV6Schema marks a valid v41 database without those columns as incomplete, so the resolver returns 6. The migration runner can then replay the full v6-to-v42 chain, including earlier backfills, instead of starting at v41. Mark all four requirements as version 42.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Superseded by #22522, the first PR of a fresh orchestration stack for structured-session coordinators.

The design changed after this PR was written, so almost no line of it survives a rework:

  • The actor key is now the Orca session id alone, spelled session:<id>, with no pane arm. A pane outlives the agent in it, so a pane-keyed actor would be inherited by the next occupant.
  • There is no credential. All agents run on one machine as one user, so the identity is the injected session id.
  • Columns are named …_actor; principal already names a richer authority record in this subsystem.

#22522 ports this PR's hard-won migration reasoning (createTables before migrate, the trigger DROP+CREATE, the real-chain tests). It also fixes a defect found here: the skew probe registered the new columns at version 41 while migrating at 42, so a v41 database replayed the whole chain from v6. Closing in favour of #22522.

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