Skip to content

perf(orchestration): bound mutation ledger and run pages - #11432

Merged
AmethystLiang merged 5 commits into
mainfrom
perf/bound-orchestration-ledger-and-index-dispatch-lookup
Jul 30, 2026
Merged

AmethystLiang merged 5 commits into
mainfrom
perf/bound-orchestration-ledger-and-index-dispatch-lookup

Conversation

@nwparker

@nwparker nwparker commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

Bounds durable orchestration mutation receipts without weakening replay protection: completed receipts expire after 30 days or are evicted oldest-first above 10,000 rows, while unresolved receipts are never evicted and new mutations fail closed if they alone fill the ledger. Adds stable cursor pagination to listRuns() and the run-list RPC/CLI with a 100-row maximum page.

Bumps the orchestration schema from v21 to v22 and adds an idempotent dispatch_contexts(assignee_handle) index. The migration runs transactionally on populated databases and preserves existing dispatch rows.

ELI5

Orca keeps a receipt for each important orchestration action so retrying the same action cannot accidentally do it twice. Finished receipts now age out or make room for newer ones, but unfinished receipts are always kept; if there are too many unfinished actions, Orca stops accepting new ones rather than forgetting safety information. Run history is delivered a page at a time, and the database now has a lookup shortcut for finding work assigned to a terminal.

Review remediation

What changed

  • Applied the existing mutation-receipt capacity gate inside the atomic createRemoteDispatchAttachment() transaction before its receipt insert. Completed rows are pruned to make room; a ledger containing 10,000 pending rows now rejects the attachment without creating either record.
  • Added regression coverage for both remote-attachment capacity branches and retained the existing mutation idempotency, replay, and resetAll() receipt-preservation behavior.
  • Made the legacy worker restart E2E exhaust every orchestration.runList cursor page before locating the recovered Run. A repository-wide search found no other orchestration.runList caller that searched only the default page.
  • Reworked the pagination regression to seed same-timestamp Runs directly. Its red phase therefore reaches listRuns() instead of failing in createRun().

ELI5

Remote worker starts write safety receipts, and that receipt drawer now follows the same size limit as every other write path: old finished receipts can make room, but unfinished ones are never discarded. Run history can arrive in several boxes, so recovery now opens every box before deciding the Run is missing.

Proof of fix

The remote-attachment red phase reconstructed the PR tip before this remediation, copied in the new test, and failed for both missing capacity behaviors:

git archive fad961f0b674f4b10d225d8de5f6389a8c7492c7 | tar -x -C "$RED_ATTACHMENT"
git show 6c8c9a7335364b2c40dbc1c341cf04903fbbcec9:src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts > "$RED_ATTACHMENT/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts"
ln -s "$WORKTREE/node_modules" "$RED_ATTACHMENT/node_modules"
(cd "$RED_ATTACHMENT" && pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts -t 'remote attachment')

Result: exit 1; 1 file failed, 2 tests failed, 6 skipped. The completed-row case retained 10,001 rows instead of 10,000, and the all-pending case did not throw mutation_ledger_full.

The pagination red phase reconstructed production before pagination, copied in the new test, and reached the listRuns() result assertion:

git archive fad961f0b674f4b10d225d8de5f6389a8c7492c7^ | tar -x -C "$RED_PAGINATION"
git show 6c8c9a7335364b2c40dbc1c341cf04903fbbcec9:src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts > "$RED_PAGINATION/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts"
ln -s "$WORKTREE/node_modules" "$RED_PAGINATION/node_modules"
(cd "$RED_PAGINATION" && pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts -t 'Run pagination')

Result: exit 1; 1 file failed, 1 test failed, 7 skipped. The intended failure was TypeError: Cannot read properties of undefined (reading 'length') at page.runs.length, proving listRuns() was called and returned the pre-pagination array shape.

Green at pushed SHA 6c8c9a7335364b2c40dbc1c341cf04903fbbcec9:

pnpm exec vitest run --config config/vitest.config.ts \
  src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts \
  src/main/runtime/orchestration/orchestration-reset-db.test.ts \
  src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts \
  src/main/runtime/rpc/methods/orchestration.test.ts \
  src/main/runtime/rpc/methods/orchestration-federation-control-mail.test.ts \
  src/main/runtime/rpc/methods/orchestration-federated-message-targeting.test.ts \
  src/main/runtime/rpc/methods/orchestration-federation-setup.test.ts \
  src/cli/handlers/orchestration-run-cli.test.ts \
  src/shared/orchestration-rpc-contract.test.ts

Result: 9 files passed, 232 tests passed.

Proof of no regression

pnpm run typecheck

Result: passed all three TypeScript projects.

pnpm exec oxlint \
  src/main/runtime/orchestration/db.ts \
  src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts \
  tests/e2e/orchestration-legacy-worker-restart-recovery.spec.ts

Result: passed with no findings. The green suite above includes adjacent receipt/reset, worker attachment, federation RPC, Run RPC/CLI, and shared-contract coverage. The v22 idempotent assignee-index migration, receipt replay semantics, and deliberate resetAll() receipt preservation remain unchanged. No screenshots apply because this remediation has no UI changes.

Backward-compatible Run listing

What changed

  • Chose omitted pagination as the compatibility signal: orchestration.runList / OrchestrationDb.listRuns({}) now returns the complete legacy result when both limit and cursor are absent. Requests that provide either pagination field remain bounded to the shared 100-Run maximum.
  • Updated the CLI and legacy restart-recovery page drainer to send limit: 100 explicitly. The drainer treats a missing nextCursor from an older server as completion, so version-skewed SSH/relay calls cannot loop.
  • Added regressions at the RPC boundary for old-client → new-server skew and at the paginating caller boundary for new-client → old-server skew. Mutation-receipt capacity behavior and the v22 dispatch_contexts(assignee_handle) index were not changed.

ELI5

Older clients ask for Run history without saying how many they want, so the server now gives those clients the whole list just as it did before. New clients explicitly ask for boxes of 100 Runs, and if an older server does not provide a “next box” marker, they stop instead of waiting forever.

Proof of fix

Both regressions were run against PR head 6c8c9a7335364b2c40dbc1c341cf04903fbbcec9 before the production fix.

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 exec vitest run --config /Users/nwparker/orca/workspaces/orca/prod-scan-07-29/config/vitest.config.ts /Users/nwparker/orca/workspaces/orca/prod-scan-07-29/src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts

Old client → new server red result: exit 1; 1 file failed, 1 test failed. An RPC request with {} saw 100 Runs instead of all 102 (expected length 102, received 100).

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 exec vitest run --config /Users/nwparker/orca/workspaces/orca/prod-scan-07-29/config/vitest.config.ts /Users/nwparker/orca/workspaces/orca/prod-scan-07-29/tests/e2e/orchestration-run-pagination.unit.test.ts

New client → old server red result: exit 1; 1 file failed, 1 test failed. The old-server response omitted nextCursor; the caller terminated after one request, but the assertion proved it had sent {} instead of explicitly opting into { limit: 100 }.

At pushed SHA 25b83c8c05e521ff0f1d53354db1d625668e5f49:

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 exec vitest run --config /Users/nwparker/orca/workspaces/orca/prod-scan-07-29/config/vitest.config.ts src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts src/main/runtime/orchestration/orchestration-reset-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/rpc/methods/orchestration.test.ts src/main/runtime/rpc/methods/orchestration-federation-control-mail.test.ts src/main/runtime/rpc/methods/orchestration-federated-message-targeting.test.ts src/main/runtime/rpc/methods/orchestration-federation-setup.test.ts src/cli/handlers/orchestration-run-cli.test.ts src/shared/orchestration-rpc-contract.test.ts tests/e2e/orchestration-run-pagination.unit.test.ts

Result: 11 files passed, 235 tests passed, including both skew directions.

Proof of no regression

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 run typecheck

Result: passed all three TypeScript projects.

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 exec oxlint src/cli/handlers/orchestration-run-cli.test.ts src/cli/handlers/orchestration.ts src/main/runtime/orchestration/db.ts src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts src/main/runtime/rpc/methods/orchestration-runs.ts src/shared/orchestration-run-pagination.ts tests/e2e/orchestration-legacy-worker-restart-recovery.spec.ts tests/e2e/orchestration-run-pages.ts tests/e2e/orchestration-run-pagination.unit.test.ts

Result: 9 changed files passed with no findings.

pnpm --dir /Users/nwparker/orca/workspaces/orca/prod-scan-07-29 run check:max-lines-ratchet

Result: passed; 354 grandfathered suppressions, no new bypasses. The focused orchestration suite still covers receipt retention/replay and the v22 dispatch-assignee index migration; neither implementation was modified by this compatibility fix.

Scan provenance

P2 — “Unbounded mutation_receipts + unpaginated listRuns(), and dispatch_contexts full-table scan.” Introduced by #9925 (cd05f2ff93); it re-entered this release through the non-linear span because revert bf27227437 exists only on the v1.4.161 side. Span-side touches: #11107 and #11150.

Made with Orca 🐋

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f035d234-686b-480f-966d-9e2c9f55ea0c

📥 Commits

Reviewing files that changed from the base of the PR and between 25b83c8 and 7d1f05c.

📒 Files selected for processing (3)
  • src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts
  • src/main/runtime/rpc/methods/orchestration-runs.ts
  • src/main/runtime/rpc/methods/orchestration.test.ts

📝 Walkthrough

Walkthrough

Orchestration run listing now supports validated limit and cursor parameters, cursor-based database pagination, and CLI continuation output. Mutation receipt storage now prunes completed records, enforces capacity during mutation and worker-dispatch creation, and exposes mutation_ledger_full errors. The database schema advances to version 22 with a dispatch assignee index and migration coverage. Tests cover pagination, retention, error handling, migration, and updated run response shapes.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the main change and testing, but it does not follow the repository's required template exactly. Add the missing template sections: Screenshots/no visual change, AI Review Report, Security Audit, and Notes, or map the current content into those headings.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main change: bounding orchestration mutation storage and paginated run listing.
✨ Finishing Touches
📝 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: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 35e1d253-e22f-4ea0-8a16-480d42c22eaf

📥 Commits

Reviewing files that changed from the base of the PR and between 24a2acc and fad961f.

📒 Files selected for processing (10)
  • src/cli/handlers/orchestration-run-cli.test.ts
  • src/cli/handlers/orchestration.ts
  • src/cli/specs/orchestration.ts
  • src/main/runtime/orchestration/db.ts
  • src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts
  • src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts
  • src/main/runtime/orchestration/orchestration-reset-db.test.ts
  • src/main/runtime/rpc/errors.ts
  • src/main/runtime/rpc/methods/orchestration-runs.ts
  • src/main/runtime/rpc/methods/orchestration.test.ts

Comment thread src/cli/handlers/orchestration.ts Resolved
Comment thread src/main/runtime/orchestration/db.ts Resolved
@greptile-apps

greptile-apps Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

Bounds the durable mutation receipt ledger to 10,000 rows (age-evicting completed rows ≥ 30 days old, then oldest-completed eviction, then fail-closed when only unresolved rows remain) and adds stable keyset-cursor pagination to listRuns() with a 100-row maximum page size. The remediation also addresses the previously flagged missing capacity gate in createRemoteDispatchAttachment and bumps the orchestration schema to v22 with an idempotent dispatch_contexts(assignee_handle) index.

  • ensureMutationReceiptCapacity() is inserted inside the existing BEGIN IMMEDIATE / catch-ROLLBACK blocks in all three write paths (beginMutationReceipt, createStartingWorkerDispatch, createRemoteDispatchAttachment), so eviction DELETEs and the guarded INSERT are atomic.
  • listRuns() returns all rows when called with an empty params object (backward compat for old clients), and bounded keyset pages otherwise; cursor_invalid was already in STRUCTURED_RUNTIME_PASSTHROUGH_CODES prior to this PR, and mutation_ledger_full is newly registered.
  • The E2E legacy-worker restart recovery and new unit tests covering both client/server skew directions confirm the pagination drainer handles old servers (absent nextCursor) without looping.

Confidence Score: 5/5

Safe to merge. All three mutation-receipt write paths are guarded within transactions, and the fail-closed behavior is tested end-to-end including the previously missing remote-attachment path.

The capacity gate is correctly positioned inside existing BEGIN IMMEDIATE / catch-ROLLBACK blocks in every write path, so a full pending ledger fails atomically without partial state. The keyset pagination tie-breaking (created_at DESC, id DESC) is consistent between the WHERE clause and the ORDER BY. Backward compatibility for old clients is explicit and regression-tested in both directions.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
src/main/runtime/orchestration/db.ts Core change: adds ensureMutationReceiptCapacity() called within existing BEGIN IMMEDIATE transactions in all three write paths, and keyset-paginated listRuns() with backward-compat empty-params path. Logic is correct and transaction-safe.
src/main/runtime/rpc/errors.ts Adds mutation_ledger_full to STRUCTURED_RUNTIME_PASSTHROUGH_CODES; cursor_invalid was already present at line 74 pre-existing.
src/main/runtime/rpc/methods/orchestration-runs.ts Expands RunListParams with optional bounded limit/cursor fields; handler passes params directly to listRuns(), which handles the legacy empty-object path internally.
src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts New comprehensive test file covering age pruning, count-capped eviction, fail-closed pending-full ledger, remote-attachment capacity, keyset pagination with same-timestamp tie-breaking, and v22 migration idempotency.
tests/e2e/orchestration-run-pages.ts Page-draining utility; correctly sends explicit limit: 100 and treats absent nextCursor from an older server as end-of-list, preventing infinite loops on version-skewed connections.
src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts Tests malformed cursor rejection and the old-client to new-server case (empty-params RPC returns all rows including the legacy run).
tests/e2e/orchestration-run-pagination.unit.test.ts Tests new-client to old-server compat: verifies listAllOrchestrationRuns sends { limit: 100 }, terminates after one response when nextCursor is absent.
src/cli/handlers/orchestration.ts Updated run-list handler to forward limit/cursor flags and default to ORCHESTRATION_RUN_PAGE_LIMIT; output appends cursor hint when more pages exist.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant RPC as orchestration.runList
    participant DB as OrchestrationDb.listRuns()

    C->>RPC: "{} old client no pagination"
    RPC->>DB: "listRuns({})"
    Note over DB: limit===undefined and cursor===undefined
    DB-->>RPC: runs all, nextCursor null
    RPC-->>C: runs all, nextCursor null

    C->>RPC: limit 100 new client page 1
    RPC->>DB: listRuns limit 100
    Note over DB: LIMIT 101, hasMore=true, encode cursor from row 99
    DB-->>RPC: runs r1-r100, nextCursor base64url
    RPC-->>C: runs r1-r100, nextCursor base64url

    C->>RPC: limit 100 cursor base64url
    RPC->>DB: listRuns limit 100 cursor base64url
    Note over DB: WHERE created_at less than or equal cursor
    DB-->>RPC: runs r101 plus, nextCursor null
    RPC-->>C: runs r101 plus, nextCursor null
Loading

Reviews (4): Last reviewed commit: "fix(orchestration): reject malformed run..." | Re-trigger Greptile

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

⚠️ Outside diff range comments (1)
src/main/runtime/orchestration/db.ts (1)

2307-2330: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Missing supporting index for listRuns keyset pagination.

The runs table (Lines 291-301) has no index on created_at/id beyond the id primary key. The new listRuns query orders by created_at DESC, id DESC and filters via (created_at, id) keyset comparison — without a matching composite index, SQLite must do a full table scan + temp b-tree sort for every page request (including the very first page), which defeats the scalability goal of adding cursor pagination as the runs table grows (it has no retention/eviction cap unlike mutation_receipts).

🚀 Proposed fix: add a covering index for the pagination sort/filter
       CREATE TABLE IF NOT EXISTS runs (
         id                    TEXT PRIMARY KEY,
         objective             TEXT NOT NULL,
         home_database         TEXT NOT NULL DEFAULT 'this_database',
         coordinator_handle    TEXT,
         coordinator_pane_key  TEXT,
         consumer_generation   INTEGER NOT NULL DEFAULT 0,
         legacy                INTEGER NOT NULL DEFAULT 0,
         created_at            TEXT NOT NULL DEFAULT (datetime('now')),
         updated_at            TEXT NOT NULL DEFAULT (datetime('now'))
       );
+
+      CREATE INDEX IF NOT EXISTS idx_runs_created_id ON runs(created_at, id);

And in the current < 22 migration block:

       if (current < 22) {
         this.db.exec(`
           CREATE INDEX IF NOT EXISTS idx_dispatch_assignee_handle
             ON dispatch_contexts(assignee_handle);
+          CREATE INDEX IF NOT EXISTS idx_runs_created_id
+            ON runs(created_at, id);
         `)
       }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6091c065-35ef-4c12-be2e-418929a370dc

📥 Commits

Reviewing files that changed from the base of the PR and between fad961f and 6c8c9a7.

📒 Files selected for processing (3)
  • src/main/runtime/orchestration/db.ts
  • src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts
  • tests/e2e/orchestration-legacy-worker-restart-recovery.spec.ts

@AmethystLiang
AmethystLiang merged commit 64a1269 into main Jul 30, 2026
1 check passed
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
)

* perf(orchestration): bound mutation ledger and run pages

Co-authored-by: Orca <help@stably.ai>

* fix(orchestration): close retention pagination gaps

* fix(orchestration): preserve unpaginated run listing

Co-authored-by: Orca <help@stably.ai>

* fix(orchestration): reject malformed run cursors

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
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.

2 participants