diff --git a/docs/plans/chat-00/README.md b/docs/plans/chat-00/README.md new file mode 100644 index 00000000..c5c63b40 --- /dev/null +++ b/docs/plans/chat-00/README.md @@ -0,0 +1,246 @@ +# CHAT-00 protocol and boundary investigation + +Issue #1507. Charter comment 26094. Darkwing authors; Filbert independently +reviews the exact candidate. Rocko supplies a separate architecture review under +comment 26098. Confirmed plan `../2026-09-13_webui-session-chat.md`, SHA-256 +`481428295199c55e0dc2f7f752b64e1dac165f02e44ceb1975004bf327513809`, published +at `4c436f4ce493028c5f602035a29ce27d8a266d92` after investigation began. +Owner confirmation 26093 and plan-review closure 26095 are separate from approval +of this research. Status: research candidate, independent review pending. + +## Finding + +A mediated conversation controller is a reasonable package boundary, but raw Pi +RPC or Claude stream-json is not that controller. Both expose engine operations +without Mosaic's actor, scope, generation or durable request admission rules. +Neither native response success nor process idle establishes stopped tool cohorts. +No running seat can be adopted by starting another engine against its UUID. + +Protocol documentation supports a bounded adapter proposal, not a live readiness +claim. Claude has a concrete compatibility concern: the brief observed 2.1.269, +whereas the installed binary now reports 2.1.270. Its own help says print mode +skips workspace trust and silently ignores invalid settings. Native approval +callbacks do not repair that difference. Keep fail-closed preflight and effective +configuration verification as implementation prerequisites. + +## Evidence and pins + +`sources.json` records byte hashes, versions, locations and inspected sections. +Pi declared and installed package both read 0.85.1. README, RPC and session-format +docs were read completely; RPC declarations and framing helper were also read. +RPC implementation excerpts confirm prompt preflight, queue and branch commands. +This is not a complete extension/SDK/launcher audit. The implementer must still +read the relevant SDK/extensions/TUI documents and examples in full before choosing +and implementing that integration. No Pi engine, loader or model was started. + +Claude 2.1.270 was queried only with `--version` and `--help`, using an empty +environment and temporary HOME/config directory, without prompt/session flags. +No actual auth file was read. The installed binary digest and help digest are +recorded. This does not prove which binary any existing seat is running. + +Official Python SDK source is pinned at commit +`37a52c9fb3f0271de017911914b0d42efea6267e`, project version 0.2.152. Its wire +request/response implementation and relevant types were inspected, not installed +or executed. The transport declares minimum Claude 2.0.0; that minimum is not a +compatibility certificate for 2.1.270. Official headless and TypeScript-reference +pages are retrieval-hash pins, not release-frozen documentation. Sections name +minimum CLI capabilities; actual capability negotiation remains untested. +An initial platform documentation URL returned HTML, not a usable protocol source; +it is excluded from supporting evidence. + +No private history, fleet configuration, account data or live registrations were +inventoried. Named classes below are requirements, not access grants or a count +of verified seats. No source package, launcher or historical contract was edited. + +## Capability matrix + +Source IDs refer to `sources.json`. DOCUMENTED means a cited protocol shape or +behavior; OBSERVED is restricted to version/help commands and pure offline checks. +UNVERIFIED is not an optional capability. + +| Requirement | Pi 0.85.1 evidence | Claude evidence | Remaining proof | +|---|---|---|---| +| Prompt and acknowledgement | P-RPC prompt, P-TYPES, P-MODE. `id` correlates acceptance; success may mean queued or extension-handled | C-TYPES/C-QUERY control envelopes; C-REF SDKUserMessage UUID and replay; C-HELP replay-user-messages | Durable Mosaic admission and duplicate/conflicting identity rejection. Neither `id` nor UUID alone supplies exactly-once effects | +| Incremental user/assistant/tool conversation | P-RPC message_start/update/end; contentIndex deltas; authoritative message_end; toolCallId | C-REF stream_event and complete assistant/user messages; parent_tool_use_id separates nested work | Bound per-execution stream ordering, temporary-to-persisted reconciliation and actual reported-return regression for both adapters | +| Settled versus finished | P-RPC agent_end can precede retry/compaction/queued continuation; agent_settled means no automatic continuation | C-HEADLESS result and background-task behavior; C-REF result correlation | Settled is still not cohort termination; do not use either to release a writer claim | +| Complete branch history | P-SESSION v3 tree, compaction/retainedTail, custom visibility; P-RPC get_entries with since and leafId, get_tree | C-REF user UUID, session_id, compaction events; C-HELP resume identity | Claude persisted branch format and exact leaf selection are not verified. Pi get_entries is append order across branches, not a safe selected-branch transcript or bounded page | +| Read-only catalogue | Pi header/cwd/tree documented; SessionManager.open can auto-migrate old files | C-HEADLESS resume searches by ID across projects, can accept a transcript path | Separate no-write parser and explicit adoption/access mapping required. Never use a migrating loader or global recent-session search | +| Native approval and denial | P-RPC extension_ui_request/response uses exact id; select/confirm/input/editor, timeouts and cancellation; not a universal builtin approval system | C-QUERY can_use_tool supplies tool/input/tool_use_id and allow/deny response; C-REF says callback only runs when native flow reaches a prompt | Inventory approved Pi extensions and Claude request choices. Native denies remain authoritative; no general chat yes, permission-mode switch or updatedPermissions forwarding | +| Approval reconnect | Pi pending UI map is in one process; no documented durable pending-dialog replay command | C-REF initialize wrapper pending_permission_requests, required from CLI 2.1.268; repeats must be idempotent | Pin/verify actual reconnect behavior, persist broker pending intent, fence stale controller replies; missing list is not an empty list | +| Ordinary Interrupt Q20 | P-RPC explicitly clear_queue BEFORE abort; abort alone continues queued work. Returned queues contain text only | C-REF direct interrupt request can use cancel_queued:true, gated by interrupt_cancel_queued_v1; SDK interrupt() does not send it. Python pinned interrupt also sends only subtype | Broker owns durable drafts and attachment identity; serialize dispatch fencing before cancellation, classify races. Cancel receipt covers UUID-bearing main-thread pending messages only, not every possible input | +| Queue edit/cancel and takeover | Native steering/followUp queues expand commands and do not offer durable per-request edit/cancel | Interrupt receipts expose still_queued/cancelled but do not establish Mosaic edit/cancel semantics | Keep queued work in broker until dispatch; retain native queues as reconciliation evidence. Never relabel already-dispatched work unsent | +| Images/files | P-RPC prompt images use base64 data/mimeType; no general private-file upload API | C-REF SDKUserMessage MessageParam; C-HELP --file is startup download, not browser staging; stream-json input advertised | Verify exact image/file limits and supported content schemas for installed Claude; privately stage other files through an approved bridge. Q17 caps cannot be assumed harness limits | +| Force stop and Resume | abort waits idle; no attributable-cohort proof in RPC response | C-HEADLESS describes SIGTERM and resume of unfinished turns; no Mosaic stop proof | Supervisor cohort identity, external-effect uncertainty, durable stop/recovery transaction and per-seat approved sacrificial test. Resume can have effects; not a read operation | +| Controller/terminal | Raw stdin is the only RPC input channel; TUI does not expose an attach-RPC command | Native attach/background/remote-control help is not a reviewed Mosaic controller interface | One broker-owned engine and mediated terminal; no second UUID writer. All old input channels must be fenced | +| Ambient config/tool isolation | P-README disables discovery/builtin tools with flags; extensions run with full process access | C-HELP tools empty, safe-mode/restricted/bare documented; print skips trust/invalid settings; C-REF allowedTools auto-approves, not allowlists | Approved tool bridge plus execution separation; verified configuration discovery/refusal behavior. Do not substitute auth-changing bare mode or permission callbacks for this gate | +| Fleet | No remote channel in Pi RPC itself | No reviewed private fleet mapping from local Claude help | Identity, transport, transfer and remote-cohort contracts; held behind Q22 prerequisites | + +## Protocol details that change the design + +1. Frame Pi JSON on LF only, retain split UTF-8 bytes and optional CRLF. A string + containing U+2028/U+2029 is one payload, not several records. Place explicit + size bounds above the engine. Do not expose unbounded get_tree/get_entries + or raw paths to a browser. A missing cursor is a reconciliation error. +2. Pi `prompt` may invoke an extension command immediately, including while busy. + Skills/templates expand before queueing. A broker cannot classify all strings + as harmless chat merely because the RPC command is named prompt. Reviewed + explicit resources and command policy must cover this route too. +3. Pi message_update contains deltas, not the former cumulative partial message. + Tool execution updates instead contain accumulated partialResult. Applying the + same append operation to both duplicates output. Reconcile with authoritative + message_end and later selected-branch persisted entries. +4. Claude wire control is `{type:control_request, request_id, request:{subtype,...}}`; + success response correlates inside `response.request_id`. Permission allow uses + `updatedInput`; deny uses behavior/message and optional interrupt. Refuse + permission mutations beyond the exact reviewed decision, even if SDK types + expose updatedPermissions. Raw engine account/config fields never go to UI. +5. Official Claude documentation exposes an important Q20 distinction. A direct + control request may set `cancel_queued:true` only after verifying advertised + `interrupt_cancel_queued_v1` and receipt support. SDK `interrupt()` alone does + not do this. Receipts may contain unknown external UUIDs and omit unnumbered + or subagent input. An empty still_queued list is not universal quiescence. + Missing capabilities block dependent behavior; do not rely on version comparison. +6. Broker-owned draft records must retain text and attachment IDs. Pi clear_queue + returns text, not all upload metadata; Claude cancel receipts return IDs. Neither + is sufficient to recreate a durable draft without the original admission record. +7. Native engine state and Mosaic policy are distinct. An engine can acknowledge a + prompt before the new answer appears. The required regression must show user, + tools and NEW assistant output in the same selected conversation, not just a + successful POST or a reused last-message preview. + +## Retained foundation requirements and proposed extensions + +Foundation RUNTIME sections 3-5 remain the accepted design baseline, not installed +runtime enforcement. No change to their files is part of CHAT-00. + +| Requirement | Disposition | CHAT-01/03 prerequisite | +|---|---|---| +| Scope/access/legacy adoption, R22/R30 | RETAIN | Approved mappings and grants for each legacy conversation; UUID/cwd/basename is not a project assignment | +| One writer and current controller generation, R24 | RETAIN | Durable claim, process incarnation, explicit transfer and controller generation check on admission AND dispatch | +| Snapshot and selected branch at Resume, R7/R25 | RETAIN | Exact private snapshot/leaf/config pins; no autoload-migration, fallback Fresh or start-before-binding | +| Mediated terminal, R34 | RETAIN | Terminal is another authorized client, not an independently writable native engine TUI | +| No builtin-tool escape, reviewed bridge, no tools in credential-bearing engine | RETAIN, ENFORCEMENT UNPROVED | Separate tool executor with least privilege; every builtin/direct shell/plugin/MCP path must be disabled, mediated or explicitly refused. Native approval callbacks are insufficient | +| No ambient extensions/context | RETAIN, COMPATIBILITY CONFLICT TO RESOLVE | Explicit manifest and discovery controls; native Claude print trust/settings behavior requires preflight/effective-state proof without dropping denies | +| Exact decision correlation and no permission widening | RETAIN | Dialog id plus actor, intent digest, scope, tool, execution and controller binding; native deny and expired/canceled requests fence replies | +| Invocation/effect evidence and no blind replay | RETAIN | Write-ahead intent, causal actor, dedup and uncertainty recovery. Signal/EOF/idle do not close effects | +| Pi-only bounded text | PROPOSED EXTENSION | Claude mappings, safe images/files, content/byte limits, approvals and transcript branches; explicit schema review in CHAT-01 | +| Browser/server durable drafts and uploads | PROPOSED EXTENSION | Private storage ownership/capacity, state transitions and crash recovery, not workspace files or immutable run-record rewrites | +| Private remote control | PROPOSED EXTENSION, Q22 HELD | Authenticated host/seat identity and permissions plus remote stop/transfer proof. No fleet path or service authority yet | + +Do not solve a conflict by installing the parked registry or weakening role/policy +contracts. Escalate an exact bounded contract disposition only when evidence shows +the retained requirement cannot be met within a proposed implementation. + +## Proposed identity and access map + +These are design fields, not a new accepted schema. Authoritative binding should +include opaque conversation id, approved project/workspace/seat identity, host id, +harness/version, private source-root reference, native session id and selected +branch, execution incarnation, supervisor cohort reference, controller generation, +authenticated actor/connection, config/permission revision and current access grant. + +Required classes: repository Pi on the workstation, repository Claude on the +workstation, local fleet Pi, remote fleet Pi, and any additional Claude/host class +identified by the separately authorized all-seat inventory. Do not assert that an +uninspected class is deployed or accessible. Existing Rocko launch identity is +not proof of its current execution, model or all fleet identities. + +Local proposal: private Unix socket with verified OS peer identity between mediated +terminal and broker; loopback WebUI server using authenticated, per-browser-session +credentials and per-conversation observer/controller grants. Bind browser requests +to exact allowed Origin and Host, reject cross-origin/CSRF mutations, avoid query +string secrets, and use secure session lifecycle, rotation, revocation and bounded +idle expiry. Cookie security must match the chosen local HTTPS/origin arrangement; +loopback HTTP cannot simply be labeled secure. No credentials are created here. + +Remote proposal: private authenticated host channel with verified peer identity, +explicit per-host/seat grants and revocation, plus a host supervisor that attests +execution/cohort incarnation. SSH or mutual TLS remains a reviewed mechanism choice, +not an installed service. Separate transport credentials from provider accounts. +Transport connectivity alone grants neither transcript visibility nor control. + +Store drafts/uploads privately, keyed by actor and approved conversation identity. +Use opaque attachment references with digest, verified bytes/type and declared +recipient. Transfer to a remote host needs exact-seat acknowledgement and replay +protection. Sent content follows conversation visibility. Enforce 10 files, +20 MiB/file and 100 MiB/message plus any stricter verified engine limit; no silent +expiry, extraction, execution or overwrite. No public staging URL or arbitrary +browser-supplied path. + +## Every-ingress disposition + +| Ingress | Required disposition before cutover | +|---|---| +| Old board Reply and agent-send/tmux | Refuse delivery to migrated engine paths or route through authenticated broker admission. Existing preamble is untrusted text. Merely hiding the old button is not fencing | +| Native TUI keyboard and external editor | Replace engine input with mediated terminal connection; observer cannot submit and takeover invalidates old connection generation | +| Raw RPC, Claude stream-json and direct shell commands | Private supervisor-owned descriptors; allowlist translated operations, never raw client forwarding | +| Engine follow-ups/steering, hooks and scheduled/internal input | Inventory/disable or mediate; reconcile inherited queues at handoff. No input can evade actor and scope admission | +| Approval replies and reinitialization | Exact outstanding request and current generation; late/duplicate-conflicting replies refused, lost replies reconciled, no auto-approval | +| Alternate launcher, crash restart, same UUID Resume | Exclusive durable writer reservation and prior-cohort proof before start; uncertainty retains claim. Record and test rollback without prompt/approval replay | + +## Synthetic checks and their limits + +Run `node docs/plans/chat-00/check.mjs`. `fixtures.json` contains synthetic cases +only. Checks cover pinned-source identities where available, LF/UTF-8 framing, +documented envelope shapes, Q20/takeover state examples, stale binding rejection, +unknown delivery and stop-proof distinctions, and upload-cap arithmetic. +They do not launch engines, read sessions/auth, make network calls or test native +permissions, live races, private remote transport, actual image support or cutover. +A passing miniature model is not runtime enforcement or Claude compatibility. + +## Blocking gates and next bounded work + +- B1: prove the exact Claude CLI/protocol combination, capability negotiation, + transcript branch selection and image/file input shapes. Public SDK/source + examples narrow uncertainty but do not certify the installed binary. +- B2: prove tool-execution separation, resource manifest isolation and native + trust/settings refusal parity. This is a security gate, not UI polish. +- B3: complete all-ingress writer/controller fencing and supervisor crash/stop + proof. No native attach or second UUID process substitutes for it. +- B4: source/access mapping and authentication design need independent contract + review and any required owner grants. No private inventory has been performed. +- B5: Q22 retains piece 5, required #1508 and fleet-launch dependencies. CHAT-04R + cannot proceed ahead of them; all-seat acceptance remains blocked meanwhile. +- B6: exact per-seat live envelope and rollback approval remains separate from + source publication. First-seat approval never authorizes later seats. + +Finish independent CHAT-00 review, then propose only the bounded CHAT-01 contract +charter with these proof gates intact. Do not silently start runtime adapters or +other queue rows. Rocko findings and Filbert verdict will be recorded as issue +comments, with exact research hashes, before claiming this task approved. + + +## Rocko architecture review and reconciled qualifications + +Rocko independently reviewed the plan and foundation boundaries and returned +findings through agent-send. He recommends distinguishing strict foundation +workers from existing host seats, with a possible host-seat ceiling of WebUI +powers no greater than native terminal powers. This is an owner/security-review +proposal, NOT an accepted exception to tool isolation. Until an exact ruling, +retain the foundation requirements and mark native parity/enforcement unproved. +No policy change or loss of required host-seat functionality is approved here. + +Keep Pi and Claude proof matrices separate and propose distinct adapter fixtures +and a mediated-terminal/old-ingress work boundary at CHAT-01. No such subtask has +started. Any required change to excluded tmux/comms paths needs explicit bounded +scope approval; disabling communication without a replacement does not meet the +all-seat outcome. Observer-mode pasted messages may be retained as untrusted +recoverable input, not silently dispatched as an authenticated controller. + +Four qualifications were sent back and Rocko explicitly accepted them: + +- Claude help's copy-when-already-running wording belongs to `--bg` with + `--resume`, not proof of ordinary `--resume` behavior. Exact same-identity + resume of a stopped seat remains a measured handoff gate. Native attach is + unverified, not proved impossible or accepted as an alternative controller. +- The official TypeScript reference documents `cancel_queued:true`; the claim + that Claude has no queue clear was withdrawn. Exact CLI behavior is still + unverified, and the SDK convenience method omits that field. +- Pi clear_queue text cannot reconstruct attachment identifiers or durable + metadata. The broker must retain the original queue records. +- Disconnect is neither auto-allow nor blanket auto-deny. Keep outstanding + approval state under the exact native timeout/cancellation semantics and + reconcile it before a current-controller reply. + +The phase-2 Pi document pin predates 0.85.1. All citations here use the current +sources.json hash and current sections, never inherited phase-2 line numbers. diff --git a/docs/plans/chat-00/check.mjs b/docs/plans/chat-00/check.mjs new file mode 100644 index 00000000..ad1d67a5 --- /dev/null +++ b/docs/plans/chat-00/check.mjs @@ -0,0 +1,114 @@ +// CHAT-00 research checker only. No engine, filesystem mutation or network. +// These small models check proposed examples, not production authorization. +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { PassThrough } from 'node:stream'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '../../..'); +const sources = JSON.parse(readFileSync(path.join(here, 'sources.json'), 'utf8')); +const fixture = JSON.parse(readFileSync(path.join(here, 'fixtures.json'), 'utf8')); +let checks = 0; +const check = (name, fn) => { fn(); checks++; console.log(`PASS ${name}`); }; +check('source IDs unique; provenance and inspected sections explicit', () => { + assert.equal(new Set(sources.sources.map(s => s.id)).size, sources.sources.length); + for (const s of sources.sources) { + assert.match(s.sha256, /^[a-f0-9]{64}$/); + assert.ok(s.locator && s.coverage && s.evidence); + } +}); +for (const s of sources.sources.filter(s => s.repoPath)) { + check(`${s.id} installed/repository source hash`, () => { + const file = path.resolve(root, s.repoPath); + assert.ok(file.startsWith(root + path.sep)); + const hash = createHash('sha256').update(readFileSync(file)).digest('hex'); + assert.equal(hash, s.sha256, 'Source changed: re-investigate, do not refresh pin blindly'); + }); +} +// Public reference caches are deliberately not a required dependency. No fetch. +check('fixture provenance is synthetic, no runtime claim', () => { + assert.equal(fixture.evidence, 'synthetic-research-only'); + assert.equal(fixture.runtimeEnforcementProven, false); +}); + +const context = fixture.binding; +const fields = ['actor', 'host', 'seat', 'project', 'workspace', 'conversation', 'branch', 'execution', 'controllerGeneration']; +const allowed = new Set(['prompt', 'approval', 'interrupt', 'force-stop', 'takeover']); +function admissible(request) { + return request.authenticated === true && request.accessGranted === true && + fields.every(f => request[f] === context[f]) && allowed.has(request.operation) && + request.source === 'broker' && request.role === 'controller' && request.claim === 'held' && + (request.operation !== 'prompt' || (typeof request.message === 'string' && !request.message.startsWith('/'))); +} +for (const c of fixture.admission) { + check(c.id, () => assert.equal(admissible({ ...fixture.request, ...c.patch }), c.expected)); +} + +function recoverQueue(queue) { + return queue.map(item => ({ ...item, state: item.state === 'queued' ? 'draft' : item.state })); +} +for (const operation of ['interrupt', 'force-stop', 'takeover']) { + check(`${operation}: only undispatched queue becomes draft, identity retained`, () => { + const result = recoverQueue(fixture.queue); + assert.deepEqual(result, fixture.recoveredQueue); + assert.equal(result[0].attachments[0], 'upload-1'); + assert.equal(result[1].actor, 'old-controller'); + }); +} +for (const c of fixture.stopProof) { + check(c.id, () => { + const stopped = c.cohortVerified && c.liveMembers === 0 && c.effectsReconciled; + assert.equal(stopped, c.expectedStopped); + }); +} +for (const c of fixture.uploads) { + check(c.id, () => { + const valid = c.sizes.length <= 10 && c.sizes.every(s => Number.isSafeInteger(s) && s > 0 && s <= 20 * 1024 * 1024) && + c.sizes.reduce((a, b) => a + b, 0) <= 100 * 1024 * 1024; + assert.equal(valid, c.expected); + }); +} +check('Claude documented interrupt cancellation is capability gated', () => { + const required = ['interrupt_receipt_v1', 'interrupt_cancel_queued_v1']; + assert.equal(required.every(x => fixture.claude.capabilities.includes(x)), true); + assert.equal(required.every(x => fixture.claude.oldCapabilities.includes(x)), false); + assert.deepEqual(fixture.claude.interrupt.request, { subtype: 'interrupt', cancel_queued: true }); + assert.equal(fixture.claude.interrupt.type, 'control_request'); + assert.equal(fixture.claude.response.response.request_id, fixture.claude.interrupt.request_id); + assert.deepEqual(fixture.claude.response.response.response.cancelled, ['message-1']); + // Empty lists are never treated as universal quiescence. + assert.equal(fixture.claude.emptyReceiptProvesCohortStopped, false); +}); +check('Pi ordinary interrupt sequence fences first, clears before abort', () => { + assert.deepEqual(fixture.pi.interruptSequence, ['fence-dispatch', 'persist-draft-intent', 'clear_queue', 'abort', 'reconcile']); + assert.deepEqual(fixture.pi.clearResponse.data.followUp, ['next']); + assert.equal(fixture.pi.clearResponsePreservesAttachmentIds, false); +}); +check('approval must match outstanding intent and cannot widen permissions', () => { + const pending = fixture.approval.pending; + const accept = response => response.id === pending.id && response.intent === pending.intent && + response.execution === pending.execution && response.generation === pending.generation && + pending.choices.includes(response.choice) && response.updatedPermissions === undefined; + for (const sample of fixture.approval.responses) assert.equal(accept(sample.value), sample.expected); +}); +check('uncertain send is not safe replay, native success is not task completion', () => { + assert.equal(fixture.uncertain.allowBlindReplay, false); + assert.equal(fixture.uncertain.releaseWriterClaim, false); + assert.equal(fixture.uncertain.promptSuccessProvesAnswer, false); +}); + +// Import only the fully inspected pure framing helper, not the harness/SDK. +const { attachJsonlLineReader } = await import('../../../node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/jsonl.js'); +check('installed Pi LF framing preserves UTF-8 chunk splits and Unicode separators', () => { + const stream = new PassThrough(); const lines = []; + const detach = attachJsonlLineReader(stream, line => lines.push(JSON.parse(line))); + const payload = { type: 'prompt', message: 'one\u2028two\u2029three 🦆' }; + const bytes = Buffer.from(JSON.stringify(payload) + '\r\n'); + for (const byte of bytes) stream.write(Buffer.from([byte])); + assert.deepEqual(lines, [payload]); + detach(); stream.destroy(); +}); +console.log(`CHAT-00 ${checks} checks passed. Synthetic/source checks only; engines, auth, live access and cutover NOT TESTED.`); diff --git a/docs/plans/chat-00/fixtures.json b/docs/plans/chat-00/fixtures.json new file mode 100644 index 00000000..bc8b19b0 --- /dev/null +++ b/docs/plans/chat-00/fixtures.json @@ -0,0 +1,450 @@ +{ + "evidence": "synthetic-research-only", + "runtimeEnforcementProven": false, + "binding": { + "actor": "actor-1", + "host": "host-1", + "seat": "seat-1", + "project": "project-1", + "workspace": "workspace-1", + "conversation": "conversation-1", + "branch": "leaf-1", + "execution": "incarnation-1", + "controllerGeneration": 2 + }, + "request": { + "actor": "actor-1", + "host": "host-1", + "seat": "seat-1", + "project": "project-1", + "workspace": "workspace-1", + "conversation": "conversation-1", + "branch": "leaf-1", + "execution": "incarnation-1", + "controllerGeneration": 2, + "authenticated": true, + "accessGranted": true, + "operation": "prompt", + "message": "hello", + "source": "broker", + "role": "controller", + "claim": "held" + }, + "admission": [ + {"id":"refuse extension command during streaming", "patch":{"message":"/dangerous-command", "streaming":true}, "expected":false}, + {"id":"refuse second launcher while claim held", "patch":{"operation":"launch"}, "expected":false}, + { + "id": "admit exact synthetic binding", + "patch": {}, + "expected": true + }, + { + "id": "refuse stale or foreign actor", + "patch": { + "actor": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign host", + "patch": { + "host": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign seat", + "patch": { + "seat": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign project", + "patch": { + "project": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign workspace", + "patch": { + "workspace": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign conversation", + "patch": { + "conversation": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign branch", + "patch": { + "branch": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign execution", + "patch": { + "execution": "foreign" + }, + "expected": false + }, + { + "id": "refuse stale or foreign controllerGeneration", + "patch": { + "controllerGeneration": 0 + }, + "expected": false + }, + { + "id": "refuse authenticated=False", + "patch": { + "authenticated": false + }, + "expected": false + }, + { + "id": "refuse accessGranted=False", + "patch": { + "accessGranted": false + }, + "expected": false + }, + { + "id": "refuse role=observer", + "patch": { + "role": "observer" + }, + "expected": false + }, + { + "id": "refuse claim=uncertain", + "patch": { + "claim": "uncertain" + }, + "expected": false + }, + { + "id": "refuse source=old-board", + "patch": { + "source": "old-board" + }, + "expected": false + }, + { + "id": "refuse source=tmux-paste", + "patch": { + "source": "tmux-paste" + }, + "expected": false + }, + { + "id": "refuse source=native-terminal", + "patch": { + "source": "native-terminal" + }, + "expected": false + }, + { + "id": "refuse operation=bash", + "patch": { + "operation": "bash" + }, + "expected": false + }, + { + "id": "refuse operation=switch_session", + "patch": { + "operation": "switch_session" + }, + "expected": false + }, + { + "id": "refuse operation=set_permission_mode", + "patch": { + "operation": "set_permission_mode" + }, + "expected": false + } + ], + "queue": [ + { + "id": "q1", + "state": "queued", + "actor": "old-controller", + "attachments": [ + "upload-1" + ] + }, + { + "id": "q2", + "state": "dispatched", + "actor": "old-controller", + "attachments": [] + }, + { + "id": "q3", + "state": "delivery-unknown", + "actor": "old-controller", + "attachments": [] + } + ], + "recoveredQueue": [ + { + "id": "q1", + "state": "draft", + "actor": "old-controller", + "attachments": [ + "upload-1" + ] + }, + { + "id": "q2", + "state": "dispatched", + "actor": "old-controller", + "attachments": [] + }, + { + "id": "q3", + "state": "delivery-unknown", + "actor": "old-controller", + "attachments": [] + } + ], + "stopProof": [ + { + "id": "idle without cohort proof is not stopped", + "cohortVerified": false, + "liveMembers": 0, + "effectsReconciled": true, + "expectedStopped": false + }, + { + "id": "live tool child is not stopped", + "cohortVerified": true, + "liveMembers": 1, + "effectsReconciled": true, + "expectedStopped": false + }, + { + "id": "unreconciled effects retain uncertainty", + "cohortVerified": true, + "liveMembers": 0, + "effectsReconciled": false, + "expectedStopped": false + }, + { + "id": "synthetic confirmed stop proof", + "cohortVerified": true, + "liveMembers": 0, + "effectsReconciled": true, + "expectedStopped": true + } + ], + "uploads": [ + { + "id": "attachment caps exact boundary", + "sizes": [ + 20971520, + 20971520, + 20971520, + 20971520, + 20971520 + ], + "expected": true + }, + { + "id": "too many attachments", + "sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "expected": false + }, + { + "id": "file over cap", + "sizes": [ + 20971521 + ], + "expected": false + }, + { + "id": "message over cap", + "sizes": [ + 20971520, + 20971520, + 20971520, + 20971520, + 20971520, + 1 + ], + "expected": false + } + ], + "approval": { + "pending": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choices": [ + "allow", + "deny" + ] + }, + "responses": [ + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choice": "deny" + }, + "expected": true + }, + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choice": "allow" + }, + "expected": true + }, + { + "value": { + "id": "other", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choice": "deny" + }, + "expected": false + }, + { + "value": { + "id": "approval-1", + "intent": "other", + "execution": "incarnation-1", + "generation": 2, + "choice": "deny" + }, + "expected": false + }, + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "old", + "generation": 2, + "choice": "deny" + }, + "expected": false + }, + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 1, + "choice": "deny" + }, + "expected": false + }, + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choice": "always-allow" + }, + "expected": false + }, + { + "value": { + "id": "approval-1", + "intent": "intent-1", + "execution": "incarnation-1", + "generation": 2, + "choice": "deny", + "updatedPermissions": [] + }, + "expected": false + } + ] + }, + "uncertain": { + "allowBlindReplay": false, + "releaseWriterClaim": false, + "promptSuccessProvesAnswer": false + }, + "pi": { + "interruptSequence": [ + "fence-dispatch", + "persist-draft-intent", + "clear_queue", + "abort", + "reconcile" + ], + "clearResponse": { + "type": "response", + "command": "clear_queue", + "success": true, + "data": { + "steering": [], + "followUp": [ + "next" + ] + } + }, + "clearResponsePreservesAttachmentIds": false + }, + "claude": { + "capabilities": [ + "interrupt_receipt_v1", + "interrupt_cancel_queued_v1" + ], + "oldCapabilities": [ + "interrupt_receipt_v1" + ], + "interrupt": { + "type": "control_request", + "request_id": "interrupt-1", + "request": { + "subtype": "interrupt", + "cancel_queued": true + } + }, + "response": { + "type": "control_response", + "response": { + "subtype": "success", + "request_id": "interrupt-1", + "response": { + "still_queued": [], + "cancelled": [ + "message-1" + ] + } + } + }, + "emptyReceiptProvesCohortStopped": false + } +} diff --git a/docs/plans/chat-00/sources.json b/docs/plans/chat-00/sources.json new file mode 100644 index 00000000..7e470942 --- /dev/null +++ b/docs/plans/chat-00/sources.json @@ -0,0 +1,130 @@ +{ + "recordedAt": "2026-09-13T01:37:49.527399+00:00", + "piVersion": "0.85.1", + "claudeObservedVersion": "2.1.270", + "claudeBinarySha256": "3a624a5a7cd79bbad4d32bd7db36f1197ecf458bc5bf1e2aed81834a01ad3ef0", + "claudeSdkCommit": "37a52c9fb3f0271de017911914b0d42efea6267e", + "claudeSdkVersion": "0.2.152", + "sources": [ + { + "id": "P-README", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/README.md", + "locator": "node_modules/@earendil-works/pi-coding-agent/README.md", + "sha256": "a63ba45d9c16ad63631c9bbeb13699a39c1d097174324e03c784fa8fb3a7b5d8", + "coverage": "entire file, 718 lines", + "evidence": "installed source/documentation" + }, + { + "id": "P-RPC", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/docs/rpc.md", + "locator": "node_modules/@earendil-works/pi-coding-agent/docs/rpc.md", + "sha256": "15fcd26bee72777b373fd5f2edd77091a01cadd4de95e48b08422ced0552a28d", + "coverage": "entire file, 1618 lines; fresh pin, not phase-2 offsets", + "evidence": "installed source/documentation" + }, + { + "id": "P-SESSION", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/docs/session-format.md", + "locator": "node_modules/@earendil-works/pi-coding-agent/docs/session-format.md", + "sha256": "c0f25475ab08fb96e244e6bf433aeaa41b69bfb5a52de5c7b01b81c900a84a54", + "coverage": "entire file, 438 lines", + "evidence": "installed source/documentation" + }, + { + "id": "P-TYPES", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts", + "locator": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts", + "sha256": "e968e5be01dc7ad9615f938ae867ef136fa495f13dcf169942e9f781a299d9eb", + "coverage": "entire file", + "evidence": "installed source/documentation" + }, + { + "id": "P-FRAME", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/jsonl.js", + "locator": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/jsonl.js", + "sha256": "049a9f8ca4242c79f1911ed977949e8d8906b4561f424c2729687f426fabaacf", + "coverage": "entire file", + "evidence": "installed source/documentation" + }, + { + "id": "P-MODE", + "repoPath": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js", + "locator": "node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js", + "sha256": "e7e4724aa55c5aac73cf36793653b26736200e5c59d58373990fc31028f86477", + "coverage": "292-346 read; targeted lookups get_entries/get_tree and UI request handling", + "evidence": "installed source/documentation" + }, + { + "id": "FOUNDATION", + "repoPath": "docs/plans/foundation-v1-candidate/RUNTIME.md", + "locator": "docs/plans/foundation-v1-candidate/RUNTIME.md", + "sha256": "b1a2b4d0df88ba6f7b197252807f3a3925ffff9375f4e70d4ff28593337c3438", + "coverage": "sections 3-5 read; accepted design not runtime proof", + "evidence": "repository design contract" + }, + { + "id": "PLAN", + "repoPath": "docs/plans/2026-09-13_webui-session-chat.md", + "locator": "docs/plans/2026-09-13_webui-session-chat.md", + "sha256": "481428295199c55e0dc2f7f752b64e1dac165f02e44ceb1975004bf327513809", + "coverage": "entire confirmed plan", + "evidence": "repository owner-confirmed brief" + }, + { + "id": "C-QUERY", + "locator": "https://raw.githubusercontent.com/anthropics/claude-agent-sdk-python/37a52c9fb3f0271de017911914b0d42efea6267e/src/claude_agent_sdk/_internal/query.py", + "cache": "/tmp/chat-00-claude-query.py", + "sha256": "c24f24ac2cb45a7debd3ba8fb945c65ffca7f1414ee0fb37a49fc500e1afcb8c", + "coverage": "469-713 read; initialization/response/cancel routing targeted lookups", + "evidence": "official SDK source at exact commit; CLI compatibility unverified" + }, + { + "id": "C-TYPES", + "locator": "https://raw.githubusercontent.com/anthropics/claude-agent-sdk-python/37a52c9fb3f0271de017911914b0d42efea6267e/src/claude_agent_sdk/types.py", + "cache": "/tmp/chat-00-claude-types.py", + "sha256": "49e6a4ca6155ad9b245133d138516d9269bbb3aad2eb21311d02d77d75bca86a", + "coverage": "1090-1154 and 2360-end read; type name searches", + "evidence": "official SDK source at exact commit; CLI compatibility unverified" + }, + { + "id": "C-TRANSPORT", + "locator": "https://raw.githubusercontent.com/anthropics/claude-agent-sdk-python/37a52c9fb3f0271de017911914b0d42efea6267e/src/claude_agent_sdk/_internal/transport/subprocess_cli.py", + "cache": "/tmp/chat-00-claude-transport.py", + "sha256": "748227e42cb9802d7316fabc836428d69bd219b63ef1b2f9f8f7a86a72a03e45", + "coverage": "targeted flag/version searches only; not a complete transport audit", + "evidence": "official SDK source at exact commit; CLI compatibility unverified" + }, + { + "id": "C-PROJECT", + "locator": "https://raw.githubusercontent.com/anthropics/claude-agent-sdk-python/37a52c9fb3f0271de017911914b0d42efea6267e/pyproject.toml", + "cache": "/tmp/chat-00-claude-pyproject.toml", + "sha256": "00bcff397a21f835b90a8ca9c1765250b47b46e85346ebcecdbd344e3bb4693a", + "coverage": "1-60 read; version 0.2.152", + "evidence": "official SDK source at exact commit; CLI compatibility unverified" + }, + { + "id": "C-HEADLESS", + "locator": "https://code.claude.com/docs/en/headless.md", + "cache": "/tmp/chat-00-claude-headless.md", + "sha256": "72521df6ef71d978d8c8298e518db15a4362b4fbf97f32a36626e31d088675a1", + "coverage": "entire file", + "evidence": "official floating documentation, retrieval bytes pinned; not CLI observation" + }, + { + "id": "C-REF", + "locator": "https://code.claude.com/docs/en/agent-sdk/typescript.md", + "cache": "/tmp/chat-00-claude-sdk-reference.md", + "sha256": "f9800fcad978115589b40b6cb73a63ab9eae3289628094a9b515793204a78cbb", + "coverage": "640-702,1223-1280,1448-1491 read; targeted capability/permission searches, not whole reference", + "evidence": "official floating documentation, retrieval bytes pinned; not CLI observation" + }, + { + "id": "C-HELP", + "locator": "Claude Code 2.1.270 --help in empty env and temp HOME/config", + "cache": "/tmp/chat-00-claude-help.txt", + "sha256": "ae85d661e9c086f05637ebcd868f5702b477ff6e55e2e65b8ada7807cd51a4b6", + "coverage": "entire help; --version reported 2.1.270", + "evidence": "help/version observation only; no engine session started" + } + ] +}