Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"deny": [
"Read(~/.config/native-agent-stack/**)",
"Edit(~/.config/native-agent-stack/**)",
"Read(~/.config/ecosystem-observability/*.env)",
"Read(~/.config/nativestack/*.key)",
"Read(~/.claude/.credentials.json)",
"Read(~/.codex/auth.json)",
"Read(~/.config/gh/hosts.yml)",
"Read(//proc/*/environ)",
"Read(.env)",
"Read(.env.*)",
"Read(!.env.example)",
"Read(!.env.*.example)",
"Bash(printenv)",
"Bash(printenv *)",
"Bash(env)",
"Bash(gh auth token)",
"Bash(gh auth token *)",
"Bash(* --show-token*)",
"Bash(git credential fill*)",
"Bash(gh auth git-credential *)",
"Bash(security find-generic-password *)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR}\"/scripts/hooks/secret_path_guard.py",
"timeout": 10
}
]
}
]
}
}
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ adoption/hosts/*.json
# attestation, never committed. See docs/decisions/2026-09-23-generated-
# explorer-sorted-manifest.md.
/docs/ecosystem/index.html

# Credential-shaped files never belong in a checkout; secrets live under
# ${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack (docs/secret-storage.md).
*.env
*.key
*.pem
.credentials.json
auth.json
hosts.yml
.netrc
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ remain in `catalogs/foundation/manifest.json`, and trading limitations in
`catalogs/us-equities/runtime-target.json` and its linked domain receipts. Historical receipts are reference
evidence, never a new host's passed status. Keep host paths and native sign-ins
private, and use the nonmutating `scripts/adoption_status.py` for prerequisites.
Credentials follow `docs/secret-storage.md` (per-provider 0600 files outside every
worktree, native sign-ins left native); check them with the value-free
`scripts/credential_status.py`, and never read, print or copy a credential value.

This is a portable reference stack with evidence, native recipes and examples. Preserve the distinction between historical host execution, reproducible artifact checks and live provider/GPU acceptance. Never describe a version check or recorded receipt replay as a new model run.

Expand Down
19 changes: 14 additions & 5 deletions adoption/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ GitHub-hosted macOS runner; see
Then confirm discovery with `codex mcp list` and `claude mcp list` before any
scoped task (native verification tier "Client activation" in
[`adoption/README.md`](README.md#native-verification-tiers)).
Provider keys (Alpaca paper, SEC contact, optional paid keys) go in
per-provider `0600` files outside the checkout; follow
[`docs/secret-storage.md`](../docs/secret-storage.md) and check with
`python3 scripts/credential_status.py` (both added after `v2026.09.24.1`).

4. **Render configs.** Use [`tools/adoption/render_config.py`](../tools/adoption/render_config.py)
with the selected host's `adoption/hosts/<host>.json` (gitignored; copy
Expand Down Expand Up @@ -194,10 +198,14 @@ GitHub-hosted macOS runner; see
last step (still only meaningful after sign-in; run it manually
afterward otherwise, exactly as the script's own closing message says).
Three idempotent sub-steps, each safe to re-run:
- **guard hook**: copies [`adoption/hooks/claude/effort-default-guard.py`](hooks/claude/effort-default-guard.py)
to `~/.claude/hooks/effort-default-guard.py`, refusing to install unless
its sha256 matches [`adoption/hooks/claude/SHA256SUMS`](hooks/claude/SHA256SUMS);
skipped if the installed copy already matches.
- **guard hooks**: copies [`adoption/hooks/claude/effort-default-guard.py`](hooks/claude/effort-default-guard.py)
to `~/.claude/hooks/effort-default-guard.py` and the secret guard
[`scripts/hooks/secret_path_guard.py`](../scripts/hooks/secret_path_guard.py)
to `~/.claude/hooks/secret_path_guard.py` (the secret guard and its
settings entries were added after `v2026.09.24.1`), refusing to install either unless
every sha256 matches [`adoption/hooks/claude/SHA256SUMS`](hooks/claude/SHA256SUMS)
(paths relative to that file); skipped per file if the installed copy
already matches.
- **agents**: copies the five [`adoption/agents/claude/*.md`](agents/claude/)
files verbatim to `~/.claude/agents/`; skipped per-file when already
byte-identical.
Expand All @@ -212,7 +220,8 @@ GitHub-hosted macOS runner; see
them). A same-named server with a different config is reported and left
unchanged unless `--replace-mcp` is given.
Then apply the settings template itself (model, effort, ultracode,
workflow env, hooks) into the live `~/.claude/settings.json` with
workflow env, hooks, and the credential deny rules plus the `PreToolUse`
secret-guard hook from [`docs/secret-storage.md`](../docs/secret-storage.md#user-level-guards-deployed-by-the-claude-profile)) into the live `~/.claude/settings.json` with
[`tools/adoption/apply_claude_settings.py`](../tools/adoption/apply_claude_settings.py),
after rendering it for this host with step 4's `render_config.py --out`:
```sh
Expand Down
261 changes: 261 additions & 0 deletions adoption/credential-inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
{
"schema_version": 1,
"as_of": "2026-09-24",
"decision": "docs/decisions/2026-09-24-secret-storage.md",
"runbook": "docs/secret-storage.md",
"checker": "scripts/credential_status.py",
"values_policy": "Names, classes, path templates and loaders only. No value, hash of a value, or host-specific path belongs in this file.",
"store_root_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack",
"store_root_mode": "0700",
"file_mode": "0600",
"file_format": "One provider per file. `export NAME=value` lines (quote a value containing spaces). Every current repository parser accepts this form; pit-availability/measure.py accepts only this form.",
"entries": [
{
"id": "alpaca-paper",
"label": "Alpaca paper broker key pair",
"class": "broker_api_key_pair",
"status": "required",
"lane": "us-equities-paper",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/alpaca-paper.env"
},
"variables": ["APCA_API_KEY_ID", "APCA_API_SECRET_KEY"],
"optional_variables": ["APCA_API_BASE_URL"],
"pointer_variables": ["PAPER_ENV_FILE", "ENV_FILE", "PIT_ALPACA_ENV_PATH"],
"loaders": [
"blueprints/us-equities/adaptive-paper/runner.py#credentials",
"blueprints/us-equities/adaptive-paper/market_research.py#credentials",
"blueprints/us-equities/broad-universe/collect_daily.py#read_credentials",
"blueprints/us-equities/mover-early-entry/collect.py#credentials",
"blueprints/us-equities/extreme-gainer-audit/audit.py#credentials",
"blueprints/us-equities/pit-availability/measure.py#load_env_file"
],
"environment_only_consumers": [
"blueprints/us-equities/alpaca-paper/paper_runner.py",
"blueprints/us-equities/alpaca-historical/collect.py",
"blueprints/us-equities/security-identity/probe.py",
"blueprints/us-equities/security-identity/quality.py",
"blueprints/us-equities/delisting-coverage/collect.py"
],
"rotation": "On any exposure trigger; before any live-scope work; unconditionally once after adopting this layout because broker-prefixed names were observed in a launcher environment on 2026-09-23.",
"notes": "Paper-only keys. Live keys are out of scope for this storage scheme. ALPACA_API_KEY/ALPACA_SECRET_KEY are read-only fallback names in pit-availability/measure.py and are never written."
},
{
"id": "sec-contact",
"label": "SEC/EDGAR declared contact identity",
"class": "contact_identity",
"status": "required",
"lane": "us-equities-research",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/sec-contact.env"
},
"variables": ["SEC_USER_AGENT"],
"optional_variables": ["EDGAR_IDENTITY"],
"pointer_variables": ["SEC_CONTACT_ENV", "PIT_SEC_ENV_PATH"],
"loaders": [
"blueprints/us-equities/catalyst-provenance/README.md#sourced-by-recipe",
"blueprints/us-equities/pit-availability/measure.py#load_env_file"
],
"environment_only_consumers": [
"blueprints/us-equities/catalyst-provenance/native_edgar.py",
"blueprints/us-equities/catalyst-provenance/catalyst.py",
"blueprints/us-equities/financial-data/sec_data.py",
"blueprints/us-equities/delisting-coverage/collect.py"
],
"rotation": "Change only when the monitored contact changes. Receipts store only its SHA-256.",
"notes": "Private personal data, not an authentication secret. The catalyst-provenance recipe sources this file with set -a; that is accepted for a contact string only."
},
{
"id": "databento",
"label": "Databento API key",
"class": "provider_api_key",
"status": "user_only_paid",
"lane": "us-equities-research",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/databento.env"
},
"variables": ["DATABENTO_API_KEY"],
"optional_variables": [],
"pointer_variables": [],
"loaders": [],
"environment_only_consumers": [],
"rotation": "Quarterly and on any exposure trigger.",
"notes": "Not wired into any recipe yet (catalog example only). Create the file only when the paid key is adopted."
},
{
"id": "typesafe",
"label": "Typesafe live-judge API key",
"class": "provider_api_key",
"status": "user_only_paid",
"lane": "foundation-evaluation",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/typesafe.env"
},
"variables": ["TYPESAFE_API_KEY"],
"optional_variables": [],
"pointer_variables": [],
"loaders": [],
"environment_only_consumers": ["tools/sota-convergence/gap_crosswalk.py"],
"rotation": "Quarterly and on any exposure trigger.",
"notes": "Only the live-judge mode needs it, in that one process. Another project's own Typesafe file is outside this inventory."
},
{
"id": "omniroute",
"label": "OmniRoute local gateway inference key",
"class": "provider_api_key",
"status": "optional",
"lane": "us-equities-routing",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/omniroute.env"
},
"variables": ["OMNIROUTE_API_KEY"],
"optional_variables": [],
"pointer_variables": [],
"loaders": [],
"environment_only_consumers": ["blueprints/us-equities/routing/omniroute-astra.py"],
"rotation": "Regenerate in the gateway on any exposure trigger.",
"notes": "Only for the optional routing recipe."
},
{
"id": "grafana-admin",
"label": "Local Grafana admin account and secret key (self-generated)",
"class": "local_service_secret",
"status": "generated_local",
"lane": "observability",
"store": {
"kind": "private_env_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/ecosystem-observability/ecosystem-grafana.env"
},
"variables": ["GF_SECURITY_ADMIN_PASSWORD", "GF_SECURITY_SECRET_KEY"],
"optional_variables": ["GF_SECURITY_ADMIN_USER"],
"pointer_variables": [],
"loaders": ["observability/backends/configure.py#systemd-EnvironmentFile"],
"environment_only_consumers": [],
"rotation": "Delete the file and rerun observability/backends/configure.py, then restart the Grafana user unit.",
"notes": "Generated with 0600 by configure.py and never supplied by the operator. Known inconsistency: the Grafana unit uses EnvironmentFile=, so these values are in that service's process environment. Accepted because Grafana listens on loopback and normal observation uses anonymous Viewer."
},
{
"id": "nativestack-generation-key",
"label": "Host nativestack generation key (self-generated)",
"class": "local_service_secret",
"status": "generated_local",
"lane": "host",
"store": {
"kind": "private_file",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/nativestack/generation.key"
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": [],
"environment_only_consumers": [],
"rotation": "Regenerate through the host's own nativestack-generation service.",
"notes": "Host-local; no code in this repository reads it. Listed so the checker and the agent deny rules cover it."
},
{
"id": "claude-native",
"label": "Claude Code native sign-in",
"class": "native_signin",
"status": "native",
"lane": "client",
"store": {
"kind": "native_store",
"path_template": "$HOME/.claude/.credentials.json"
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": ["claude"],
"environment_only_consumers": [],
"rotation": "`/logout` in Claude Code, then sign in again.",
"notes": "On macOS the client may keep this in the login Keychain and the file can be absent. Never copied between hosts."
},
{
"id": "codex-native",
"label": "Codex native sign-in",
"class": "native_signin",
"status": "native",
"lane": "client",
"store": {
"kind": "native_store",
"path_template": "${CODEX_HOME:-$HOME/.codex}/auth.json"
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": ["codex"],
"environment_only_consumers": [],
"rotation": "`codex logout`, then `codex login --device-auth`.",
"notes": "Never copied between hosts."
},
{
"id": "gh-native",
"label": "GitHub CLI native sign-in",
"class": "native_signin",
"status": "native",
"lane": "client",
"store": {
"kind": "native_store",
"path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/gh/hosts.yml"
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": ["gh", "git credential helper via `gh auth setup-git`"],
"environment_only_consumers": [],
"rotation": "`gh auth refresh`, or `gh auth logout` then `gh auth login`; revoke stale tokens in GitHub settings.",
"notes": "When gh stores the token in an OS keyring, hosts.yml holds no token. Scripts never export `gh auth token`."
},
{
"id": "ibkr-gateway",
"label": "IB Gateway / TWS interactive login",
"class": "native_signin",
"status": "interactive_only",
"lane": "us-equities-paper",
"store": {
"kind": "interactive_login",
"path_template": ""
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": ["blueprints/us-equities/engine-nautilus/ibkr-paper-orders/run.py#--host/--port"],
"environment_only_consumers": [],
"rotation": "Change the IBKR password in the IBKR portal.",
"notes": "Nothing is stored. TWS_*/IBKR_* names stay unset; worker_env_check.sh asserts they never reach workers."
},
{
"id": "github-actions",
"label": "GitHub Actions secret FOUNDATION_RESTORE_FIXTURE_20260920 and the per-job github.token",
"class": "ci_secret",
"status": "ci_only",
"lane": "ci",
"store": {
"kind": "github_actions",
"path_template": ""
},
"variables": ["FOUNDATION_RESTORE_FIXTURE_20260920"],
"optional_variables": [],
"pointer_variables": [],
"loaders": [
".github/workflows/native-offhost-app-state.yml",
".github/workflows/native-offhost-restore.yml"
],
"environment_only_consumers": [],
"rotation": "`gh secret set FOUNDATION_RESTORE_FIXTURE_20260920` from your own terminal.",
"notes": "Held by GitHub; no local copy. No personal access token is stored as a repository secret."
}
],
"must_not_be_set": [
"GITHUB_TOKEN", "GH_TOKEN", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "CODEX_API_KEY",
"HF_TOKEN", "QDRANT_API_KEY", "MASSIVE_API_KEY", "ALPACA_API_KEY", "ALPACA_SECRET_KEY",
"TWS_USERNAME", "TWS_PASSWORD", "TWS_ACCOUNT", "IBKR_ACCOUNT_ID",
"MISTRAL_API_KEY", "PREFECT_API_KEY", "MC_API_KEY", "MSB_API_KEY",
"PAPERCLIP_API_KEY", "OPENROUTER_API_KEY"
]
}
1 change: 1 addition & 0 deletions adoption/hooks/claude/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
df3248659c191d8800dfd64a8c7d415fc13652cebee08c9437bf579862c2e561 effort-default-guard.py
653a990deef23cfcda736a35079df079e420c34e5efd669dcd325be85f82e460 ../../../scripts/hooks/secret_path_guard.py
Loading
Loading