docs: publish reviewed CHAT-01C private readback contracts (#1507)
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
# CHAT-01C private readback and recovery contracts
|
||||
|
||||
R2 draft candidate for independent review, not an implemented API.
|
||||
R1 request 26137 received REQUEST CHANGES from Filbert and Dewey via agent-send.
|
||||
Both complete verdicts were collected before revision; R1 remains archived at
|
||||
`/tmp/chat-01c-r1-frozen-yvsfowd1`.
|
||||
|
||||
Jason approved the contract-only proposal in #1507 comment 26131 in the
|
||||
control-board conversation and required performing-agent attribution. Darkwing
|
||||
authors these four files. Filbert independently reviews; Dewey reviews consumer
|
||||
semantics. No earlier CHAT-01 approval applies to this candidate.
|
||||
|
||||
Baseline: reviewed CHAT-01 commit `28d4e98ad8b406ca84b30170558bee22402f1e55`.
|
||||
Those files remain unchanged. This companion adds private requests/projections;
|
||||
it does not widen the base command parser, grants or native capabilities.
|
||||
|
||||
## Boundary and attribution
|
||||
|
||||
Only this directory's README, schema, fixtures and checker are authored, plus
|
||||
scoped shared tracking and append-only logs. No runtime endpoints, engine
|
||||
adapters, native sessions, live access, policy changes or deployments. CHAT-02
|
||||
and CHAT-03I/03D retain their separate charters and unresolved gates.
|
||||
|
||||
Darkwing's seat credentials were verified to authenticate as Gitea `darkwing`,
|
||||
account 104. Use that identity for issue actions. Do not use the legacy API
|
||||
helper's default Jason credentials. Future commits explicitly use the performing
|
||||
agent as author and committer, without changing shared Git defaults. Existing
|
||||
published history is not rewritten. No credential value belongs in these files.
|
||||
|
||||
## Closed messages and authority
|
||||
|
||||
The draft 2020-12 schema defines six closed top-level message variants and five
|
||||
closed private item projections. Every property is required. Requests alone use
|
||||
`privateClientRequest`; the other variants are server responses, not client input.
|
||||
Validate that request definition, not the union of requests and responses, at
|
||||
untrusted ingress. Version 1 identifies this new proposal, not a live migration.
|
||||
|
||||
IDs are opaque bounded references, never paths or authority. The server resolves
|
||||
connection, actor, current grant, approved scope and storage epoch. A client cannot
|
||||
post an actor, grant, snapshot contents or private storage reference. Native
|
||||
controller ownership is not required for reading one's own data or explicitly
|
||||
recovering one's own refused input. Separate read-private/recover-private grants
|
||||
are proposed names only; no role file or actual grant is changed here.
|
||||
|
||||
Every operation requires connected authenticated ingress, active/unexpired current
|
||||
actor grant, approved conversation/scope and the relevant capability. The model's
|
||||
scope digest binds the full inherited host/seat/project/workspace/conversation
|
||||
scope. These are supplied synthetic facts, not an authentication implementation.
|
||||
Browser origin/CSRF, private channel identity, storage provenance and actual grant
|
||||
producers remain prerequisite runtime work. Current authority is checked on every
|
||||
page, range, preview and recovery, including an idempotent retry.
|
||||
|
||||
| Command | Behavior |
|
||||
|---|---|
|
||||
| read-private-state | Bounded actor-private snapshot page; optional exact opaque cursor |
|
||||
| read-upload-range | Exact owned immutable upload revision and verified byte range |
|
||||
| preview-upload-text | Bounded complete UTF-8 preview as literal text, never active content |
|
||||
| recover-refused-draft | Explicit CAS recovery of own never-dispatched refused input into a private unsent draft |
|
||||
|
||||
Refusals use the closed `privateRefusal` reason set. The reference functions return
|
||||
symbolic `refused:reason` strings internally; `response()` demonstrates the typed
|
||||
wire projection. Refusal is not permission to change source or retry native input.
|
||||
There are no hidden writes, engine commands or automatic resume operations.
|
||||
|
||||
## Safe private-state restoration
|
||||
|
||||
The page includes authenticated actor, conversation, opaque snapshot ID, source
|
||||
revision, items and next cursor. Items contain only explicit allowlisted fields:
|
||||
|
||||
* Draft: original branch, text, upload IDs, revision, state and recovery lineage.
|
||||
* Upload: display filename/media type, total and committed received byte counts,
|
||||
full digest, revision and state.
|
||||
* Queue: revision, actual state, causal admission request, original draft, exact
|
||||
target and frozen payload digest. Bounded frozen input includes original branch,
|
||||
draft revision, text and immutable upload IDs/revisions/filenames/sizes/digests.
|
||||
* Receipt: causal request, monotonic source revision, actual/uncertain state/reason.
|
||||
* Confirmation: exact target, operation, stop context, expiry and display state.
|
||||
|
||||
No privateBlobRef, source path, credential, grant or arbitrary internal property is
|
||||
serialized. Filenames and media labels remain untrusted display data. This is
|
||||
actor-private restoration, not a shared transcript or permission to adopt another
|
||||
actor's drafts. Reading any earlier branch never changes the active branch.
|
||||
|
||||
Queue and receipt request fields reference the same causal admission. Queue draft
|
||||
and target fields preserve the original lineage and branch; never join pending
|
||||
input to a newer mutable draft's text. Frozen input resolves by immutable digest,
|
||||
not the current mutable queue/draft contents. Missing/corrupt or mismatched retained
|
||||
content yields frozenInput null and integrity-unavailable, without inventing text
|
||||
or changing actual queue status. No private blob reference is projected. Snapshot
|
||||
retention must preserve referenced immutable payloads for valid cursors.
|
||||
|
||||
For every snapshot page, resolve each confirmation's CURRENT authoritative record
|
||||
by scoped actor/conversation/ID. Overlay current consumed/cancelled/expired outcomes.
|
||||
Do not promote an old snapshot to more permissive authority. Missing or changed
|
||||
current revision, operation, connection/incarnation, target, expiry or stop context
|
||||
makes the pending/confirmed snapshot reconfirm-required. A changed current stop
|
||||
invalidates it even when connection and execution target did not change.
|
||||
An old connection/incarnation or changed target makes a pending/confirmed
|
||||
confirmation display reconfirm-required. Expiry is explicit. Consumed/cancelled/
|
||||
expired terminal outcomes are preserved rather than relabelled by reconnect.
|
||||
Reading a confirmation never renews it or creates a replacement. The base control
|
||||
protocol still validates exact current target/stop context and single use.
|
||||
The miniature clock and projected confirmation expiry use UTC epoch milliseconds.
|
||||
Production normalization must convert authoritative timestamps without guessing.
|
||||
|
||||
### Pagination and snapshot rules
|
||||
|
||||
Capture the authorized private records at one atomic cut. Sort by kind and opaque
|
||||
ID; the cursor retains the exact immutable snapshot and next offset. A page has
|
||||
at most 100 items and 8 MiB serialized UTF-8, whichever is reached first. Reduce
|
||||
the number of items to fit; do not truncate text or silently omit records. An
|
||||
individual over-budget item refuses projection-size. Field limits keep valid
|
||||
individual draft projections below the page ceiling.
|
||||
|
||||
A cursor binds actor, full scope digest, conversation, private-state purpose,
|
||||
source epoch/revision, expiry, snapshot ID and offset. Only its opaque ID crosses
|
||||
the client boundary. Expiry equality is expired. Unknown/foreign/stale cursors
|
||||
refuse; never follow an alternate file or substitute a newer snapshot. Validate
|
||||
snapshot record ownership again before projection. Expiry affects the cursor,
|
||||
not the retained draft or upload. Cursor retention needs bounded storage/quota
|
||||
and cleanup in the eventual implementation; none is provided by this model.
|
||||
|
||||
Here sourceRevision means replacement of the private store/source identity, not
|
||||
every ordinary item edit. Per-item revisions track mutations. An existing snapshot
|
||||
retains its old content while mutable source items advance. Current authorization
|
||||
and confirmation actionability are rechecked at read time and can become stricter
|
||||
than the snapshot; they never inherit an old grant's authority.
|
||||
|
||||
After browser/server restart, reconstruct drafts, queue/receipts and confirmations
|
||||
from the durable private index. Preserve delivery-unknown; absent native evidence
|
||||
never becomes unsent. The JSON round-trip fixture tests reference-state readback,
|
||||
not disk durability, transactions or authenticated reconnection. Full persistence,
|
||||
source-epoch management, grant revocation and race injection remain runtime gates.
|
||||
|
||||
## Attachment access and preview
|
||||
|
||||
Resolve only an owned upload in the approved recipient scope. Require exact
|
||||
revision and a completed staged/delivered/sent record. Refuse receiving, rejected,
|
||||
discarded, foreign or missing uploads. Check stored bytes against the complete
|
||||
length and SHA-256 before returning any range. A posted filename, size, media type
|
||||
or blob reference is not evidence. Missing/corrupt bytes refuse integrity.
|
||||
|
||||
Ranges are explicit offset/length, at most 256 KiB, wholly within a file of at most
|
||||
20 MiB. This contract refuses overrun instead of silently clamping. Responses give
|
||||
actual offset/length/total, full digest, range digest and canonical base64 bytes.
|
||||
They specify attachment disposition and application/octet-stream. Transport must
|
||||
use authenticated JSON/binary responses with nosniff; never reflect an uploaded
|
||||
media label into active HTML or an executable navigation response. Complete byte
|
||||
assembly must match the advertised file digest before being called a download.
|
||||
|
||||
Text preview accepts only a complete file of at most 256 KiB that decodes as strict
|
||||
UTF-8. It escapes non-display controls, escape characters and directional controls
|
||||
into visible Unicode escape notation. HTML/Markdown remains literal text. The
|
||||
response explicitly requires textContent-only rendering. It does not authorize
|
||||
innerHTML, Markdown execution, terminal escapes, SVG documents or embedded scripts.
|
||||
The original bytes/digest remain unchanged; preview is a separate display value.
|
||||
Invalid UTF-8 or over-budget content refuses preview without truncating it. Other
|
||||
formats remain downloadable; image/document rendering needs the separately reviewed
|
||||
CHAT-05 decoder/renderer behavior. The reference checks do not prove browser safety.
|
||||
|
||||
### Receiving upload restoration
|
||||
|
||||
Upload projections retain authoritative receivedBytes and revision even while
|
||||
receiving; ranges/preview still refuse incomplete uploads. After a lost append
|
||||
acknowledgment or restart, obtain a FRESH private-state snapshot, not an old cursor's
|
||||
progress, before constructing another append. Reselect the original local file
|
||||
explicitly and match full byte count and digest, never filename alone. Do not
|
||||
persist or guess a browser-local file path. The reference resumeOffset check gives
|
||||
the restored revision/offset only after that match. No file, wrong bytes or invalid
|
||||
progress means no append; preserve the staging record and let the actor explicitly
|
||||
select matching bytes or discard/start a new upload through the base lifecycle.
|
||||
|
||||
Construct the existing CHAT-01 append command using that revision and committed
|
||||
offset. Its CAS must reject progress that advanced again after readback. If all
|
||||
bytes are received but state is still receiving, use explicit completion rather
|
||||
than append duplicates; full server digest verification still gates staged state.
|
||||
The lost-ack fixture starts with a GIVEN authoritative committed append result,
|
||||
then tests readback and local-file matching. It does not prove the append producer,
|
||||
disk durability, native delivery or a live browser's file picker.
|
||||
|
||||
No extraction, execution, native delivery, workspace staging or remote forwarding
|
||||
occurs from readback or preview. These operations expose only already authorized
|
||||
private content to its actor, not to the agent engine before Send.
|
||||
|
||||
## Recovering refused input without replay
|
||||
|
||||
This closes the private recovery path tracked as R3-2 in #1507 comment 26128.
|
||||
Eligibility is deliberately narrow: owned item, state dispatch-refused, exact item
|
||||
revision and payload digest, and null nativeRequest. Dispatched, acknowledged,
|
||||
working, delivery-unknown, finished and recovery-failed items are refused. The
|
||||
trusted queue journal must establish that dispatch-refused means no native attempt;
|
||||
this model assumes that upstream fact and does not reconstruct it from a timeout.
|
||||
|
||||
Verify the retained CHAT-01 frozen payload with its existing canonical digest,
|
||||
including complete upload revisions/digests and original branch. Verify current
|
||||
owned upload revisions, recipient scopes and actual retained bytes too. A missing,
|
||||
changed or corrupt payload/upload leaves the queue and original data intact with
|
||||
an integrity refusal. Never fabricate a draft from partial metadata or returned
|
||||
native text. The fixture copies a synthetic frozen example from the published
|
||||
baseline and computes the same sorted-key, UTF-8 digest excluding its digest field.
|
||||
|
||||
One idealized transaction creates a NEW private unsent draft, preserves actor,
|
||||
branch, exact text and upload references, records recoveredFrom, advances the queue
|
||||
revision and marks recovered-as-draft. Original immutable payloads remain intact.
|
||||
No native outbox entry is emitted. The draft still needs explicit Send and normal
|
||||
current controller/branch/grant checks under CHAT-01.
|
||||
|
||||
Recovery dedup is scoped to authenticated actor, conversation and client request ID,
|
||||
encoded as a canonical JSON tuple, with canonical command fingerprint. Delimiter
|
||||
concatenation is forbidden: opaque IDs may themselves contain colons. The shared
|
||||
ledger fixture tests colliding colon spellings in two independently authorized
|
||||
conversations, legitimate distinct recoveries and conflicting reuse. Reconnect/restart retry returns the original
|
||||
result without creating another draft. Conflicting reuse refuses. A fresh request
|
||||
ID cannot recover an already recovered item. The model persists the idealized
|
||||
receipt in its synthetic state; actual multi-record crash atomicity is not proved.
|
||||
|
||||
R3-1, reconciliation of dispatched-but-unconsumed native input after fencing,
|
||||
belongs to CHAT-02 adapter evidence. It is NOT made recoverable by this companion.
|
||||
|
||||
## Evidence and completion gates
|
||||
|
||||
Run without installs or network:
|
||||
|
||||
```sh
|
||||
node --check docs/plans/chat-01c/check.mjs
|
||||
node docs/plans/chat-01c/check.mjs
|
||||
node docs/plans/chat-01/check.mjs
|
||||
node docs/plans/chat-00/check.mjs
|
||||
```
|
||||
|
||||
The checker uses Node and installed Python jsonschema. Missing validation fails.
|
||||
R2 validates eleven closed request/response examples and 187 omission/extra-field
|
||||
refusals, plus the reference scenarios. New regressions cover terminal/stop changes
|
||||
between confirmation pages and after restart, colon-ID cross-conversation dedup,
|
||||
two immutable queue inputs with distinct branches and receipt/draft joins despite
|
||||
mutable draft edits, and receiving-offset restoration after a lost append reply.
|
||||
It checks closed request/response examples, required-field/extra-field refusals,
|
||||
private filtering, cursor actor/scope/purpose/source/expiry, immutable pagination,
|
||||
synthetic restart, confirmation restoration, byte ranges/digests, literal preview,
|
||||
corrupt upload/payload refusal, immutable recovery, repeated request identity and
|
||||
no native replay. It does not implement HTTP, real authentication, storage quotas,
|
||||
durable transactions, every record mutation, native adapters or UI rendering.
|
||||
|
||||
Independent exact-candidate approval by Filbert and Dewey is required before Q21
|
||||
scoped publication. Existing B1-B6, CHAT-02/03I/03D, Q22, live-cutover and all-seat
|
||||
acceptance gates remain. #1507 stays open. No new queue item starts because this
|
||||
draft exists.
|
||||
Reference in New Issue
Block a user