215 lines
11 KiB
Markdown
215 lines
11 KiB
Markdown
# Session 0044.0 — Transcript
|
|
|
|
> Date: 2026-05-29
|
|
> Goal: Isolate this machine's multiple Google Cloud stacks so OHM/flotilla
|
|
> gestures can never accidentally drive the wrong account/project — first as
|
|
> operator discipline (a named gcloud config + CLAUDE.md instructions), then
|
|
> as an enforced guard in flotilla itself.
|
|
>
|
|
> Outcome: **Shipped flotilla v1.4.0 (gcloud config guard).** Created an
|
|
> inactive `wiggleverse` gcloud config; added the `CLOUDSDK_ACTIVE_CONFIG_NAME`
|
|
> discipline to CLAUDE.md; added a per-deployment `gcloud_config` record field
|
|
> (migration 006) + `ohm_rfc_app_flotilla/gcloud.py` guard enforced at both
|
|
> `run_deploy` entry and `ssh.run_remote`; set the live OHM record to
|
|
> `gcloud_config=wiggleverse`. 214 tests pass. Merged to main, tagged v1.4.0,
|
|
> pushed. No OHM app redeploy (operator-tool code only).
|
|
>
|
|
> Highlights / §19.2 candidates surfaced:
|
|
> - flotilla relies on env *inheritance* for the config pin — it doesn't set
|
|
> `CLOUDSDK_ACTIVE_CONFIG_NAME` itself. Candidate: have it assert-or-set.
|
|
> - `gcloud compute ssh` pins `--project` but not `--account`. Candidate:
|
|
> store the operator account in the record and pass `--account` too.
|
|
> - Secret Manager calls use ADC, which `CLOUDSDK_ACTIVE_CONFIG_NAME` does not
|
|
> select — the guard is scoped to the gcloud-subprocess path by design.
|
|
>
|
|
> Timing: the session ran ~30 minutes (start ~07-27, end 07-57 PST). The
|
|
> exact start minute was not auto-recorded; 07-27 is derived from the
|
|
> operator-confirmed ~30-minute duration against the real 07-57 end.
|
|
|
|
---
|
|
|
|
## Pre-session state
|
|
|
|
- **flotilla** (`ohm-rfc-app-flotilla`): `main` at `242bb3a` (= `origin/main`),
|
|
last release tag `v1.3.0` (`fb9bd04`). Working tree clean. 203 tests green.
|
|
- **OHM live**: unchanged this session — no deploy performed. The local
|
|
deployment record (`~/.ohm-rfc-app-flotilla`) had one row, `ohm-rfc-app`,
|
|
`target_vm_project=wiggleverse-ohm`, `gcloud_config=NULL` (column did not
|
|
exist yet).
|
|
- **gcloud configs on the machine** at open:
|
|
- `benstull-infra` (ACTIVE) → `ben.stull@wiggleverse.org` / `wiggleverse-ohm`
|
|
- `benstullbets` → `ben.stull@benstullbets.com` / `benstullbets`
|
|
- `default` → `ben@wiggleverse.org` / `wiggleverse-rfc`
|
|
- **In-flight sessions** per the shared repo at claim time: `0026.0` and
|
|
`0027.0` (both `--INPROGRESS` from 2026-05-28T13-46, ~18h stale; neither
|
|
touches flotilla, so the shared checkout was safe to work in directly).
|
|
|
|
---
|
|
|
|
## Turn-by-turn arc
|
|
|
|
### Arc 1 — The original ask: a named config + instruction discipline
|
|
|
|
The operator runs more than one Google Cloud stack on this machine and wanted
|
|
process-scoped isolation: always select the config via
|
|
`CLOUDSDK_ACTIVE_CONFIG_NAME=xyz` (which never touches the shared on-disk
|
|
active-config pointer) plus belt-and-suspenders explicit `--project`/`--account`.
|
|
|
|
Inspected the existing configs/accounts and how flotilla invokes gcloud:
|
|
`ssh.py` already passes `--project` from the deployment record; Secret Manager
|
|
uses ADC; nothing pinned the account or a config. So the env-var discipline was
|
|
exactly the missing identity pin.
|
|
|
|
The two named stacks didn't map cleanly to the existing config names, so I
|
|
asked the operator (AskUserQuestion) for the account+project mapping per config.
|
|
Answers: **wiggleverse** → `ben.stull@wiggleverse.org` / `wiggleverse-ohm`;
|
|
**benstull** → *"don't worry about this one — I'm setting it up in a different
|
|
session."* So this session created only the `wiggleverse` config.
|
|
|
|
Created it inactive and populated it without disturbing the active pointer:
|
|
```
|
|
gcloud config configurations create wiggleverse --no-activate
|
|
CLOUDSDK_ACTIVE_CONFIG_NAME=wiggleverse gcloud config set account ben.stull@wiggleverse.org
|
|
CLOUDSDK_ACTIVE_CONFIG_NAME=wiggleverse gcloud config set project wiggleverse-ohm
|
|
CLOUDSDK_ACTIVE_CONFIG_NAME=wiggleverse gcloud config set disable_usage_reporting true
|
|
```
|
|
Verified `benstull-infra` stayed `IS_ACTIVE=True`. Added an "Always pin the
|
|
gcloud config" section to `CLAUDE.md` codifying the discipline (env var on
|
|
every gcloud call and on `flotilla` invocations so subprocesses inherit it;
|
|
hard "never mutate the shared active config" rule; read-back + `--no-activate`
|
|
escape hatches). Did **not** rewrite the operator-guide's many gcloud commands
|
|
(they already pin `--project`); flagged that as optional.
|
|
|
|
### Arc 2 — The operator opted into the enforced guard
|
|
|
|
I noted flotilla doesn't *assert* the config, only inherits it, and offered a
|
|
code-level guard. Operator: **"yes."**
|
|
|
|
Key design decision (two-layer rule): the config name `wiggleverse` is an
|
|
OHM-specific value, so it must live in the §13 deployment record, **not** be
|
|
hardcoded in the CLI. Implementation:
|
|
|
|
- **Migration `006_gcloud_config.sql`** — nullable `gcloud_config TEXT` on
|
|
`deployments`. `NULL` = guard off (backward compatible; generic /
|
|
flotilla-core deployments unaffected).
|
|
- **`ohm_rfc_app_flotilla/gcloud.py`** — `check_active_config(expected, env)`:
|
|
no-op when `expected` falsy; else raises `GcloudConfigError` (with the exact
|
|
`CLOUDSDK_ACTIVE_CONFIG_NAME=<name> <command>` remediation) on mismatch *or
|
|
unset*. Failing on unset is deliberate — forces the explicit per-process pin.
|
|
- **Two-layer enforcement**: `run_deploy` checks at gesture entry (before a
|
|
`deploys` row opens → clean refusal, no orphan row) and re-raises as
|
|
`DeployError`; `ssh.run_remote` re-checks per call (the literal
|
|
`gcloud compute ssh` chokepoint) via a new `gcloud_config` field on
|
|
`SshTarget`. `--dry-run` (which goes through `assemble_plan`, not
|
|
`run_deploy`) stays unguarded.
|
|
- **registry**: `Deployment.gcloud_config` field, row mapping, `add_deployment`
|
|
param, `UPDATABLE_FIELDS`. **cli**: `deployment add/update --gcloud-config`
|
|
(empty string clears to NULL), and `gcloud_config` added to `deployment show`.
|
|
|
|
Micro-corrections mid-flight (honest record):
|
|
- Importing the `gcloud` module into `ssh.py` and `deploy.py` shadowed local
|
|
variables named `gcloud` (the gcloud *binary* path). Renamed those locals to
|
|
`gcloud_bin` in both files.
|
|
- Initially numbered the new SPEC section §11.5 — but §11.5 is already the
|
|
v1.3.0 webhook-reads boundary. Renumbered the guard to **§11.6** across code
|
|
comments, the migration, and SPEC; left the genuine webhook §11.5 refs alone.
|
|
|
|
Tests: new `tests/test_gcloud.py` (5), plus guard cases in `test_ssh.py`,
|
|
`test_deployment.py`, and `test_deploy.py` (refuse-before-row-opens +
|
|
proceed-when-matches). Full suite **214 passed**. CLI smoke test on a fresh
|
|
temp DB confirmed add/show/update-clear round-trips.
|
|
|
|
### Arc 3 — Coherence, live record, release
|
|
|
|
- **SPEC**: §5.5 (record field), §11.6 (the guard mechanism + ADC caveat +
|
|
belt-and-suspenders framing), §13.9 (`gcloud_config: wiggleverse` for OHM),
|
|
and the §12 verb-surface line.
|
|
- **CHANGELOG** 1.4.0 with an RFC-2119 upgrade-steps block; **VERSION** +
|
|
**pyproject** → 1.4.0; **register-ohm.sh** now sets `--gcloud-config
|
|
wiggleverse` on bring-up; **operator-guide** got a §11.6 callout box.
|
|
- **Live OHM record**: `deployment update ohm-rfc-app --gcloud-config
|
|
wiggleverse` (migration 006 applied automatically on the read). The guard is
|
|
now active for OHM — future deploys MUST be prefixed.
|
|
- **Release**: branched `session-0044/flotilla-gcloud-config-guard`, committed,
|
|
fast-forwarded `main`, tagged `v1.4.0`, pushed main + tag + branch to origin.
|
|
|
|
---
|
|
|
|
## Cut state (end of session)
|
|
|
|
| | |
|
|
| --- | --- |
|
|
| flotilla | `5f1689f` on `main` (= `origin/main`), tag **`v1.4.0`**; branch `session-0044/flotilla-gcloud-config-guard` also pushed |
|
|
| OHM live | **unchanged** — no deploy this session; still on its prior `deploys.id`/version |
|
|
| ohm-rfc pin | unchanged |
|
|
| local deployment record | `ohm-rfc-app` now carries `gcloud_config=wiggleverse` (operator-machine state, not a repo change) |
|
|
| gcloud configs | added inactive `wiggleverse` (`ben.stull@wiggleverse.org` / `wiggleverse-ohm`); shared active pointer untouched (`benstull-infra`) |
|
|
|
|
| Ledger | Status |
|
|
| --- | --- |
|
|
| gcloud config isolation — named config + CLAUDE.md discipline | ✅ shipped |
|
|
| flotilla gcloud-config guard (v1.4.0) | ✅ shipped (merged, tagged, pushed) |
|
|
| `benstull` gcloud config | ⏸ deferred — operator handling in a separate session |
|
|
|
|
---
|
|
|
|
## §19.2 candidates surfaced
|
|
|
|
1. **flotilla self-asserting vs. self-setting the config.** The guard relies on
|
|
the *parent* process exporting `CLOUDSDK_ACTIVE_CONFIG_NAME` (gcloud
|
|
subprocesses inherit it). flotilla could instead *set* it from the record
|
|
when it shells out — but that would mask the guard's intent (the point is to
|
|
force the operator/assistant to be explicit). Worth a deliberate call.
|
|
2. **`--account` pin on `gcloud compute ssh`.** Today only `--project` is
|
|
passed; the config name carries the account. Storing the operator account in
|
|
the record and passing `--account` would be true belt-and-suspenders.
|
|
3. **ADC identity isolation.** `secret set`/`bind` and deploy-time secret reads
|
|
use ADC, which `CLOUDSDK_ACTIVE_CONFIG_NAME` does not select. If multi-stack
|
|
ADC ever becomes a real hazard, that needs its own mechanism; for now the
|
|
project is always passed explicitly on Secret Manager calls.
|
|
|
|
---
|
|
|
|
## What lands on the operator's plate
|
|
|
|
1. **Set up the `benstull` gcloud config** in the other session (deferred here
|
|
by operator request). Mirror the gesture used for `wiggleverse`:
|
|
`gcloud config configurations create benstull --no-activate` then populate
|
|
with env-scoped `gcloud config set …`.
|
|
2. **Every OHM deploy/ssh gesture now MUST be prefixed**
|
|
`CLOUDSDK_ACTIVE_CONFIG_NAME=wiggleverse …`, or it refuses fast (no orphan
|
|
`deploys` row). This is the intended behavior, but it changes the muscle
|
|
memory.
|
|
3. (Optional) Wire a Claude Code `SessionStart`/env hook to export the var for
|
|
OHM sessions automatically, if hand-prefixing proves annoying.
|
|
|
|
---
|
|
|
|
## Prompt the operator can paste into the next Claude Code session
|
|
|
|
```
|
|
You're an OHM driver session anchored in ohm-rfc-app-flotilla. Claim your own
|
|
session ID first (~/git/ohm-infra/scripts/claim-session-id.sh --start <now>);
|
|
the number in this prompt is advisory.
|
|
|
|
Context from session 0044: flotilla v1.4.0 shipped a gcloud config guard.
|
|
The OHM deployment record now carries gcloud_config=wiggleverse, so EVERY
|
|
deploy/ssh gesture against OHM must be prefixed
|
|
CLOUDSDK_ACTIVE_CONFIG_NAME=wiggleverse — otherwise flotilla refuses before
|
|
opening a deploys row. An inactive `wiggleverse` gcloud config
|
|
(ben.stull@wiggleverse.org / wiggleverse-ohm) exists; the shared on-disk active
|
|
pointer (benstull-infra) was left untouched. flotilla is at main=5f1689f, tag
|
|
v1.4.0, pushed to origin. 214 tests green. No OHM app redeploy was needed.
|
|
|
|
Open items you might pick up:
|
|
- The `benstull` gcloud config still needs creating (operator was handling it
|
|
separately — confirm before assuming it's done).
|
|
- §19.2 candidates from 0044: (a) should flotilla self-set CLOUDSDK_ACTIVE_
|
|
CONFIG_NAME rather than only assert it? (b) pin --account on gcloud compute
|
|
ssh, not just --project? (c) ADC identity isolation for Secret Manager calls.
|
|
- Otherwise consult /Users/benstull/projects/wiggleverse/ohm-rfc/ROADMAP.md for
|
|
the next substantive item.
|
|
|
|
Don't paste secret bytes into the conversation (SPEC §3 invariant 1 / CLAUDE.md).
|
|
```
|