135 lines
7.1 KiB
Markdown
135 lines
7.1 KiB
Markdown
# Remora API implementation contract
|
|
|
|
These files are mutable implementation artifacts. They describe the current
|
|
HTTP operations and WebSocket frames but are not authoritative over
|
|
[`Remora.tla`](../protocol/Remora.tla) or
|
|
[`architecture.md`](../spec/architecture.md). Conflicts are corrected here.
|
|
|
|
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, or payload
|
|
returns `precondition_failed` without mutation. First observation is accepted
|
|
only when the corresponding `JobProtocol` action is enabled. Later eligibility
|
|
changes do not alter a stored response, while a fresh operation is evaluated
|
|
against current eligibility.
|
|
|
|
Renewal follows `RenewLease`: it may extend an existing live coordination lease
|
|
after durable authority changes. It never creates or restores a claim. Progress
|
|
and outcome operations require current fenced authority and return
|
|
`precondition_failed` for stale or non-authoritative tuples.
|
|
|
|
Activation returns the live lease expiry and a recommended renewal time. Renewal
|
|
returns the corresponding updated projection. These timestamps schedule worker
|
|
behavior; the coordination store remains authoritative for whether the lease is
|
|
live.
|
|
|
|
A successful claim returns the attempt authority and the attempt's
|
|
`ExecutionEnvelope`. The worker verifies that their job, attempt, fence, and
|
|
worker incarnation agree before source or target I/O. Resolved-source and
|
|
checkpoint descriptors identify durable streamable state; they are not
|
|
reconstructed from coordination data.
|
|
|
|
Resolved-source and checkpoint references carry their descriptor kind, schema
|
|
version, byte length, and SHA-256. The service accepts them only when their kind,
|
|
ownership, integrity, and fenced attempt authority match the mutation.
|
|
|
|
[`RemoraApiMutation`](../protocol/RemoraApiMutation.tla) models this durable
|
|
operation ledger around an abstract successful `JobProtocol` effect. It does
|
|
not redefine job phases or attempt-authority transitions. Its `Rejected`
|
|
response abstracts the endpoint-specific `idempotency_conflict` and
|
|
`precondition_failed` codes enforced by the executable contract.
|
|
|
|
## 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.
|
|
|
|
Credential creation uses a closed material shape selected by `kind`; rotation
|
|
must use the existing kind's shape. Custom HTTP headers use lowercase canonical
|
|
names and reject connection-nominated, routing, framing, cookie, proxy, and
|
|
authentication headers.
|
|
|
|
## 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.
|
|
|
|
Before hard deletion, job reads return the complete `job` projection. Once only
|
|
the permanent minimal record remains, authorized reads and deleted-job pages use
|
|
the distinct `tombstone` projection rather than inventing discarded job fields.
|
|
|
|
A deletion whose exact remaining work cannot progress because target access or
|
|
configuration is unavailable enters `blocked` with a redacted problem. It
|
|
retains the deletion obligation and can return to active processing when the
|
|
dependency is restored. Owner access remains authorized through the concrete
|
|
target retained in the tombstone.
|
|
|
|
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; 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.
|
|
|
|
`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
|
|
HTTP snapshot revision and fetches a new authorized snapshot when it is newer.
|
|
It advances its applied revision only from that snapshot. Matching progress may
|
|
be displayed provisionally while the fetch is pending.
|
|
|
|
`deletion_changed` is likewise an invalidation. Its state is a display hint;
|
|
the deletion resource remains the authoritative HTTP projection.
|
|
|
|
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.
|