Skip to content

[RCL-40] rcl review --attest: run-bound Harness credential from the Actions OIDC token - #54

Merged
mstroeck merged 3 commits into
mainfrom
rcl-40-attest
Sep 8, 2026
Merged

[RCL-40] rcl review --attest: run-bound Harness credential from the Actions OIDC token#54
mstroeck merged 3 commits into
mainfrom
rcl-40-attest

Conversation

@mstroeck

@mstroeck mstroeck commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Phase 3 client side of the Review Council evidence ledger (epic IO-12475, sections 4.1 and 8.5).

What. rcl review owner/repo#N --attest runs inside the organization's gate workflow on GitHub Actions. Before any key, config or reviewer work it (1) reads ACTIONS_ID_TOKEN_REQUEST_URL / _TOKEN (set by the runner only for jobs with id-token: write), (2) requests the job's OIDC token with the Harness origin (HARNESS_API_URL) as audience, (3) exchanges it at POST /api/v1/reviews/attest with a pre-minted UUIDv7 run id, and (4) uses the run-bound credential (rbc_…, source: 'attest') for everything that run says to Harness: model keys (GET /model-keys), model stats, the envelope, its artifacts. The run header carries the bound id, so Harness stores the run as credential_kind: attested.

Fails loudly, never falls back. Outside Actions, without HARNESS_API_URL (TLS only), off a pull request target (a local diff or patch file cannot be attested), with --no-telemetry / RCL_TELEMETRY=off / harness.telemetry: off, or when the exchange is refused, the review does not start and the process exits 1 with the reason (401 invalid_attestation, 403 attestation_refused with a hint per reason, 409 run_exists); a 5xx/network failure at the exchange is retried twice, then reported. HARNESS_API_TOKEN and the stored login are never used in its place. --attest implies --evidence-required, and an attested runtime never spools: the credential does not outlive the workflow run, so an unreachable Harness is exit 4 with nothing left in the outbox. The runner token and the OIDC token are redacted from every message.

Where. src/telemetry/attest.ts (new: attestRun, AttestError), src/telemetry/credentials.ts (source: 'attest'), src/telemetry/read-sink.ts + src/config/harness.ts (a credential already in hand replaces resolution; the attestation stands in for .harness-cli/config.json), src/telemetry/deliver.ts (RuntimeOptions.credential, TelemetryRuntime.attested, no spooling), src/index.ts (--attest on review, validation order, run id, wiring into keys, weights, header and runtime). README (--attest section with a workflow snippet, env table) and CHANGELOG (Unreleased).

Tests. test/telemetry/attest.test.ts (fake runner + fake Harness: the exchange, audience, headers, every refusal, retries, malformed answers, redaction), test/telemetry/attested-delivery.test.ts (keys, stats, runtime and delivery under the credential; no spool), test/review-cli.test.ts (outside Actions exits 1 with a clear message before any network; local diff and patch refused; --no-telemetry contradiction). npm run lint && npm test: 57 files, 825 tests green.

End to end. Two throwaway Actions runs on this repository (branch rcl-40-smoke, deleted) ran --attest against production with a real OIDC token: the push-triggered run was refused at wrong_event (https://github.com/allocator-one/rcl/actions/runs/34270491743), the workflow_dispatch run at workflow_not_allowed (https://github.com/allocator-one/rcl/actions/runs/34270561865) — each with the server reason and hint, rcl exit=1, no review started. The OIDC leg, audience, exchange and refusal mapping are verified for real; the recorded attested run follows with the allow-listed gate workflow of IO-12484 and the App's actions: read grant.

Closes RCL-40

mstroeck and others added 3 commits September 8, 2026 21:39
…un-bound Harness credential

Inside the organization's gate workflow on GitHub Actions, --attest asks
the runner for the job's OIDC token with the Harness origin as audience,
exchanges it at POST /api/v1/reviews/attest for the run-bound credential
of one pre-minted run id, and records the review under it: envelope,
artifacts, model keys and model stats. Fails loudly outside Actions,
without HARNESS_API_URL, off a pull request target, with telemetry off or
when the exchange is refused; never falls back to another credential,
implies --evidence-required, never spools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…, bounded reads

The telemetry level is resolved from the environment and the project
config before any token is requested and must be full; the credential is
minted again for the same run id before delivery when a long review has
used up most of it; the OIDC request does not follow redirects; answers
are read up to the limit, a body-read failure retries, and a malformed
2xx names its cause. The README workflow example passes inputs through
the environment instead of interpolating them into the command line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ights read, expiry validated

The pre-exchange telemetry check reads the file --config names; the run-bound
credential is renewed before the model-stats read as well as delivery; a
credential answer must carry a parseable expiry; the attest orchestration
moves out of the command handler and no longer mutates its options; the CLI
tests never inherit a runner's OIDC variables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mstroeck
mstroeck merged commit e741e4a into main Sep 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant