# 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).
```
