Record version. v0.1
Date. 2026-06-26
Change Request. CR-2026-123 — Current-engagement spine — v0.3 (2026-06-26; supersedes v0.1/v0.2).
Status. Executed on branch cr-2026-123-current-engagement-spine through Step 6; suite green at every step; the 8-step database-truth walk passed. NOT pushed — halt before push, awaiting Operator authorization.
Executing agent. Claude Code on DUNIN7-M4.
Operator. Marvin Percival.
Grounded by. CR-2026-123 v0.3 (docs/phase-crs/ on the branch); scoping note loomworks-current-engagement-spine-scoping-note-v0_2; architecture spec v0.6 §08 (the spine reconciliation); the Step-4 name-resolver match-policy findings (read-only, 2026-06-26); Step 0 re-confirmation against engine 59c8249.
The Companion now has a server-side memory of "the engagement you're in right now." Each person's small record (host_account) gained one line — current_engagement_id — beside the "Personal engagement" line that already worked the same way. The conversation path reads it: when a turn names no project, the scope defaults to the focused engagement; when nothing is focused and nothing is named, the Companion asks rather than guessing Personal. A focus-set operation writes it on "open X," a unified resolver turns any handle (your number for it, its name, its E-label, a UUID, or "Personal") into the engagement, and a "where am I" read says where you are by name + your number + E-label.
This CR was re-scoped once, mid-build, and the re-scope is recorded here rather than smoothed. v0.2 framed Step 4 as "only the number lookup is new — reuse an existing name resolver and an existing 'open X' handler." Execution found that false: the cited name resolver runs the wrong direction (id→name), and there is no "open X" intent in the Companion to wire into. The build halted, the Operator chose pause-and-revise, and the work split: v0.3 (this record) builds the engine handle-resolution and the spine; the spoken "open X" classifier intent is the sibling CR-2026-124. v0.3 alone delivers handle resolution at the endpoint, tested and usable; v0.3 + CR-2026-124 stacked is the full voice "open X."
At Step 4 the v0.2 plan met the codebase and two of its three "reuse" claims were false against engine 59c8249:
engagement/redirect.py:103 _resolve_engagement_name as reusable name resolution. That function is id→name — it reads the Seed's engagement name to label a cross-engagement redirect. "open Goosey" needs name→id, which existed nowhere.IntentLabel carried 23 intents, none open/navigate/switch. The only by-number "open" was a frontend list-expansion shorthand with no engine path.
So two of three handles were new construction, not reuse — the "imagined state encoded as fact" the CR's own Finding ② warns against, surfacing one step late. The build halted at the Step 4 boundary (Steps 1–3 already committed and green). A redraft handoff was filed (cr-drafting-handoffs/loomworks-cr-2026-123-step-4-redraft-handoff-v0_1.md), the match-policy questions were answered against live data, and CR v0.3 re-scoped Step 4 to engine resolution only, splitting the spoken intent to sibling CR-2026-124. All name-match and collision policies in v0.3 are settled decisions grounded in the live findings (name match = exact, case-insensitive, person-scoped; duplicate title = ask-don't-guess with candidates; "open 0" = Personal).
Cleared against candidate seed v0.12 (silent on current-engagement focus / converse-path addressing) and architecture spec v0.6 (the §08 spine reconciliation: surface signals posture when a surface signals; the carried current engagement supplies scope when none does; ask-don't-guess at the null boundary). The authentication framework is untouched — current_engagement_id is a focus pointer, resolution never keys on email. Addressing vocabulary (name + per-Operator number + E####; UUID invisible) follows the engagement-identity record and does not reopen it.
Branch cr-2026-123-current-engagement-spine off engine main 59c8249. Not pushed.
| Commit | Step | Change |
|---|---|---|
| 0b1279e | 0 | Archive CR v0.2 as branch base (baseline: main 59c8249, suite 2940/46, alembic 0094). |
| c88b30c | 1 | host_account.current_engagement_id — nullable FK clone of personal_engagement_id; migration 0095 (down_revision 0094), up adds column + FK host_account_current_engagement_id_fkey, down drops both; up/down/up clean. |
| 419b180 | 2 | Converse scope defaults to focus when project_id omitted (engagement_id = body.project_id or host_account.current_engagement_id), threaded through membership-verify/context/history/classifier/route. Null boundary asks; no Personal fallback. current_engagement_id added to the HostAccount view. +3 tests. |
| 000396e | 3 | PUT /me/current-engagement set/clear + set_current_engagement write seam; reuses verify_project_membership; close (null) clears. +4 tests. |
| 99e5d89 | — | Archive CR v0.3 (Step 4 re-scope) onto the branch. |
| 1f97230 | 4 | api/handle_resolver.py resolve_engagement_handle — delegate Personal/"0"/E####/UUID to resolve_engagement_address first, then bare-int → number lookup (memberships.operator_sequence_number, person-scoped), else name lookup (exact, case-insensitive, person-scoped). Duplicate title → AmbiguousEngagementHandleError with candidates. Wired into the endpoint (handle precedence; unknown → 404; ambiguous → 409 with candidates). +16 tests. |
| 05e3392 | 5 | GET /me/current-engagement — "where am I": name + per-Operator number + E#### (UUID out of labels); null focus → in_engagement false, no guessed Personal. +3 tests. |
Suite trajectory: baseline 2940 → Step 2 2943 → Step 3 2947 → Step 4 2963 → Step 5 2966 passed, 0 failed, 46 skipped. API docs regenerated at Steps 3/4/5; freshness CI guard green at each. Every commit lands green; per-step reversibility holds.
0094, unmoved — grounding held; the new migration's down_revision is cleanly 0094. Migration 0095 added; up/down/up round-trip clean on playground_dev; test DB migrated to 0095.engagement_id = body.project_id or host_account.current_engagement_id. Callers that pass project_id are byte-for-byte unaffected — every existing test has null focus, so the default never fires for them (2940→2943 with only the 3 new focus tests moving). Null boundary asks; personal_engagement_id is never a routing scope (no silent Personal fallback).PUT /me/current-engagement — per the /me/* convention for calling-person state writes (/me/settings, /me/spend). Reused the existing verify_project_membership; no new auth surface. The "open X" handler (CR-2026-124) reuses the same set_current_engagement seam.uq_memberships_person_operator_sequence_number (UNIQUE per person — confirmed live, returns at most one). Name lookup exact/case-insensitive/person-scoped against free-text engagements.title. Duplicate-title path raises AmbiguousEngagementHandleError carrying candidates (id + E#### + per-Operator number) — ask-don't-guess, never silent first-match. "0"/"Personal"/E####/UUID delegated to resolve_engagement_address, not re-implemented; "open 0" → Personal (consumed before the number path). No 4-digit E#### assumption; the spec's Planned identifier collapse is not pre-adopted (E#### stays delegated).GET /me/current-engagement — symmetric with the PUT (the Step 5(a) default; a read, not a converse intent).host_account.current_engagement_id) + its FK. No other schema change.playground_dev, non-destructive (rolled back)All eight passed, exercised at the resolution + endpoint level (the spoken "open X" utterance walk belongs to CR-2026-124):
resolve_engagement_address) → Goosey — PASSproject_id default is None (asks, no Personal fallback) — PASSproject_id → wins over focus (focus ignored) — PASS
(The converse-default halves of steps 1/7/8 are also pinned by the committed Step 2 integration tests, green against playground_test.)
project_id; project_id callers unaffected; ask-don't-guess at the null boundary. ✔0b1279e..05e3392) is not pushed. On the Operator's review and explicit authorization, push CR-2026-123 (merge --no-ff to engine main per convention). Until then nothing is on origin.resolve_engagement_handle → set_current_engagement. It inherits the duplicate-title disambiguation surface (the spoken "did you mean A or B?", which v0.3 surfaces as a 409/typed error). v0.3 + CR-2026-124 = the full voice "open X" acceptance walk.host_account row as a later, separate CR. Not built.api.loomworks.dunin7.com is not served (deferred per the spine manifest). 0095 runs when production is stood up, as part of standing it up; no separate pending production migration.cr-2026-123-current-engagement-spine at 05e3392, 7 commits ahead of main 59c8249. Not pushed. Working tree clean. dev + test DBs at alembic 0095.change-requests/), the Step-4 redraft handoff (cr-drafting-handoffs/, already filed at 24d6bb3), and this completion record. Architecture spec v0.6 already filed and pushed (3d43996).DUNIN7 — Done In Seven LLC — Miami, Florida Completion Record — Current-engagement spine — v0.1 — 2026-06-26 CR-2026-123 v0.3