Category: spec-conformance Severity: major
Location: Sources/ARCP/Runtime/JobManager.swift:577-611
Spec: ARCP v1.1 §7.2, §9.8.2
What
§7.2: "the runtime MUST return the same job.accepted payload for any subsequent submission with the same key and identical parameters." The replay path synthesizes a fresh job.accepted with credentials: nil and re-emits the cached terminal envelope. Two problems: (1) the replayed job.accepted differs from the original (which carried credentials), violating "same payload"; and (2) more seriously, the replay never provisions credentials, so a client that legitimately retries an interrupted submit (the whole point of idempotency) gets a terminal result it cannot act on if the job's output depended on credential-mediated upstreams. Worse, the original credentials were already revoked at job termination (§9.8.2), so they cannot be re-emitted either — the cache is fundamentally unable to honor a credential-bearing idempotent replay. The correct behavior is to cache and replay the original job.accepted payload exactly, or to document that credential-bearing jobs are not idempotency-cacheable.
Evidence
try? await send(
Envelope(
sessionId: sessionId,
jobId: jobId,
correlationId: invokeId,
payload: .jobAccepted(JobAcceptedPayload(jobId: jobId, credentials: nil)) // original had credentials
)
)
Only the terminal envelope is cached (persistIdempotencyIfNeeded), never the original job.accepted payload, so the replayed accept is necessarily a fabricated one.
Proposed fix
- Cache the full original
job.accepted payload alongside the terminal envelope (keyed by idempotency key), and replay it verbatim — but redacting credentials.value per §9.8.2 since the original credentials are revoked.
- Alternatively, for jobs that provisioned credentials, return
DUPLICATE_KEY or re-provision fresh credentials on replay; document the chosen semantics.
Acceptance criteria
Category: spec-conformance Severity: major
Location:
Sources/ARCP/Runtime/JobManager.swift:577-611Spec: ARCP v1.1 §7.2, §9.8.2
What
§7.2: "the runtime MUST return the same
job.acceptedpayload for any subsequent submission with the same key and identical parameters." The replay path synthesizes a freshjob.acceptedwithcredentials: niland re-emits the cached terminal envelope. Two problems: (1) the replayedjob.accepteddiffers from the original (which carriedcredentials), violating "same payload"; and (2) more seriously, the replay never provisions credentials, so a client that legitimately retries an interrupted submit (the whole point of idempotency) gets a terminal result it cannot act on if the job's output depended on credential-mediated upstreams. Worse, the original credentials were already revoked at job termination (§9.8.2), so they cannot be re-emitted either — the cache is fundamentally unable to honor a credential-bearing idempotent replay. The correct behavior is to cache and replay the originaljob.acceptedpayload exactly, or to document that credential-bearing jobs are not idempotency-cacheable.Evidence
Only the terminal envelope is cached (
persistIdempotencyIfNeeded), never the originaljob.acceptedpayload, so the replayed accept is necessarily a fabricated one.Proposed fix
job.acceptedpayload alongside the terminal envelope (keyed by idempotency key), and replay it verbatim — but redactingcredentials.valueper §9.8.2 since the original credentials are revoked.DUPLICATE_KEYor re-provision fresh credentials on replay; document the chosen semantics.Acceptance criteria
job.acceptedmatches the original payload's non-secret fields.