Implement replay-safe browser synchronization
This commit is contained in:
parent
06a7ac56b7
commit
459ffa04c6
2 changed files with 2734 additions and 25 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -384,10 +384,35 @@ paths:
|
|||
$ref: '#/components/responses/NotFound'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/event-tickets:
|
||||
post:
|
||||
operationId: createEventTicket
|
||||
description: >-
|
||||
Creates an independent short-lived, single-use browser WebSocket
|
||||
credential bound to the authenticated principal and request origin.
|
||||
This ephemeral authentication exchange does not enter the public
|
||||
domain mutation idempotency ledger.
|
||||
responses:
|
||||
'201':
|
||||
description: Event ticket created. The response is not cacheable.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EventTicket'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/events:
|
||||
get:
|
||||
operationId: connectEvents
|
||||
security:
|
||||
- oidc: []
|
||||
- eventTicket: []
|
||||
parameters:
|
||||
- name: ticket
|
||||
in: query
|
||||
description: Short-lived single-use browser credential created by createEventTicket. Native clients may use OIDC bearer authentication instead.
|
||||
schema:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
- name: cursor
|
||||
in: query
|
||||
description: Opaque authorization-bound replay cursor from an HTTP snapshot or prior frame.
|
||||
|
|
@ -643,6 +668,10 @@ components:
|
|||
openIdConnectUrl: https://identity.invalid/.well-known/openid-configuration
|
||||
serviceAuth:
|
||||
type: mutualTLS
|
||||
eventTicket:
|
||||
type: apiKey
|
||||
in: query
|
||||
name: ticket
|
||||
parameters:
|
||||
IdempotencyKey:
|
||||
name: Idempotency-Key
|
||||
|
|
@ -1619,6 +1648,16 @@ components:
|
|||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
EventTicket:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [ticket, expires_at]
|
||||
properties:
|
||||
ticket:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
ApiError:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
|
|
|||
Loading…
Reference in a new issue