Skip to main content

Job output secrets

A job that produces values — Terraform outputs, provisioned resource ids, generated connection strings — publishes them as project/environment-rung secrets through one route, the mirror of the lease-bound resolve:

POST /v1/organizations/{orgId}/projects/{projectId}/state/runs/{runUlid}/output-secrets

Request and response

{
"runnerId": "runner-8f2c",
"jobId": "api.prod.deploy",
"leaseEpoch": 1,
"secrets": [
{ "key": "DATABASE_URL", "value": "postgres://…" },
{ "key": "QUEUE_ENDPOINT", "value": "https://…" }
]
}
{
"data": {
"published": ["DATABASE_URL", "QUEUE_ENDPOINT"],
"environment": "prod",
"component": "api"
},
"meta": { "requestId": "req_5f2d1c0b9a8e7f6d5c4b3a36", "cursor": null }
}

Gates

The same two-gate posture as the resolve:

  1. Bearer authorization — the action is state.object.write, the state plane's write posture. Workflow actors grant by token-bound scope; everyone else via role policy. Denied as 404.
  2. A live lease — the body's (runnerId, jobId, leaseEpoch) must match the live lease on this run's job; only the runner actually holding the job can publish. A lapsed lease is 409 lease_lost.

Scope is 100% server-derived

The publish can aim nowhere the leased job does not already live:

  • Org and project come from the route (already authorized).
  • Component comes from the leased run_jobs row.
  • Environment is parsed from the job id, which is shaped <component>.<environment>.<job> — using the row's component as the authoritative prefix.

Nothing in the body can point the write elsewhere. A job with no derivable (component, environment) cannot publish at all — 422, there is no rung to aim at.

Validation

RuleLimit
Keys per publish≤ 32
Key shape^[A-Z][A-Z0-9_]{0,127}$ (UPPER_SNAKE), unique within the request
ValuesNon-empty strings, ≤ 64 KiB

Validation errors never echo values — a malformed slot may itself be a secret.

What the write does

The publish lands on the project/environment rung of the secret store:

  • First publish of a key creates the secret — source: "static", created by the system actor.
  • Later publishes of the same key append a new version — a rotate, never an overwrite. History is preserved, and consumers pinned to @N keep resolving the version they pinned.

Provenance — the run, job, and component that produced the value — rides the audit event, so every published value is traceable to the job that emitted it.

warning

This run-lease channel is the only way CI writes secrets. The config API's create and rotate routes are not CI-writable — workflow actors can read secret metadata under their bound project but hold no write grant on the config surface (an earlier CI write grant there was reverted in favor of this channel). See CI workflow grants.