## 2026-09-02 (latest) — caddie wears its own standards (0.3.3 → 0.4.1)

**What landed.** Easel's two proposals and its fourteen-version standards
review were read and decided item by item — `.logs/planning/07-easel-
coordination.md` is the verdict sheet — and caddie was brought up to the
standards it publishes:

- **The `caddie-ui` pack is canonical here**: `packs/caddie-ui/` (v1.1.0),
  `packs/stamp-caddie-ui.sh <workspace>` stamps, `--check` verifies
  byte-identity (exit 1 on drift). Pack changes on the way in: `ThreadStarter`,
  the `decision` kind, `domId` + the settle wash, `PACK_VERSION`.
- **Caddie's chrome is the pack's**: two-row shell (`GlobalBar` with brand ·
  TEAM/Client chip · locator · bell · account; `AppBar` on the project page
  with the Timeline/Reference navigator and the one Discussion toggle), the
  one `SidePanel` as the Discussion (`Discussion.tsx`), the pack's `Bell`
  behind a 40-line adapter, `Stages` rail with a read-selection, `Prose`
  bodies, `Lightbox`. `md.tsx` and `StageComments.tsx` are gone.
- **04 §3–4 complete on the backend** (`app/services/interaction.py` is the
  extraction source for `bw_interaction`): the project is a subject; one
  discussion read per page; resolve/reopen with `resolved` propagating to the
  bell; edit; soft delete; move; `decision` for approvals; breadcrumb
  `context_label`s. Migration `0003_conversation_state`.
- `/api/whoami` carries `first_name` / `display_name` / `is_agency`
  (`app/services/profile.py`); the client reads both error shapes.
- **Docs**: 01 (§4 journey rule; D16–D20; versioning), 02 (§2 stages mirror;
  §5 round-closer rule + easel row; §6 host bar INTO the global row + the
  tour hand-off; §10), 04 (§4.1, §5, §6, §8, §9, addendum wording), 05 (§1,
  §2 stamp step, new §3a shell + §3b practices, §4, §5 graduated), 06
  addendum (asks 7–10 to punchlist), brief, agents (pack rule + the deploy
  gate), v8-feedback (three kit/template asks).

**Verified.** 105 tests green (10 new in `tests/test_discussion.py`, one in
`test_shell.py`); `tsc` clean; pack `--check` green; deployed, migration ran
on Postgres, `/api/meta` = 0.4.1. **And the browser, this time** — by DOM
inspection through the Chrome MCP, because its tab was `visibilityState:
hidden` and screenshots time out on a hidden tab (the review's own
background-tab rule, met on day one): global row renders with brand / TEAM
chip / "Projects" locator / bell / account; "Hi Rian."; the project page's
app row, rail and rows; the panel pins at the measured 40px and pushes the
page and app row; eight breadcrumb-labelled starters; starter → inline
composer (with attach) → post → card with anchor, body, Reply; Reply inline
→ "1 reply" with `id="c-…"` on the row; move menu, edit/remove tools;
resolve → "0 open", the app-row count chip hides, ↶ Reopen restores; a stage
row's count chip opens the panel at that stage's starter; a deep link
`?stage=…#c-…` opens the panel on that reply (`is-target` applied), expands
its card, selects the stage in the rail and opens the row with its prose;
`?stage=` alone does the same with the panel closed. **Not verified:**
pixels — spacing and colour of the new pieces have had no eyes on them. Rian
should look at the `Standards Walkthrough` project.

**Found on the way.** `index.html` went out with no `Cache-Control`, so a
plain visit after a deploy rendered the previous bundle (worse since 0.3.2:
a stale shell points at bundles the image no longer has). 0.4.1 serves it
`no-cache`; the scaffolder's template has the same gap (v8-feedback).

**Left in the data.** A test project `standards-walkthrough` ("Standards
Walkthrough", one thread + one reply, both by rian). Caddie has no
delete/archive path for a project yet — candidate M2 hygiene item; until
then it doubles as the smoke-test project.

**Decisions worth knowing.** D16–D20 (01 §8). Declined:
`client_closed_round_at` in the report body (D17 — maps onto the six states).
Deferred: Highlight/Tour/InlineThread as pack pieces (one consumer), the
`bw_interaction` extraction (M2), punchlist's migration to the pack (06).
For rian only: anonymous commenters (07).

**Next.** Rian eyeballs 0.4.1 and runs the phone gate. The easel session
re-stamps the pack (07 asks 1–5; its copy is at 1.0.0 and `--check` names
the five files); the punchlist session adopts it (06 asks 7–10). Then M2.

## 2026-08-24 — M1 is code-complete; the gate runs as a test (v0.3.1)

**Landed:** comment attachments, the dictionary picker, the reference editor,
and the deep link that actually lands on the comment. 94 tests pass, live at
0.3.1.

**`tests/test_m1_gate.py` walks the whole roadmap §10.1 gate as one
narrative** — project from the template, external client granted, two stages
active closing independently, client sees only what's shared, taps Approve,
comments with a screenshot, `@mention` lights the agency bell with a deep link
ending `#c-{id}`, the screenshot 404s for a stranger, and "Staging site URL" is
created once then *found by search* and reused on a second project (with the
duplicate attempt interrupted by near-matches). Keeping the gate as a test
rather than a one-time ritual means a later change that breaks it fails here
instead of in front of a client.

**What still needs rian, and only rian:** a real external client signing in on
a real phone. Nothing in a test can stand in for that. Everything else in the
gate is asserted.

**Two decisions worth knowing:**
- The field-type vocabulary is now served by `GET /admin/field-definitions`
  (`types`), because the picker's dropdown hardcoding its own list is a silent
  drift from what `create()` accepts — it had already drifted ("choice" vs
  "select") before the wire was up.
- A `select` field's `options` now travel with its value in the project
  payload. The dictionary's promise is that label AND shape are the same on
  every project; an editor that must fetch the definition to draw itself
  breaks that promise at the first slow request.

**Flagged, not changed — the container runs as root.** No `USER` directive,
and the scaffolder's template has none either, so every BW app on this box
does. Logged in `.logs/v8-feedback.md` with a candidate fix; it needs the owner
because `/srv/system/` is operating-manual territory. It doesn't block the
gate, but caddie now accepts uploaded files, so it matters more than it did.

**Next:** run the gate with rian's phone, then M2 (tool plumbing, `/go/`,
the contract made real).

## 2026-08-24 (later) — the Interaction kit landed (v0.2.1)

Extracted from punchlist's implementation rather than rewritten (04 §9 names
punchlist as the extraction source), and generalised on the way: threads are
subject-agnostic (`subject_type`/`subject_id`), so the same kit hangs off
caddie stages here and punchlist items there.

**One decision I made that goes beyond the extraction:** caddie stores the
**04 §5 event shape** — `app`, `category`, `context_label`, ABSOLUTE `url`,
`source_type`/`source_id`, `project_id`, `dedupe_key` — even though the inbox
is local-only today. Punchlist's version stores a relative `path`. Storing the
shared shape now means the joint gate is a REPOINT rather than a migration,
and I have asked the punchlist session to do the same (ask 2 in the
coordination doc). This is the one place caddie should lead rather than copy.

Rules carried over intact, each earned elsewhere: mentions parsed SERVER-side
(a crafted payload must not be able to page anyone), never notify the author
of their own action, dedupe every emit, and "needs you" (mention/turn) renders
differently from "unread" (news).

Wired: a stage going active or closing rings the client; an approval rings the
agency. Nothing else rings — reshuffling a plan is not an event. 75 tests pass.

## 2026-08-24 — coordination with the punchlist session

Rian is running punchlist in a parallel session, standing up a REAL client
board now, and wants it importable into caddie later. Wrote
`.logs/planning/06-punchlist-coordination.md` — hand that path to the other
session. The substance:

- **Members and comments are deliberately NOT shared.** Identity is the IdP
  (same BW username in both apps); comments live with the subject in the app
  that owns it. Only the BELL aggregates.
- **The genuine gap: the contract only describes provision-CREATES-NEW (§2).**
  Rian's board will already exist, so both sides need an "attach existing"
  path binding to a known `external_ref`. Not yet specified anywhere.
- **The trap inside that import:** the contract's revocation convergence drops
  grants for users no longer in `participants` — which on a pre-existing board
  would evict the humans rian invited by hand. The contract's own answer is
  that only LAUNCH-PROVISIONED grants are ever dropped, so **punchlist must
  record grant provenance before any grant exists that we would be sad to
  lose.** That is ask 5 and it is the cheapest-now/most-expensive-later item
  on the list.

## 2026-08-24 — caddie M1: the spine is live (v0.2.0)

Punchlist had raced past caddie (v0.15.0 vs a bare scaffold), so this session
built caddie's actual product.

**Shipped and deployed at caddie.bowden.works:**
- **Postgres sidecar** + SQLAlchemy 2.0 + Alembic. The auth kit's four tables
  moved into the same database via `bw_store_sqlalchemy.managed(...)` — one
  database, one migration history, Alembic owns kit and domain alike.
  NOTE: `bw_store_sqlalchemy.py` is an optional PACK, not something the
  scaffolder vendors; copy it from the kit dir (never from a sibling app).
- **All 13 §5.1 domain tables**, including the tool tables — schema now so
  caddie M2 needs no migration, router deliberately absent.
- **Services**: projects (create makes the kit instance AND the row in ONE
  path — split them and the invite/grant screens have nothing to grant on),
  stages (concurrent, independent, no cursor), approvals (append-only, name
  snapshotted), activity (append-only, `notified_at` reserved), and the field
  dictionary with search-by-usage, near-match warnings on create, and merge.
- **Routes**: client-facing + admin, 404-never-403 on invisible projects.
- **Frontend**: project list (one project → straight to it) and the project
  page — "Needed from you" strip, timeline with done compact / active
  expanded / upcoming muted, reference tab, agency stage controls.
- **"Website build v1"** seeded: Onboarding → Design direction → Mockups →
  Build → Staging → Reviewing → Launch. Tool slots carry a suggestion only and
  render as prose until M2, which is the M1 gate's intent.
- 69 tests pass — the standard conformance pack AND caddie's spine tests.

**The trap that cost the most time, worth knowing:** caddie's own tests leaked
kit state into the standard conformance pack (35 failures that had nothing to
do with the code). The fix is the `_fresh_domain_db` autouse fixture in
`tests/app_conftest.py` — and it must be listed in `__all__`, because the pack
star-imports and star-import silently skips underscore names. Punchlist hit
exactly this; the pattern is now in both.

**Remaining for the M1 gate:**
1. **The Interaction kit on stages** — comments, @mentions, screenshot
   attachments, the bell (04 §9). Punchlist is the reference implementation;
   extract rather than rewrite.
2. Agency surfaces the API has but the UI does not yet: the dictionary
   search/create/merge screens, reference editing, adding ad-hoc stages.
3. Then run the gate itself: fresh external client on a phone, approve,
   comment with a screenshot, @mention lights the bell, two stages active at
   once, a field reused by name on a second project.

# caddie — handoff

## 2026-08-19 (later still) — third pass: stages re-modelled, project context added

Rian corrected the spine's central assumption. Plans updated; still no code.

**The correction:** stages are NOT a linear state machine with one active step.
They are **concurrent workstreams** — a project has a planned roadmap
(onboarding → design → mockups → staging → reviewing → launch) but stages run
together (staging + reviewing), close independently (onboarding early), and
some stay open after others finish (reviewing continues past launch). So:
the one-active-stage unique index is gone, `position` is display order only,
and the `kind` enum is dropped — a stage now COMPOSES prose + an optional
approval + an optional tool (D13).

**And caddie does not own the ball (D14).** "It's not really caddie's job to
tell who has the ball, that's the mini apps. Caddie is the framework, roadmap,
and running commentary." So `sync_turn`, the derived ball-in-court, and the
turn state machine are **deleted from caddie's design**. Caddie renders which
stages are active plus whatever tools report; the granular "you have 3 things
to do" is the tool's, over the contract's existing status report. Net effect:
the spine got materially simpler.

**New: project context via a shared field dictionary (D15, §5.3).** Not a
required-fields gate, not a free-for-all. `field_definitions` is a growing
dictionary (key, label, type, usage count); `project_fields` holds sparse
per-project values. Adding context always searches the dictionary first,
"create new" is secondary and warns on near-matches, and an owner-only MERGE
exists from day one because duplicates are inevitable. Boundary with `with`:
facts that outlive the project (client Drive folder) live on the organization
in `with`; project facts (staging URL, launch date) live here.

Also: optional unenforced `target_date` on stages; §5 renumbered (5.1 tables,
5.2 stages/status, 5.3 context, 5.4 API, 5.5 templates) and top-level sections
renumbered 6–12; caddie M1 gate now includes two concurrent stages and reusing
a field definition across projects.

**Open for rian (§11):** only the inbox service name remains.

## 2026-08-19 (later) — second planning pass: the interaction layer

Rian described caddie's actual feature set and a second shared standard the
first pass had underweighted. Plans updated; still no product code.

**What changed:**
- **New `.logs/planning/04-interaction-standard.md`** — comments, @mentions,
  screenshot attachments, and ONE cross-app notification bell. The key call:
  comments live in the app that owns the subject (sovereign, survives a caddie
  rebuild); notifications aggregate into one inbox, which is its OWN small
  service rather than a caddie feature. Modelled on the review app, whose
  0014→0017 migrations show exactly what a v1 must ship (deep links,
  `category`, `context_label`, `resolved`, thread-participation) and whose
  three weaknesses are corrected (email-keyed mentions → BW usernames;
  Supabase Realtime → polling; per-app login-only → cross-app inbox + a
  channel-agnostic delivery seam).
- **`with` conventions corrected from the frozen SATELLITE-CONTRACT**: store
  `client_party_id` (a party is a person OR an organization), keep the client
  name only as a refreshable hint (never truth), store no role, and never
  conflate the BW username with the party. Integration itself is a separate
  bridge app, later — but the columns land now (01 §6a, D6).
- Decided: review gets **rebuilt** as a caddie app (not retrofitted);
  recurring/retainer projects are confirmed future (D12); notification delivery
  is explicitly multi-channel (email/Telegram/Slack/WhatsApp), not an email
  endpoint (D8).
- caddie M1 now includes the interaction kit in local-only mode; the inbox
  service stands up at the joint gate; punchlist's "I need help" reuses comment
  threads instead of its own message columns.

**Open for rian (01 §10):** target dates / whether "milestone" is its own
thing (recommendation: an `is_milestone` flag on a stage) · how far
"tracking where users are" goes (ball-in-court vs per-participant) · a name
for the inbox service.

## 2026-08-19 — planning session: architecture locked, three plans written

Rian explained the vision (client hub + pluggable tool apps; first tool =
onboarding tasklists, named **Punchlist**). This session produced the plans —
no product code was written.

**Landed:**
- `.logs/planning/01-vision-and-roadmap.md` — vision, v1 domain model,
  decision log D1–D10, cross-project roadmap, risks R1–R3.
- `.logs/planning/02-tool-contract.md` — the Caddie Tool Contract ct1:
  link-out handoff core (adopted over iframe/proxy after an adversarial
  3-way design review), optional embed mode, HMAC S2S, 6-state report enum.
- `.logs/planning/03-punchlist-plan.md` — self-contained plan for the first
  tool; COPY it into /srv/apps/punchlist/.logs/planning/ when that workspace
  is created.
- v8 workspace scaffolded on the rebuilt app; brief.md filled; agents.md
  carries the six earned rules; CLAUDE.md is now the symlink.

All three docs passed an adversarial 4-reviewer verification pass
(substrate-facts, security, buildability, client-UX); every FIX-FIRST finding
was applied — notably: registration is a two-half owner flow (host script +
admin UI), auto-provisioning uses the kit's owner-actor path (no system actor
exists), revocation is acked + participant-convergent, launch burns jti first
then 303s off the token URL, "I need help" is a real message channel, and the
milestone sequence is caddie M1 → punchlist M1 → punchlist M2 (standalone) →
caddie M2 (stub-tool gate) → punchlist M3 × caddie joint bootstrap gate.

**Next session (build order per roadmap §9):**
1. caddie M1 — bare spine: Postgres sidecar + managed kit store (PACKS.md
   managed mode — NOT scout's custom adapter), tables/routes/views per plan
   §5, "Website build v1" template. Gate: phone demo with a fresh external
   client.
2. In parallel or after: punchlist M1 in its own workspace (create commands in
   its plan §2; M1 includes board create + single-flow instantiation).

**Open (rian's decisions, listed in plan §10):** central notify endpoint
(substrate ask) · with/party_id wiring timing · review retrofit-vs-rebuild ·
marketing recurring stages confirmed v2.

> Append-only, **newest first**. What the last session did, what the next should
> pick up, what is blocked. Read this second (after `brief.md`) when orienting.
> Clear-after-read: once an entry is acted on it can be trimmed.
