From 2c99cf70cd4063f836843b94530c17bd3aeeb6f9 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Sun, 23 Aug 2026 00:24:08 -0700 Subject: [PATCH] deploy(vera): seed her own Infisical connection (ADR 0080 machine identity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vera fetches her own runtime secrets with a universal-auth machine identity instead of taking a one-shot env injection from a human's `infisical run` session. Two observed failures, not theoretical ones: 1. A human session expires. On 2026-08-21 ava's had, and the fleet watchdogs had already run two cycles without DISCORD_WEBHOOK_ALERTS — the checks passed, but had one failed it would have "alerted" to a logfile nobody reads. 2. Injected env is a snapshot. Rotate a secret in the vault and the running container keeps the stale value until someone recreates it. `refresh_seconds: 300` makes rotation propagate on its own. `required: true` is deliberate: with the secrets out of compose, a failed fetch means no GitHub App key and no gateway key, so she would boot and then fail every review for reasons visible only in her own logs. Fail-fast turns that into a restart the healthcheck reports. `A2A_AUTH_TOKEN` is deliberately NOT hydrated — the operator bearer is how you reach the API to fix a broken secrets connection. `host` takes no `/api` suffix even though the CLI's `--domain` does; probed both against the live server (bare → 401 = right endpoint, `/api` → 404). Fixed upstream in core 0.145.0 (protoAgent#2958) to tolerate both, but the seed spells it correctly regardless. Seed is COPY-ONCE, so this reaches fresh instances only — the running Vera is untouched until the config volume is cleared. Companion compose change and the admin checklist live in homelab-iac `vera/infisical-machine-identity` and qaEngineer#47. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FnbQVnHNvTsvbnJ1pdUDdD --- deploy/vera.langgraph-config.yaml | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/deploy/vera.langgraph-config.yaml b/deploy/vera.langgraph-config.yaml index 5bb04f0..49624c4 100644 --- a/deploy/vera.langgraph-config.yaml +++ b/deploy/vera.langgraph-config.yaml @@ -67,3 +67,45 @@ pr_reviewer: shadow_mode: false promotion_owner: true default_repo: "" + +# External secrets (ADR 0080) — Vera fetches her own runtime secrets from Infisical +# using a UNIVERSAL-AUTH MACHINE IDENTITY, instead of having them injected once at +# `docker compose up` by a human's `infisical run` session. +# +# WHY THIS EXISTS. The injected-at-deploy shape has two silent failure modes, and we +# hit both. (1) A human CLI session expires — on 2026-08-21 ava's had, and the fleet +# watchdogs had been running for two cycles WITHOUT `DISCORD_WEBHOOK_ALERTS`, which +# means every alarm they could have raised would have gone to a log file instead of +# Discord. The guard was disarmed and nothing said so. (2) Injected env is a SNAPSHOT: +# rotate a secret in the vault and the running container keeps the old value until +# someone remembers to recreate it. A machine identity has no session to expire, and +# `refresh_seconds` re-fetches, so rotation propagates on its own. +# +# HOST HAS NO `/api` SUFFIX. The CLI's `--domain` wants `https://secrets.proto-labs.ai/api`; +# this provider appends `/api/v1/...` itself, so the same value here 404s. Verified by +# probing both shapes against the live server (bare host → 401 = endpoint found and +# credentials rejected; with /api → 404). +# +# CREDENTIALS COME FROM ENV, never this file: INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET +# (the provider's `bootstrap_env`). They are also PROTECTED — no fetched value can +# overwrite them, so the identity cannot rotate itself out of existence mid-run. +# +# `required: true` is deliberate. With the secrets moved out of compose, a failed fetch +# means no GitHub App key and no gateway key: she would boot and then fail every review +# for reasons visible only in her own logs. Fail-fast makes it a restart loop the +# healthcheck reports, which is the loud version of the same fact. +# +# NOT hydrated here: `A2A_AUTH_TOKEN`. The operator bearer is how you reach the API to +# FIX a broken secrets connection — putting it behind that connection locks the door +# with the key inside. It stays in compose. +secrets_manager: + enabled: true + provider: infisical + host: https://secrets.proto-labs.ai + project_id: 8da5101a-4bde-4c9b-a42a-76da1965f2fe + environment: prod + path: / + recursive: true + refresh_seconds: 300 + required: true + override_env: false