remora/docs/spec/api/README.md

77 lines
4 KiB
Markdown

# Remora API contract semantics
OpenAPI `3.2.0` describes HTTP operations and JSON Schema Draft 2020-12
describes WebSocket frames. The API contract version is `1.0.0`; additive
fields require clients to tolerate unknown response fields only after a
contract version that permits them, while incompatible changes use a new major
path or media type.
## Idempotency
Every public 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`.
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
deletion and cancellation semantics still use this request identity.
Internal claim uses job ID plus `operation_id` because no attempt exists yet.
Renew, progress, and outcome use attempt ID, fencing token, worker incarnation,
and `operation_id`. Each operation identity retains a request fingerprint. An
exact replay has the original effect; a changed worker, fence, payload, stale,
or non-authoritative tuple returns `precondition_failed` without mutation.
## Authorization and errors
Public operations apply the authorization rules in the architecture. List is
filtered before pagination. A missing and an unauthorized resource both return
`not_found`; callers cannot probe job existence. Cursors and event replay are
bound to the authenticated subject and filter.
`ApiError` fields are an allowlist. Messages and field diagnostics never carry
credential values, bearer material, URL userinfo, raw upstream response bodies,
internal stack traces, or unredacted configured endpoints.
## Jobs, pages, and results
`Job.phase` uses the exact `JobProtocol` phase vocabulary; this contract does
not define phase transitions. `lifecycle`, current authoritative attempt,
current matching progress, reusable-checkpoint projection, result, and failure
are separate fields. Progress is present only when its attempt ID and fence
match the authoritative attempt in the same projection.
Job pages order by `(created_at DESC, id DESC)`. The first request fixes a
membership snapshot, authorization/filter fingerprint, and high-water mark.
`next_cursor` is an opaque integrity-protected keyset cursor for that snapshot;
new jobs do not enter later pages. Invalid, altered, cross-subject, or
cross-filter cursors return the corresponding structured cursor error.
A result endpoint returns only a visible `CommittedResult`. Tombstoned,
unauthorized, absent, and never-committed results are not exposed. The hard
delete endpoint returns the durable deletion resource established by the
architecture.
Worker failure outcomes accept only attempt-reportable runtime classes and do
not accept a caller-supplied retryable flag. The service derives the durable
disposition from the class mapping in the architecture. Validation,
cancellation, and lost-attempt projection use their dedicated boundaries.
## WebSocket replay
An HTTP job or list snapshot carries `event_cursor` at its consistency
watermark. The client connects to `/v1/events` with that cursor. Server frames
are ordered by increasing `sequence` for a connection and carry an opaque next
cursor. Delivery is at least once; clients deduplicate event IDs and ignore a
job revision no newer than the applied revision.
Replay applies current authorization before delivery. If a cursor is absent,
invalid, expired, authorization-incompatible, or no longer available after
coordination loss, the server sends `resync_required`; the client obtains a new
HTTP snapshot and reconnects from its cursor. Progress frames for a stale
attempt/fence are suppressed before emission and ignored defensively by the
client.