feat: celld - #206
Merged
patrickleet merged 37 commits intoAug 24, 2026
Merged
Conversation
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]]
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
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]]
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]]
Pack-smoke now lists matchDistributedRoute. Snapshot tail loads clamp prefix to the durable stream so a planted-ahead cache misses and replays. CausalDispatchResult/OutboxMessage implement PartialEq so graphql lib tests compile. Chat Send no longer stays disabled while Eventual projected is still catching up. Implements [[tasks/portable-command-hosts-11]]
Keep snapshot-only SQLite loads when event rows were deleted; clamp prefix only when a stream version exists so planted-ahead cache still misses. Tail-only hydrate keeps post-snapshot events in memory. Eventual `projected` settles when a committed result frame names the command (or has no command payload), even if membership fences keep the list overlay. Chat Send is disabled only while busy so it re-enables after projected with an empty composer. Implements [[tasks/portable-command-hosts-11]]
Settling projected whenever a live frame named the command made status-regression tests miss their rejection. Query/@LiVe frames have no command payload; those still settle so chat Send can clear busy while membership fences keep the overlay. Implements [[tasks/portable-command-hosts-11]]
Atomic direct projection was taking Eventual list membership fences. Blob, new games, and client-side navigations never get @LiVe, so those fences rejected later complete snapshots and the UI waited seconds. Eventual chat still fences list membership so stale complete @LiVe cannot drop a posted row. Chat Send stays disabled while busy or empty; tests wait for enabled after fill so Playwright binds the draft. Implements [[chat-celld-wait-path-keeps-graphql-live]] Implements [[tasks/portable-command-hosts-11]]
Postgres listen/subscribe drained to idle and rebuilt Service on every quiet stretch, so chat projection lagged several seconds. Long-running hosts now idle-poll instead of exiting the consumer. Zitadel scrape treated duplicate outbox ids as done and skipped the directory upsert. Those events never reached bus_log, so auth_users stayed empty and chat author names disappeared. Scrape now writes auth_users from the Management API profile even when outbox already has the delivery. Implements [[chat-celld-wait-path-keeps-graphql-live]] Implements [[tasks/portable-command-hosts-11]]
6 tasks
Keep long-running consumers alive across idle polls, route every Todo transition to one cell, persist fenced cell command replays, enforce CellByKey row policies, and run the real browser lifecycle in celld CI. Refs [[incidents/pr-206-e2e-ui-command-latency-1]]
Trust exact authenticated projection deltas instead of refetching solely because they have no obligations. Preserve conservative revalidation for unconditional recovery cases, cover rapid Todo transitions, and keep newly-created Todo controls pending until the durable receipt arrives.
Install the anonymous public Chat client during client-side route entry even when SvelteKit omits data-request hydration. Atomically seal locally provable collection membership from authoritative direct command rows so Blob start is visible without refresh, while leaving unprovable membership stale.
patrickleet
merged commit Aug 24, 2026
84c1576
into
tasks--outbox-immediate-nonblocking-1
14 checks passed
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second command host for portable domain commands: a celld Durable Object runs the same
todo-domaincreate/completehandleas SOARoutes::mount. Local compose uses Azurite (az://celld). The worker is workers-rs wasm aroundAggregateCell<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
celldCargo 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 celldfixture path (noCELLD_URL)worker-build+celld deploy;CELLD_URL=http://127.0.0.1:18880 cargo test --test celld— PUT/POST/GET/isolationdocker compose restart celldstill hydrates the sourced Todo (events + snapshot cache)