Category: spec-conformance Severity: blocker
Location: src/Arcp.Runtime/Internal/JobLauncher.fs:65-77
Spec: ARCP v1.1 §14
What
emit funnels through JobManager.EmitEventAsync → ArcpServer.BuildOutbox.EmitJobEventAsync, which fans the status event to every subscriber of the job. §14 mandates that credential value MUST NOT be echoed to subscribers, and that subscribers MUST NOT receive credentials from job.accepted; the same secrecy applies on rotation per §9.8.2.
Evidence
let rotateCredential (credentialId: string, newValue: string, ct: CancellationToken) : Task =
task {
let message = Json.serialize {| id = credentialId; value = newValue |}
do! emit (JobEventBody.Status(StatusPhases.CredentialRotated, Some message))
do! credentialRegistry.RevokeCredentialAsync(credentialId, ct)
}
Proposed fix
Send the rotation status with the new value only to the submitting session (record.SessionId), and fan out a redacted variant (id only, value omitted/redacted) to subscribers. Add a dedicated outbox path that splits owner vs subscriber payloads instead of reusing EmitJobEventAsync.
Acceptance criteria
Category: spec-conformance Severity: blocker
Location:
src/Arcp.Runtime/Internal/JobLauncher.fs:65-77Spec: ARCP v1.1 §14
What
emit funnels through JobManager.EmitEventAsync → ArcpServer.BuildOutbox.EmitJobEventAsync, which fans the status event to every subscriber of the job. §14 mandates that credential
valueMUST NOT be echoed to subscribers, and that subscribers MUST NOT receive credentials from job.accepted; the same secrecy applies on rotation per §9.8.2.Evidence
Proposed fix
Send the rotation status with the new value only to the submitting session (record.SessionId), and fan out a redacted variant (id only, value omitted/redacted) to subscribers. Add a dedicated outbox path that splits owner vs subscriber payloads instead of reusing EmitJobEventAsync.
Acceptance criteria
valuefield (or a redacted placeholder), while the submitting session continues to receive the new value.