# BYOK provenance API quickstart The default-off provenance API signs an asset with a caller-supplied P-256 key, verifies the resulting sidecar envelope, and can retain a passport-signed verification record. In the BYOK beta, Crux receives the key for one request and does not retain, log, or echo it. This is a local beta workflow. Metering is still a no-op, the feature flag is off by default, and enabling or publishing the hosted surface remains an operator gate. ## Prerequisites - a local source build of `corecruxd`; - `curl`, `jq`, and OpenSSL; - ports `14800` and `14801` available. The script uses `dev_scopes`, which is suitable only on a loopback listener. Hosted deployments must use JWT auth behind authenticated TLS termination. ## 1. Start a disposable local daemon From the repository root, use a fresh data directory: ```bash export CORECRUXD_DATA_DIR="$(mktemp -d)" export CORECRUXD_AUTH_MODE=dev_scopes export CORECRUXD_FEATURE_PROVENANCE_API=1 cargo run --release --bin corecruxd ``` Wait for readiness from another terminal: ```bash curl -fsS http://127.0.0.1:14800/readyz ``` Do not reuse `dev_scopes` on a non-loopback listener. The provenance routes will not mount with auth off, or on a non-loopback listener unless JWT auth and `CORECRUXD_PROVENANCE_TLS_TERMINATED=1` are both present. ## 2. Run the round-trip ```bash ./examples/provenance-byok/quickstart.sh ``` The script creates a disposable self-signed P-256 certificate, signs [`asset.txt`](../examples/provenance-byok/asset.txt), verifies its asset binding, creates a retained verification record, and repeats that request with the same `Idempotency-Key`. The replay must return HTTP 200 and the original record id rather than appending a second record. It writes four non-secret response artifacts to `./provenance-byok-output/`: - `sign-response.json` — the sidecar envelope and asset hash; - `verify-response.json` — integrity, asset-binding, and trust posture; - `verify-record-response.json` — the retained record and receipt; - `verify-record-replay.json` — the idempotent replay result. The private key, certificate, and request bodies live only in a permission-controlled temporary directory and are removed on exit. The script never prints the key. ### Optional local P95 baseline Run up to 30 create-path samples per operation without exceeding the local per-credential window used by the quickstart: ```bash CORECRUXD_BUILD_PROFILE=release \ PROVENANCE_BENCH_ITERATIONS=30 \ ./examples/provenance-byok/quickstart.sh ``` This adds `benchmark.json`, with min/P50/P95/max latency for sign, stateless verify, and new retained-record creation. The record includes the corpus (`provenance-byok-sample-v1`), source commit, build profile, lane flags, and a unique run id. It is a loopback baseline, not the hosted-beta P95 required by the M9 launch gate. The first reproducible release-profile run is retained at [`docs/bench/provenance-byok-local-2026-07-21.json`](bench/provenance-byok-local-2026-07-21.json). ## Reading the verification result For the disposable self-signed sample, `ok` is true while `identity_trusted` and `chain_validated` are false. Those fields answer different questions: - `ok` means the canonical envelope signature and supplied asset binding both verify; - `identity_trusted` means the signer identity satisfied operator-pinned policy: either the exact, currently-valid leaf certificate is in the operator's `CORECRUXD_PROVENANCE_TRUSTED_LEAF_SHA256` list, or the presented certificate chain validated to a root pinned in `CORECRUXD_PROVENANCE_TRUSTED_ROOT_SHA256`; - `chain_validated` is true only when the CA-chain trust mode is active (at least one pinned root) and the presented x5chain cryptographically validated to an operator-pinned root — per-link signatures, current validity, BasicConstraints/KeyUsage/EKU profiles, path length, and fail-closed handling of unsupported critical extensions. It stays false for this disposable self-signed sample. Pins are narrow operator policy, not a claim that a public CA validated the signer; revocation and public C2PA trust-list membership are not evaluated. Malformed pin configuration fails closed and leaves the routes unmounted. ## Abuse-control layers The three provenance operations share one tenant-scoped budget of 120 calls per minute for each verified stable JWT `sub` or `passport_id`. Refreshing a token or switching from `verify` to `sign` does not reset that allowance. Hosted JWTs without either stable identity claim fail closed; loopback-only `dev_scopes` retains a hashed development-credential fallback. A rejected call returns `429` and `Retry-After: 60`. This principal budget complements the daemon-wide effective-client-IP token bucket, request-body caps, and global in-flight/load-shed guard. A reverse proxy's forwarded address is trusted only when its peer CIDR is explicitly in `CORECRUXD_TRUSTED_PROXY_CIDRS`; loopback is otherwise exempt by default. Both rate tables are process-local, so a horizontally scaled hosted deployment must also enforce a shared edge/gateway limit and verify it during the external beta drill. ## Optional retained-record lifecycle Automatic deletion is off unless the operator selects a window before daemon startup: ```bash export CORECRUXD_PROVENANCE_RETENTION_DAYS=90 ``` The accepted range is 1–3,650 days; zero, malformed, and out-of-range values fail closed and keep the provenance routes unmounted. An authenticated `verify-record` call can sweep that tenant before retaining the new result. Full scans are limited to once per tenant per hour and the cadence table is bounded; saturation preserves records instead of opening an unbounded memory or I/O path. The sweep validates every segment before its first mutation, runs under the same process and cross-process locks as appends, and atomically replaces partially retained files. Every record whose timestamp can drive a deletion must also pass its daemon-passport signature and body-hash check; unknown signers or tampered timestamps preserve the tenant and fail closed. After an intentional daemon passport rotation, provide a bounded `CORECRUXD_PROVENANCE_RECORD_SIGNER_KEYRING_JSON` object mapping each retained historical `p_…` fingerprint to its Ed25519 public-key hex. The fingerprint is re-derived from every configured key; malformed, mismatched, or unknown keys never authorize deletion. Active legal holds are checked while the legal-hold read lock is held through the sweep. A tenant-wide hold preserves every verification record. A scoped hold can target `provenance::verification_record::` (or the broader `provenance::verification_record::` prefix). Malformed newest hold state is resolved by the legal-hold store's tenant-wide fail-closed fallback. Every deletion-producing sweep first durably mints a count-only `planned` governance receipt under `__governance__::retention`; deletion does not begin if that intent cannot be recorded. A second `completed` or `failed` receipt uses the same sweep id after the attempt, including failures before the first file mutation. These contain the tenant hash and counts, never raw tenant ids, record ids, asset bytes, or caller text. The triggering response reports the final receipt: - `X-Cuecrux-Retention-Receipt-Status: recorded|pending`; - `X-Cuecrux-Retention-Receipt-Id` when minting succeeds; - `X-Cuecrux-Retention-Records-Dropped`. `pending` is loud audit debt: the deletion is not rolled back, and the daemon increments/logs its governance-receipt failure signal. Inactive-tenant scheduling is a separate, default-off operator choice: ```bash export CORECRUXD_PROVENANCE_RETENTION_SCHEDULER=true export CORECRUXD_PROVENANCE_RETENTION_INTERVAL_SECS=3600 export CORECRUXD_PROVENANCE_RETENTION_MAX_TENANTS_PER_PASS=100 ``` It requires the retention-days policy above, skips the immediate boot tick, discovers only exact hash-bound tenant directories, and rotates through a bounded batch (interval 60–86,400 seconds; batch 1–1,000). Explicit malformed or out-of-range scheduler bounds disable the task rather than being clamped. Each pass starts its 30-second deadline before discovery, checks it and shutdown during discovery, lock acquisition, and record validation, and conservatively debits discovered store bytes from the 512-MiB cap before rechecking the remaining budget under each tenant lock. Malformed no-newline records are cut off at the line bound rather than scanned to EOF. A budget-hit directory advances the round-robin cursor so it cannot starve later tenants. Once a durable `planned` receipt exists, that tenant's bounded atomic mutation and terminal receipt finish before shutdown; while waiting to mint that intent, shutdown/deadline cancellation is still observed. Later tenants are skipped. The scheduler shares the request-path hourly tenant cadence. Scheduled receipts use the fixed actor `provenance-retention-scheduler` and trigger `scheduled`; they contain the same counts and hashed tenant identity as request-triggered receipts. Automatic verification-record deletion (request-triggered or scheduled) is currently enabled only on Linux, where every existing directory component is opened descriptor-relative with no symlink traversal and mutations remain anchored to the opened tenant directory. Other platforms preserve records and fail the configured deletion attempt closed. The scheduler is independent of `CORECRUXD_FEATURE_PROVENANCE_API`: routes may stay off while a separately approved lifecycle task runs. A hosted activation and deletion drill remain operator gates. ## API shape All three routes require `provenance:write` or `admin:write`, plus an explicit tenant authorized by the caller's credential: | Method | Route | Success | |---|---|---| | `POST` | `/v1/provenance/sign` | `201` | | `POST` | `/v1/provenance/verify` | `200` | | `POST` | `/v1/provenance/verify-record` | `201`, or `200` for an exact idempotent replay | The daemon-wide ingress layer supplies trusted-proxy-aware client-IP limiting; the provenance handlers add a bounded per-credential limiter. Configure `CORECRUXD_TRUSTED_PROXY_CIDRS` only for proxies that strip inbound `Forwarded` and `X-Forwarded-For` before setting the real client address. ## Stop and clean up Stop the foreground daemon with `Ctrl-C`. The data directory named by `CORECRUXD_DATA_DIR` contains the retained sample record; delete that disposable directory only after checking the path.