Date. 2026-08-13 · CR. change-requests/cr-2026-209-loomworks-title-both-faces-v0_1.
State. Built and tested. HALTED before tag and push, per the Checkpoint A instruction. Engine main local commit f011c80 (NOT pushed; no tag). Surface untouched, as the CR anticipated — Step 1 confirmed list rows, chips, and the focus chip all read title and needed no change.
Baseline note (Step 0). The CR states baseline cd91176; the tree stood at c04afd9 — exactly one commit ahead (CR-2026-207's engine half, engagement-list projection; no overlap with titling). Built on c04afd9. Tree was clean.
name_candidate_from_seed (engagement/title.py), called at all four enumerated seed-landing sites: the draft route (engagements.py), door 3's extract (seed_extraction.py), the brief-path commit (seed_commit_from_brief.py), and the conversational me-create path (me_create_engagement.py). COALESCE-guarded (COALESCE(NULLIF(title,''), :title)) — an operator-set title always survives, the promote branch's discipline. Pre-seed candidates (door 3's seedless window) stay untitled until extract; the surface falls back gracefully (title ?? display_identifier in list rows — verified, no blank).derive_title_from_seed: operator name first (unchanged, 80-clamp); else the first sentence clipped at the LAST clause boundary (comma/semicolon/dash) within a 60-char word-boundary budget — a complete clause ends clean, no ellipsis; only a mid-clause (word-boundary) cut carries the ellipsis. Colon deliberately excluded from boundaries ("A guide:" is a worse label than the cut). Deterministic, never model-derived — the settled ruling, not re-raised.name_null_titled_candidates pins state = 'candidate' in its row query; committed engagements are out of reach by construction, not by convention. Thin CLI at scripts/backfill_candidate_titles.py. Idempotent (second run: 0 named — tested).Per the Operator's Step-1 instruction: the backfill can't reach E0127/E0089, and didn't. Three layers of confirmation:
state = 'candidate' pin — E0127/E0089/E0104 are active. Additionally E0127/E0089 hold non-NULL titles, failing the second predicate too.test_backfill_names_null_titled_candidates_only seeds all three shapes — a NULL-titled candidate (named), a committed NULL-titled row (E0104's shape — stays NULL), a committed truncated-title row (E0127/E0089's shape — byte-identical after).named: 2, skipped: 0. After: E0128 = "A one-page closing procedure checklist for a community tool…", E0129 = "A sign-out sheet template for borrowed tools"; E0089 and E0127 byte-identical; E0104 still NULL. The defect stays visible on E0127/E0089 — as ruled, correct: their names are in use.
New tests/test_cr_2026_209_title_both_faces.py (12 tests): face-2 clause vectors (short/comma/dash/semicolon/no-boundary — E0128's real seed text as the ellipsis case), operator-name-wins, the COALESCE guard, draft-route end-to-end, and the backfill boundary + idempotence. Face-2 failures observed against the old derivation (which shipped "A pocket field guide to urban mushrooms, with seasonal maps and a spore key for…"); face-1/backfill observed failing as missing imports. One legacy expectation updated with a comment (test_engagement_title.py — the 69-char sentence now cuts at the budget; the deliberate behavior change). Full suite 3722 passed / 68 skipped; ruff clean; mypy at the pre-existing 537-error baseline exactly (the CR's own +2 fixed via a SeedLike Protocol; the scoped CI gate will rule at push).
Engine restarted on the working tree (LaunchAgent kickstart — the same code that ships after the gate; CR-206/207's pre-handoff precedent). As Marvin-Test2 (minted session): door 2's seed-creation-conversation recorded 11 turns; the converse commit turn (intent_hint=create_engagement_commit) drafted E0130, a fresh conversational candidate carrying the clause label "A laminated one-page allergy reference card for the cafe…" through candidacy — verified in the DB row AND rendered in the home list (screenshot kept: /tmp/cr209-home-list.png). Cleanup per the e2e rule: E0130 discarded (204), all 22 test-persona creation-block turns deleted, leaving no residue. (First attempt used the repro persona, which hit the credit-exhaustion gate — the honest reply worked as designed; switched personas.)
discard_candidate_engagement deletes the engagement row without handling conversation_turns rows referencing it (conversation_turns_engagement_id_fkey violation → 500). Conversational creation now attaches turns to the candidate at draft, so every conversationally-created candidate is currently undiscardable through the endpoint — E0128 itself would 500 if discarded today. Out of CR-209's scope (nothing here touched discard or turns); found by the eye-test's cleanup. Suggested as a B-item at the gate.
Kind A as expected, recorded verbatim-heading style: title derivation READS the seed (what_the_work_is, additional_assertions.engagement_name) at draft/extract time and stops at the engagement row — a computed signal that stops at a boundary. If the seed later moves (B-69's standing question), the title does not follow; that was promote-time behavior before, now merely earlier. No new seed write path; the seed is untouched by naming.
Tag cr-2026-209-title-both-faces; push f011c80; watch the run. (No surface deploy needed. The engine already runs this code locally via the eye-test restart; the push is the record catching up.)
DUNIN7 — Done In Seven LLC — Miami, Florida — CR-2026-209 Checkpoint A — v0.1 — 2026-08-13 Named when nameable; a label, not a description; committed names stay.