add ohm/0094/SESSION-0094.0-TRANSCRIPT-2026-06-09T04-45--2026-06-09T05-37.md + replace placeholder/variant SESSION-0094.0-TRANSCRIPT-2026-06-09T04-45--INPROGRESS.md
This commit is contained in:
@@ -0,0 +1,131 @@
|
|||||||
|
# Session 0094.0 — Transcript
|
||||||
|
|
||||||
|
> App: ohm
|
||||||
|
> Start: 2026-06-09T04-45 (PST)
|
||||||
|
> End: 2026-06-09T05-37 (PST)
|
||||||
|
> Type: planning-and-executing
|
||||||
|
> Status: **FINALIZED**
|
||||||
|
|
||||||
|
## Launch prompt (/goal)
|
||||||
|
|
||||||
|
```
|
||||||
|
Harden the two §9-surfaced framework fragilities as patch release(s) shipped
|
||||||
|
localhost→PPE→prod: (1) auth.provision_user 500s on a duplicate email — reconcile
|
||||||
|
by email; (2) the registry/startup reconcile is additive and never prunes removed
|
||||||
|
projects/collections/entries — add safe prune-on-reconcile that must NOT wipe on a
|
||||||
|
transient registry read error.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Outcome (one line)
|
||||||
|
|
||||||
|
Both fragilities fixed and shipped to OHM prod through the full §9 pipeline:
|
||||||
|
**v0.54.1** (provision_user reconciles by email) and **v0.55.0** (registry
|
||||||
|
prune-on-reconcile). prod live @ 0.55.0; both 0.55.0 startup prunes a verified
|
||||||
|
no-op (no data loss).
|
||||||
|
|
||||||
|
## Pre-state
|
||||||
|
|
||||||
|
Opened via `/goal` right after session 0093 finalized. Clean `origin/main` at
|
||||||
|
`f7b93d7` (v0.54.0). Worked in an isolated worktree off `origin/main`.
|
||||||
|
|
||||||
|
## Bug 1 — `auth.provision_user` dup-email 500 → v0.54.1 (patch)
|
||||||
|
|
||||||
|
**Root cause:** the Gitea-OAuth callback's `provision_user` matched a `users` row
|
||||||
|
only by `gitea_id`. A human who signed in first via OTC owns an email-only row
|
||||||
|
(`gitea_id` NULL); a later OAuth sign-in with the same email missed it and
|
||||||
|
INSERTed a duplicate → collided on the `idx_users_email` case-insensitive unique
|
||||||
|
index → IntegrityError → 500.
|
||||||
|
|
||||||
|
**Fix:** when no `gitea_id` row exists, reconcile by email and link the OAuth
|
||||||
|
identity (`gitea_id`/`gitea_login`/profile) onto the existing row — the mirror of
|
||||||
|
`otc.provision_or_link_user`. Owner-zero (§6.1) bootstrap applied on link
|
||||||
|
(matching the fresh-insert path); `permission_state` preserved so linking never
|
||||||
|
changes admission status as a side effect.
|
||||||
|
|
||||||
|
**Tests:** `test_provision_user_email_link.py` (3) — OTC→OAuth links onto the same
|
||||||
|
row (no 500), fresh OAuth still inserts (granted), returning gitea_id user not
|
||||||
|
duplicated. **Ship:** PR #50 → merged; tag v0.54.1 + mirror; PPE deploy
|
||||||
|
(`deploys.id=31`) + e2e regression green → prod (`deploys.id=32`, live=0.54.1).
|
||||||
|
|
||||||
|
## Bug 2 — registry reconcile is additive → v0.55.0 (minor)
|
||||||
|
|
||||||
|
**Root cause:** `registry.apply_registry` upserts but never deletes. A re-pin to a
|
||||||
|
different registry stranded the old project's collections + cached entries as dead
|
||||||
|
rows that, at scale, starved writes (PPE's stale-ecomm 1238-row 524, reset by hand
|
||||||
|
in 0089).
|
||||||
|
|
||||||
|
**Design (the careful part):** the post-029 schema keys the 13 entry-corpus tables
|
||||||
|
on `collection_id` (not `project_id`), 7 tables keep `project_id`, and one
|
||||||
|
non-keyed descendant (`thread_messages → threads`) exists — established empirically
|
||||||
|
by building the live schema in a temp DB and classifying every table. New
|
||||||
|
`projects.prune_absent_projects(config, present_ids)` deletes a removed project +
|
||||||
|
its collections + all those rows in one FK-off transaction with a
|
||||||
|
`PRAGMA foreign_key_check` backstop that rolls back rather than leave a dangling
|
||||||
|
ref (mirrors `restamp_default_project` / `reconcile_default_collection_id`).
|
||||||
|
|
||||||
|
**Safety scoping (each a deliberate decision):**
|
||||||
|
- Whole-project granularity only — a present project is untouched (per-entry prune
|
||||||
|
belongs to the best-effort content cache, whose list can transiently fail).
|
||||||
|
- The **default project is never pruned**, whatever the registry says.
|
||||||
|
- Reached only after a successful parse of a **non-empty** registry
|
||||||
|
(`parse_registry` rejects empty; the read raises on transport error), so a
|
||||||
|
transient read can't wipe.
|
||||||
|
- Prune runs ONLY on the full-reconcile triggers — **startup** + the **periodic
|
||||||
|
sweep** (`prune=True`) — never on incidental refreshes (collection create,
|
||||||
|
webhook, in-app create-project; `prune=False`). This last decision was forced
|
||||||
|
by the `test_s3_scope_roles_vertical` regression: that test seeds DB projects
|
||||||
|
directly and creating a collection triggers a refresh — pruning there would have
|
||||||
|
wiped them. (In-app create-project DOES write `projects.yaml` first, so real
|
||||||
|
projects are always registry-backed.)
|
||||||
|
|
||||||
|
**Tests:** `test_prune_absent_projects.py` (5) — removes an absent project + all
|
||||||
|
its data (FK-integrity backstop asserted), no-op when all present, never removes
|
||||||
|
default (even on empty present set), empty registry rejected, incidental refresh
|
||||||
|
(webhook, prune=False) does NOT prune. **Ship:** PR #51 → merged; tag v0.55.0 +
|
||||||
|
mirror; PPE deploy (`deploys.id=33`) — startup prune verified no-op (PPE projects
|
||||||
|
`[ohm]` + bdd(3) intact) + e2e regression green → prod (`deploys.id=34`,
|
||||||
|
live=0.55.0). Prod projects `[ohm, ecomm, rfc-app]` all survived the prune (the
|
||||||
|
session-0090 dogfood `rfc-app` project is in `projects.yaml`).
|
||||||
|
|
||||||
|
## Cut state
|
||||||
|
|
||||||
|
- origin `ben.stull/rfc-app` `main` = `c3efe89`; tags v0.54.1 + v0.55.0 on origin
|
||||||
|
+ benstull mirror; mirror `main` fast-forwarded.
|
||||||
|
- OHM **prod live @ v0.55.0** (`deploys.id=34`); **PPE @ v0.55.0** (`deploys.id=33`).
|
||||||
|
Pins: prod `.rfc-app-version`=0.55.0, PPE `.rfc-app-version.ppe`=0.55.0.
|
||||||
|
- backend 685 green (+8 across the two fixes); no migrations.
|
||||||
|
- Worktree removed; no open PRs. Local `main` checkout left at `ff88be2` (behind
|
||||||
|
origin) — untouched.
|
||||||
|
|
||||||
|
## Deferred decisions
|
||||||
|
|
||||||
|
- **Owner-zero bootstrap on the email-link path** (bug 1): applied it so an
|
||||||
|
owner-login human who OTC'd first still becomes owner on OAuth (matches the
|
||||||
|
fresh-insert path). Preserved `permission_state` rather than auto-granting a
|
||||||
|
pending OTC row on link — the minimal, no-admission-change choice. Low-confidence
|
||||||
|
on the grant question; preserve felt safest.
|
||||||
|
- **Prune scoped to startup + sweep only** (bug 2): chose not to prune on every
|
||||||
|
`refresh_registry` (incidental refreshes add wipe-risk for no benefit, and broke
|
||||||
|
the s3 vertical). Trade-off: a registry edit removing a project is reconciled at
|
||||||
|
the next startup / periodic sweep, not instantly. Acceptable — the incident is a
|
||||||
|
re-pin (a restart).
|
||||||
|
- **Shipped as two separate releases**, not one bundle — isolates the destructive
|
||||||
|
prune (0.55.0) from the safe auth fix (0.54.1) for blast-radius. Cost: two
|
||||||
|
PPE→prod cycles.
|
||||||
|
- **PPE metadata.spec SLICE-3 is red on PPE** from stale-seed drift (prior runs
|
||||||
|
mutated bdd priorities; the spec needs a fresh seed) — unrelated to either change;
|
||||||
|
`main-view-ask.spec.js` (the auth/sign-in-exercising regression) is green on PPE
|
||||||
|
for both releases.
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
(Two §9 fragilities, two focused releases each crossing localhost→PPE→prod — see
|
||||||
|
the bug sections above for the realized design.)
|
||||||
|
|
||||||
|
## Next-session prompt
|
||||||
|
|
||||||
|
The §9-fragility backlog is now empty. Next is a fresh roadmap pick:
|
||||||
|
|
||||||
|
```
|
||||||
|
/goal Pick the next milestone from ohm-rfc's roadmap (Phase G / next item) and execute it, per the ohm-rfc roadmap repo. (Optionally instead: the deferred full G-5 write-family slug de-dup — slug-keyed branch/thread/change/PR tables → collection-scoped — the larger migration G-15 left out.)
|
||||||
|
```
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Session 0094.0 — Transcript
|
|
||||||
|
|
||||||
> App: ohm
|
|
||||||
> Start: 2026-06-09T04-45 (PST)
|
|
||||||
> Type: planning-and-executing
|
|
||||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
|
||||||
>
|
|
||||||
> This file reserves session ID 0094 for ohm. The driver replaces this
|
|
||||||
> body with the full transcript and renames the file to its final
|
|
||||||
> SESSION-0094.0-TRANSCRIPT-2026-06-09T04-45--<end>.md form at session end.
|
|
||||||
|
|
||||||
## Launch prompt
|
|
||||||
|
|
||||||
```
|
|
||||||
Harden the two §9-surfaced framework fragilities as patch release(s) shipped
|
|
||||||
localhost→PPE→prod:
|
|
||||||
(1) auth.provision_user raw-INSERTs and 500s on a duplicate email — a user who
|
|
||||||
signs in via OTC (email-only row) then Gitea OAuth (gitea_id row, same email)
|
|
||||||
crashes the callback; reconcile by email instead.
|
|
||||||
(2) the registry/startup reconcile is ADDITIVE — it never prunes
|
|
||||||
projects/collections/entries removed from a deployment's registry (caused PPE's
|
|
||||||
stale-ecomm 1238-row 524); add safe prune-on-reconcile that must NOT wipe on a
|
|
||||||
transient registry read error.
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deferred decisions
|
|
||||||
|
|
||||||
_Autonomous-mode low-confidence calls the driver made and would have
|
|
||||||
liked operator input on. Appended as the session runs; surfaced at
|
|
||||||
finalize. Empty if none._
|
|
||||||
Reference in New Issue
Block a user