Category: spec-conformance Severity: major
Location: src/Arcp.Runtime/Internal/CredentialRegistry.fs:31-57
Spec: ARCP v1.1 §9.8.2
What
Spec §9.8.2: 'Revocation is best-effort; the runtime SHOULD retry on transient failure and MUST log permanent failures.' §14: runtimes must 'surface unrevocable credentials to operators.' When the retry loop exhausts with revoked=false, the credential id stays in perJob/store with no log, no telemetry, no operator-visible signal.
Evidence
let revokeWithRetryAsync ... =
task {
let mutable revoked = false
let mutable attempt = 0
while not revoked && attempt < retryDelays.Length do
let! doneOrPermanent = provisioner.RevokeAsync(credentialId, ct)
if doneOrPermanent then revoked <- true
else
do! Task.Delay(retryDelays.[attempt], ct)
attempt <- attempt + 1
if revoked then
...
}
Proposed fix
On loop exit with revoked=false: emit a structured log/trace (ILogger or built-in Console fallback) including jobId and credentialId, and persist a revocation_failed marker in the store so operators can list dangling credentials. Optionally also push to an IRevocationFailureSink.
Acceptance criteria
Category: spec-conformance Severity: major
Location:
src/Arcp.Runtime/Internal/CredentialRegistry.fs:31-57Spec: ARCP v1.1 §9.8.2
What
Spec §9.8.2: 'Revocation is best-effort; the runtime SHOULD retry on transient failure and MUST log permanent failures.' §14: runtimes must 'surface unrevocable credentials to operators.' When the retry loop exhausts with revoked=false, the credential id stays in
perJob/store with no log, no telemetry, no operator-visible signal.Evidence
Proposed fix
On loop exit with revoked=false: emit a structured log/trace (ILogger or built-in Console fallback) including jobId and credentialId, and persist a
revocation_failedmarker in the store so operators can list dangling credentials. Optionally also push to an IRevocationFailureSink.Acceptance criteria