Repository navigation
Spec: the core owns the request table; shells carry one generic server request - #64
Merged
Merged
Conversation
… request table
The six request-specific effects become ServerRequest { id, method, path,
bearerToken?, body? } and the seven settle commands become ServerResponse
{ id, status, body }. The request table (endpoints, bodies, response
parsing, outcome routing) lives in cascade-core's new server module.
Requests get ids from a non-persisted counter; the pending account request
and the in-flight sync remember theirs and responses are routed by id.
DeleteListeningData and DeleteAccount carry newDeviceId, adopted only on a
confirmed delete; RequestSignInLink takes an optional platform. The account
and listening-sync suites settle with ServerResponse, with new tests for
the table, status 0, unparseable 2xx, a negative total and unknown ids.
ADR 0001 decision 5 is amended.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mark the ADR's earlier decision-5 bullets as superseded by the issue #58 amendment, word RequestSignInLink's platform per the glossary, rename the response classification to StatusClass and Request::bearer to bearer_only, give the ledger an awaits(id) check, and share the listening-push predicate (PUT only) in the test support module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core: one ServerRequest effect and one ServerResponse command own the request table
The DTOs mirror the core's new wire: one serverRequest effect in place of the six request effects, one serverResponse command in place of the seven settles, newDeviceId on deleteListeningData / deleteAccount (a fresh UUID made when the user asks) and the optional platform on requestSignInLink. ServerCarrier replaces the typed SyncApi client and its response types: it sends any request to the sync-server base URL as described and returns the serverResponse that settles it, status 0 with no sync server or on a network error, timeout or cancellation. AppStore carries every request, the sign-out revoke included, and dispatches the settle on the main actor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shell's DTOs mirror the core's new wire: one ServerRequest effect in place of the six request effects, one ServerResponse command in place of the seven settles, newDeviceId on DeleteListeningData / DeleteAccount (a fresh UUID when the user asks) and the optional platform on RequestSignInLink. The RequestEffect sealed interface is gone. SyncApi and its response types are replaced by ServerHttp, which sends any ServerRequest verbatim (method, base URL + path, bearer, JSON body) and settles it with the status and body, or status 0 for no sync server, a network error, a timeout or a cancel mid-flight. SyncManager hands every request to it with no per-request branching; the revoke is settled too. JVM unit tests cover the adapter against a local HTTP server and the request wire; Android CI now runs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web shell's types mirror the core's new wire: one serverRequest effect in place of the six request effects, one serverResponse command in place of the seven settles, newDeviceId on deleteListeningData and deleteAccount (a fresh UUID made when the user asks), and the optional platform on requestSignInLink. The carrier sends any serverRequest as described (method, base URL plus path, bearer token, JSON body verbatim, keepalive on the pagehide flush) and settles it with the HTTP status and body, or status 0 with no sync server, on a network error or after a 30 s timeout. The sign-out revoke is settled too. The typed sync client is deleted; only the base URL and syncAvailable remain, in sync/config.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #57 puts a shell test harness out of scope, so the JVM tests, their JUnit dependency and the CI step are removed. The adapter launches its carry atomically so a request cancelled before it starts is settled too. RequestSignInLink's platform is worded as the core words it, SyncManager no longer also calls itself the one carrier, and the response body gets its own name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web shell: carry every server request through one HTTP adapter
The shell's DTOs mirror the core's new wire: ServerRequest replaces the six request effects, ServerResponse the seven settle commands, the delete commands carry newDeviceId and RequestSignInLink an optional platform. SyncServer.CarryAsync sends any ServerRequest verbatim (method, base URL + path, Bearer token, JSON body) and answers with its ServerResponse: status and body, or 0 for no sync server, a network error, a timeout or a cancellation. The typed SyncApi client and its response types are gone; SyncConfig stays. The view model routes ServerRequest by an explicit case, settles every request (the sign-out revoke included) on the UI thread via the dispatcher queue, and sends platform "windows" for the link hand-off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android shell: carry every server request through one HTTP adapter
Apple shell: carry every server request through one HTTP adapter
Windows shell: carry every server request through one HTTP adapter
parse_verified returns the account's Session instead of a bare (token, email) pair, and begin_listening_sync asserts that the push carries the id it handed the ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web names its ServerResponse command and types the carrier's settle with it; web and Windows say 'the platform whose app' the sign-in link hands off to; Windows leaves a null platform off the wire, as the core does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
The core now owns the request table. Each shell just carries one generic request.
flowchart LR subgraph Core[cascade-core] direction TB table[server.rs: request table<br/>path, method, body, parsing] --> route[route ServerResponse by id<br/>+ 401 rule] end Core -->|"ServerRequest { id, method, path, bearerToken?, body? }"| Adapter[one HTTP adapter per shell<br/>web · Android · Apple · Windows] Adapter -->|"ServerResponse { id, status, body }"| Core Adapter --> Server[(sync server)]serverTotalMsis read as a signed integer and clamped at 0. A 2xx response whose body does not parse is a plain failure.Evidence
Before: the request rules lived in four shells and had no tests. On web and Android, a negative server total was rejected. On Windows, an unhandled request effect was dropped and its in-flight guard never cleared.
After:
cargo test -p cascade-corepasses every suite. account has 50 tests and listening_sync has 36. New tests lock the table and the routing, for example:These also pass, as checked during review:
cargo check -p cascade-uniffi -p cascade-wasmnpm run wasm && tsc -b(web)The Android, Apple and Windows builds were not run in this review environment. Those shells are verified by building them and checking sign-in, sync and delete by hand, as the Spec sets out.
Merge Danger
Door: two-way
The core and all four shells move to the new wire together, and nothing persisted changes shape: request ids are never stored. Reverting the merge restores the old pair of effect and command families.
Blast Radius: accounts
Sign-in, listening sync, sign-out and both deletes go through the new adapter on every platform. A shell whose DTO drifts from the core's wire would break account features on that platform, but playback is unaffected.
Unaddressed findings
Standards
account::Pendingandaccount::PendingRequestnames are unclear (Mysterious Name). Skipped. Both are internal to the core and their doc comments separate id + request from kind, so a rename would be churn.ServerHttp,ServerCarrier,SyncServer,carrier.ts), andSyncConfiglives in different places. Skipped. Each name follows its own shell's conventions, and renaming three shells we can't build here adds risk for no behaviour change.methodis a bare string in the Android and Windows DTOs (Primitive Obsession). Skipped. The adapters pass it straight to the HTTP stack, and an enum would add deserialization plumbing on two shells that can't be built here.UUID().uuidStringinline inAccountControlsViewrather than in anewDeviceId()helper (Duplicated Code). Skipped. These are two one-line call sites in one view, and the Apple shell can't be built here._dispatcher.TryEnqueueignores afalsereturn. Skipped. It only fails while the UI dispatcher shuts down, and at that point the core and its in-flight guards go away with the process.Spec
api.tsdid not have (scope creep). Kept. Without it, a hung fetch would never be settled and would hold the core's in-flight guard forever. That would break "every request is settled".sessionTokencounts as an invalid link (scope creep). Kept. An empty token is no session, and treating it as one would persist an unusable account.ServerRequest.methodas aString. Skipped. This is the same reason as the Standards finding above.TryEnqueuecan drop a settle during shutdown. Skipped. This is the same reason as the Standards finding above.Closes #57
Tickets
🤖 Generated with Claude Code