ADR 0149 — Session and agent credential model: bounded scripts and per-repo scoped write identities; no shared admin
Status: Accepted 2026-10-30 by Kristerpher Henderson (operator). Previously
Proposed (2026-08-30). The acceptance was conveyed to the session that merged
this PR by the operator's merge directive of 2026-10-30; at merge there was no
approval comment under the operator's own GitHub identity on the PR and no cited
AskUserQuestion transcript, so per .claude/agents/_shared-conventions.md
§Operator authorization identity this line is the record of the decision, not
the authorization artifact (see the open item in §9).
Date: 2026-08-30 UTC (authored; the PR that carries it was opened the same
day). Accepted 2026-10-30 UTC; facts in §1–§3 and §9 were re-checked against
develop at acceptance (the decision itself is unchanged).
Deciders: Kristerpher Henderson (operator) — accepted 2026-10-30;
software-architect (proposed this design); control-session (sibling
implementation: bounded CI/fleet wrapper scripts, landed in #4566 — see §3.2 —
and the operator Bash allow-list rules that invoke them, not yet landed;
referenced here, not authored by this ADR).
Scope: Session and dispatched-agent access to Infisical (vault.raxx.app), AWS
SSM, the Woodpecker CI API, and any other credential surface a session or agent
needs at runtime. Extends ADR-0130 (vault multi-agent machine identity,
Accepted) and ADR-0131 (handoff agent least-privilege identity, Proposed,
two open operator questions) from "which identity does an agent hold" to
"where does write privilege live at all, and what is a session ever allowed
to invoke directly." ADR-0151 (Accepted 2026-08-31) builds on this ADR and
carves a scoped break-glass exception to §3.1 — see §3.8.
Refs: ADR-0130, ADR-0131, ADR-0151, docs/architecture/vault-multi-agent-access-pattern.md,
docs/architecture/handoff-agent-least-privilege-identity.md,
docs/architecture/agent-github-identity.md, docs/ops/runbooks/vault-access.md,
docs/ops/runbooks/vault-coverage-audit.md, docs/ops/runbooks/ci-woodpecker.md,
.claude/agents/_shared-conventions.md
§Operator authorization identity, the MooseQuest-LLC/vault-kit repository
(bin/rvault, scripts/registry.toml; the /provision-vault-secret and
/onboard-vault skills named below are operator-local and not in any
repository, so they are described rather than linked), issues https://github.com/raxx-app/TradeMasterAPI/issues/4544, https://github.com/raxx-app/TradeMasterAPI/issues/4545,
https://github.com/raxx-app/TradeMasterAPI/issues/3738, https://github.com/raxx-app/TradeMasterAPI/issues/1903,
PRs #4566 (the two CI/fleet wrappers) and #4577 (ADR-0151).
1. Context
On 2026-08-29 UTC (with fleet follow-ups into 2026-08-30), two production-deploy recovery efforts
(the stale HEROKU_PROD_API_KEY WP org secret,
https://github.com/raxx-app/TradeMasterAPI/issues/4544, and the
CI-fleet/monitoring incidents chained off it,
https://github.com/raxx-app/TradeMasterAPI/issues/4545 — see that issue's
comment timeline for the minute-by-minute account of agent-orphan recovery, ASG
floor drift, and instance sizing) dead-ended repeatedly on credentials, not on
engineering judgment.
The estate already has a read identity for Infisical: a shared Universal Auth
machine identity for the raxx-secrets project, VAULT_PATH defaulting to
/MooseQuest/, carried by every Woodpecker-org repo and every developer shell.
Every session and dispatched agent that day had that identity available for
reads. What none of them had — correctly, by the existing permission model — was
any path to a write or a mutating action:
- Every vault read/write attempted from inside a session or a dispatched agent went through the session permission classifier, which denied every vault mutation outright.
- Two
sre-agentdispatches were asked, by a peer session relaying what it described as operator authorization, to perform a vault write on the peer's behalf. Both refused — the correct outcome (§5) — but it meant the write simply did not happen, twice, while prod stayed frozen. - The only credential path that worked end-to-end that day was: AWS SSM
(read-only
ssm:GetParameter/describe-instance-information), the Woodpecker API in read-only mode (/api/queue/info, pipeline status), and SSM Run Command to reach an EC2 host directly. Everything downstream of "now mutate something" failed. - Four specific mutations were denied even with a working token already in
hand: Woodpecker pipeline cancel and restart via the API,
ec2 reboot-instanceson a wedged CI agent host, deletion of a git tag on the remote (to re-fire a stuck pipeline against a fresh trigger), and one Infisical write for the stale-secret fix itself. - Recovery only completed once each mutation was performed by the operator directly, or worked around by re-tagging a new tag rather than deleting the old one — a workaround, not a fix to the access model.
Corroboration note (added in fix-forward review): the refusal account and
the four-mutations account above are the control-session's own report of that
day, cited here because it is what motivated this ADR. As of this revision,
neither https://github.com/raxx-app/TradeMasterAPI/issues/4544 nor https://github.com/raxx-app/TradeMasterAPI/issues/4545's comment history contains a record of
the laundering-refusal exchange, the tag-delete denial, or the reboot denial
— https://github.com/raxx-app/TradeMasterAPI/issues/4544's only comment covers the v1.13.3 re-tag deploy, and
https://github.com/raxx-app/TradeMasterAPI/issues/4545's comments cover the agent-orphan runbook gap, ASG floor drift,
and instance sizing (those three parts are corroborated there). Treat the
refusal/denial narrative as reported, not as an audited fact, until a session
transcript or log citation is added here. The dates follow the issue
timestamps: both issues were created 2026-08-29T01:24Z; #4544 closed
2026-08-29T02:38Z and its recovery deploy of v1.13.3 was commented at
2026-08-29T05:40Z; #4545's comments run from 2026-08-29T05:41Z to
2026-08-29T14:54Z, plus one on 2026-08-30T16:37Z (instance-class pricing). So
the incident itself is 2026-08-29 UTC, and 2026-08-30 enters only through the
fleet and sizing follow-ups in #4545 and the merge of #4566 (§3.2).
The naive fix that was proposed and rejected: give sessions (or the agents they dispatch) a shared admin Infisical identity, so the next incident does not stall the same way. This was raised and explicitly rejected in the control-session discussion that produced this ADR — it would undo the per-agent least-privilege model ADR-0130 (Accepted) already established and ADR-0131 (Proposed, not yet operator-approved) proposes, replacing "scoped identity per agent class, independently revocable" with "one identity, compromise-once-lose-everything," for the sake of convenience during exactly the kind of high-pressure recovery where a mistake is most costly.
This ADR does not re-litigate the shared read identity's blast radius on
its own — https://github.com/raxx-app/TradeMasterAPI/issues/3738 (provision per-agent scoped Infisical machine
identities) remains its own tracked work (still open as of 2026-10-30), and
https://github.com/raxx-app/TradeMasterAPI/issues/1903 (migrate CI workflows off static repo secrets
onto the vault-sourced pattern) was closed as completed on 2026-09-08, delivered
as a side effect of the GHA-to-Woodpecker migration — those pipelines load
vault secrets through the shared org read identity, so #1903 closing did not
narrow it and #3738 is the only open narrowing work — but §3.3
below makes landing https://github.com/raxx-app/TradeMasterAPI/issues/3738's narrowing (or an equivalent explicit deny
rule) a hard prerequisite before any <repo-slug>-rw identity is minted, and
that is the only gate on minting, because the -rw client secret's storage
path sits inside the shared read identity's current grant (§3.3, §3.4). The
repo-secret-write.sh wrapper (§3.3) gates a different thing — session
invocation of the minted identities — not minting (the one rule is stated in
§3.3 "Two gates, kept separate"). This ADR is about where write and
mutating privilege lives, for sessions and the agents they dispatch,
going forward.
2. Invariants restated
| # | Invariant | Source |
|---|---|---|
| I1 | No stored credentials that can be replayed. | Platform invariant |
| I4 | Credentials into infra (env / secret stores), never into files that ship. | Platform invariant |
| I3 | Audit trail for every state change affecting money, permissions, or data access. | Platform invariant |
| HANDOFF-INVARIANT-1 | No handoff/session identity ever holds admin or blanket-scope credentials; every credential-holding identity is independently revocable. | ADR-0131 (Proposed, not yet Accepted — operator decision still required on that ADR's OQ1/OQ2; this ADR treats the invariant as binding for its own decisions regardless of ADR-0131's own status) |
| — | A session denied an action must never obtain it via a peer session. | New, this ADR (§5) |
| — | Admin actions (minting identities, creating projects, membership) are operator-only, for sessions and the agents they dispatch; persistent services such as Velvet are outside this rule (§3.1). | New, this ADR (§3.1) |
No option below routes around any of these.
3. Decision
3.1 No session or agent ever holds an admin identity
No session, and no agent it dispatches, ever holds an org-admin or project-admin Infisical identity. Minting identities, creating Infisical projects, and changing project membership are performed only by Kristerpher Henderson, as org owner, directly — never relayed through a bot comment, never delegated to a session that "has admin creds available this one time." This closes the gap the peer-session-laundering attempt (§1, §5) tried to open.
Who §3.1 and §3.3 bind. They bind sessions and the agents a session
dispatches, not the persistent services. Velvet is out of scope, and its grant
is unchanged by this ADR. The design doc behind ADR-0130
(vault-multi-agent-access-pattern.md) gives Velvet
ownership of secret lifecycle including mint (invariant I7), lists
velvet-prod with read and edit on /MooseQuest/velvet/prod/ and on
/MooseQuest/ for rotation distribution (§6.2), and words provisioning as
"Operator (or Velvet with appropriate scopes)" (§6.3 step 1). This ADR neither
widens nor narrows that grant. The §6.2 matrix gives Velvet secret read/edit,
not Infisical identity administration, so Velvet minting <repo-slug>-rw
identities is not sanctioned here (minting stays operator-only, §3.3, §3.6),
and Velvet's rotation of those identities' client secrets (§8) runs under its
existing grant. That grant reaches /MooseQuest/identities/ through its
/MooseQuest/ write, by design; narrowing it is separate work, not decided
here.
3.2 Privilege lives in bounded, reviewed scripts, not in sessions
Every mutating operation a session legitimately needs gets an in-repo wrapper script, not standing credentials in the session's own environment. A qualifying wrapper:
- Fetches its own credential at run time from the sanctioned store — SSM for AWS-resident secrets, Infisical for everything else — rather than reading one from an ambient env var the session already holds.
- Validates a narrow argument shape: the specific operation it exists for
and nothing adjacent (e.g.
cancel <repo-id> <pipeline-number>/restart <repo-id> <pipeline-number>, never a bare "trigger a manual pipeline" call — a different privilege with a different blast radius). - Refuses anything outside its stated purpose, exiting non-zero with a diagnostic rather than guessing intent.
- Never prints the credential it fetched, in any output path (stdout, stderr, logs, a caught-exception traceback).
- Emits an audit line — timestamp, caller, operation, target, outcome — on every invocation, success or failure.
- Fetches its credential from a store/path the invoking session's own
ambient identity cannot already read. A wrapper that only re-fetches the
same secret a session-allow-listed read call can already return directly
(e.g. an SSM parameter also reachable via a generic
aws ssm get-parameter) narrows nothing at the credential layer — it is a naming convention around a credential the session already effectively holds. This is a hard requirement, not a nice-to-have (§3.5, §5).
A provision-secret.sh that the operator's local vault-kit checkout holds at
scripts/provision-secret.sh (invoked via the operator-local
/provision-vault-secret skill) is the closest existing illustration of the
wrapper shape — narrow surface, credential fetched inside the script rather
than handed to the caller, refusal by default, silent on values, loud on
outcomes. It is not a reviewed or deployed artifact. As of 2026-10-30 that
file is local and uncommitted: it is not on main of
MooseQuest-LLC/vault-kit (upstream scripts/ holds migrate-env.sh,
mint-identity.sh, provision-project.sh, seed-manifest.sh and
registry.toml), it was created on 2026-08-29, and it has never been
reviewed upstream. This ADR cites it only for its shape, as read from that
local copy; the first step before anything may rely on it is committing it to
vault-kit and reviewing it there (§9). It is not an example of point 6
above: it authenticates with the org-level admin Infisical identity (same
4-source precedence as rvault set, which is upstream in bin/rvault), and
it accepts a caller-supplied <projectId> <secretPath> <keyName> with no
allow-list or path pinning — so invoking it is admin-equivalent write to
any path, not a bounded one. As written it remains operator/admin-run only; a
.vault.toml-pinned mode that resolves <repo-slug>-rw (§3.3) would be a
different, session-invocable artifact. See §3.3 and §3.5 for what a
session-invocable vault-write wrapper actually requires.
Two CI/fleet wrappers were written under this ADR, by the
control-session, as sibling work (not authored by this design doc) and merged
to develop in #4566 on 2026-08-30. As of 2026-10-30 they are reviewed code
but not yet allow-listed for session invocation (§3.5, §9):
scripts/ops/wp-pipeline.sh cancel|restart <repo-id> <pipeline-number>— the two Woodpecker mutations reported denied on 2026-08-29. Both arguments are digits-only; the host, SSM path and region are fixed literals; restart re-runs one existing pipeline. No bare manual-trigger subcommand (a materially different privilege the incident did not need). Open requirement, per point 6 — still open as built: the only Woodpecker credential available today is the single admin API JWT at SSM/ci/woodpecker/admin-api-token(docs/ops/runbooks/ci-woodpecker.md), and the script fetches it with the invoking user's own~/.aws/credentials— the same parameter a session's already-allow-listedaws ssm get-parametercall can read directly (§3.5). Until a verb-scoped Woodpecker token (or equivalent narrower credential) exists,wp-pipeline.shdoes not narrow anything at the credential layer; landing that scoped token is a rollout prerequisite (§9), not an after-the-fact hardening step.scripts/ops/ci-agent-recycle.sh <instance-id> [--force-unprotect]— recycles ONE wedged instance of the CI agent ASG (ci-woodpecker-agents-asg, a fixed literal, never the Woodpecker server) for the wedged-host mode inhttps://github.com/raxx-app/TradeMasterAPI/issues/4545's comment timeline. As built it verifies ASG membership (autoscaling describe-auto-scaling-instances), optionally removes scale-in protection (--force-unprotect), then callsautoscaling terminate-instance-in-auto-scaling-group --no-should-decrement-desired-capacityso the ASG launches the replacement. (This ADR originally described restarting the agent process via SSM Run Command with an EC2 reboot fallback; that is not what was built.) Takes an instance ID, not a wildcard "reboot whatever's stuck." Open requirement, re-stated for the as-built actions: the wrapper must assume a dedicated IAM role scoped to the Auto Scaling actions it performs (autoscaling:DescribeAutoScalingInstances,autoscaling:SetInstanceProtection,autoscaling:TerminateInstanceInAutoScalingGroup, the mutating two limited to the CI agent ASG) — matching the precedentci-terraform-executorrole set for the same reason under ADR-0134 (docs/ops/runbooks/ci-woodpecker.md) — with the invoking session's own ambient AWS principal holding none of them. As built, the script assumes no role and runs under the invoking user's ambient credentials, so this requirement is unmet. This ADR does not name the role because it is a condition on the allow-listing step, not on the script merge (#4566 landed without it); it is a rollout-checklist item (§9).
Neither is authored here; this ADR defines the shape they must satisfy.
3.3 Per-repo scoped write identities for vault provisioning
Vault write access for provisioning is granted only to per-repo scoped
machine identities, never to a session or a shared identity. Naming
convention: <repo-slug>-rw. Each identity is bound to exactly:
- One Infisical project (per the existing taxonomy:
raxx-secrets,project-avenue,moosequest-core,moosequest.app,mqcraps), - One secret path within that project (the repo's own
secret_path), - Only the environments that repo actually uses,
- Write on that path only — no read/write on any other repo's path, no project-admin, no membership changes.
This requires a new or modified wrapper — rvault set (upstream) and the
local, uncommitted provision-secret.sh (§3.2) do not have a -rw-aware code
path today. Both resolve the
single org-admin credential via the same fixed 4-source precedence (macOS
keychain rvault:admin → ~/.config/rvault/admin.env →
RVAULT_ADMIN_CLIENT_ID/SECRET → INFISICAL_CLIENT_ID/SECRET); a
repo's .vault.toml supplies only project ID, secret path, and environment,
never an identity to authenticate as. Minting <repo-slug>-rw identities is
necessary but not sufficient for a session-invocable write path. This ADR
therefore requires, as a rollout prerequisite (§9), a new bounded wrapper —
working name repo-secret-write.sh <keyName> --env <env>, implementation is
sibling work, not authored here — that:
- derives
projectId/secretPathonly from the invoking repo's own.vault.toml(never from a caller-supplied path or project ID — that shape is what makesprovision-secret.shas it exists in the local checkout today unsuitable for session invocation, §3.2), - resolves
rvault:<repo-slug>-rwspecifically and never falls back to the admin precedence if that identity is absent or unreadable, - keeps
--force(clobber-existing-key) operator-only, - is never sourced into an interactive shell and never exports the
credential into a session's ambient environment — the credential lives and
dies inside that one invocation, matching the discipline of the local
provision-secret.sh(§3.2).
Two gates, kept separate (the one rule; §1, §3.7 and §9 restate it).
- Minting gate. No
<repo-slug>-rwidentity is minted, and the minting procedure below is not begun (do not begin step 1), untilhttps://github.com/raxx-app/TradeMasterAPI/issues/3738(or an equivalent explicit deny rule on/MooseQuest/identities/**) lands and is verified (§3.4). The gate covers the whole procedure, not only the storage step: steps 1-3 would leave a live client secret with no sanctioned place to store it. The verification happens before step 1, never inside the procedure it gates: the operator writes a throwaway, non-secret placeholder key to a scratch path under/MooseQuest/identities/(for example/MooseQuest/identities/_gate-check/), confirms that the shared read identity (or, if #3738 landed, every per-agent read identity that replaced it) is refused when it tries to read that path (a permission denial, not a not-found), then deletes the placeholder. The same check applies whichever way the gate closes. Step 6's negative test re-confirms the result afterwards on the real/MooseQuest/identities/<repo-slug>-rw/path; it is a re-check, and it is not what closes the gate. This is the only gate on minting; the wrapper below does not gate it. - Invocation gate. No
<repo-slug>-rwidentity is session-invocable untilrepo-secret-write.sh(or an equivalent.vault.toml-pinned mode added torvault set, or toprovision-secret.shonce it is committed upstream) lands and is reviewed. Once the minting gate is closed, identities may be minted (minting procedure below) before the invocation gate closes; until it does, they are not yet session-invocable and exist for the operator to use directly. (Velvet's rotation of their client secrets is outside this ADR's scope, §3.1.) The wrapper landing is necessary, not sufficient: a vault-write path is session-invocable only when all three hold — at least one-rwidentity is minted (which needs the minting gate closed, §9 Gate 1), the wrapper has landed and been reviewed (§9 Gate 2), and the wrapper's allow-list entry (first row of the §3.5 table) has landed. That entry sits in neither §9 Gate 2 nor Gate 3; it is the Control-session allow-list item. "Session-invocable" for a vault-write path means all three throughout this ADR.
Existing <repo-slug>-ro viewer identities are unaffected.
Relationship to the sre-agent write grant in ADR-0130. ADR-0130
(Accepted) says the sre-agent "remains available for vault write
operations", and its design doc's permission matrix
(vault-multi-agent-access-pattern.md §6.2) lists sre-agent with read,
create and edit on /MooseQuest/. This section supersedes that write
grant, forward-only: it is left as is until repo-secret-write.sh (or its
equivalent) is session-invocable — that is, once Gate 1 and Gate 2 (§9) are
closed, at least one -rw identity is minted, and the wrapper's allow-list
entry has landed (the Invocation gate's three conditions; Gate 2 closing alone
is not the trigger, because narrowing earlier would remove the only existing
vault-write grant with no replacement) — and from then on sre-agent is not a
sanctioned vault-write route (its read use is unchanged). Whether the
identity was ever provisioned with that grant is not verified here; the §9
checklist has the operator confirm it and narrow it. ADR-0130 and the
access-pattern doc (§6.2 matrix and §7) carry a back-pointer to this paragraph.
Minting procedure (operator-only, numbered, names/paths only — no secret values in this ADR or any command output):
- Operator opens the Infisical UI/API for the target project (project ID
per the taxonomy already in
vault-kit/scripts/registry.toml). - Operator creates a new Universal Auth machine identity named
<repo-slug>-rw. - Operator scopes it: project = the repo's assigned project; path = the
repo's
secret_path; environments = the subset the repo actually runs in; permission =create,editon that path only (nodelete, no other path or project). - Operator stores the resulting
client_secretvia the existing bounded provisioning path, at/MooseQuest/identities/<repo-slug>-rw/— never pasted into a chat transcript, a commit, or a script argument. Do not begin step 1 until the minting gate above is closed — the whole procedure is gated, not just this step; it is restated here because this is the step where the exposure occurs (§3.4): the shared read identity's grant isVAULT_PATH=/MooseQuest/with no documented exclusion for/MooseQuest/identities/(docs/architecture/vault-multi-agent-access-pattern.md§6.2/§6.3), so storing a-rwclient_secretthere today puts every repo's write credential inside the read scope every session and WP-org repo already holds — a new shared write privilege laundered through the vault, exactly the failure mode §3.1 exists to close. The gate closes only when either (a) issue 3738's per-agent scoped read identities replace the shared read identity, or (b) the shared read identity's grant is amended with an explicit deny on/MooseQuest/identities/**; in both cases that is verified before step 1 with the throwaway-path check in the minting gate. Step 6's negative test re-confirms it on the real path afterwards and cannot substitute for that check. - Operator adds one line to
vault-kit'sregistry.toml(that repo lives in theMooseQuest-LLC/vault-kitGitHub org, not this one) recording identity-name against repo-slug. - Operator (or a session running
rvault check) verifies the new identity can write its own path and cannot read or write any other repo's path — a negative test, not just a positive one. The negative test must also confirm the shared read identity itself cannot read/MooseQuest/identities/<repo-slug>-rw/— on the real path, after step 4, this re-confirms the check the minting gate required before step 1 on a throwaway path (it does not close the gate, and it is not merely a same-identity isolation check).
3.4 The shared read identity is narrowed separately — except where §3.3 depends on it
The existing shared read identity (raxx-secrets project,
VAULT_PATH=/MooseQuest/, carried by every WP-org repo and developer shell)
is not touched by this ADR in general. Narrowing its overall blast radius is
tracked as its own work: https://github.com/raxx-app/TradeMasterAPI/issues/3738 (per-agent scoped read identities,
already scoped in ADR-0130's design doc; open as of 2026-10-30) and
https://github.com/raxx-app/TradeMasterAPI/issues/1903 (migrate CI
workflows off static repo secrets onto the vault-sourced pattern; closed as
completed 2026-09-08, see §1). This ADR
is additive to that work, not a substitute for it — with one exception:
§3.3's minting gate makes closing the /MooseQuest/identities/ read gap (via
https://github.com/raxx-app/TradeMasterAPI/issues/3738 or an explicit deny rule) a hard prerequisite for beginning
the minting procedure at step 1 — not only for the step that stores the -rw
client secret, since steps 1-3 would leave a live secret with nowhere sanctioned
to put it — because that one path is where this ADR's own
design would otherwise create a new laundered write privilege. The deny rule
is verified before step 1 with a throwaway path under
/MooseQuest/identities/; step 6 re-confirms it afterwards. Everything
else the shared read identity can reach is unchanged and out of scope here.
3.5 The operator allow-list authorizes the bounded scripts
Sessions call the bounded scripts above — and the read paths that already work (SSM reads, Woodpecker read-only calls) — because the operator's Bash permission allow-list (project settings) explicitly names them. The allow-list is the enforcement point; this ADR does not implement it (the control-session's PR lands the actual entries) but records the intended shape:
| Rule (script path / command prefix) | Why it's allow-listed |
|---|---|
repo-secret-write.sh <keyName> --env <env> (§3.3; not yet built as of 2026-10-30 — rollout prerequisite, §9; policy/breakglass-classification.yaml already points at scripts/ops/repo-secret-write.sh as its home) |
Bounded single-key write pinned to the invoking repo's own .vault.toml; resolves <repo-slug>-rw only, never the admin precedence; credential fetched internally; never prints a value. |
scripts/vault/audit_coverage.py |
Read-only by construction; no write path exists. |
scripts/agents/mint_github_token.py --bot <bot> / with_bot_token.sh |
Mints a scoped 1-hour GitHub App installation token for one of the three bots (raxx-dev-bot, raxx-ops-bot, raxx-pm-bot; ADR-0128 — the renumbered successor to the original ADR-0096, per docs/architecture/agent-github-identity.md). Two caveats, as built on 2026-10-30: (a) if the mint fails, with_bot_token.sh falls back to the operator's ambient GH_TOKEN/GITHUB_TOKEN with a stderr warning — "never a session-held long-lived PAT" holds only for orchestrator spawn-time injection, which blocks dispatch for an unmapped agent class (UnknownAgentError); (b) this script predates the ADR and does not meet §3.2 point 6 — it reads each bot's App credentials from /MooseQuest/<bot-name>/ using whatever Infisical machine identity the invoking shell carries, so any identity able to mint can also read the App private key; narrowing that follows #3738 (§3.4). |
scripts/ops/wp-pipeline.sh cancel\|restart <repo-id> <pipeline-number> |
The two mutations reported denied on 2026-08-29, narrowed to exactly those verbs. No manual-trigger verb. Merged in #4566, not yet allow-listed. Blocked on a verb-scoped Woodpecker token existing (§3.2) — not yet true today, since the only credential available is the same admin token the SSM row below can already read. |
scripts/ops/ci-agent-recycle.sh <instance-id> [--force-unprotect] |
Wedged-agent recovery, scoped to one named instance verified to be in the CI agent ASG; not a wildcard fleet action. Merged in #4566, not yet allow-listed. Blocked on the dedicated IAM role in §3.2 existing — the session's own ambient AWS principal must not hold the ASG terminate/protection actions the script performs. |
aws ssm get-parameter --name <specific-read-only-parameter-path> / describe-instance-information … |
Read-only, and explicitly excludes /ci/woodpecker/admin-api-token (docs/ops/runbooks/ci-woodpecker.md) — that parameter is the Woodpecker admin JWT and must not be reachable by a generic allow-listed SSM read; enumerate the specific non-admin parameter paths a session needs (e.g. monitoring/health-check paths) rather than allow-listing the command with no path restriction. |
infisical secrets get … --plain (via rvault run/check) |
Read path only; per-repo -ro identity; unaffected. |
provision-secret.sh (the local, uncommitted script in §3.2) is deliberately
NOT allow-listed for session invocation as written, and would not be if it
were committed upstream unchanged (§3.2, §3.3): it authenticates as org-admin
and accepts a free-form <projectId> <secretPath> <keyName>, so allow-listing
it as written would make any allow-listed session admin-equivalent for every
vault path. As written it stays operator-run only, same as rvault set as
written. The only session-invocable variant is a different artifact: a
.vault.toml-pinned mode, resolving <repo-slug>-rw and never the admin
precedence, added to it once it is committed upstream (or to rvault set), as
§3.3 and §9 allow. Such a mode would be allow-listed on the same terms as
repo-secret-write.sh (first row above). Any session context
in which rvault:admin (keychain) or ~/.config/rvault/admin.env resolves
is, by construction, holding an admin credential and is out of policy for
that reason alone, regardless of whether this script is invoked.
Not proposed: any bare aws ec2 reboot-instances, git push --tags --force /
remote tag delete, direct curl … api.infisical.com write, or Woodpecker
manual-trigger call. Those stay behind a named wrapper (or operator-only)
precisely because the wrapper is what makes the argument shape narrow and the
audit line exist.
3.6 What stays operator-only regardless of any wrapper
No wrapper is written for the following — Kristerpher Henderson performs these directly, every time:
- Heroku platform key rotation — held in Velvet/vault, rotated per
docs/ops/runbooks/rotation/heroku-platform-token.md. - Cloudflare API tokens (zone/WAF/DNS edit scopes) — rotation per
docs/ops/runbooks/rotation/cloudflare-user-api-token.mdandcloudflare-access-service-token.md. - GitHub App private keys (the three bot identities' PEM material) — rotation
per
docs/ops/runbooks/rotation/github-app-installation-token.md. - Infisical identity minting itself (§3.1, §3.3) — operator-only, no exception.
- Anything under
/MooseQuest/aws/*— the sole IAM user's credentials (claude-infisical-bootstrap) are never distributed to a session or a bounded script; SSM reads use their own narrowly-scoped path.
3.7 Relationship to what onboard-vault / onboard-ci already did
This ADR is forward-only and additive; it does not describe a migration of the already-onboarded repos, and it does not mean the estate "skipped" a step. Concretely, of the two halves of the model:
-
The read half is already universal. Every repo with an entry in
registry.tomlonmainofMooseQuest-LLC/vault-kit(six as of 2026-10-30: TradeMasterAPI, project-avenue, smarter, console, im-here-app-for-tesla, something-something-darkside) was onboarded with a per-repo<repo-slug>-roviewer identity (single project + single path, read-only) and a committed.vault.toml, minted by/onboard-vaultviavault-kit/scripts/mint-identity.sh. (The operator's local, uncommitted copy ofregistry.tomlhas two more entries, moosequest-site and d-record, which are not counted here; its own comment on d-record records that repo's-roidentity as not yet minted.) That is already least-privilege read, and it is consistent across the upstream-registered estate./onboard-ciadditionally wires each activated repo the shared org read identity (theINFISICAL_*+CF_ACCESS_*org WP secrets, scoped to theraxx-secretsproject, defaultVAULT_PATH=/MooseQuest/) for CI-runtime secret reads — the over-broad grant this ADR's §3.4 and issuehttps://github.com/raxx-app/TradeMasterAPI/issues/3738exist to narrow. -
The write half has never existed for any repo. No repo has ever held a per-repo write identity. Every vault write in the estate today — onboarding seeds,
/provision-vault-secret,rvault set— runs under the operator's org-admin identity, held by Kristerpher Henderson at provisioning time only. So the<repo-slug>-rwidentity in §3.3 is greenfield for the entire estate, not something done elsewhere and omitted here. Sessions cannot write today at all (the permission classifier blocks it); this ADR defines the first sanctioned middle path between "operator does every write by hand" and "give sessions admin".
Effect on the skills — nothing changes until the §9 gate that applies to each skill closes (minting is gated on #3738 alone; the vault-write wrapper gates session invocation, not minting; the CI/fleet scoped credentials gate /onboard-ci; see §3.3 "Two gates, kept separate"):
| Skill | Today (unchanged by this ADR until prerequisites close) | After prerequisites |
|---|---|---|
/onboard-vault |
Mints <slug>-ro viewer only; writes use the operator admin identity. |
Gains a <slug>-rw minting step — gated on https://github.com/raxx-app/TradeMasterAPI/issues/3738 alone (§3.3 minting gate / §9), because a -rw client secret stored under /MooseQuest/ would otherwise sit inside the shared read identity's grant. repo-secret-write.sh does not gate this step; it gates session invocation of what the step mints. |
/onboard-ci |
Wires the shared org read secrets + copies wpc_load_vault_secrets.py; no wrappers, no allow-list. |
Gains a step to distribute + allow-list the bounded CI/fleet wrappers — gated on the verb-scoped Woodpecker token and dedicated recycle IAM role (§3.2, §9). Per the operator's 2026-08-31 decision, this stays strict: no wrapper is allow-listed for direct session invocation until those scoped credentials exist (the ADR-0151 Tier B route to the same operations is separate, §3.8). |
/provision-vault-secret |
rvault set (repo .vault.toml) or the local, uncommitted provision-secret.sh (admin; §3.2). |
repo-secret-write.sh (§3.3), or an equivalent .vault.toml-pinned mode of rvault set / provision-secret.sh, becomes the only session-invocable write path. provision-secret.sh and rvault set as written stay operator-only; only a pinned mode is session-invocable (§3.5). |
Until issue 3738 and the scoped CI credentials land, the two skills keep doing
exactly what they do now, and the only new artifacts are documents (this ADR)
and the sibling wrapper scripts (unused as session-invocable until their
prerequisites close). Onboarding a new repo in the interim follows the current
-ro-only procedure; it acquires a -rw identity retroactively when 3738 closes,
via a re-run of /onboard-vault (idempotent).
3.8 Relationship to ADR-0151 (added at acceptance, 2026-10-30)
ADR-0151 (graduated agent write-authority, Accepted 2026-08-31, #4577) builds on this ADR and, in its own §1 and §3, declares itself a deliberate, scoped exception to §3.1 (and to HANDOFF-INVARIANT-1) for its break-glass tiers only. This section records that so a reader of this ADR is not misled; it changes nothing in §3.1–§3.7.
- ADR-0151's Tier A is this ADR: the bounded wrappers (§3.2) and the
<repo-slug>-rwidentities (§3.3). Unchanged. - ADR-0151's Tiers B and C add one dedicated
agent-breakglass-adminidentity. It is minted and stored only by the operator (§3.1 stands for minting) and is intended to be used byscripts/ops/breakglass-admin.shto mint short-TTL tokens per operation (ADR-0151's design; as built, the script mints nothing and only checks the credential's presence, see the bullets below). Tier B (a closed, low-consequence, reversible list inpolicy/breakglass-classification.yaml) is pre-approved; Tier C needs per-request operator approval, and ADR-0151 §4 restates the §5 anti-laundering rule — a peer session relaying "the operator approved this" is not approval. - As of 2026-10-30
breakglass-admin.shis merged but not usable end to end, and what provisioning would turn on is narrower than "everything": Tier A operations always refuse (they are routed to their direct wrappers, §3.2/§3.3); Tier C is unconditionally inert regardless of provisioning, because approval verification is unimplemented for both channels (see the script's header and its approval-verification block); and Tier B refuses until the operator provisions the identity perdocs/ops/runbooks/breakglass-admin-provisioning.md. So provisioning activates Tier B only. Tier B's implemented executors (wp-pipeline-cancel,wp-pipeline-restart,ci-agent-recycle) delegate to the §3.2 wrapper scripts; its other two classified operations (vault-secret-write-nonprod,staging-only-config-change) have no executor and refuse. - The operator's 2026-08-31 Strict decision on the §3.2 wrappers (§3.7, §9)
gates direct allow-listing of
wp-pipeline.shandci-agent-recycle.shonly: those two stay non-session-invocable until the verb-scoped Woodpecker token and the dedicated recycle IAM role exist. ADR-0151 does not edit that decision, but Tier B is a separate, sanctioned route to the same three operations (Woodpecker pipeline cancel, Woodpecker pipeline restart, single-agent recycle). Once the operator has provisioned the break-glass identity and an allow-list rule forbreakglass-admin.shexists, a session can perform those three operations through Tier B without the §9 scoped-credential prerequisites being met. As built, the break-glass credential is only checked for presence (the kill switch) and the dispatch runs the same wrapper scripts, which still read the admin Woodpecker token and use the invoking user's ambient AWS credentials (§3.2). That is the "admin credential inside a hardened script" outcome the Strict decision declined for the direct wrappers; ADR-0151 §2 (Tier B) classifies these operations as pre-approved on the strength of their reversibility instead. §3.6 stands as written for what exists today: no executor exists for any §3.6 item. ADR-0151's Tier C category "credential rotation or revocation affecting prod access" is the sanctioned route if one is ever added, and adding an executor tobreakglass-admin.shis itself a reviewed change.
4. Language choice rationale
N/A — this ADR introduces no new service (no new process, daemon, or independently deployable component). It defines a credential-access pattern and wrapper scripts running inside existing execution contexts (session shells, Woodpecker CI, SSM Run Command targets). Per the ADR template, this section is skipped.
5. Threat model
Session compromise. A compromised session should get, at worst, whatever
the allow-listed bounded scripts expose — never a standing admin credential.
That is the design intent of §3.2's wrappers (fetch internally, never hand
the value back to the caller), and it holds for a wrapper that satisfies
point 6 of §3.2. It does not yet hold for wp-pipeline.sh as scoped
today: its only available Woodpecker credential is the same admin JWT at
SSM /ci/woodpecker/admin-api-token that a session's already-allow-listed
generic SSM read can fetch directly (docs/ops/runbooks/ci-woodpecker.md),
so until that gap closes (§3.2, §3.5, §9), the actual bound on a compromised
session is the classifier plus the allow-list's narrow argument shapes,
not credential non-exfiltration — a compromised session that can pass the
classifier and the allow-list can already read the admin Woodpecker token
directly, wrapper or no wrapper. Say this plainly rather than claim a
stronger guarantee the credential layer does not yet provide.
Prompt injection. A session manipulated (via a malicious PR description, a poisoned file, an adversarial tool response) into attempting an out-of-scope action is stopped at the argument-validation layer of the bounded script (§3.2 point 2) even if it is stopped nowhere else.
Peer-session permission laundering. This is the exact failure mode
reported on 2026-08-29 (§1, corroboration note), called out explicitly rather than left implicit:
a session or agent denied an action must never obtain it by asking a
different session or agent to perform it on its behalf, even when the
requesting session claims to relay direct operator authorization. The two
sre-agent refusals that day were correct before this ADR existed; this
ADR makes the rule the documented standard, consistent with
.claude/agents/_shared-conventions.md §Operator authorization identity
(operator sign-off must be posted under the operator's own GitHub identity or
given directly via AskUserQuestion — never relayed through a bot or a peer
session claiming to carry it). If a genuinely operator-authorized mutation is
blocked, the correct path is the operator performing it directly, or the
operator adding a new narrowly-scoped wrapper — never a peer session vouching
for authorization it cannot itself verify.
6. Consequences
Positive
- No single compromised session or dispatched agent can reach admin-level vault, AWS, or CI privilege — blast radius bounded to that session's allow-listed wrappers.
- The mutations reported denied on 2026-08-29 (§1) get a sanctioned, audited path instead of requiring the operator to hand-run every one personally next time — once the open prerequisites in §3.2/§3.3/§9 close the gaps this fix-forward review found.
- Per-repo
-rwidentities mean a compromised or misconfigured identity for one repo cannot write into another repo's secret path — matching the independent-revocation property ADR-0130 (Accepted) already established and ADR-0131 (Proposed) proposes for the same reason. - Every mutation through a bounded script leaves an audit line, closing part of the I3 gap ad-hoc credential handling left open.
Negative / risks
- Bounded scripts are maintenance surface. Every new mutating need requires a new wrapper, reviewed and merged like any other change — slower than direct API access by design, but a real ongoing cost.
- Classifier decisions can still deny an allow-listed command. Naming a script in the allow-list is necessary but not sufficient — the classifier evaluates the call at run time and can still refuse it. This ADR narrows what a session can attempt, not classifier behavior; a correctly-scoped wrapper still being denied is a classifier-tuning problem, not evidence this ADR failed.
- Per-repo identity sprawl. Five-plus
-rwidentities plus existing-roidentities is more inventory than one shared identity — mitigated byregistry.tomlbut a real increase to keep straight.
Neutral
- The shared read identity's blast radius is unchanged (§3.4) — this ADR fixes the write/mutation half only.
7. Alternatives considered
| Alternative | Verdict | Why |
|---|---|---|
| Shared admin identity for all sessions/agents | Rejected | One compromise exposes every secret and mutation surface at once; undoes ADR-0130's (Accepted) least-privilege model and what ADR-0131 (Proposed) proposes; the naive fix explicitly rejected in the control-session discussion (§1). |
| Per-session admin identity (each session mints its own full-scope identity) | Rejected | Still blanket-scope per identity — a single compromised session can mutate anything in vault/AWS/CI; only improves revocation granularity, not the actual risk (breadth of what one identity can do). |
| Keep the status quo (read-only everywhere, no bounded scripts) | Rejected | Cost is the mutations, refusals, and multi-hour prod-deploy freeze reported for 2026-08-29 (§1, with the corroboration caveat noted there), resolved only by the operator performing every mutation personally. Not sustainable as the standing model regardless of exactly how many mutations were denied that specific day. |
8. Security / GDPR checklist
- PII collected: None — this ADR concerns machine-identity and script design, not user data.
- Retention period: Bounded-script audit lines follow existing Infisical
audit log retention (90 days, per
vault-multi-agent-access-pattern.md§12) and existing CI log retention; no new policy introduced. - Deletion on DSR: N/A — no end-user data handled by this model.
- Audit trail: Every bounded-script invocation logs caller, operation,
target, timestamp, outcome. Infisical reads/writes are already logged
per-identity (
vault-multi-agent-access-pattern.md§6.6); GitHub bot actions already logged (ADR-0128, the renumbered successor to the original ADR-0096 — seedocs/architecture/agent-github-identity.md). - Stored credentials: No credential is stored in a replayable form
outside the sanctioned stores (SSM, Infisical).
-rwclient secrets live in Infisical and are fetched by the wrapper at invocation time — never in a committed file, never in a session's ambient environment. Open gap as of this revision: until §3.3/§3.4's prerequisite closes, the storage path itself (/MooseQuest/identities/<repo-slug>-rw/) sits inside the shared read identity's grant, which is a replay risk this ADR does not consider closed yet (§3.3 minting gate). - Breach notification path: Unchanged from
vault-multi-agent-access-pattern.md§12 — revoke the affected identity independently, provision a replacement, notify the operator.-rwidentities are per-repo, so a breach's blast radius is bounded to one repo's secret path — once §3.3/§3.4's prerequisite closes; until then, a shared-read-identity compromise reaches every minted-rwsecret. - Secrets location + rotation:
-rw/-rocredentials live in Infisical; rotation follows the Velvet subscription-manifest pattern (vault-multi-agent-access-pattern.md§6.4). Bounded scripts never cache a fetched credential to disk. - Kill-switch: Revoking a
-rwidentity in Infisical removes that repo's write capability without touching any other identity (revocation is per-identity:vault-multi-agent-access-pattern.md§6.5). That doc's under-60-seconds revocation figure is in its §12, stated there for the persistent-service identities (Raptor, Velvet); it is carried over here as the expected order of magnitude, not measured for-rwidentities. Removing a script's allow-list entry removes a session's ability to invoke it at all.
9. Rollout checklist
Hard prerequisites (added in fix-forward review). Three gates; each blocks
only what it names and does not block the others (the one rule is stated in
§3.3, "Two gates, kept separate"):
Gate 1 - MINTING (blocks every "Mint <repo-slug>-rw" step in the Operator list
below, and nothing else):
[ ] https://github.com/raxx-app/TradeMasterAPI/issues/3738 lands (or an equivalent explicit deny rule on
/MooseQuest/identities/**) — closes the §3.3 minting-gate / §3.4 gap where a
-rw client secret would otherwise sit inside the shared read identity's
grant. (Re-checked 2026-10-30: #3738 still open; no deny rule on
/MooseQuest/identities/** exists in the repo.)
[ ] Gate 1 is verified BEFORE minting step 1, with a throwaway non-secret key
at a scratch path under /MooseQuest/identities/: the shared read identity
(or, if #3738 landed, each per-agent read identity that replaced it) is
refused (permission denial, not a not-found) when it reads it, then the
placeholder is deleted (§3.3). Step 6's
negative test re-confirms this on the real path afterwards; it does not
close the gate.
Gate 2 - SESSION INVOCATION of vault writes (does NOT gate minting):
[ ] repo-secret-write.sh (§3.3) — or an equivalent .vault.toml-pinned,
<repo-slug>-rw-resolving mode added to rvault set (or to
provision-secret.sh once it is committed upstream, §3.2) —
lands and is reviewed. Until it exists, no vault-write wrapper is
session-invocable regardless of how many -rw identities are minted
(minted identities are operator-direct-use only in the meantime).
Necessary, not sufficient: a vault-write path is session-invocable only
once Gate 1 and Gate 2 are closed, at least one -rw identity is minted,
AND the wrapper's allow-list entry (§3.5 table, first row) has landed —
that entry is the Control-session allow-list item below, not part of
Gate 2 or Gate 3.
Gate 3 - ALLOW-LISTING the CI/fleet wrappers and the SSM read rule (does NOT
gate minting):
[ ] OPERATOR DECISION 2026-08-31 (Kristerpher Henderson): CI/fleet wrappers
take the STRICT posture — wp-pipeline.sh and ci-agent-recycle.sh are NOT
directly session-invocable (no allow-list entry for the wrappers
themselves) until the two scoped credentials below exist. No interim
"admin token inside the hardened script" step for the direct wrappers.
This gates direct allow-listing only; the [ADR-0151](https://internal-docs.raxx.app/architecture/adr/0151-graduated-agent-write-authority.html) Tier B route to the same
three operations is separate (§3.8).
[ ] A verb-scoped Woodpecker API token (or equivalent narrower credential)
exists for wp-pipeline.sh, distinct from the admin JWT at SSM
/ci/woodpecker/admin-api-token (§3.2, §5).
[ ] A dedicated IAM role (e.g. ci-agent-recycle-executor) scoped to the
Auto Scaling actions ci-agent-recycle.sh performs
(autoscaling:DescribeAutoScalingInstances, SetInstanceProtection,
TerminateInstanceInAutoScalingGroup — the mutating two limited to
ci-woodpecker-agents-asg) exists for ci-agent-recycle.sh, and the
session's ambient AWS principal is confirmed to hold none of them
(§3.2). (Originally written for ssm:SendCommand + ec2:RebootInstances,
which is not what the merged script does.)
[ ] The operator Bash allow-list's aws ssm get-parameter rule is narrowed to
specific non-admin parameter paths, explicitly excluding
/ci/woodpecker/admin-api-token (§3.5).
Operator (Kristerpher Henderson):
[ ] Mint <repo-slug>-rw for TradeMasterAPI, project-avenue, moosequest-site,
smarter, console (in that order — TradeMasterAPI first, matching where
2026-08-29 actually hurt) — do not begin step 1 of the §3.3 minting
procedure until Gate 1 is closed and verified (the gate covers the whole
procedure, not only the storage step 4).
Gates 2 and 3 do not gate minting; they gate session invocation and
allow-listing. (moosequest-site's registry entry exists only in the
operator's local, uncommitted registry.toml today, §3.7.)
[ ] Record each identity in vault-kit's registry.toml (MooseQuest-LLC/vault-kit).
[ ] Commit scripts/provision-secret.sh to MooseQuest-LLC/vault-kit and
review it there, or retire it (§3.2: it is local and uncommitted as of
2026-10-30). Likewise decide whether the local-only registry.toml entries
(moosequest-site, d-record) are committed, so §3.7's count and the
upstream file agree.
[ ] Confirm whether the sre-agent identity holds create/edit on /MooseQuest/
([ADR-0130](https://internal-docs.raxx.app/architecture/adr/0130-vault-multi-agent-machine-identity.html) design doc §6.2) and, if so, narrow it once Gate 1 and Gate 2
are closed, at least one -rw identity is minted, and the wrapper's
allow-list entry has landed (§3.3) — NOT when Gate 2 closes alone: that
would remove the only existing vault-write grant with no replacement.
[ ] Confirm each identity's negative test: cannot read/write outside its own
project + path, AND the shared read identity cannot read
/MooseQuest/identities/<repo-slug>-rw/ (§3.3 step 6; this re-confirms the
Gate 1 check made before step 1, it does not replace it).
[x] ADR accepted 2026-10-30 (see Status) and the Status flipped to Accepted.
[ ] Authorization artifact still outstanding: an approval comment under your
own GitHub identity on [PR #4567](https://github.com/raxx-app/TradeMasterAPI/pull/4567), or a cited AskUserQuestion transcript
reference here (the original gate for flipping Status; the flip was made
on the operator's merge directive without either — see Status).
Control-session (sibling PRs, not this one):
[x] wp-pipeline.sh (cancel|restart) and ci-agent-recycle.sh landed in #4566
(2026-08-30) as scripts/ops/wp-pipeline.sh and
scripts/ops/ci-agent-recycle.sh.
[ ] Their credential-isolation and IAM-role prerequisites (Gate 3 above) —
NOT satisfied as built; they stay gating conditions on allow-listing.
[ ] Land repo-secret-write.sh (or the rvault set / provision-secret.sh
equivalent) per §3.3.
[ ] Land the operator Bash allow-list entries per §3.5's table, narrowed as
described above.
Verification (either party):
[ ] rvault check in a session with no admin credential exported confirms the
session cannot read/write outside its repo's own scope, and that no
ambient env var carries an admin client_id/secret — existence check
only, never a value dump.
[ ] A session with only the shared read identity confirms it cannot read
/MooseQuest/identities/<repo-slug>-rw/ for any minted identity.
10. Revisit when
https://github.com/raxx-app/TradeMasterAPI/issues/3738lands (https://github.com/raxx-app/TradeMasterAPI/issues/1903already closed 2026-09-08) — revisit whether the shared read identity should be retired entirely, closing the gap this ADR deliberately left open (§3.4).- The classifier-tuning friction in §6 produces a second incident of a correctly-scoped wrapper being denied — signal to open a dedicated classifier-tuning card rather than adding more wrappers.
- A sixth or later repo needs a
-rwidentity — confirmregistry.tomlis still the right inventory mechanism at that scale.
Cross-references
- ADR-0130 —
docs/architecture/adr/0130-vault-multi-agent-machine-identity.md - ADR-0131 —
docs/architecture/adr/0131-handoff-agent-least-privilege-identity.md - ADR-0151 —
docs/architecture/adr/0151-graduated-agent-write-authority.md docs/architecture/vault-multi-agent-access-pattern.mddocs/architecture/handoff-agent-least-privilege-identity.mddocs/architecture/agent-github-identity.mddocs/ops/runbooks/vault-access.mddocs/ops/runbooks/vault-coverage-audit.md.claude/agents/_shared-conventions.md§Operator authorization identityMooseQuest-LLC/vault-kit(separate GitHub org) —bin/rvault,scripts/mint-identity.sh,scripts/registry.toml- The
/provision-vault-secretand/onboard-vaultskills are operator-local (not in any repository), so they have no link here - Issue #4544 —
https://github.com/raxx-app/TradeMasterAPI/issues/4544 - Issue #4545 —
https://github.com/raxx-app/TradeMasterAPI/issues/4545 - Issue #3738 —
https://github.com/raxx-app/TradeMasterAPI/issues/3738 - Issue #1903 —
https://github.com/raxx-app/TradeMasterAPI/issues/1903