Status: active doctrine (drafted 2026-07-18). Governs how credentials are held, transported, and logged by anyone running prism against their own vendor and broker accounts. Agent conduct lives in AGENTS.md; research law in SPEC.md. This file states operational invariants only.
| Credential | Env var | Transport | Notes |
|---|---|---|---|
| Twelve Data key | TWELVEDATA_API_KEY |
query param (vendor requirement) | redaction invariant applies (§2.4) |
| Alpaca key pair | APCA_API_KEY_ID / APCA_API_SECRET_KEY |
request headers only | paper endpoint by default (§2.5) |
| FRED key | FRED_API_KEY |
query param (vendor requirement) | redacted at FredClient (src/prism/regime/fetch.py) |
| Alpha Vantage key | observatory repo secret | query param | lives in prism-observatory Actions secrets, never in this repo |
Prism never touches bank credentials. Funding, ACH linkage, and account numbers live at the broker; the only thing this codebase ever holds is broker API keys. Any doc or discussion phrased as "connect a routing/account number" is wrong by construction.
- Env-only. Credentials enter through the process environment or a
gitignored
.env(loaded insrc/prism/config.py). Never CLI arguments (shell history,ps), never tracked files, never code literals. - Fail-loud on absence (N7). Every
from_envconstructor raises when a required credential is missing (alpaca.py,alpaca_data.py,regime/fetch.py). No silent degraded mode for live paths; the loader's keyless warning applies to cached-only research reads. - Headers over query params. Keys travel in request headers wherever the vendor allows (Alpaca). Query-param transport exists only where the vendor requires it (Twelve Data, FRED) — which is what makes invariant 4 binding.
- Redaction invariant. Any log or exception line that can embed a request
URL passes through a redactor before logging:
DataLoader._redact(src/prism/io/loader.py),FredClient.series(src/prism/regime/fetch.py:99precedent).requests.HTTPErrortext embeds the full?apikey=…URL — that is the leak vector. Enforced by the credential-hygiene tests intests/test_data_loader.py. - Paper-first.
AlpacaClient.from_envresolves to the paper endpoint unlessAPCA_API_BASE_URLis explicitly set (src/prism/live/alpaca.py). Pointing the loop at a live account is a deliberate, explicit act. - Rotation on exposure. A key that has ever appeared in a log, artifact, or terminal scrollback is rotated, not shrugged at. Exposure through a pre-redaction log line counts.
- No secrets in tracked artifacts.
results/JSON, docs, and universe files carry no credentials.logs/is untracked but treated as sensitive anyway: it is included in off-box backups (operator concern), so invariant 4 must hold before lines reach disk, not be cleaned after.
-
2026-07 — Twelve Data key in fetch-error logs. Loader error paths logged
str(exception), which embeds the full request URL includingapikey=, during vendor-outage and rate-limit events. Fixed by the §2.4 redaction invariant (DataLoader._redact, test-enforced); exposed key rotated 2026-07-18. Closed. This incident is why §2.4 is stated as an invariant and not a style preference. -
2026-07-18 — WSL disk-flush failure wrote zero-byte git objects. During a ratification commit, a flush failure produced four zero-byte loose objects (one commit plus its tree/blob set, destroyed before push). Repair: purge the empty objects, rebuild the index (
rm .git/index+git read-tree HEAD— a stale index referencing purged blobs breaksgit reset), expire reflogs, recommit from worktree text,fsckclean. Consequences adopted: the paper-loop ledgers underruns/— the one artifact class that cannot be regenerated, only re-accumulated — are snapshotted to a dedicated backup repository and pushed off-box to a private remote (prism-artifacts) by the nightly sync (~/bin/prism_artifacts_sync.sh, wired into the nightly runner and unconditional on loop exit status — a red loop is exactly when the ledger copy matters most); ratification commits carry never-retargetedratify/*tags on origin with OpenTimestamps proofs in-tree (docs/anchors/). Closed 2026-07-19: first off-box push verified.