Skip to content

feat(meta): DO-based role arbiter replacing Neon cc_node_leases - #131

Merged
chitcommit merged 2 commits into
mainfrom
feat-do-coordinator
Aug 3, 2026
Merged

feat(meta): DO-based role arbiter replacing Neon cc_node_leases#131
chitcommit merged 2 commits into
mainfrom
feat-do-coordinator

Conversation

@chitcommit

@chitcommit chitcommit commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Why

ADR-001 elects a meta-orchestrator leader across chittymini-01..06 using Neon cc_node_leases. Two things are true on the live system today:

  • The daemon runs nowhere. No chittycommand systemd unit and no daemon/loop process on chittyserv-vm. The Worker plane being up masks it — the interface is live while the always-on coordinator is not, so "brain never stalls" is currently false.
  • The fleet it floats across is 6/7 offline. chittymini-01..06 last seen ~7 days ago. chittymini-00 is the operator seat and must not hold persistent infra. A leader designed to float freely has nowhere to float.

A Durable Object is already a strongly-consistent, single-threaded singleton. Using one does not reimplement leader election — it removes the need for it, along with the fleet dependency and the Neon lease table. Neon cost pressure makes this favourable; it would be the right shape regardless.

What

  • meta/coordinator.tsCommandCoordinator DO arbitrating role leases in DO storage
  • meta/lease-types.ts — runtime-free vocabulary shared by workerd and Node
  • daemon/coordinator-lease.ts — drop-in client, same four exports and signatures as daemon/leader.ts, so daemon/loop.ts switches by changing one import
  • Route at /api/meta/coordinator/*, behind the existing authMiddleware

Fails closed with POLICY_BLOCKED_COORDINATOR_UNAVAILABLE rather than making an unarbitrated local decision, which would permit split-brain. Wire semantics are a 1:1 port of the SQL, preserving both prior findings: session ownership on heartbeat (codex-p2 PR#101 finding-5) and on release (finding-2).

Nodes keep a role as executors that pull work — justified by needing local filesystem and repo access. Leader election never was that reason.

Validation

  • 24 tests in real workerd against real DO storage. No mocks.
  • npx tsc --noEmit clean
  • esbuild --platform=node bundles the daemon client clean (regression check — see below)

Separated adversarial review

Reviewed by a different agent in a fresh context, prompted to break it. Split-brain, SQL semantic parity, and auth came back clean (concurrent-claim trials through the real HTTP path, clause-by-clause SQL comparison, 401/200 auth confirmation). Three real defects found and fixed in b71ac24:

Sev Defect Fix
P1 Client imported a value from meta/coordinator.ts, pulling cloudflare:workers into a Node process. The documented one-line switch would crash the daemon at module load — before main(), before any log line. tsc passed; failure is runtime/bundle-only. Extracted meta/lease-types.ts
P2 COORDINATOR_URL set without COORDINATOR_TOKEN sent unauthenticated requests → permanent 401 retry loop that logs like a transient blip Both required, fail closed
P3 Greedy /^.*\/coordinator/ matched the last occurrence, so /a/coordinator/release dispatched release Anchored on first segment

Plus a corrupt leaseExpiresAt parsing to NaN left the role permanently unclaimable (no SQL analogue — Postgres typed the column).

The review's sharpest finding was that my original 13 tests covered the class and skipped exactly the seam where P1 lived. Tests went 13 → 24, adding the full HTTP surface. One test (extends the expiry on a valid heartbeat) was vacuous — it claimed at 1s and heartbeat at 60s, so the assertion held by construction; rewritten.

Both new regression tests were mutation-checked: reverting each fix makes exactly that test fail.

Scope

daemon/leader.ts and all Tier-5 Neon usage are untouched. daemon/loop.ts still imports ./leader — this PR makes the switch possible, it does not make it. Migrating the Tier-5 app off Neon (~20 files, postgresqlsqlite) is a separate decision that should be made on spend data.

Known, not fixed here

daemon/loop.ts:277-295 logs exec_heartbeat_lost on takeover without aborting the in-flight dispatch, so a demoted leader finishes its current intent alongside the new one. Pre-existing, unchanged by this PR, and unreachable from the DO — but it is the one real split-brain path.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cz2Px2ekaArQVnCjz3RT1x

Summary by CodeRabbit

  • New Features

    • Added coordinator-backed leadership leases with claim, heartbeat, release, and status operations.
    • Enforced lease ownership, expiration handling, role isolation, and configurable lease durations.
    • Added authenticated coordinator API routing and durable storage support.
  • Bug Fixes

    • Invalid or missing coordinator configuration now fails safely.
    • Expired and incomplete lease records are handled reliably.
  • Tests

    • Added comprehensive integration coverage for lease behavior, validation, routing, and persistence.

chitcommit added 2 commits August 3, 2026 02:06
ADR-001 elected a meta-orchestrator leader across chittymini-01..06 using
Neon `cc_node_leases`. Two problems with that in practice:

  - The daemon runs nowhere. No systemd unit, no daemon/loop process on
    chittyserv-vm. The Worker plane being up masks it: the interface is
    live while the always-on coordinator is not.
  - The fleet it floats across is 6/7 offline (chittymini-01..06 last seen
    ~7d). chittymini-00 is the operator seat and must not hold persistent
    infra, so a leader designed to float has nowhere to float.

A Durable Object is already a strongly-consistent, single-threaded
singleton. Using one does not reimplement leader election — it removes the
need for it, along with the fleet dependency and the Neon lease table.
Neon cost pressure makes this favourable; it would be the right shape
regardless.

CommandCoordinator (meta/coordinator.ts) arbitrates role leases in DO
storage. daemon/coordinator-lease.ts is a drop-in client exporting the same
four functions with identical signatures, so daemon/loop.ts switches by
changing one import. It fails closed with
POLICY_BLOCKED_COORDINATOR_UNAVAILABLE rather than making an unarbitrated
local decision, which would permit split-brain.

Wire semantics are a deliberate 1:1 port of the SQL, preserving both prior
review findings: session ownership required on heartbeat (codex-p2 PR#101
finding-5) and on release (finding-2).

Nodes keep a role as executors that pull work — justified by needing local
filesystem and repo access. Leader election never was that reason.

Tests: 13 cases in real workerd against real DO storage, no mocks. Covers
exclusion, idempotent re-claim, expiry takeover, both session-ownership
rejections, heartbeat extension, release/reclaim, role isolation, lease
clamping, and validation.

Scope: daemon/leader.ts and the Tier-5 Neon usage are untouched. Migrating
those is a separate decision that should be made on spend data.
Separated review (silent-failure-hunter, fresh context) found three real
defects plus a vacuous test. Split-brain, SQL semantic parity, and auth
came back clean and are unchanged.

P1 — daemon/coordinator-lease.ts could not load on Node. It imported
META_LEADER_ROLE (a value) from meta/coordinator.ts, which evaluates
`cloudflare:workers`. The documented one-line switch in daemon/loop.ts
would have crashed the daemon at module load, before main() and before any
log line: no leader claimed, intent queue silently stopped. tsc passed
because the failure is runtime/bundle-only. Extracted the runtime-free
meta/lease-types.ts; both sides import from it.
  Verified: `esbuild --platform=node` fails on the old code with
  `Could not resolve "cloudflare:workers"` and succeeds on the new.

P2 — fail-open on partial config. COORDINATOR_URL set with no
COORDINATOR_TOKEN sent unauthenticated requests, drawing a 401 that
loop.ts logs as a claim error and retries forever — a permanently dead
daemon whose logs read like a transient auth blip. Both values are now
required, failing closed with POLICY_BLOCKED_COORDINATOR_UNAVAILABLE.

P3 — greedy path regex. /^.*\/coordinator/ matched the LAST occurrence, so
`/api/meta/coordinator/a/coordinator/release` dispatched `release` from a
path that does not name it. Anchored on the first segment.

Also: a corrupted leaseExpiresAt parsed to NaN, and every NaN comparison is
false, leaving the role permanently unclaimable — a fail-closed deadlock
with no SQL analogue, since Postgres typed the column. Unparseable now
reads as expired.

Tests 13 → 24. The review's sharpest point was that the suite covered the
class and skipped the seam where P1 lived, so this adds a full HTTP surface
suite: path parsing, method switch, 404s, malformed-body 400, the bare-null
describe signal, and the {released} envelope the client unwraps. Plus
MIN-clamp, release-by-other-node, omitted-vs-null sessionId, and corrupt-
expiry recovery.

`extends the expiry on a valid heartbeat` was vacuous — it claimed at 1s and
heartbeat at 60s, so the assertion held by construction and would have
passed against an implementation computing expiry from claimedAt. Rewritten
to use identical leaseSeconds on both calls.

Both new regression tests were mutation-checked: reverting each fix makes
exactly that test fail.

Kept on review advice: claimedAt preserved across takeover. Not a defect —
it reads as "when this role was first continuously held" — and it is
load-bearing, since the client returns null on falsy claimedAt. Comment
corrected to say so.

Not fixed here (pre-existing, outside this diff): daemon/loop.ts:277-295
logs exec_heartbeat_lost on takeover without aborting the in-flight
dispatch, so a demoted leader finishes its current intent alongside the new
one. That is the one real split-brain path and it is unreachable from the
DO.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
chittycommand b71ac24 Aug 03 2026, 02:18 AM

@chitcommit
chitcommit merged commit c979f6a into main Aug 3, 2026
9 of 11 checks passed
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
  1. @coderabbitai review
  2. @copilot review
  3. @codex review
  4. @claude review
    Adversarial review request: evaluate security, policy bypass paths, regression risk, and merge-gating bypass attempts.

@chitcommit
chitcommit deleted the feat-do-coordinator branch August 3, 2026 02:18
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e483cb2c-b159-4131-82df-5de738840844

📥 Commits

Reviewing files that changed from the base of the PR and between d1ac49a and b71ac24.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • daemon/coordinator-lease.ts
  • meta/coordinator.ts
  • meta/lease-types.ts
  • package.json
  • src/index.ts
  • tests/workers/coordinator.test.ts
  • tests/workers/env.d.ts
  • vitest.workers.config.mts
  • wrangler.jsonc

📝 Walkthrough

Walkthrough

Changes

Coordinator-backed lease management

Layer / File(s) Summary
Shared lease contracts
meta/lease-types.ts, meta/coordinator.ts, daemon/coordinator-lease.ts
Defines lease roles, duration limits, stored lease data, claim inputs, and client-facing lease types.
Durable Object lease state
meta/coordinator.ts
Adds claim, heartbeat, release, and describe operations with durable storage, ownership checks, expiry handling, routing, and JSON errors.
Worker route and lease client
src/index.ts, wrangler.jsonc, daemon/coordinator-lease.ts
Registers the CommandCoordinator Durable Object, adds the authenticated coordinator route, and adds bearer-authenticated client operations.
Worker test execution and validation
tests/workers/*, vitest.workers.config.mts, package.json
Adds Cloudflare worker test execution and integration coverage for lease behavior, validation, HTTP responses, and routing. _

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LeaseClient
  participant WorkerRoute
  participant CommandCoordinator
  participant DurableObjectStorage
  LeaseClient->>WorkerRoute: Send authenticated lease operation
  WorkerRoute->>CommandCoordinator: Forward request to meta-orchestrator
  CommandCoordinator->>DurableObjectStorage: Read or update stored lease
  DurableObjectStorage-->>CommandCoordinator: Return lease state
  CommandCoordinator-->>WorkerRoute: Return JSON result
  WorkerRoute-->>LeaseClient: Return coordinator response
Loading

Possibly related PRs

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-do-coordinator

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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