From 993673865a61e7f74fbb402e79361c2a3cd55e00 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sat, 26 Sep 2026 15:35:15 -0500 Subject: [PATCH] docs(records): Jason's walkthrough rulings, Sage moved to SetSpark, CHAT-02 go Jason ruled on seven open items (20:27Z-20:45Z): seat Gitea tokens read in place, one Discord restart after 6b with row 25 live, row 8 limited to the dev seats, DYOR in dyor-stack-v4 with Sage moved to SetSpark, skills/aws-* excluded locally, no second WebUI return defect, and go on CHAT-02 only. Sage persona files name SetSpark as its business work. Darkwing's SOUL drops harness names that were wrong for T3. DEFERRED adds the slash-prefix paste hazard and the board Host/Origin gap, and moves the ledger T3 item to Done. Dewey's approved CHAT-02 brief (636b0fac) and Filbert's review are recorded. Co-Authored-By: Claude Opus 5.5 --- BUILD-LOG.md | 24 ++ agents/darkwing/SOUL.md | 3 +- .../dewey/work/chat-02/BRIEF-r1-314da8b0.md | 201 +++++++++ .../dewey/work/chat-02/BRIEF-r2-ed177bf6.md | 201 +++++++++ .../dewey/work/chat-02/BRIEF-r3-3b81a3f2.md | 379 +++++++++++++++++ agents/dewey/work/chat-02/BRIEF.md | 382 ++++++++++++++++++ ...board-researcher-2026-09-26T20-15-18Z.json | 18 + .../evidence/board-sends-repo-pi.jsonl | 30 ++ .../chat-02/evidence/replay-0913/README.md | 17 + .../replay-0913/removed-vs-42c08d52.diff | 16 + .../chat-02/evidence/replay-0913/replay.tap | 16 + .../replay-0913/return-flow-0913.test.mjs | 64 +++ .../evidence/researcher-pane-2026-09-26.txt | 7 + agents/dewey/work/chat-02/evidence/sends.mjs | 16 + .../chat-02-brief-r2-review-2026-09-26.md | 250 ++++++++++++ agents/sage/CONTEXT.md | 15 +- agents/sage/DISCORD-USER.md | 11 +- agents/sage/README.md | 9 +- agents/sage/SOUL.md | 40 +- docs/SESSIONS.md | 5 + ...26-09-26_fleet-seats-onto-mosaic-launch.md | 13 + docs/plans/2026-09-26_lead-decisions.md | 85 +++- docs/plans/DEFERRED.md | 21 +- 23 files changed, 1756 insertions(+), 67 deletions(-) create mode 100644 agents/dewey/work/chat-02/BRIEF-r1-314da8b0.md create mode 100644 agents/dewey/work/chat-02/BRIEF-r2-ed177bf6.md create mode 100644 agents/dewey/work/chat-02/BRIEF-r3-3b81a3f2.md create mode 100644 agents/dewey/work/chat-02/BRIEF.md create mode 100644 agents/dewey/work/chat-02/evidence/board-researcher-2026-09-26T20-15-18Z.json create mode 100644 agents/dewey/work/chat-02/evidence/board-sends-repo-pi.jsonl create mode 100644 agents/dewey/work/chat-02/evidence/replay-0913/README.md create mode 100644 agents/dewey/work/chat-02/evidence/replay-0913/removed-vs-42c08d52.diff create mode 100644 agents/dewey/work/chat-02/evidence/replay-0913/replay.tap create mode 100644 agents/dewey/work/chat-02/evidence/replay-0913/return-flow-0913.test.mjs create mode 100644 agents/dewey/work/chat-02/evidence/researcher-pane-2026-09-26.txt create mode 100644 agents/dewey/work/chat-02/evidence/sends.mjs create mode 100644 agents/filbert/work/chat-02-brief-r2-review-2026-09-26.md diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 142e3fd1..5d93ae21 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -2944,3 +2944,27 @@ Limit: the fix changes zero current counts. Table 2 reads only those files. T3 traffic lives in harness transcripts the ledger doesn't read, so a T3-routed prompt counts nowhere, as agent or as human. Gate F must not use Table 2 until a T3 thread source exists (brief to follow). Not pushed. + +## 2026-09-26: Jason's walkthrough rulings, Sage moved to SetSpark, CHAT-02 go (Sage) + +Before: seven open items with Jason: seat tokens, the Discord restart, row 8 +scope, DYOR, `skills/aws-*`, the WebUI return question and CHAT-02. Sage's +persona files still named DYOR as its business work. +After: Jason ruled on all seven between 20:27Z and 20:45Z. The rulings are +recorded in `docs/plans/2026-09-26_lead-decisions.md` and the row 8 stub. +- Tokens: seat Gitea tokens are read in place. +- Discord: one restart after 6b is approved, with row 25 going live. +- Row 8: dev seats only. +- DYOR: it belongs in dyor-stack-v4, and Sage moves to SetSpark. +- `skills/aws-*`: excluded locally. +- WebUI: "pong" appeared without Refresh, so there's no second return defect. +- CHAT-02: go. CHAT-03 to 08 stay held. +Sage's SOUL, CONTEXT, README and DISCORD-USER now name SetSpark as its +business work. The Discord Sage loads them at its next restart. Darkwing's +SOUL no longer names harnesses for Rocko and Filbert, which were wrong for T3. +DEFERRED gains the slash-prefix paste hazard and the board Host/Origin gap, +and the ledger T3 item moves to Done with ef0020ad. Dewey's approved CHAT-02 +brief (636b0fac) and Filbert's review are committed as records. +Discord restart held: the unit runs from this checkout, and Darkwing's +uncommitted 6b engine change is in the tree. `discord.sh check` passed at +20:17Z. diff --git a/agents/darkwing/SOUL.md b/agents/darkwing/SOUL.md index 20b9f034..85b0893d 100644 --- a/agents/darkwing/SOUL.md +++ b/agents/darkwing/SOUL.md @@ -23,8 +23,7 @@ and authorization rules as any other system change. Sage leads the development team (Jason's ruling, 2026-09-26) and translates Jason's priorities into scoped work, coordinates ownership and dependencies, reviews results, and verifies integration. You are a collaborating seat. -Dewey owns frontend design and UX. Rocko (Claude Code with Sonnet) and Filbert -(Pi with OpenAI Codex GPT-6 Astra, low thinking) support general project needs, +Dewey owns frontend design and UX. Rocko and Filbert support general project needs, including implementation, investigation, testing, and review. Reconcile concurrent edits with Sage before integration. Keep Jason informed of outcomes and decisions that require his input. Your role does not expand the diff --git a/agents/dewey/work/chat-02/BRIEF-r1-314da8b0.md b/agents/dewey/work/chat-02/BRIEF-r1-314da8b0.md new file mode 100644 index 00000000..1ed9c9f1 --- /dev/null +++ b/agents/dewey/work/chat-02/BRIEF-r1-314da8b0.md @@ -0,0 +1,201 @@ +# CHAT-02 brief: read-only histories and Age (#1507, row 5) + +Author: Dewey, 2026-09-26. Draft for Sage, then Filbert. No source edits. +Plan row: `docs/plans/2026-09-13_webui-session-chat.md` line 214. Depends on +CHAT-01 (`28d4e98a`) and uses the CHAT-01C companion (`b023841c`). + +## 1. Is there a second defect in the return path? + +Short answer: no second defect in the return path shows up for repository Pi +seats. The answers reach the transcript, the board reads them, and the Console +shows them. What Jason hit on 09-13 is best explained by the product gap: the +Console offered one "Last assistant text" field, clipped at 240 characters, in +an inspector, with no reply thread and no pending signal. 42c08d52 fixed the +pending display. The clip and the missing thread are CHAT-02's job. + +I found one real defect on the send side, not the return side (§1.3). + +### 1.1 Evidence so far + +1. **Transcripts.** I scanned every `.pi/state/*/sessions` file (darkwing, + dewey, filbert, researcher) for board sends. The script is + `evidence/sends.mjs` and the output is `evidence/board-sends-repo-pi.jsonl`. + There are 30 sends and 29 have a final answer in the same file. Latency is + 1.5 s minimum, 14.6 s median and 1019 s maximum. In 16 of the 29 answers + the text exceeds 240 characters, so the Console showed them clipped. +2. **The unanswered send.** Filbert, 2026-09-12T16:33:17Z. The seat started + working on it: it read files until 16:35:44Z, the last entry is an + `aborted` assistant turn, and a new filbert session began at 16:36:26Z. The + seat was relaunched mid-turn. That lost the answer, but the loss is in the + seat, not in the return path. +3. **Jason's report window.** At 2026-09-13T00:49:59Z he asked dewey "What + are the Gate E criteria?". The answer landed at 00:50:07Z with 365 + characters, clipped to 240 in the Console. He wrote the report at 00:56:51Z. + The report says the sessions "are all cards within the project dashboard and + not available as independent chat interfaces". That describes the missing + thread, not a missing answer. +4. **The 09-13 Console, replayed.** I extracted `ea00ec66` into a scratch + directory and ran the return-flow test with the pending and Age + assertions removed. It passed: the new answer arrived through the 10 s + poll, once, in the open inspector. That page did not drop answers. +5. **Jason's live send today.** Researcher, user entry at + 2026-09-26T20:10:57.429Z, answer "pong" at 20:11:06.173Z (8.7 s). At + 20:15:18Z `/api/board` showed the researcher row as `idle` with + `lastAssistantText: "pong"`, `lastActivity` 20:11:06.173Z, the same + `sessionFile`, and a live registration (`evidence/board-researcher-*.json`). + The live WebUI (pid 1266267, port 7330) serves `app.js` byte-identical to + HEAD (`d1a51646…`), so the 42c08d52 pending notice is live. The WebUI + server code has not changed since that process started on 09-13 16:19 CDT. + The board (pid 3977979, port 7331) started today at 15:03 CDT. + +### 1.2 What is still unverified + +- **What Jason's screen showed.** For today's send I have the transcript and + the board JSON, not the Console DOM or a screenshot. The one missing fact is + whether "pong" appeared in the open inspector without a manual refresh. +- **The 09-13 processes.** The board keeps no reply receipts and the 09-13 + board and WebUI processes are gone. I cannot prove what those processes + served at 00:50Z. I can only show that the same code, replayed, works. +- **Fleet seats.** I did not scan `~/.mosaic` fleet transcripts. The rule for + this phase is to leave them alone, and CHAT-02 does not cover fleet + catalogues. +- **Clipping in a live send.** "pong" is 4 characters, so today's send does + not exercise the 240-character clip. + +### 1.3 Send-side defect found in passing + +Today's user entry starts with `/`: `/[dragon-lin:control-board -> +dragon-lin:researcher] ping`. `agent-send.sh` adds the header, so the `/` was +already sitting in the Pi editor when the paste arrived. `send-message.sh` +pastes into whatever is in the composer (`paste-buffer -p`, then Enter). It +does not clear it first. Here Pi treated the result as plain text. If the +composer had held a real command prefix, a board reply could have become a +slash command. This is the unmediated-ingress hazard that CHAT-01 assigns to +CHAT-03I (B3). `tools/tmux/**` is excluded from the WebUI plan, so I +record it and do not propose a fix here. Sage decides whether to note it on +#1507 for the CHAT-03I charter. + +### 1.4 Evidence that settles it + +One question for Jason settles the live case: **after today's ping, did +"pong" appear in the Console inspector without pressing Refresh?** + +- If yes, there is no second defect. CHAT-02 proceeds as a product-gap fix. +- If no, `/api/board` had the answer, so the defect is in the Console or the + WebUI proxy. Then capture the Console DOM and the network log for + `/api/board` during one send. +- If `/api/board` had lacked it, the defect would be in the scanner (wrong + newest file). If the transcript had lacked it, the defect would be in the + transport or the seat. Neither happened today. + +To make the check complete for the clip, one more live send can ask for an +answer over 240 characters. Every step above then repeats, and the Console +should show the text ending in "…". That proves the clip is what cut the +09-13 answer short. + +## 2. Scope + +CHAT-02 per the plan: both harness catalogues, safe full branch history with +pagination and cursors, separate timestamps, relative Age from last +activity, and fixtures for malformed, truncated, replaced and touched logs, +cross-project and symlink denial, and no log writes. Read-only: opening a +conversation never resumes, forks, launches or controls anything. + +### 2.1 Backend: `packages/conversation/**` + +The owner is not yet assigned. The plan proposes Darkwing; Sage now assigns. + +- **Catalogue.** Approved source roots only: the board's repository specs + (`/.pi/state//sessions`) and live seat registrations. There is + no global scan and no browser-supplied path. Each conversation gets an + opaque ID mapped server-side to a file. Catalogue creation time, engine + launch time and last activity are separate nullable fields. +- **No-write Pi parser.** Never `SessionManager.open`, which can migrate old + files (CHAT-00 line 67). Open read-only. Record size, mtime and inode + before and after each read, and the fixture asserts they are unchanged. +- **Branches.** Build the `id`/`parentId` tree and select a leaf explicitly. + The default leaf is the last appended entry, which the implementer must + confirm against the pinned Pi session docs. Other leaves are read-only + branches. + Compaction entries render as markers. Pi `get_entries` order is not a + branch transcript (CHAT-00 line 66), so the parser does not use it. +- **Pages.** CHAT-01 limits: at most 100 parts, 8 MiB serialized UTF-8, 64 + blocks per part and 262144 characters per string. Oversize content splits + into continuation parts and is never clipped. The cursor binds actor, + purpose, conversation, branch, snapshot, source epoch and expiry. A + replaced file (new inode or a shorter length) is a new source epoch, so old + cursors refuse and the client keeps its view with a reconcile marker. It + never silently switches files. +- **Damaged input.** A malformed line becomes an unavailable part with its + position, and reading continues. A truncated trailing line counts as + incomplete, not as an error. +- **Denials.** A symlink anywhere under the root, a path that resolves + outside the root, a parent-session reference, or a conversation from + another project all refuse, with fixtures. + +### 2.2 Console: `packages/webui/**` (Dewey) + +This is the smallest piece that answers the 09-13 complaint: a read-only +conversation view per session, opened from the card, table or inspector. It +renders the selected branch in full, unclipped: user text, assistant text, +tool calls and results collapsed, thinking hidden by default, and Markdown as +untrusted text with no active HTML. Age stays as 42c08d52 shipped it. Reply +keeps the existing board path unchanged. The view gets the new answer through +polling, as the inspector does now. Streaming belongs to CHAT-03. + +The full chat UI (sidebar, composer, queue, approvals, uploads) stays in +CHAT-05. + +### 2.3 Return-flow regression (required by the plan) + +Extend `packages/webui/tests/return-flow.test.mjs`, or add a sibling test. +Send from the conversation view. Then the seat appends user, toolCall, +toolResult and a final answer longer than 240 characters. Assert that the +whole answer appears once, unclipped, in the same open view, with no manual +refresh, and that the draft and caret survive. Add a delayed-result variant +where the toolResult lands after a poll. This covers the Pi engine only; see +D2. + +## 3. Decisions needed from Sage + +- **D1: deferred items.** CHAT-01 line 342 defers "the actual + execution/writer-claim record" to CHAT-02. CHAT-01C line 217 defers R3-1, + reconciling dispatched but unconsumed input, to "CHAT-02 adapter evidence". + A read-only reader needs neither. I recommend moving both to CHAT-03, which + owns binding and the single writer, and recording that on #1507. +- **D2: Claude catalogue.** B1 is open: Claude's persisted branch format and + leaf selection are unverified (CHAT-00 line 66). I recommend shipping Pi in + CHAT-02, with the Claude catalogue refusing `unsupported-harness` under a + fixture, and adding Claude once B1 evidence exists. Rocko is the only + Claude seat. The plan asks for both harnesses, so this is a scope change for + Sage to accept or refuse. +- **D3: endpoint home.** The WebUI knows only the board URL, and the board + owns discovery and the approved roots. I recommend that + `packages/conversation` stay a library with no server, and that the board + add two read-only routes: catalogue and page. That is a board integration + (`packages/control-board/src/serve.mjs`, `scan.mjs`), which the plan says + needs approval and Darkwing's coordination. The alternative is for the + WebUI to read seat registrations itself, which duplicates discovery. +- **D4: backend author** for `packages/conversation/**`, and the sequencing: + backend first, then the Console against its fixtures. +- **D5: Jason's gate.** CHAT-02..08 were held pending Jason. This brief does + not lift that hold. Building needs Sage's go under whatever Jason has ruled. + +## 4. Acceptance + +- The new `packages/conversation` tests cover every fixture in §2.1, including + a before/after hash, mtime and inode check proving no log writes. +- The return-flow regression from §2.3 passes on the served WebUI. +- These still pass: `node docs/plans/chat-00/check.mjs`, `chat-01/check.mjs`, + `chat-01c/check.mjs`, and the control-board, webui and seat suites. +- Browser evidence: conversation view at 320 and 1440 in both themes, with a + long answer, a tool call, a malformed-line marker and a stale-cursor + reconcile marker. No horizontal overflow at 320. +- Filbert approves the exact candidate hashes. Live check: one board send + with a long answer, visible in full in the view. + +## 5. Not in scope + +Live adapters, control, streaming, queues, uploads, approvals, fleet seats, +Claude history until B1, any change to `tools/tmux/**`, `roles/**` or +session logs, and the `/` paste hazard (CHAT-03I). diff --git a/agents/dewey/work/chat-02/BRIEF-r2-ed177bf6.md b/agents/dewey/work/chat-02/BRIEF-r2-ed177bf6.md new file mode 100644 index 00000000..57c8f322 --- /dev/null +++ b/agents/dewey/work/chat-02/BRIEF-r2-ed177bf6.md @@ -0,0 +1,201 @@ +# CHAT-02 brief: read-only histories and Age (#1507, row 5) + +Author: Dewey, 2026-09-26. R2: Sage's decisions on D1-D5 are recorded in §3 +(R1 frozen as `BRIEF-r1-314da8b0.md`). For Filbert's review. No source edits. +Plan row: `docs/plans/2026-09-13_webui-session-chat.md` line 214. Depends on +CHAT-01 (`28d4e98a`) and uses the CHAT-01C companion (`b023841c`). + +## 1. Is there a second defect in the return path? + +Short answer: no second defect in the return path shows up for repository Pi +seats. The answers reach the transcript, the board reads them, and the Console +shows them. What Jason hit on 09-13 is best explained by the product gap: the +Console offered one "Last assistant text" field, clipped at 240 characters, in +an inspector, with no reply thread and no pending signal. 42c08d52 fixed the +pending display. The clip and the missing thread are CHAT-02's job. + +I found one real defect on the send side, not the return side (§1.3). + +### 1.1 Evidence so far + +1. **Transcripts.** I scanned every `.pi/state/*/sessions` file (darkwing, + dewey, filbert, researcher) for board sends. The script is + `evidence/sends.mjs` and the output is `evidence/board-sends-repo-pi.jsonl`. + There are 30 sends and 29 have a final answer in the same file. Latency is + 1.5 s minimum, 14.6 s median and 1019 s maximum. In 16 of the 29 answers + the text exceeds 240 characters, so the Console showed them clipped. +2. **The unanswered send.** Filbert, 2026-09-12T16:33:17Z. The seat started + working on it: it read files until 16:35:44Z, the last entry is an + `aborted` assistant turn, and a new filbert session began at 16:36:26Z. The + seat was relaunched mid-turn. That lost the answer, but the loss is in the + seat, not in the return path. +3. **Jason's report window.** At 2026-09-13T00:49:59Z he asked dewey "What + are the Gate E criteria?". The answer landed at 00:50:07Z with 365 + characters, clipped to 240 in the Console. He wrote the report at 00:56:51Z. + The report says the sessions "are all cards within the project dashboard and + not available as independent chat interfaces". That describes the missing + thread, not a missing answer. +4. **The 09-13 Console, replayed.** I extracted `ea00ec66` into a scratch + directory and ran the return-flow test with the pending and Age + assertions removed. It passed: the new answer arrived through the 10 s + poll, once, in the open inspector. That page did not drop answers. +5. **Jason's live send today.** Researcher, user entry at + 2026-09-26T20:10:57.429Z, answer "pong" at 20:11:06.173Z (8.7 s). At + 20:15:18Z `/api/board` showed the researcher row as `idle` with + `lastAssistantText: "pong"`, `lastActivity` 20:11:06.173Z, the same + `sessionFile`, and a live registration (`evidence/board-researcher-*.json`). + The live WebUI (pid 1266267, port 7330) serves `app.js` byte-identical to + HEAD (`d1a51646…`), so the 42c08d52 pending notice is live. The WebUI + server code has not changed since that process started on 09-13 16:19 CDT. + The board (pid 3977979, port 7331) started today at 15:03 CDT. + +### 1.2 What is still unverified + +- **What Jason's screen showed.** For today's send I have the transcript and + the board JSON, not the Console DOM or a screenshot. The one missing fact is + whether "pong" appeared in the open inspector without a manual refresh. +- **The 09-13 processes.** The board keeps no reply receipts and the 09-13 + board and WebUI processes are gone. I cannot prove what those processes + served at 00:50Z. I can only show that the same code, replayed, works. +- **Fleet seats.** I did not scan `~/.mosaic` fleet transcripts. The rule for + this phase is to leave them alone, and CHAT-02 does not cover fleet + catalogues. +- **Clipping in a live send.** "pong" is 4 characters, so today's send does + not exercise the 240-character clip. + +### 1.3 Send-side defect found in passing + +Today's user entry starts with `/`: `/[dragon-lin:control-board -> +dragon-lin:researcher] ping`. `agent-send.sh` adds the header, so the `/` was +already sitting in the Pi editor when the paste arrived. `send-message.sh` +pastes into whatever is in the composer (`paste-buffer -p`, then Enter). It +does not clear it first. Here Pi treated the result as plain text. If the +composer had held a real command prefix, a board reply could have become a +slash command. This is the unmediated-ingress hazard that CHAT-01 assigns to +CHAT-03I (B3). `tools/tmux/**` is excluded from the WebUI plan, so I +record it and do not propose a fix here. Sage confirmed it as a safety gap +and is adding it to DEFERRED.md under CHAT-03I/B3. It gets one line on #1507. +There is no tools/tmux change now. + +### 1.4 Evidence that settles it + +One question for Jason settles the live case: **after today's ping, did +"pong" appear in the Console inspector without pressing Refresh?** + +- If yes, there is no second defect. CHAT-02 proceeds as a product-gap fix. +- If no, `/api/board` had the answer, so the defect is in the Console or the + WebUI proxy. Then capture the Console DOM and the network log for + `/api/board` during one send. +- If `/api/board` had lacked it, the defect would be in the scanner (wrong + newest file). If the transcript had lacked it, the defect would be in the + transport or the seat. Neither happened today. + +To make the check complete for the clip, one more live send can ask for an +answer over 240 characters. Every step above then repeats, and the Console +should show the text ending in "…". That proves the clip is what cut the +09-13 answer short. + +## 2. Scope + +CHAT-02 per the plan: both harness catalogues, safe full branch history with +pagination and cursors, separate timestamps, relative Age from last +activity, and fixtures for malformed, truncated, replaced and touched logs, +cross-project and symlink denial, and no log writes. Read-only: opening a +conversation never resumes, forks, launches or controls anything. + +### 2.1 Backend: `packages/conversation/**` + +Dewey authors it (D4). + +- **Catalogue.** Approved source roots only: the board's repository specs + (`/.pi/state//sessions`) and live seat registrations. There is + no global scan and no browser-supplied path. Each conversation gets an + opaque ID mapped server-side to a file. Catalogue creation time, engine + launch time and last activity are separate nullable fields. +- **No-write Pi parser.** Never `SessionManager.open`, which can migrate old + files (CHAT-00 line 67). Open read-only. Record size, mtime and inode + before and after each read, and the fixture asserts they are unchanged. +- **Branches.** Build the `id`/`parentId` tree and select a leaf explicitly. + The default leaf is the last appended entry, which the implementer must + confirm against the pinned Pi session docs. Other leaves are read-only + branches. + Compaction entries render as markers. Pi `get_entries` order is not a + branch transcript (CHAT-00 line 66), so the parser does not use it. +- **Pages.** CHAT-01 limits: at most 100 parts, 8 MiB serialized UTF-8, 64 + blocks per part and 262144 characters per string. Oversize content splits + into continuation parts and is never clipped. The cursor binds actor, + purpose, conversation, branch, snapshot, source epoch and expiry. A + replaced file (new inode or a shorter length) is a new source epoch, so old + cursors refuse and the client keeps its view with a reconcile marker. It + never silently switches files. +- **Damaged input.** A malformed line becomes an unavailable part with its + position, and reading continues. A truncated trailing line counts as + incomplete, not as an error. +- **Denials.** A symlink anywhere under the root, a path that resolves + outside the root, a parent-session reference, or a conversation from + another project all refuse, with fixtures. + +### 2.2 Console: `packages/webui/**` (Dewey) + +This is the smallest piece that answers the 09-13 complaint: a read-only +conversation view per session, opened from the card, table or inspector. It +renders the selected branch in full, unclipped: user text, assistant text, +tool calls and results collapsed, thinking hidden by default, and Markdown as +untrusted text with no active HTML. Age stays as 42c08d52 shipped it. Reply +keeps the existing board path unchanged. The view gets the new answer through +polling, as the inspector does now. Streaming belongs to CHAT-03. + +The full chat UI (sidebar, composer, queue, approvals, uploads) stays in +CHAT-05. + +### 2.3 Return-flow regression (required by the plan) + +Extend `packages/webui/tests/return-flow.test.mjs`, or add a sibling test. +Send from the conversation view. Then the seat appends user, toolCall, +toolResult and a final answer longer than 240 characters. Assert that the +whole answer appears once, unclipped, in the same open view, with no manual +refresh, and that the draft and caret survive. Add a delayed-result variant +where the toolResult lands after a poll. This covers the Pi engine only; see +D2. + +## 3. Decisions (Sage, 2026-09-26) + +- **D1: moved.** CHAT-01 line 342 had deferred "the actual + execution/writer-claim record" to CHAT-02. CHAT-01C line 217 had deferred + R3-1, reconciling dispatched but unconsumed input, to "CHAT-02 adapter + evidence". A read-only reader needs neither, so both go to CHAT-03, which + owns binding and the single writer. This is noted on #1507. +- **D2: accepted.** Pi ships in CHAT-02. The Claude catalogue refuses + `unsupported-harness` under a fixture until B1 has evidence (Claude's + persisted branch format and leaf selection, CHAT-00 line 66). This narrows + the plan's "both harnesses". Sage records the scope change in the lead + decisions file. +- **D3: accepted.** `packages/conversation` is a library with no server. The + board adds two read-only routes, catalogue and page, in + `packages/control-board/src/serve.mjs` and `scan.mjs`. Darkwing reviews + that change before it lands. The coordination note goes to Darkwing when + the backend reaches review, not before. +- **D4: Dewey authors both.** The backend comes first, then the Console + against its fixtures. Filbert reviews this brief now and the code after. +- **D5: stays with Jason.** Sage set the live reply test as the condition, + and it passed. Sage is asking Jason for the go on CHAT-02. No code goes + under `packages/conversation` until Sage relays his answer. + +## 4. Acceptance + +- The new `packages/conversation` tests cover every fixture in §2.1, including + a before/after hash, mtime and inode check proving no log writes. +- The return-flow regression from §2.3 passes on the served WebUI. +- These still pass: `node docs/plans/chat-00/check.mjs`, `chat-01/check.mjs`, + `chat-01c/check.mjs`, and the control-board, webui and seat suites. +- Browser evidence: conversation view at 320 and 1440 in both themes, with a + long answer, a tool call, a malformed-line marker and a stale-cursor + reconcile marker. No horizontal overflow at 320. +- Filbert approves the exact candidate hashes. Live check: one board send + with a long answer, visible in full in the view. + +## 5. Not in scope + +Live adapters, control, streaming, queues, uploads, approvals, fleet seats, +Claude history until B1, any change to `tools/tmux/**`, `roles/**` or +session logs, and the `/` paste hazard (CHAT-03I). diff --git a/agents/dewey/work/chat-02/BRIEF-r3-3b81a3f2.md b/agents/dewey/work/chat-02/BRIEF-r3-3b81a3f2.md new file mode 100644 index 00000000..93a620e0 --- /dev/null +++ b/agents/dewey/work/chat-02/BRIEF-r3-3b81a3f2.md @@ -0,0 +1,379 @@ +# CHAT-02 brief: read-only histories and Age (#1507, row 5) + +Author: Dewey, 2026-09-26. R3, for Filbert's delta review. It answers his R2 +verdict (revise), `agents/filbert/work/chat-02-brief-r2-review-2026-09-26.md` +(`0f0154b7…7fdf`). Earlier revisions are frozen as `BRIEF-r1-314da8b0.md` and +`BRIEF-r2-ed177bf6.md`. §6 maps each finding to its change. No source edits. + +Plan row: `docs/plans/2026-09-13_webui-session-chat.md` line 214. Depends on +CHAT-01 (`28d4e98a`) and uses the CHAT-01C companion (`b023841c`). + +## 1. Is there a second defect in the return path? + +Short answer: no second return-path defect has shown up for repository Pi +seats. Two legs are shown directly: answers reach the transcript, and the +board serves them. The Console leg is shown only by replaying the 09-13 +code (item 4), until Jason answers the §1.4 question about today's send. + +The best explanation for what Jason hit on 09-13 is the product gap. The +board collapses the last answer to 240 characters +(`packages/control-board/src/scan.mjs`, `TEXT_LIMIT`, line 23 at ea00ec66 +and line 25 at HEAD). The Console showed that one clipped field in an +inspector, with no conversation view and no pending signal. 42c08d52 fixed +the pending display. CHAT-02 covers the clip and the missing view, which means +the new history routes must not go through the board's summary text. + +I found one real defect on the send side (§1.3). While reviewing the new +routes, I found a missing Host check on the board (§1.5). + +### 1.1 Evidence so far + +1. **Transcripts.** `evidence/sends.mjs` walks every `.pi/state/*/sessions` + file. That is five seat directories: darkwing, dewey, filbert, researcher + and sage, and sage has no board sends. For each board send, the script + records the *first* later assistant entry with `stopReason: stop`. The + output is `evidence/board-sends-repo-pi.jsonl`. + - Of 30 sends, 29 are followed by a stop answer in the same file. + - Latency is 1.5 s minimum, 14.6 s median and 1019 s maximum. + - 16 of the 29 answers exceed 240 characters, so the board clipped them. + - The attribution is by file order only. In 7 of the 29, another + user-role entry lands between the send and the answer, so the answer may + also cover that later input. By my count, the first such entry in all 7 + is a peer agent-send from dewey, darkwing, filbert or rocko. Filbert + classified them as two peer agent-sends plus five entries carrying other + content. Either way, Jason's 09-13 question (item 3) is not one of the 7. + - Filbert confirmed that none of the nine files involved branches, so file + order equals branch order. +2. **The unanswered send.** Filbert, 2026-09-12T16:33:17Z. The seat started + working on it and read files until 16:35:44Z. The last entry is an + `aborted` assistant turn, and a new filbert session began at 16:36:26Z. + The seat was relaunched mid-turn, so the answer was lost in the seat, not + in the return path. §2.3 point 5 turns this into a test. +3. **Jason's report window.** At 2026-09-13T00:49:59Z he asked dewey "What + are the Gate E criteria?". The answer landed at 00:50:07Z: 365 characters, + which the board clipped to 240. He wrote the report at 00:56:51Z. It says + the sessions "are all cards within the project dashboard and not available + as independent chat interfaces". That describes the missing view, not a + missing answer. +4. **The 09-13 Console, replayed.** The pinned files are in + `evidence/replay-0913/`: the test, the diff against 42c08d52, the TAP + output and a README. + - The scratch extract of `ea00ec66` is byte-identical to that commit (45 + files checked). + - The test is the 42c08d52 return-flow test with the Age, pending and + clear-once assertions removed, since ea00ec66 has none of those. + - It passed (1/1, 20.7 s): the new answer arrived through the 10 s poll, + once, in the open inspector. + - The replay's answer is short, so it does not exercise the clip. +5. **Jason's live send today.** Researcher, user entry at + 2026-09-26T20:10:57.429Z, answer "pong" at 20:11:06.173Z (8.7 s). + - At 20:15:18Z, `/api/board` showed the row `idle`, with + `lastAssistantText: "pong"`, the same `sessionFile` and a live + registration (`evidence/board-researcher-*.json`). + - The live WebUI (pid 1266267, port 7330) serves `app.js` byte-identical + to HEAD (`d1a51646…`). Its server code has not changed since that + process started on 09-13 at 16:19 CDT. + - The board (pid 3977979, port 7331) started today at 15:03 CDT. + +### 1.2 What is still unverified + +- **What Jason's screen showed.** I have the transcript and the board JSON + for today's send, but not the Console DOM. The Console leg for a live send + is unproven until Jason answers §1.4. +- **The 09-13 processes.** The board keeps no reply receipts, and the 09-13 + processes are gone. Item 4 shows that the same code works. It cannot show + what those processes served. +- **Fleet seats.** I did not scan `~/.mosaic` transcripts. They are out of + scope for CHAT-02. +- **The clip in a live send.** "pong" is 4 characters, so today's send + doesn't exercise the clip. + +### 1.3 Send-side defect found in passing + +Today's user entry starts with `/`: + + /[dragon-lin:control-board -> dragon-lin:researcher] ping + +`agent-send.sh` adds the header, so the `/` was already in the Pi composer +when the paste arrived. `send-message.sh` pastes onto whatever the composer +holds, then presses Enter. Here Pi treated the result as plain text. If the +composer had held a real command prefix, a board reply could have become a +Pi slash command. + +Sage confirmed this as a safety gap under CHAT-03I/B3 and recorded it in +DEFERRED.md. It is on #1507 (comment 26538). There is no `tools/tmux` change +now. + +### 1.4 Evidence that settles it + +Sage is putting one question to Jason: **after today's ping, did "pong" +appear in the Console inspector without pressing Refresh?** + +- **Yes:** there is no second defect, and CHAT-02 proceeds as a + product-gap fix. +- **No:** `/api/board` had the answer, so the defect is in the Console or the + WebUI proxy. Capture the Console DOM and the `/api/board` network log during + one send. +- **Missing from `/api/board`:** that would point at the scanner. **Missing + from the transcript:** that would point at transport or the seat. Neither + happened today. + +One more live send, asking for an answer over 240 characters, would show +the clip ending in "…". + +### 1.5 Board Host check (found while reviewing D3) + +The Console server checks `Host` and `Origin` +(`packages/webui/src/serve.mjs` lines 54–59). The board server does not. It +checks only the bind address. + +A DNS-rebinding page that reaches port 7331 could: + +- read `/api/board`: 240 characters per row, plus task and cwd; +- `POST /api/reply` with `application/json`. The board requires that content + type but not a same-origin `Host`, so the text would be pasted into a live + seat pane. + +Modern browsers restrict some local-network requests, but I have not tested +any browser against this. That makes it a finding, not a demonstrated +exploit. + +The new D3 routes must carry the guard (§2.1). Whether the same guard should +be applied to the existing board routes in that `serve.mjs` change is Sage's +decision. I recommend doing it. + +## 2. Scope + +CHAT-02 per the plan, narrowed by D2: + +- a Pi catalogue; +- safe, full branch history, with pagination and cursors; +- separate timestamps; +- relative Age from last activity; +- no writes to logs. + +It is read-only. Opening a conversation never resumes, forks, launches or +controls anything. + +### 2.1 Backend: `packages/conversation/**` (Dewey, D4) + +**Catalogue sources.** Only approved source roots count: the board's +repository specs (`/.pi/state//sessions`). There is no global +scan, and no path comes from the browser. A seat registration is +seat-written, so it is a hint, not authority (CHAT-01 line 62). Its +`sessionFile` is accepted only when the file is under an approved root for +the same project. Each conversation gets an opaque ID, which the server maps +to a file. Catalogue creation time, engine launch time and last activity are +separate nullable fields. + +**Opening a file safely.** +- Check every path component with `lstat`: no symlinks, and it stays inside + the root. +- Open with `O_RDONLY | O_NOFOLLOW`, then `fstat` the descriptor. The + descriptor's (dev, ino) must match the checked path. +- Read only from that descriptor. +- Never use `SessionManager.open`, which can migrate files (CHAT-00 line 67). + +**Parser.** +- Build the `id`/`parentId` tree and select a leaf explicitly. The default is + the last appended entry, which the implementer confirms against the pinned + Pi session docs. Other leaves are read-only branches. +- Compaction entries render as markers. +- Pi `get_entries` order is not a branch transcript (CHAT-00 line 66), so it + is not used. +- The parser never follows `parentSession`. The conversation still renders, + with a "forked from an earlier session" marker, and the parent file is never + opened. +- If the Pi header's `cwd` names another project, the conversation is refused. +- A malformed line becomes an unavailable part at its position, and reading + continues. A truncated trailing line is incomplete, not an error. + +**Pages.** +- CHAT-01 limits: at most 100 parts, at most 8 MiB of serialized UTF-8 bytes + (enforced on bytes, not characters), 64 blocks per part, and 262144 + characters per string. +- Oversize content splits into continuation parts and is never clipped. + +**Snapshots and epochs.** +- The page reads up to the snapshot length that its cursor pins, so growth + during a read is cut there. +- A source epoch is (dev, ino) plus a SHA-256 of the prefix the snapshot + covers. Growth past that prefix is the same epoch. +- A different inode, a shorter file, or a changed prefix digest (an in-place + rewrite with the same inode) is a new epoch. + +**Cursors.** +- A cursor binds actor, purpose, conversation, branch, snapshot, source epoch + and expiry. +- These all refuse, keep the old view and show a reconcile marker: an + unknown, foreign, expired or source-replaced cursor. +- Nothing ever switches files silently. +- On this unauthenticated loopback route there is one actor, + `local-operator`, and cursors bind to it. That is not multi-actor safety. + Authenticated actors come with CHAT-04R and must not be claimed here. + +**Board routes (D3).** Two read-only `GET` routes, catalogue and page. +- `Host` must be the loopback name and the board's own port. +- A cross-origin `Origin` is refused. +- No CORS headers are sent. +- Responses are `application/json` with `nosniff` and `no-store`. + +**Fixtures.** Each one names its expected refusal or result. + +| # | Fixture | Expected | +|---|---|---| +| F1 | Malformed line | Unavailable part at its position, reading continues | +| F2 | Truncated trailing line | Incomplete marker | +| F3 | Replaced file (new inode) | Old cursors refuse, reconcile | +| F4 | Same-inode prefix rewrite | Old cursors refuse, reconcile | +| F5 | Touched: growth between two pages and during one read | Same epoch, page cut at the pinned length | +| F6 | Unknown, foreign (actor, purpose, conversation or branch) and expired cursor | Each refuses and keeps the old view | +| F7 | Symlink at the file and at a directory component | Refused, never opened | +| F8 | File swapped for a symlink between catalogue and read | Refused (O_NOFOLLOW or a dev/ino mismatch) | +| F9 | Registration naming a file outside the roots, a symlink, or another project's file | Refused, never opened | +| F10 | Pi header `cwd` naming another project | Refused | +| F11 | `parentSession` pointing outside the root | Renders with a marker, and the parent is never opened | +| F12 | Branched file with two leaves | Default leaf shown, other branch readable, no merge | +| F13 | Compaction | Marker, then retained content | +| F14 | A string over 262144 characters, and a multibyte page reaching 8 MiB before 100 parts | Continuation parts, reassembled exactly, byte cap enforced | +| F15 | Claude harness | `unsupported-harness` refusal (D2) | +| F16 | Foreign `Host` and a cross-origin `Origin` on both routes | 403, no CORS headers | +| F17 | No writes | Before and after every fixture: size, SHA-256, mtime, (dev, ino), and the session directory listing (no new files) are unchanged. Atime is excluded because relatime can update it on read. | + +### 2.2 Console: `packages/webui/**` (Dewey) + +This is the smallest thing that answers the 09-13 complaint: a read-only +conversation view per session, opened from the card, the table or the +inspector. +- It renders the selected branch in full, with nothing clipped: user text, + assistant text, and tool calls and results collapsed. +- Thinking is hidden by default. +- Markdown is untrusted, so it has no active HTML, no unsafe URLs and no + terminal escapes. +- Age stays as 42c08d52 shipped it. +- Reply keeps the existing board path. The view gets new answers through + polling. Streaming belongs to CHAT-03. + +The full chat UI (sidebar, composer, queue, approvals and uploads) stays in +CHAT-05. + +**Hostile-render fixture (R1).** Assistant text and tool output contain: +- HTML, including `