Files
stack/docs/plans/chat-01/README.md
T

26 KiB

CHAT-01 draft conversation and control contracts

Issue #1507, authorized charter 26103. Darkwing authors; Filbert independently reviews; Dewey reviews UI semantics; Rocko reviews architecture. Research baseline is 370823b354400a73425b4180e07747d97ff87e7a.

R3 draft, superseding rejected R1 request 26105 and R2 request 26117. R2 findings are Filbert 26119, Dewey 26120 and Rocko's adversarial re-review via agent-send. All three accept the named companion gates below. Approval must name all four current file hashes; prior reviews do not approve changed bytes.

These are proposed contracts and synthetic reference models. They implement no endpoint, engine adapter, permission grant or live migration. Version 2 is a draft revision, not a migration of an existing Mosaic record. Q21 publication does not authorize deploying it. Only this directory's four files and scoped tracking are in scope. Strict foundation isolation remains; no host-seat exception is approved.

Record and trust boundary

The local-only draft 2020-12 schema has 24 closed record variants and 20 commands. Every declared property is required. Null means absent or unknown, not permission. IDs are opaque references, never filesystem paths, bearer credentials or proof. UTC dates receive calendar validation. Integers fit JavaScript's safe range. The example.invalid schema ID requires no network access.

Only clientRequest is untrusted client input. Its actor, authenticated channel, grant, policy and supervisor facts come from authoritative server state, never from a request header, posted record or tmux preamble. Other variants describe server records or authorized projections. Shape validity authenticates nothing. Never expose binding, grant, proof records or private blob storage wholesale. Text, filenames, Markdown, arguments and labels remain untrusted render data.

Records Meaning
binding, grant, connection Approved exact scope, source/config/engine pins, execution/cohort, current generation and fence; independent actor authority; authenticated connection incarnation
clientRequest, request, receipt Expected target and command; immutable causal admission; monotonically revised actual or uncertain outcome
catalogueItem, clientView Authorized catalogue plus safe current target, connection, view revision, observer/controller mode, supported operations/reasons and attachment limits
entry, event, cursor, page Stable history/message/block fragments, ordered stream, actor-bound opaque cursor, exact snapshot and stream watermark
draft, upload, draftListing Actor-private composer revision, private staged bytes and bounded private listing; no native delivery before Send
frozenPayload, queueItem Immutable accepted text/upload revisions and digest; queue revision, admission lineage, original actor and dispatch disposition
nativeDecision, approval Stable native decision identity and generation-specific UI projection with exact intent display, native choices and causal decider
confirmation Short-lived single-use actor/connection-incarnation/target/operation/intent confirmation
stop, cohortProof, effectReport, turnProof Recorded stop transitions, selected-cohort observations, explicit effect dispositions and native-turn reconciliation

Capabilities in this proposal are names for future reviewed grants, not changes to roles/. Missing grants, mappings, evidence, supported operations or required recording capacity refuse effects. Do not turn a fail-closed refusal into a fallback tmux paste, CLI launch or guessed source path.

Safe bootstrap, history and rendering

An authorized catalogue response includes currentView. It supplies the target needed for a first observe/takeover request without exposing the private binding. View revisions increase on refresh. Transfer, revocation and reconciliation require a fresh view. Availability intersects current grants, verified adapter support and control/fence state; request-specific prerequisites still apply. Disabled controls carry reasons. A stale view never authorizes a command. Recheck authoritative state at admission and dispatch, including stricter verified byte limits.

All connections start as observers. Catalogue creation time, engine launch time and last activity are separate nullable values. Relative Age uses last activity. Earlier conversations and branches are read-only. Opening them never resumes, forks or launches. Each conversation needs an approved project/workspace mapping; OS readability, cwd and seat labels do not establish membership.

Cursors bind actor, purpose, conversation, branch, snapshot, source epoch and expiry. Refuse unknown, foreign, expired or source-replaced cursors and retain the old view with a reconciliation indicator. Never silently switch to a newer file. History pages contain at most 100 parts and must also fit 8 MiB serialized UTF-8. Each part has at most 64 blocks; strings cap at 262144 Unicode characters. Split into continuation parts instead of clipping. Byte enforcement and renderer safety need separate implementation tests; schema character limits are insufficient.

Render Markdown/code/tool details and permitted reasoning without active HTML, unsafe URLs or terminal escape execution. Unavailable reasoning has empty text; unavailable events have empty content. The schema rejects hidden payloads under an unavailable label. This does not prove renderer safety or source redaction.

History and stream identity

Every content event carries an explicit bounded role, including message-start and final parts. Role comes from authorized native message metadata, never a request ID or guessed text. Entry and stream roles must agree for the same stable message; conflicting role or persisted-entry attribution requires reconciliation. A final event is self-describing even when no history row preceded it. Empty-history NEW user then assistant output, null causal request and reconnect overlap are tested.

entry.message and event.message are stable within the execution. A broker may allocate a transient message key before native persistence, but must preserve it when attaching a persisted entry ID. Unknown causal requests stay null. A content event cannot omit its message key merely because request and entry are unknown.

Every block carries block, fragment, lastFragment. Block ordinals start at zero per message; fragments start at zero per block and stay contiguous across entry parts. Continuations retain type and tool-call identity. Do not render a split tool invocation as several independent calls.

Final message events use updateMode:replace, exact entry/message, part and lastPart. They replace only that addressed part, not the entire message. Text and reasoning deltas append to the addressed block fragment. Tool updates replace the addressed fragment. Once authoritative final parts arrive, replace provisional content rather than appending it again. Missing parts/fragments are incomplete, never silently dropped. Preserve branch and subagent separation.

A page's streamEpoch and throughSequence describe an atomic snapshot cut. Subscribe/replay after that watermark. Deduplicate overlap; repeated event IDs must have identical bytes. A final-part overlap conflicting with the snapshot, sequence gap, unavailable replay or new epoch requires reconciliation. Do not concatenate across a gap. The source adapter must produce this atomic cut or advertise streaming as unavailable. R3 checks multipart finals, two blocks, null request correlation, duplicate delivery, page overlap and reconnect gap/epoch refusal. It does not prove a native stream producer or every delta subtype.

Keep loaded history, selection/caret/scroll and private drafts when paused or offline. Show stale/paused state and reconcile on reconnect. Native acknowledgment is not an assistant answer. Actual return-flow acceptance still requires sending and receiving NEW assistant/tool text in the selected UI without manual refresh.

Admission, dispatch and deduplication

Resolve authenticated connection and actor, current grant revision/expiry/scope, approved mapping, verified command support, configuration/trust/policy and evidence capacity. Browser Origin/Host/CSRF and private peer authentication remain required runtime gates. Nothing in these files implements authentication.

Commands assert conversation/branch/execution/generation; those assertions never choose a host process or storage path. Private drafts/uploads need their own actor-scoped grants, not existing input control. Observe can read an authorized history branch without acquiring control. Native work needs current control, except independent takeover and recovery-control acquisition described below.

Commit intent, frozen bytes and queue transition before reporting admission. The future broker must serialize dispatch, transfer, edits and cancellation. Several file renames are not a proven multi-record transaction.

Canonical fixture hashing uses UTF-8 JSON, lexically sorted object keys, preserved array order and no whitespace. Production decoding must reject duplicate keys, unpaired surrogates, non-finite numbers and unsafe integers. No text normalization or claim of full RFC 8785 conformance is made.

The client fingerprint covers target and validated command, excluding transport connection and client request ID. The durable prompt/edit operationDigest also covers the frozen payload digest, including text and complete upload revisions. Dedup keys are authenticated actor, approved conversation and client request ID. An exact reconnect retry returns the existing receipt, without requiring that the new connection is controller and without redispatch. Conflicting content refuses. Resolve referenced revisions against the original immutable admission on retry, not the latest mutable draft. Delivery-unknown is never permission to replay. The checker models a single receipt lookup and derives its current queue outcome when available, including recovery after takeover. It does not implement a durable multi-request index or monotonically persisted receipt revisions.

Dispatch resolves queueItem and its stored admission, then rechecks CURRENT grant/policy/target/controller/fence and the frozen digest/revision. New client commands require a connected authenticated channel. The scheduler instead uses the authenticated durable admission and retained execution/controller claim; ordinary browser disconnect alone does not pause already broker-acknowledged queued work. Broker acknowledgment means durable admission, not native acceptance. Native acknowledgment occurs later and never grants replay. Revocation, grant expiry, generation change or fences still refuse dispatch.

A pre-dispatch refusal moves a still-queued item to dispatch-refused, stores failureReason and emits queue-changed. It is not eligible for another scheduler attempt. Existing recovered/terminal dispositions are retained. Reconciliation returns that actual state, not a stale queued receipt. Frozen content remains preserved; no automatic re-admission or native replay is inferred. Missing or altered frozen content refuses. A later mutable source-draft revision is orthogonal and does not invalidate an unchanged admitted snapshot. An edit creates a new immutable admission and queue revision; original admission and payload stay unchanged. Per-item cancel affects only that item. Bulk recovery classifies all selected snapshots before committing dispositions. Valid content becomes recovered drafts; missing/corrupt content becomes recovery-failed with failureReason and queue-changed evidence. No text is invented from damaged bytes. Control transfer and cancellation must still succeed. Stop records list successful queueDrafts separately from queueFailures. These failed items preserve their references for authorized repair/readback, never automatic resend.

Commands and private composer lifecycle

All twenty commands are closed unions; no raw RPC/shell/session/provider/permission command is accepted. Leading slash text, including leading whitespace, receives text-policy refusal in the model. Complete slash/resource suppression and native command interpretation still block CHAT-03 under B3.

Commands Required authority and transition
observe observe plus authorized source/cursor; no launch/control
create-draft, update-draft, discard-draft, list-drafts draft grant; actor/conversation/branch ownership; unsent state and revision CAS for mutations
begin-upload, append-upload, complete-upload, discard-upload upload grant; private recipient scope, revision and committed offset CAS; verify staged bytes/digest before completion
issue-confirmation, answer-confirmation confirm grant; issuance also needs the selected destructive-operation grant; actor, connection incarnation, target, expiry and pending-state checks
prompt send/current controller; own exact unsent draft and valid uploads; freeze and queue, no blind interrupt
edit-queued, cancel-queued send/current controller; owned queued item and exact revision; refuse dispatch races
takeover independent take-control plus observe, active/open binding, expected generation; refuse self-takeover
acquire-recovery-control independent recover-control plus observe, closed admission, absent/disconnected/revoked prior controller and exact confirmation
approval approve/current controller, pending stable native decision, current projection/intent and enabled exact supported choice
interrupt interrupt/current controller; fence, recover undispatched input, request native cancellation and reconcile
force-stop force-stop/current controller and exact confirmation; allowed from active/stopping/uncertain even when work admission is closed
recover recover/current controller, exact confirmation and stopped prior cohort/effects proof; eligibility only, no launch

Draft creation/editing and upload staging emit no native input. Reload retrieves only the authenticated actor's drafts. Private cursor/large-list support is held by the companion below; the model refuses rather than silently truncating. Failed upload or prompt admission must retain the original text and bytes. The fixture uses synthetic in-memory bytes, not tested durable storage or disk-full recovery.

At most 10 attachments, 20 MiB/file, 100 MiB/message, narrowed by verified harness limits. limits.verified:false disables attachment acceptance in a real consumer; ceiling values alone are not evidence of native support. The miniature upload model tests ownership, byte count, digest, offset, storage capacity and refusal under unknown/stricter synthetic limits, not MIME sniffing or actual native limits. Chunks cap at 256 KiB. A filename is display text only. Never extract/execute uploads or put private staging in an agent's workspace before Send. No implicit TTL removes unsent drafts or uploads.

Uploads progress receiving -> staged, then transferring/delivered/sent where a verified adapter requires that path. Transfer acknowledgment must bind recipient, incarnation and bytes. A posted acknowledgment ID proves nothing. Frozen uploads cannot be discarded out from under an admitted payload. Preview/download access is NOT authorized by a privateBlobRef.

Explicit companion dependencies, not hidden endpoints

R3 is not a complete deployable UI API. The following are named prerequisite contracts, not authorized implementation tasks or additional queue starts:

  • CHAT-01C private readback/content companion must define authenticated attachment preview/download ranges, safe public upload projections, durable queue/receipt/confirmation restoration, and actor-bound paginated private-state snapshots/cursors. Until reviewed, these controls remain unavailable. A small own-draft listing fixture does not claim complete browser/server restart recovery. The UI must not invent a side API or enable durable Send acceptance before this dependency is disposed.
  • CHAT-03I ingress/handoff companion must resolve board Reply, agent-send/tmux, native terminal/internal queued input and launcher/supervisor restart. R3's disposition for unmediated input is unmediated-ingress refusal, not successful message delivery. There is no deliver-message operation or authenticated peer sender mapping here. Jason's ruling and a bounded reviewed charter are still required before changing fleet communications. All-seat control acceptance is blocked until this conflict is resolved. Existing live seats are untouched.
  • CHAT-03D native dialog companion must map native confirm/select/input/editor forms without pretending that every extension dialog is a tool permission. They are explicitly unsupported in this draft. No generic yes/no substitution or hidden text-entry endpoint is allowed.

These dependencies belong on subsequent CHAT task charters before implementation. Their names do not authorize CHAT-02/03, other queue rows, live work or policy grants.

Control loss, approvals and stopping

Takeover atomically increments generation, makes the old controller an observer, recovers only undispatched work and reprojects pending decisions. It does not interrupt active work. Recovered drafts retain original actor, attachments and lineage. A different actor cannot read/adopt them by taking control. Same-actor interfaces may explicitly resend after reconciliation; never resend automatically.

Disconnect does not release execution ownership, cancel work, allow a pending decision or infer denial. Revocation closes affected ingress, clears the revoked controller reference, increments generation and publishes control transfer. Grant revocation/expiry must trigger the same ingress reconciliation in production; the server fixture explicitly exercises connection revocation. Missing that event producer is a runtime blocker, not permission to retain unsafe control.

Ordinary takeover is unavailable while fenced. Separately authorized recovery-control acquisition can bind an observer when the previous controller is gone. It increments generation without reopening admission, releasing the execution claim or launching. After reconnection, destructive confirmation must be issued and answered again; confirmation binds connection generation, target, operation and stop context and is single-use. The digest includes the selected recovery stop or current predecessor stop for force-stop. Changing that context requires a new confirmation. Self-takeover is refused rather than becoming a queue-cancel shortcut.

An active/open controller revocation creates a revocation-mode fence record with the revoked connection and no fabricated client request. A replacement controller may acquire recovery-control, then request server-origin reconciliation. A trusted turnProof must establish cleared native input, no revoked pending input, valid current control and re-projected pending approvals or evidenced terminal decisions. Use turnState:input-reconciled and approvalDisposition:pending-reprojected for this path; the active turn need not be interrupted. The server records control-reconciled and reopens admission without killing/restarting the engine. Already fenced Interrupt/Force-stop paths retain their existing stop and proof requirements. Grant revocation/expiry producers must use the equivalent protocol.

nativeDecision retains nativeRequest/toolCall/intent/policy identity across control transfer. Each takeover creates a NEW approval projection ID with the same decision reference and unchanged intent display. Old projections become superseded. Dedupe native responses by stable native decision/request identity, not projection ID. resolvedBy links to a causal request/actor even after takeover. Once a decision is terminal, an old projection cannot resolve it again.

The approval includes bounded safe intent text before the tool transcript exists. If the full authorized intent cannot be displayed, do not enable approval. Render exact native labels. Permission-widening choices remain disabled with a reason. Non-permission forms are explicitly blocked by CHAT-03D. A cancel choice is enabled only with an independently verified adapter mapping proving non-allow semantics; Pi select cancellation cannot inherit confirm cancellation's semantics. Claude's permission callback has no invented cancel response.

A local deadline makes an unresolved decision uncertain until native evidence establishes timeout/cancellation/resolution. Interrupt/Force stop likewise mark pending approvals uncertain, not automatically denied or cancelled. Reconcile native outcomes before reopening. Native denial remains authoritative. No tool callback substitutes for strict tool isolation. A turnProof that settles an uncertain decision must include decisionOutcomes with nativeEvidence. Native resolution with no representable client choice uses resolved-natively, not an unexplained resolved state. chosen may then be null, but native evidence may not. This does not invent a human resolvedBy actor for a native timeout or decision.

Interrupt creates a stop record, closes dispatch and recovers queued drafts. It may advance fenced -> cancelling -> uncertain. A trusted turnProof tied to that stop/cohort must establish interrupted turn, cleared native queue with no remaining IDs, native approval disposition and recorded effects. Then publish reconciled with the stop reference, mark turn-interrupted and reopen only under current valid control/trust/policy. Before this transition, prompt and ordinary takeover refuse. If cancellation hangs, separately confirmed Force stop remains available.

For Pi, documented clear_queue precedes abort; returned text cannot reconstruct attachments. Claude cancel_queued is capability-gated; SDK interrupt() alone is insufficient. Neither adapter is proven by these fixtures.

Force stop creates a new stop record referencing its predecessor, fences input, then records fenced -> cancelling/stopping -> uncertain/stopped. A successor marks its unfinished predecessor superseded, a terminal presentation state. Immediate trusted death/effect evidence may move fenced directly to stopped; intermediate signal observations are not mandatory invented events. Only trusted cohort/effects observations may promote stopped, and the binding cannot lead its stop record. SIGTERM, EOF, abort acknowledgment or idle does not prove death.

cohortProof binds stop, authority, conversation/execution/cohort and membership epoch; it includes complete membership, boot/pid/start identity, per-member death time, observation time and verification digest. effectReport lists invocation dispositions with evidence. Recovery derives no-live-members and reconciled effects from these records, not world booleans. The fixture's trusted digest registry stands in for an authenticated supervisor evidence store. Matching a self-posted hash is NOT trust; a real producer/verifier and complete cohort containment remain B3/B4. External effects can remain explicitly uncertain; killing never proves rollback.

At most one non-stopped binding may hold a conversation or approved native-session identity. Active/reserved duplicates refuse already-active; uncertain/stopping or unproven stopped replacements refuse unsafe-replacement. Recovery needs the same conversation/branch/config pins, retained/reacquired exclusive claim, current authority and confirmation. It only reports eligibility for separately authorized Resume. Browser/server crash, lost acknowledgment or selecting history cannot start another engine. The actual execution/writer-claim record is deferred to CHAT-02; the single-binding fixture models its assumed retained identity, not a claim store. No private-host command is admitted before Q22/CHAT-04R.

Verification and retained gates

node --check docs/plans/chat-01/check.mjs
node docs/plans/chat-01/check.mjs
node docs/plans/chat-00/check.mjs

Node plus installed Python jsonschema 4.26.0, no install/network. Missing validator or invalid schema fails closed. R3 currently tests 98 shapes, 322 required-field omissions, 76 reference cases and seventeen named lifecycle sequences, plus per-item recovery, immutable queue-edit, bootstrap and multipart-stream regressions. These are finite synthetic examples, not comprehensive model checking or runtime proof.

Explicit model limits accepted for bounded review:

  • Unmediated-ingress refusal is an asserted CHAT-03I disposition, not a modelled ingress path. It is excluded from lifecycle counts. Refusal names are bounded but unregistered draft IDs; no closed public reason-code registry is claimed.
  • Control transfer rechecks grants/mapping/recording and verified operation support, but intentionally does not require native preflight. It is bookkeeping only; reprojecting an approval never sends a native response. Native actions still gate.
  • Leading slash text is refused. Other prefixes such as ! and @, and slash commands on later lines, remain admitted unchecked by the miniature text model. Their native interpretation is unverified and held under B3, not declared safe.
  • publicView uses a separate illustrative predicate which can drift from admission, including native preflight details. It is advisory, never authority. A production view must derive availability from shared authoritative eligibility rules; these fixtures do not prove complete UI refusal-reason coverage.

Admission fixtures assume authenticated channels and verified operation registries. They do not produce those facts. Native byte limits, permissions, trusted signing, durable transactions, full cursor/readback protocols, all delta/dialog forms, process observation and actual UI behavior remain unverified. Never report a passing synthetic fixture as security enforcement or all-seat acceptance.

B1/B2 retain exact Pi/Claude branch/image/protocol, trust/settings and tool-isolation proof. B3/B4 retain every-ingress fencing, crash recovery, approved access mappings and supervisor evidence. B5/Q22 retain piece-5/#1508/fleet ordering. B6 requires a separately approved cutover/rollback envelope for each live target. #1507 remains open for the all-seat demonstration and Jason's workday ruling.