Files

5.5 KiB

W02 error, idempotency, and fencing contract

This document is part of the agent.v1 project-owned baseline. It does not change the SaaS/MQ contract.

Transport and domain errors

Handlers return normal gRPC status codes and, when a response message exists, put the stable FailureCode in Failure.code as well:

FailureCode gRPC status Retry rule
INVALID_ARGUMENT InvalidArgument Fix the request; never retry unchanged
UNAUTHENTICATED Unauthenticated Re-establish mTLS/session; do not replay business work
PERMISSION_DENIED PermissionDenied Stop; require a new authorization
FAILED_PRECONDITION FailedPrecondition Refresh state/barrier, then use the original operation ID only if allowed
ABORTED Aborted Re-read the CAS revision; do not assume the operation applied
RESOURCE_EXHAUSTED ResourceExhausted Wait for durable quota/resource release
UNAVAILABLE Unavailable Reconnect and reconcile the original operation before any retry
DEADLINE_EXCEEDED DeadlineExceeded Result is unknown unless a durable receipt exists
NOT_FOUND NotFound Do not create a substitute execution
ALREADY_EXISTS AlreadyExists Read the existing operation/result; do not create a second one

A transport success is not an application receipt. RESULT_CODE_ACCEPTED means that the receiving side durably recorded the request; APPLIED requires the specified state transition and barrier evidence.

Idempotency keys

RequestMeta.idempotency_key is required for mutating methods. The key is scoped by (agent_id, operation_id, idempotency_key) and the durable operation record also stores the request content digest. Reusing a key with different content returns ABORTED/RESULT_CODE_CONFLICT; it never overwrites the first request.

  • ActivateAgent: activation_operation_id is the idempotency key. A repeated identical request returns the same session generation and credential metadata.
  • SetAdmissionState: barrier_id plus the expected admission generation is persisted. A replay cannot move the generation twice.
  • Execute: the execution binding and permit ID are the deduplication identity. An unknown result is reconciled with QueryExecution; it is never retried as a new originate.
  • ExecuteAuthorized (agent-authorized-origination.v0.1, isolated Mock only): the Dispatcher first persists one issued or refused decision per execution. The Agent durably records UNKNOWN before invoking the mock adapter; identical operation replays return the stored receipt, changed content or a second operation for the same execution is a conflict. A lost reply or mock adapter failure requires QueryExecution, never another originate or a new execution ID. An expired Dispatcher-issued deadline cannot invoke the adapter.
  • GetExecutionPermit: the reservation, binding, expected revision and idempotency key are persisted. A permit is not issued after the reservation is released or fenced.
  • ApplyTaskControl: (execution_id, expected_task_revision, action, idempotency_key) is CAS-checked. STOP is terminal; PAUSE may be resumed only by a new authorized request.
  • ReportExecutionEvent: (fact_id, content_sha256) is the durable fact key. An identical duplicate returns the original receipt; a digest mismatch is a conflict.
  • RequestUpload/CompleteUpload: upload_id and the asset checksum are retained. Completion is returned only after the recording.uploaded fact is durably queued; a local PUT success alone is not recording.uploaded.

Read-only methods may be retried, but callers still preserve the original request and trace identity: GetAgentStatus, GetBootstrap, and QueryExecution.

Fencing and session rules

  1. The Dispatcher creates a new opaque dispatcher_epoch when its active instance changes. The Agent accepts mutations only for the active epoch.
  2. The Agent identity is the mTLS certificate plus the Dispatcher-approved (agent_id, cell_id, boot_id, session_generation) binding. Self-reported identity or endpoint values are not authorization.
  3. A newer boot or session generation fences older requests. The old request returns UNAUTHENTICATED or ABORTED and cannot release an unknown lease.
  4. The existing Execute permit contains the dispatcher epoch, session generation, reservation and fencing_token; the Agent checks them before that legacy path. The separate Mock-only ExecuteAuthorized path requires an active session and the Dispatcher-issued exclusive deadline, without a second business-policy calculation or an implicit permit/fallback. Its mixed/real execution is disabled pending separate authorization.
  5. Admission close/drain is a prerequisite barrier. SetAdmissionState and ApplyTaskControl are applied only when their expected generation/revision matches durable state.
  6. When the result of a mutation is unknown, the caller first queries the original operation/execution and records UNKNOWN if evidence is absent. No new execution ID or new permit is invented for recovery.

Upload boundary

The Agent receives a restricted UploadGrant and uploads directly to the approved OSS target. The Dispatcher never receives audio bytes. The Agent reports only asset metadata/checksum through CompleteUpload; the Dispatcher reliably queues the original recording.uploaded fact in MQ. This project does not request a SaaS upload session, wait for verified, or invent an OSS ID; MQ publisher confirmation is not SaaS application receipt.