Skip to content

feat: celld - #206

Open
patrickleet wants to merge 28 commits into
tasks--outbox-immediate-nonblocking-1from
tasks--portable-command-hosts-celld
Open

feat: celld#206
patrickleet wants to merge 28 commits into
tasks--outbox-immediate-nonblocking-1from
tasks--portable-command-hosts-celld

Conversation

@patrickleet

Copy link
Copy Markdown
Collaborator

Summary

Second command host for portable domain commands: a celld Durable Object runs the same todo-domain create/complete handle as SOA Routes::mount. Local compose uses Azurite (az://celld). The worker is workers-rs wasm around AggregateCell<Todo>. The event log and repository snapshot cache persist in Durable Object SQLite (cell_events, cell_snapshots) and survive a celld restart.

Stacked on #205 (Todo/Chat/Blob domain mounts). No celld Cargo feature (PCH-DEC-005). GraphQL/projectors stay off the cell.

Implements [[tasks/portable-command-hosts-4]] [[tasks/portable-command-hosts-5]] [[tasks/portable-command-hosts-6]] [[tasks/portable-command-hosts-7]] [[tasks/portable-command-hosts-8]] [[tasks/portable-command-hosts-9]]

Test plan

  • cargo test --lib cell_host (8 tests: dispatch, parent-shard, snapshot restore)
  • cd tests/e2e-ui && cargo test -p todo-domain (18 tests)
  • cargo test --test celld fixture path (no CELLD_URL)
  • Live: Azurite compose + worker-build + celld deploy; CELLD_URL=http://127.0.0.1:18880 cargo test --test celld — PUT/POST/GET/isolation
  • GET after docker compose restart celld still hydrates the sourced Todo (events + snapshot cache)
  • CI on this PR

Second portable-command host: CausalWorkspace talks to a per-shard
CellStreamStore (in-process stand-in for private SQLite, not sqlx and
not a celld Cargo feature). AggregateCell mounts the same PortableCommand
declarations as SOA Routes and dispatches them without GraphQL or
projectors.

Implements [[tasks/portable-command-hosts-4]]
CellStreamStore::for_parent_shard holds sibling streams (map, player,
bomb) in one cell SQLite and one CommitBatch. Bomberman tick shards by
game id (`game:{game_id}`), not player/bomb. Blob cells stay
`blob:{game_id}`. There is no two-cell transaction API.

Implements [[tasks/portable-command-hosts-5]]
One SQLite Durable Object class per todo id, official celld image via
Docker Compose. Fixture tests always run; live HTTP create/complete is
gated on CELLD_URL. No MinIO, no celld Cargo feature, no secrets.

Implements [[tasks/portable-command-hosts-6]]
Azurite is the documented local bucket (az://celld). Docker Desktop
injects extra_hosts, so celld cannot share Azurite's network namespace;
socat forwards 127.0.0.1:10000 to the azurite service.

Implements [[tasks/portable-command-hosts-6]]
Replace the JS TodoCell with a workers-rs Durable Object that mounts
todo-domain create/complete through AggregateCell. wasm32 uses a JS
Date wall clock because SystemTime::now panics on unknown-unknown.

Implements [[tasks/portable-command-hosts-7]]
CellStreamStore dumps EventRecords into the DO cell_events table and
restores them on each request. GET after celld restart still hydrates
the event-sourced Todo.

Implements [[tasks/portable-command-hosts-8]]
AggregateCell can use with_snapshots; CellStreamStore implements
SnapshotStore and get_stream_tail. Todo is Snapshottable. The worker
persists cell_snapshots next to cell_events so load after restart is
snapshot plus event tail.

Implements [[tasks/portable-command-hosts-9]]
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e08aa545-53d2-4e08-b135-beacb50ac767

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

Make Service::dispatch_causal_with_receipt callable outside crate::microsvc
and add an integration test that asserts payload plus receipt.

Implements [[tasks/portable-command-hosts-10]]
POST /{command} and gRPC Dispatch accept { commandId, input } and return
payload plus receipt. Identity comes from transport headers/metadata.
Bus::send stays fire-and-forget.

Implements [[tasks/distributed-command-surfaces-2]]
Mutations and status resolve via LocalCommandHost or HttpCommandHost.
HTTP/WebSocket request data no longer carries Arc<Service>.

Implements [[tasks/distributed-command-surfaces-3]]
graphql_router_with_dispatcher is a CommandHost; GraphQL-only
engines wait-dispatch to HTTP writers. Task 20 mTLS stays the
CMP envelope; wait-path remote is HttpCommandHost.

Implements [[tasks/distributed-command-surfaces-3]]
Persist GET sealed JSON next to events/snapshots. Todo cell POST
/{command} with { commandId, input }. GET queues behind POST on
the same isolate.

Implements [[tasks/distributed-command-surfaces-4]]
Mount store per model on the engine, not the ReadModel type.
Cell-by-key compiles PK/by-id only and rejects list/filter/join.

Implements [[tasks/distributed-command-surfaces-5]]
Named profile under tests/e2e-ui/celld-nats-profile. Default
one-process host.rs / make run is unchanged.

Implements [[tasks/distributed-command-surfaces-6]]
@patrickleet patrickleet changed the title feat: celld host for portable Todo aggregates feat: celld Aug 23, 2026
authorized_unknown_status_returns_only_public_state no longer
puts Arc<Service> in request data.

Implements [[tasks/distributed-command-surfaces-3]]
make run stays the one-process playground. Bring-up, smoke, and
teardown of celld+NATS are named targets.

Implements [[tasks/distributed-command-surfaces-6]]
Reuse a running compose NATS; if 14222 is taken by something
else, print the listener and how to override NATS_PORT.
down-celld-nats also removes a stray docker-run container.

Implements [[tasks/distributed-command-surfaces-6]]
CommandHost routers need /graphql/ws for live chat. Export
ProtocolResponseAccumulator so out-of-crate hosts can implement
CommandHost, and let wait-path clients remap payload JSON.

Implements [[tasks/distributed-command-surfaces-7]]
Sibling example of e2e-ui (not make run). New todo/chat/blob/graphql
service crates reuse the e2e-ui domain crates. Todo create/complete
go through HttpCommandHost to {CELLD_URL}/todo/{id}/{command}; SQL
lists dual-write locally so the playground UI can render.

Implements [[tasks/distributed-command-surfaces-7]]
Navbar shows a CELLD badge when PUBLIC_E2E_PROFILE=celld-nats.
make run stays the one-process playground.

Implements [[tasks/distributed-command-surfaces-7]]
PCH-DEC-001 asked for a macro beside the Routes builder. #[command]
already exists, so the function-like form is portable_command!. Todo
thin commands (complete, rename, reopen, archive, purge) expand to
shard + invoke + Eventual. create and force_archive keep handle:.

Implements [[tasks/portable-command-hosts-2]]
Chat is lobby posts only. Identity owns ingress, scrape, and the
AuthUsers projector on its own outbox leaf — not the chat aggregate.

Implements [[tasks/distributed-command-surfaces-7]]
Stack badges, portable_command! walkthrough, and both make run recipes.
Domain declarations stay the same; the celld host wait-dispatches Todo.

Implements [[tasks/distributed-command-surfaces-7]]
Wait-path returns events+outbox from the cell SQLite. GraphQL publishes
via MessagePublisher (NATS here), fire-and-forgets outbox.complete, and
seals Eventual projection metadata from those occurrences without a
second command write. Chat @LiVe stays on the GraphQL process.
e2e-ui make run is unchanged.

Implements [[chat-celld-wait-path-keeps-graphql-live]]
Implements [[tasks/distributed-command-surfaces-7]]
CelldCommandHost and cell outbox drain live in distributed::cell_host.
Aggregate crates only supply CelldRoute. GraphQL is the user OIDC edge
(engine OidcBearer); the Tower JWT-to-header layer is gone. make run
cargo-watches GraphQL and the worker.

Implements [[chat-celld-wait-path-keeps-graphql-live]]
Implements [[tasks/distributed-command-surfaces-7]]
The example host no longer falls back to sqlite:./e2e-celld.db. DATABASE_URL
comes from e2e-ui.env (make -C tests/e2e-ui up). Cells still keep private
SQLite per Durable Object.

Implements [[chat-celld-wait-path-keeps-graphql-live]]
Fence Eventual projection-delta rows so a later complete @LiVe snapshot
cannot drop them after Delivered. Skip GraphQL SSR seeds on SvelteKit
isDataRequest so client navigations use the replica; hover prefetches
the route operation in the browser.

Implements [[specs/e2e-ui/sveltekit-dx]]
Add integration-celld.yaml: e2e-celld workspace tests plus live
Azurite+celld+NATS (`make test-celld`). Wire it into the PR and main
gates so live HTTP no longer skips without CELLD_URL.

Implements [[tasks/portable-command-hosts-11]]
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