# Security Authority Proof

Runx receipts must explain the authority boundary without becoming a secret
side channel. The compact proof lives in receipt metadata under
`authority_proof` and validates against `runx.authority-proof.v1`.

Allowed public fields:

- `run_id`, `skill_name`, and `source_type`
- requested connected-auth scopes and whether the skill declared mutating work
- scope admission status, granted scopes, grant id, and decision summary
- provider, connection id, grant reference, and `material_ref` hash
- sandbox profile, declared enforcement, runtime enforcer, and approval result
- redaction policy status

Banned fields:

- raw access tokens, refresh tokens, API keys, passwords, client secrets, and
  provider credential bodies
- full private stdout or stderr bodies in public projections
- ambient environment dumps or unbounded local command logs
- unchecked provider output bodies in comments, public evidence, or ledgers

Credential material is represented by hashed opaque handles such as
`material_ref_hash`. Receipt writers still hash stdout and stderr, and metadata is
passed through the receipt redactor before signing. Hosted workers and local
runners use the same `authority_proof` schema name; consuming repos add policy
for source channels, assignees, and target repositories outside the core proof.
Runtime secret handoff is owned by `credential-broker-delivery-contract-v1`:
secret values may cross only the trusted broker/supervisor delivery channel, not
authority proofs, receipts, invocation metadata, adapter observations, or public
provider evidence.

## Ownership Boundary

The Rust `AuthorityProof` wire structs are policy-owned in `runx-core`, not
promoted into `runx-contracts`. The proof is produced only by the policy kernel,
shares admission support types such as `ScopeAdmission`, `AuthorityKind`, and
`CredentialGrantReference`, and is validated as a contract through generated
schema checks in `runx-contracts`. Future contract-spine work should treat this
as an explicit exception unless it can move the full boundary without changing
the `runx.authority-proof.v1` JSON shape.

The local kernel resolves authority in this order:

1. Structural policy admission runs before connected auth resolution.
2. Grant resolution returns only grant descriptors.
3. Sandbox approval gates run before execution.
4. Credential resolution returns an opaque credential envelope only after
   admission.
5. The signed receipt records the proof, hashes outputs, and omits raw secrets.

## Provider-Permission Grants

`provider_permission` graph policy may declare required scopes, an expected
grant id, and the authority verb. It must not declare `granted_scopes`; granted
scopes come only from operator-carried runtime grant evidence.

Provider-permission steps fail closed unless the operator supplies both:

- `RUNX_PROVIDER_PERMISSION_GRANT_ID`
- `RUNX_PROVIDER_PERMISSION_GRANTED_SCOPES`

This is intentional. Older local runs that relied on an implicit grant id must
set `RUNX_PROVIDER_PERMISSION_GRANT_ID` explicitly before executing
provider-permission steps.

When a provider-permission effect is admitted, the sealed step receipt records
the operator grant as a typed `runx:grant:*` reference under
`receipt.authority.grant_refs`. The grant id is evidence of the authority that
admitted the effect; it is not a credential body and does not carry provider
token material.

## Payment Aggregate Spend Caps

Spend-class payment authority must carry an aggregate cap (`max_per_run_units`
or `max_per_period_units`) in addition to any per-call cap. Both aggregate caps
are enforced by the runtime spend ledger.

Per-run: each run's reserved spend is bounded by the smaller of the two
declared caps, because a run never spans more than one period.

Per-period: when the authority also declares a `period` of `daily`, `weekly`,
or `monthly`, every spend is additionally reserved against a durable
calendar-window ledger in the effect state file (`RUNX_EFFECT_STATE_PATH` or
`<receipt dir>/effect-state.json`), so the cap holds across runs inside one
UTC window. An unrecognized `period` value fails closed at admission instead
of becoming an unenforced annotation. A period cap declared without a `period`
is enforced only as the run-level clamp, and the period ledger only exists
when an effect state path is configured — operators who want cross-run spend
bounds must configure a stable state path.

Period ledgers are bounded during the same locked state transaction that
records the reservation. For each family/authority/currency/period tuple, the
runtime retains at least the active reservation window and the immediately
previous window, keeps any newer windows already present, and prunes older
period-ledger rows. Idempotency entries, finality records, finality events,
consumed capabilities, and run-spend ledgers are not pruned by this retention
pass. Out-of-order reservations remain safe because retention is computed
relative to the reservation being recorded, not relative to the newest window
currently present in the file.

When a payment effect is admitted, the sealed step receipt records authority
evidence under `receipt.authority.grant_refs`: the admitted payment authority
reference and the spend-capability reference. Replay receipts preserve those
same authority references so a replayed sealed effect remains verifiable
against the same admitted authority boundary.

Payment supervisor proofs bind the original settlement evidence through
`evidence_digest`. Rebinding a stored proof to a re-sealed receipt first
re-verifies that the stored evidence still hashes to the sealed digest, so
evidence altered after issuance is rejected instead of silently re-blessed.

## Offline Receipt Verification

`runx verify [receipt-id] [--receipt-dir dir] [--receipt <path|->] [--json]`
re-checks sealed receipts from disk with no runtime or network dependency:
canonical body digests, content-addressed ids, linked-tree parent/child
integrity, scope adherence for privileged effects, and — when
`RUNX_RECEIPT_VERIFY_KID` and
`RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64` are set — production Ed25519
signatures against the operator-trusted key. Store mode groups receipts into
trees by lineage; a chain that points at a receipt missing from the store is
reported as incomplete and fails verification. Single-receipt mode emits one
`runx.verify_verdict.v1` JSON verdict suitable for hosted notaries and other
embedding surfaces. Because a single document cannot prove tree membership,
lineage is reported as `unverified` without failing an otherwise valid
receipt. The command exits non-zero on invalid receipts, so it can gate
automation.

`fixtures/receipt-verify/` is the conformance corpus for machine consumers.
Every embedding surface that claims to verify a runx receipt must replay those
fixtures through the pinned `runx` binary and match the expected verdicts
instead of carrying a second verifier implementation in another language.

Scope adherence is intentionally pure and offline. Any act carrying typed
`EffectEvidence` without corresponding `receipt.authority.grant_refs` produces
`EffectGrantEvidenceMissing`, fails verification, and exits non-zero. This is
the boundary between a signed activity log and a governance proof: the receipt
must show both the privileged effect and the operator-granted authority that
admitted it.

## Operator Authority Diagnostics

`runx doctor authority [--json]` gives operators a redacted authority readiness
view before exercising privileged effects. It reports:

- receipt signer readiness, naming `RUNX_RECEIPT_SIGN_KID`,
  `RUNX_RECEIPT_SIGN_ED25519_SEED_BASE64`, and
  `RUNX_RECEIPT_SIGN_ISSUER_TYPE`
- receipt verification readiness, naming `RUNX_RECEIPT_VERIFY_KID` and
  `RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64`
- the resolved effect-state path when configured
- the consequence when `RUNX_EFFECT_STATE_PATH` is unset: cross-run spend caps,
  payment idempotency, and effect replay recovery are not durable without a
  configured state path
- provider-permission grant readiness, naming
  `RUNX_PROVIDER_PERMISSION_GRANT_ID` and
  `RUNX_PROVIDER_PERMISSION_GRANTED_SCOPES`

The diagnostic may show key ids and resolved filesystem paths. It must not show
signing seeds, public key material, provider scope values, grant ids, or
credential bodies.
