Implement replay-safe browser synchronization

This commit is contained in:
ms2k 2026-07-18 00:16:21 +09:00
commit 459ffa04c6

View file

@ -13,13 +13,17 @@ path or media type.
## Idempotency
Every public mutation requires `Idempotency-Key`. The identity is caller
Every public domain mutation requires `Idempotency-Key`. The identity is caller
subject, operation, and key. The server canonicalizes the semantic request,
including the authorization target, with RFC 8785 and stores its SHA-256
fingerprint and complete response atomically with the mutation. The default
retention is 24 hours and is never shorter than the advertised
`Idempotency-Expires-At`.
Event-ticket issuance is an ephemeral authentication exchange rather than a
domain mutation. Each retry independently mints a short-lived credential; it
does not refine a `JobProtocol` effect or enter the public mutation ledger.
The same identity and fingerprint replays the original status, resource, and
body after duplicate or ambiguous delivery. A different fingerprint returns
`idempotency_conflict` and cannot affect either request. Naturally idempotent
@ -118,7 +122,15 @@ An HTTP job or list snapshot carries `event_cursor` at its consistency
watermark. The client connects to `/v1/events` with that cursor; there is no
subscription frame. Server frames are ordered by increasing `sequence` for a
connection and carry an opaque next cursor. Delivery is at least once, and
clients deduplicate event IDs.
clients deduplicate event IDs. Each connection starts at sequence one and
increments it by one for every frame, including heartbeats and replayed event
IDs; a client resnapshots when it observes a gap.
Browser clients first create a short-lived, single-use event ticket through an
OIDC bearer-authenticated HTTP request. The server stores only its digest and
binds it to the authenticated principal and exact allowed origin. Redemption
atomically consumes it during the WebSocket handshake. Native clients may
authenticate that handshake directly with the OIDC bearer header.
`job_changed` is an invalidation with an attempt-aware display hint, not a
resource replacement or patch. A client compares `job_revision` with the last