@earendil-works/pi-durable 0.87.0 → 0.87.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +29 -0
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/{memory-storage.d.ts → storage/memory.d.ts} +12 -6
  7. package/dist/storage/memory.d.ts.map +1 -0
  8. package/dist/{memory-storage.js → storage/memory.js} +236 -24
  9. package/dist/storage/memory.js.map +1 -0
  10. package/dist/storage/sqlite/database.d.ts +26 -0
  11. package/dist/storage/sqlite/database.d.ts.map +1 -0
  12. package/dist/storage/sqlite/database.js +2 -0
  13. package/dist/storage/sqlite/database.js.map +1 -0
  14. package/dist/storage/sqlite/index.d.ts +4 -0
  15. package/dist/storage/sqlite/index.d.ts.map +1 -0
  16. package/dist/storage/sqlite/index.js +3 -0
  17. package/dist/storage/sqlite/index.js.map +1 -0
  18. package/dist/storage/sqlite/migrations.d.ts +11 -0
  19. package/dist/storage/sqlite/migrations.d.ts.map +1 -0
  20. package/dist/storage/sqlite/migrations.js +104 -0
  21. package/dist/storage/sqlite/migrations.js.map +1 -0
  22. package/dist/storage/sqlite/node.d.ts +25 -0
  23. package/dist/storage/sqlite/node.d.ts.map +1 -0
  24. package/dist/storage/sqlite/node.js +97 -0
  25. package/dist/storage/sqlite/node.js.map +1 -0
  26. package/dist/storage/sqlite/storage.d.ts +45 -0
  27. package/dist/storage/sqlite/storage.d.ts.map +1 -0
  28. package/dist/storage/sqlite/storage.js +524 -0
  29. package/dist/storage/sqlite/storage.js.map +1 -0
  30. package/dist/types.d.ts +113 -34
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js.map +1 -1
  33. package/docs/pico-v5-chord-usage.md +64 -63
  34. package/docs/pico-v5-handoff.md +92 -57
  35. package/docs/pico-v5.md +519 -350
  36. package/package.json +20 -3
  37. package/dist/memory-storage.d.ts.map +0 -1
  38. package/dist/memory-storage.js.map +0 -1
@@ -12,8 +12,8 @@ This guide uses the contracts in the [Pico5 specification](pico-v5.md).
12
12
  - **Durable document:** JSON persisted as a complete base, Chord operations, and
13
13
  checkpoints. Its definition token declares schema, version, scope, and initialization;
14
14
  conversation scope also declares history and fork behavior.
15
- - **Transaction draft:** the mutable tracked object from `await tx.doc`, valid
16
- only inside that commit callback, including all nested objects.
15
+ - **Transaction draft:** the revocable copy-on-write object from `await tx.doc`,
16
+ valid only inside that commit callback, including all nested objects.
17
17
  - **DocumentSource:** an opaque handle to one document incarnation's committed changes,
18
18
  not a mutable value or a public subscription API.
19
19
  - **ReplicatedState:** Chord's immutable complete values through `value` and
@@ -32,36 +32,30 @@ Examples build on one another. Pico5 names (`defineDoc`, `defineDocFamily`,
32
32
  refer to normative contracts, without a specified import path or runnable Pico5
33
33
  package. In those contracts, `ConversationRecord`, `EntryRecord`, and
34
34
  `TaskRecord` are persisted records, while `Conversation` is the public
35
- conversation object and `Entry`/`Task` are typed definitions. These Chord imports
36
- exist today:
35
+ conversation object and `Entry`/`Task` are typed definitions. The Chord imports
36
+ below are the concrete APIs used by the examples:
37
37
 
38
38
  ```ts
39
39
  import {
40
40
  createFacetHost, createRemoteServiceBinding, defineFacet, defineService,
41
+ replicatedState,
41
42
  type Context, type Facet, type JsonValue, type RemoteServiceTransport,
42
43
  type ReplicatedState,
43
44
  } from "@earendil-works/chord";
44
45
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
45
46
  import { applyImmutable } from "@earendil-works/chord/delta";
46
47
 
47
- // The spec calls this JsonObject; current Chord exports JsonValue, not JsonObject.
48
+ // The Pico specification calls this object-root constraint JsonObject.
48
49
  type JsonObject = { [key: string]: JsonValue };
49
-
50
- // Required by Pico5, but not a current Chord export yet.
51
- declare function documentReplicatedState<T extends JsonObject>(
52
- source: DocumentSource<T>, context: Context,
53
- ): Promise<{ readonly state: ReplicatedState<T | null>; dispose(): void }>;
54
50
  ```
55
51
 
56
- The adapter must register Chord-recognized state, atomically hydrate a matching
57
- committed value and adapter-owned delivery sequence, and forward committed ops without another
58
- tracker or re-diff. Disposal releases observation, not the document. Even Chord's
59
- hydration requests must never flush an uncommitted draft.
60
-
61
- Current `env.replicatedState(initial)` creates an **in-memory**, immutable
62
- latest-value source with transaction-scoped `.change(context, callback)` and
63
- `.replace(context, value)`, not a durable source adapter. There is no
64
- `replicatedState.own(source)`. Do not mirror documents into another state source.
52
+ Chord adoption atomically captures a committed snapshot, buffers every later
53
+ source frame, installs the frame listener, and drains the buffer before returning.
54
+ Operations already reflected in the snapshot are never redelivered. It forwards
55
+ committed values and operations without another tracker or re-diff. Disposing the
56
+ adopted state releases observation, not the document. A source-backed state is
57
+ publication-only; Pico remains the sole mutator. Even hydration must never prepare
58
+ or publish an uncommitted draft.
65
59
 
66
60
  ## 1. A Session-wide canvas
67
61
 
@@ -86,20 +80,23 @@ interface CanvasService {
86
80
  const Canvas = defineService<CanvasService>("app.canvas");
87
81
 
88
82
  async function createCanvasFacet(session: Session, context: Context): Promise<Facet> {
89
- const target = { scope: "session" } as const;
90
- // Get-or-create: an absent canvas is initialized in a serialized commit.
91
- const source = await session.documentSource(CanvasDoc, target, context);
92
- const replica = await documentReplicatedState(source, context);
83
+ // Creation is explicit; observation never writes.
84
+ await session.commit(async tx => {
85
+ await tx.doc(CanvasDoc);
86
+ }, context);
87
+ const source = await session.documentSource(CanvasDoc, context);
88
+ if (source === undefined) throw new Error("canvas was retired during setup");
93
89
  return defineFacet({
94
90
  id: "app.canvas/session",
95
91
  setup(env) {
96
- env.own(() => replica.dispose());
92
+ const state = replicatedState(source);
93
+ env.own(() => state.dispose());
97
94
  env.provide(Canvas, {
98
- state: replica.state,
95
+ state,
99
96
  async addStroke(stroke, context) {
100
97
  await session.commit(async tx => {
101
- const draft = await tx.doc(CanvasDoc, target);
102
- draft.strokes.push(stroke); // The draft now owns stroke; caller must not mutate it.
98
+ const draft = await tx.doc(CanvasDoc);
99
+ draft.strokes.push(stroke); // Chord copies the assigned stroke by value.
103
100
  }, context);
104
101
  },
105
102
  });
@@ -133,7 +130,8 @@ async function runCanvasExample(session: Session): Promise<void> {
133
130
  }
134
131
  ```
135
132
 
136
- The application owns the open Session. Acquire state **before** synchronous
133
+ The application owns the open Session. Acquire the document source
134
+ asynchronously before synchronous `setup`, then adopt it synchronously during
137
135
  `setup`; `env.provide` cannot run in `onActivate`. Install one provider per
138
136
  Session host. Chord may reload presentation facets independently, but v1 does
139
137
  not use facet reload to replace Session-side task or hook implementations. A
@@ -166,7 +164,7 @@ async function connectCanvas(transport: RemoteServiceTransport, context: Context
166
164
  ```
167
165
 
168
166
  ```text
169
- first acquisition -> commit base { strokes: [] }
167
+ explicit first tx.doc -> commit base { strokes: [] }
170
168
  addStroke(A) -> commit A -> local and remote subscribers see A
171
169
  worker restarts -> open same durable storage; install canvas facet again
172
170
  acquire source -> load base + committed deltas, without rerunning initial()
@@ -181,7 +179,7 @@ A method's successful commit does not promise every remote callback has run yet.
181
179
 
182
180
  A **document family** uses one definition for many instances. The logical key is
183
181
  `(kind, conversationId, key)`; the persisted numeric document ID identifies an
184
- incarnation and is never reused. Initializer input is not part of the key.
182
+ incarnation and is never reused. The creation seed is not part of the key.
185
183
 
186
184
  ```ts
187
185
  type ReviewInput = { path: string; patch: string };
@@ -190,7 +188,7 @@ type ReviewState = ReviewInput & { comments: ReviewComment[] };
190
188
  const ReviewDoc = defineDocFamily<ReviewState, ReviewInput>({
191
189
  kind: "app.diff-review", version: 1, family: true, scope: "conversation",
192
190
  history: "latest", fork: "current",
193
- initial: input => ({ path: input.path, patch: input.patch, comments: [] }),
191
+ initial: seed => ({ path: seed.path, patch: seed.patch, comments: [] }),
194
192
  checkpointWhen: value => value.comments.length % 50 === 0,
195
193
  });
196
194
  interface DiffReviewService {
@@ -202,7 +200,7 @@ const DiffReviews = defineService<DiffReviewService>("app.diff-reviews");
202
200
 
203
201
  function reviewFacet(
204
202
  session: Session, conversationId: Id,
205
- reviews: readonly { key: string; initial: ReviewInput }[], context: Context,
203
+ reviews: readonly { key: string; seed: ReviewInput }[], context: Context,
206
204
  ): Facet {
207
205
  return defineFacet({
208
206
  id: "app.diff-reviews/session",
@@ -210,18 +208,21 @@ function reviewFacet(
210
208
  const instances = env.provideMany(DiffReviews);
211
209
  env.onActivate(async () => {
212
210
  for (const review of reviews) {
213
- const target = { scope: "conversation" as const, conversationId, ...review };
214
- const source = await session.documentSource(ReviewDoc, target, context);
215
- const replica = await documentReplicatedState(source, context);
216
- env.own(() => replica.dispose());
211
+ await session.commit(async tx => {
212
+ await tx.doc(ReviewDoc, conversationId, review.key, review.seed);
213
+ }, context);
214
+ const source = await session.documentSource(ReviewDoc, conversationId, review.key, context);
215
+ if (source === undefined) throw new Error("review was retired during setup");
216
+ const state = replicatedState(source);
217
+ env.own(() => state.dispose());
217
218
  // Chord instance keys route services; they are not numeric document incarnation IDs.
218
219
  instances.spawn(JSON.stringify([conversationId, review.key]), {
219
- state: replica.state,
220
+ state,
220
221
  async identity() { return { conversationId, key: review.key }; },
221
222
  async addComment(comment, context) {
222
223
  await session.commit(async tx => {
223
- const draft = await tx.doc(ReviewDoc, target);
224
- draft.comments.push(comment); // Do not mutate comment after this assignment.
224
+ const draft = await tx.doc(ReviewDoc, conversationId, review.key, review.seed);
225
+ draft.comments.push(comment); // Chord copies the assigned comment by value.
225
226
  }, context);
226
227
  },
227
228
  }); // The facet owns spawned service lifetimes automatically.
@@ -232,7 +233,7 @@ function reviewFacet(
232
233
  }
233
234
  ```
234
235
 
235
- Example input: `[{ key: "review-7", initial: { path: "a.ts", patch: "-old\n+new" } }]`.
236
+ Example input: `[{ key: "review-7", seed: { path: "a.ts", patch: "-old\n+new" } }]`.
236
237
  Pass distinct family keys and a real conversation ID; install the facet as above.
237
238
  Keyed consumers use `env.observe(DiffReviews, handler)`, not `env.use`. The handler
238
239
  receives `(review, context)`; call `review.identity(context)` to identify it and
@@ -249,9 +250,9 @@ fork at E with current: child review-7 contains A and B
249
250
  child adds C: parent still contains only A and B
250
251
  ```
251
252
 
252
- With `fork: "initial"`, no instance copies; first child access uses supplied input.
253
+ With `fork: "initial"`, no instance copies; the first child `tx.doc()` uses its supplied seed.
253
254
  `latest` cannot read history or fork `asOf`; use `history: "rewindable"` with `asOf`
254
- to reflect E's commit. Reaccess ignores `initial`; updating the patch requires
255
+ to reflect E's commit. Reaccess ignores later seeds; updating the patch requires
255
256
  an explicit mutation or a distinct review key.
256
257
 
257
258
  ## 3. Task-scoped output and a tool/task watch
@@ -267,16 +268,13 @@ const JobOutputDoc = defineDoc<JobOutput>({
267
268
  initial: () => ({ stdout: "", chunks: 0 }),
268
269
  checkpointWhen: value => value.chunks % 100 === 0,
269
270
  });
270
- function jobOutputTarget(taskId: Id) {
271
- return { scope: "task" as const, taskId };
272
- }
273
271
  async function appendJobOutput(
274
272
  runtime: TaskRuntime<JobInput, { phase: "running" }, null, {}>,
275
273
  chunk: string, context: Context,
276
274
  ): Promise<void> {
277
275
  // Read process output outside this callback. The runtime gates the live task.
278
276
  await runtime.commit(async tx => {
279
- const draft = await tx.doc(JobOutputDoc, jobOutputTarget(runtime.taskId));
277
+ const draft = await tx.doc(JobOutputDoc, runtime.taskId);
280
278
  draft.stdout = (draft.stdout + chunk).slice(-50_000);
281
279
  draft.chunks += 1;
282
280
  }, context);
@@ -285,8 +283,8 @@ async function observeJob(
285
283
  api: DocumentObserver, producerTaskId: Id,
286
284
  finished: Promise<void>, context: Context,
287
285
  ): Promise<void> {
288
- const target = jobOutputTarget(producerTaskId);
289
- const watch = await api.watchDoc(JobOutputDoc, target, context);
286
+ const watch = await api.watchDoc(JobOutputDoc, producerTaskId, context);
287
+ if (watch === undefined) return;
290
288
  let value = watch.value;
291
289
  console.log(value === null ? "retired" : value.stdout);
292
290
  try {
@@ -304,8 +302,9 @@ async function observeJob(
304
302
 
305
303
  The running phase calls `appendJobOutput` and must also commit its next checkpoint
306
304
  or terminal outcome. `api` is the invocation's `DocumentObserver`; `TaskRuntime`
307
- includes that interface. Acquisition validates the target task is live and
308
- derives its conversation from the task record.
305
+ includes that interface. `tx.doc()` creation validates that the target task is live and derives its
306
+ conversation from the task record. A later non-creating watch lookup returns
307
+ `undefined` after the task is terminal and its document has retired.
309
308
 
310
309
  ```text
311
310
  watchDoc: capture immutable V0 + register for later committed operation batches
@@ -321,8 +320,9 @@ Watches automatically stop when their invocation ends. A Session-acquired watch
321
320
  is caller-owned and stops on Session close. Previously delivered snapshots never
322
321
  mutate. Slow or unstarted delivery may coalesce an undelivered suffix into a
323
322
  complete reset, so a watch is convergent state observation rather than a
324
- transition journal. Retirement ends the incarnation's stream; recreation
325
- requires a new watch/source and a new numeric document ID. A terminal task cannot
323
+ transition journal. Retirement ends the incarnation's stream; a service exposing that source must
324
+ withdraw or remain terminal at `null`. Recreation requires a new watch/source,
325
+ service attachment, and numeric document ID. A terminal task cannot
326
326
  create more output. Task-scoped documents
327
327
  never copy into forks. Preserve required output in result entries or Session- or
328
328
  conversation-scoped documents in the terminal commit before retirement. Use a
@@ -348,28 +348,29 @@ services or trusted invocation watches. A retired source publishes `null`; a
348
348
  dynamic service should then withdraw its instance rather than expose stale data.
349
349
 
350
350
  ```text
351
- addStroke -> hold Session mutation line -> await tx.doc -> mutate tracked draft
352
- callback succeeds -> tracker flush: incremental ops + candidate value
351
+ addStroke -> hold Session mutation line -> await tx.doc -> mutate tracker change draft
352
+ callback succeeds -> tracker prepare: immutable candidate + frozen ops
353
353
  Session checkpoint predicate selects a base or delta exactly once
354
354
  atomic storage commit: persist selected document and record writes
355
- storage succeeds -> materialize immutable published value + enqueue value/ops, still on line
355
+ storage succeeds -> adopt candidate + enqueue candidate/ops, still on line
356
356
  release line -> deliver committed source ops -> adapter -> local/remote Chord consumers
357
357
  late subscriber -> atomically capture committed value + adapter sequence + subscription
358
358
  ```
359
359
 
360
- No visible-undurable path exists. Callback failure restores the unchanged
361
- baseline. Failure after flush, including checkpoint/storage failure, publishes
362
- nothing and poisons the open Session; close and reopen it instead of continuing.
360
+ No visible-undurable path exists. Callback, tracker preparation, and checkpoint
361
+ failure abort normally before Storage admission. An uncertain Storage failure
362
+ publishes nothing and poisons the open Session; close and reopen it instead of
363
+ continuing.
363
364
 
364
365
  ## Footguns
365
366
 
366
- - Never retain drafts, nested proxies, or bound array methods across commits.
367
- Never mutate inserted aliases or insert one mutable object at multiple paths.
367
+ - Never retain drafts, nested proxies, or bound array methods across commits;
368
+ they are revoked. Assigned containers are copied by value.
368
369
  - Async commit holds the line through storage settlement and baseline adoption.
369
370
  Await document access there, not models, processes, network calls, or humans.
370
- - Normal `snapshot`, `documentSource`, and `watchDoc` reads are get-or-create and
371
- lazily migrate through the supplied token. Harness open does not scan ordinary
372
- documents. Family `initial` is first-creation input, not an update.
371
+ - Only `tx.doc()` creates. `snapshot`, `documentSource`, and `watchDoc` return
372
+ `undefined` when absent and never write. Family seeds are used only when the
373
+ first `tx.doc()` creates an incarnation; later seeds are ignored.
373
374
  - Initialize from the fixed `watch.value` before `start()`. Slow or unstarted
374
375
  delivery may coalesce an undelivered suffix into a root replacement when its
375
376
  operation count exceeds the limit, omitting intermediate states. Queue
@@ -11,13 +11,13 @@ facades, membranes, document routing, view projection, events, or clone chains.
11
11
 
12
12
  - Obsolete `pico` and `pico4` prototypes were removed.
13
13
  - `pico3` remains.
14
- - No Pico5 implementation exists.
14
+ - Packages 1–3 are implemented in `packages/durable`; later Pico5 runtime packages remain.
15
15
 
16
16
  ## 1. Records, cursors, and memory tables
17
17
 
18
18
  Implement IDs, sequences, reserved root conversation ID `1`,
19
- `ConversationRecord`, `EntryRecord`, tagged inputs, live/terminal `TaskRecord`
20
- values, document lifecycle records, storage writes, backend-opaque JSON cursors, and
19
+ `ConversationRecord`, `EntryRecord`, strict input/write `SubmissionRecord`
20
+ values, live/terminal `TaskRecord` values, document records, storage writes, backend-opaque JSON cursors, and
21
21
  detached `MemoryStorage` tables.
22
22
  Reserve `Conversation` for the public conversation object, `Entry` for the typed
23
23
  entry definition, and `Task` for the typed executable definition returned by
@@ -26,14 +26,16 @@ entry definition, and `Task` for the typed executable definition returned by
26
26
  Test reserved root identity and immutable creation, mixed atomic commits,
27
27
  rollback, detached reads/writes, cursor boundaries,
28
28
  fork-aware entry scans through deep ancestor caps, head lookup,
29
- entry-to-commit lookup, and full task replacement.
29
+ entry-to-commit lookup, full task replacement, and submission replacement/
30
+ request-ID lookup.
30
31
 
31
32
  ## 2. Memory document records
32
33
 
33
34
  Add selected document base/delta writes, retirement, reincarnation,
34
35
  current/as-of membership, exact logical-address lookup, scoped scans, and
35
- base-plus-tail reconstruction. Storage receives no definition callbacks or
36
- unused candidate values.
36
+ materialized point-in-time reads. Storage keeps base/delta revisions private and
37
+ returns a detached value plus its stored definition version. It applies Chord
38
+ `Op[]` directly and receives no definition callbacks or unused candidate values.
37
39
 
38
40
  Test Session-, conversation-, and task-scoped documents, half-open lifetimes,
39
41
  create-plus-retire, retired historical membership, family queries, current-only
@@ -70,28 +72,39 @@ never reclaimed and default no-fsync behavior matches the specification.
70
72
 
71
73
  ## 6. Tracker transaction core
72
74
 
73
- Keep one tracker per loaded document. Add transaction begin, callback rollback
74
- from the unchanged baseline, flush, fatal post-flush failure, committed baseline
75
- adoption, and eviction. After storage succeeds, materialize one immutable
76
- published value per changed document with `applyImmutable(previous, ops)`,
77
- separate from mutable tracker and borrowed storage candidates.
78
-
79
- Do not add membranes, transaction proxy graphs, capability facades, or defensive
80
- full-document clones. Test callback failure, no-op transactions, storage failure
81
- poisoning, immutable prior published values, replacement-payload ownership, and
82
- unload/reload.
75
+ **Prerequisite:** Chord Delta exposes the normative `beginChange`/`prepare`/
76
+ `abort`/`adopt` tracker contract, including revocable async-lifetime drafts,
77
+ frozen operation batches, and deep no-op normalization.
78
+
79
+ Keep one Chord Delta tracker per loaded document. On first `tx.doc()` access,
80
+ call `tracker.beginChange()` and memoize its revocable copy-on-write draft by
81
+ logical address before awaiting acquisition. Repeated access returns that same
82
+ draft for the whole possibly async Session callback. On callback failure, abort
83
+ all changes. A callback that settles with an unresolved acquisition rejects;
84
+ seal `Tx`, drain and abort the acquisition, and observe its failure. Otherwise,
85
+ prepare every open change, then evaluate checkpoints and commit Storage. Only after Storage succeeds,
86
+ adopt every prepared tracker change and publish its candidate/ops directly.
87
+
88
+ Preparation and checkpoint errors roll back normally; an uncertain Storage
89
+ failure poisons the Session. Test callback failure, escaped-draft revocation,
90
+ concurrent duplicate acquisition, callback failure/success with a pending
91
+ acquisition, late acquisition after sealing, family first-seed wins, no-op normalization, multi-document preparation failure, storage
92
+ failure poisoning, immutable prior and candidate values, frozen operation
93
+ metadata, assignment copying, and unload/reload.
83
94
 
84
95
  ## 7. Document definitions and access
85
96
 
86
- Implement `defineDoc`, `defineDocFamily`, document kind/key validation, the three
87
- direct scopes, and get-or-create `tx.doc`, `snapshot`, and `documentSource`
88
- acquisition. Definitions are explicit typed arguments, not registered declarations;
89
- conflicting definitions that claim one persisted kind are unsupported caller
90
- misuse.
97
+ Implement scope-preserving singleton/family tokens and overloads for Session,
98
+ conversation, and task owners. Only `tx.doc()` is get-or-create: singleton tokens
99
+ supply `initial()`, while family calls always supply key and seed and use only the
100
+ first seed when absent. Snapshot, source, and watch lookup never create and return
101
+ `undefined` when absent. Definitions are explicit typed arguments, not registered
102
+ declarations; conflicting definitions claiming one persisted kind are unsupported
103
+ caller misuse.
91
104
 
92
105
  Test concurrent initialization once, initial bases, detached snapshots, family
93
- initializer use only on first creation, scope/target mismatch, terminal-task
94
- rejection, task-derived conversation identity, retirement, and
106
+ initializer use only on first creation, scope/token mismatch, non-creating reads,
107
+ terminal-task rejection, task-derived conversation identity, retirement, and
95
108
  reincarnation-bound sources. Include create-task-then-document,
96
109
  document-after-terminal rejection, and create-document-then-terminal settlement
97
110
  in one transaction; internal candidate validation must not trigger
@@ -99,16 +112,18 @@ in one transaction; internal candidate validation must not trigger
99
112
 
100
113
  ## 8. Checkpoints and migration
101
114
 
102
- After tracker flush, Session evaluates `checkpointWhen(value, ops)` exactly once
115
+ After tracker preparation, Session evaluates `checkpointWhen(value, ops)` exactly once
103
116
  for ordinary mutations and sends Storage only the selected base or delta.
104
117
  Implement required creation/version bases and lazy all-older-version migration
105
118
  on typed access; Harness open does not scan ordinary documents.
106
119
 
107
- Test latest migration persistence, `tx.doc()` migration rollback and coalescing
108
- with later edits, rewindable migration on current/historical read, first
109
- post-migration mutation base, newer-version rejection, unaccessed and unavailable-definition
110
- preservation, predicate failure poisoning, and checkpoint starvation without
111
- backend heuristics.
120
+ Test read-only in-memory migration, `tx.doc()` migration rollback and coalescing
121
+ with later edits, rewindable migration on current/historical read, the first
122
+ successful `tx.doc()` version base even without a JSON change, newer-version
123
+ rejection, migrated source/watch hydration without a write, subsequent operations
124
+ against that migrated baseline, stored-version fork copying, unaccessed and unavailable-definition
125
+ preservation, predicate failure rollback before Storage admission, and checkpoint
126
+ starvation without backend heuristics.
112
127
 
113
128
  ## 9. Conversation document forks
114
129
 
@@ -121,34 +136,47 @@ of task- and Session-scoped documents.
121
136
 
122
137
  ## 10. Chord structural array operations
123
138
 
124
- Make ordinary positional mutations encode scattered removals without carrying
125
- retained payloads. Callers must not write operations manually.
139
+ **Chord-owned prerequisite/integration:** the canonical Delta revision differ
140
+ must be fixed in `packages/chord`; Pico only verifies and consumes it.
141
+
142
+ Improve the canonical Chord Delta revision differ so ordinary positional
143
+ mutations encode scattered removals without carrying retained payloads. Callers
144
+ must not write operations manually.
126
145
 
127
146
  Test front/tail/middle/scattered/all/no removal, retained 256 KiB and 1 MiB
128
147
  payloads, append plus removal, later nested/index writes, exact replay, and
129
- unchanged previous immutable snapshots. Internal flushes must remain one commit.
148
+ unchanged previous immutable snapshots. One prepared document change remains one
149
+ Session commit; no intermediate candidate is adopted or published.
130
150
 
131
151
  ## 11. Chord document source
132
152
 
133
- Add the thin opaque-source adapter to `ReplicatedState`. It must use committed
134
- value/ops directly, with no tracker or re-diff.
153
+ **Prerequisite:** Chord exposes the normative atomic `ReplicatedStateSource`
154
+ attachment and `replicatedState(source)` adoption contract.
155
+
156
+ Make Chord replicated state adopt Pico's opaque committed document source through
157
+ a supported race-free source contract. It must atomically attach to the source's
158
+ current immutable value and later committed operations without another tracker
159
+ or re-diff. Pico remains the sole document mutator.
135
160
 
136
- Test contiguous Chord adapter delivery sequences, atomic hydrate/subscribe,
137
- attachment races, retirement ending one incarnation, recreation requiring
161
+ Test contiguous Chord delivery sequences, atomic hydrate/subscribe, a snapshot
162
+ that already covers a queued publication without duplicate application,
163
+ retirement between source acquisition and attachment hydrating `null` rather than
164
+ a replacement, retirement ending one incarnation, recreation requiring
138
165
  reacquisition, and listener isolation. Reuse the transaction core's immutable
139
166
  published value; do not materialize another document copy.
140
167
 
141
168
  ## 12. Document watches
142
169
 
143
- Implement get-or-create `watchDoc` as an incarnation-bound `WatchHandle` that
144
- atomically captures one fixed immutable value and registers for later committed
170
+ Implement non-creating `watchDoc` as an incarnation-bound `WatchHandle` that
171
+ returns `undefined` when absent and atomically captures one fixed immutable value
172
+ while registering for later committed
145
173
  operation batches. `start()` installs one serialized asynchronous listener.
146
174
  Bound the pending queue only by the total number of operations in its undelivered
147
175
  batches. Never estimate serialized bytes or call `JSON.stringify()` for delta
148
176
  queue accounting. When the operation-count limit is exceeded, compact the entire
149
177
  undelivered suffix into one root replacement using the matching latest immutable
150
- published value from package 6; never retain mutable tracker or borrowed storage
151
- candidates.
178
+ published value from package 6; never retain a transaction draft or borrowed
179
+ storage candidate.
152
180
 
153
181
  Test updates between acquisition/return/start; asynchronous consumer
154
182
  initialization; no callback overlap; listener-initiated commits; compaction
@@ -214,15 +242,20 @@ retirement of task-scoped documents, default non-inheritance, inheritance from
214
242
  the current committed tail, an empty source conversation, document fork
215
243
  policies, and explicit model/section seed overrides.
216
244
 
217
- ## 18. Inputs and positional inbox
245
+ ## 18. Submissions and positional inbox
218
246
 
219
- Define the initial inbox and turn-control documents, then implement request-ID
220
- deduplication, awaitable input handles, busy admission, withdrawal, queue modes,
221
- and `postTools`/`final` boundaries. Use a fake successor task.
247
+ Define the initial inbox and turn-control documents, then implement strict input/
248
+ write `SubmissionRecord` variants, `Conversation.submit()`, request-ID
249
+ deduplication, awaitable/reacquirable submissions, busy admission, withdrawal, queue modes,
250
+ and `postTools`/`final` boundaries. Successful input settlement requires an
251
+ answer; write settlement means entry placement and never starts a turn. Use a fake successor
252
+ task.
222
253
 
223
- Table-test every input transition, interleaved steer/follow-up/write selection,
224
- self-head cuts, stale targets, successor triggers, reopen waits, compact
225
- large-payload removals, and orphan/fault cleanup of active turn control.
254
+ Table-test every submission transition, cross-type request-ID conflicts,
255
+ interleaved steer/follow-up/write selection, self-head cuts, stale targets,
256
+ successor triggers, reopen waits, writes pending without a later boundary,
257
+ compact large-payload removals, abort results for queued/placed/terminal
258
+ submissions, and orphan/fault cleanup of active turn control.
226
259
 
227
260
  ## 19. Remaining built-in documents and view
228
261
 
@@ -280,7 +313,7 @@ cancellation through pi-ai's exported `Models` interface. Do not add a Pico
280
313
  model adapter. The faux test double implements that same interface.
281
314
 
282
315
  Test every phase before and after reopen, aborted partial conversion, overflow
283
- through a fake collapse kind, input settlement, and no visible-undurable update.
316
+ through a fake collapse kind, input-submission settlement, and no visible-undurable update.
284
317
  Replace the fake tool successor and rerun the package 21 integration tests.
285
318
 
286
319
  ## 23. Collapse task
@@ -294,18 +327,19 @@ overflow integration test.
294
327
 
295
328
  ## 24. Harness integration
296
329
 
297
- Implement the exact Pico3-shaped public surface in specification §2.2:
330
+ Implement the exact public surface in specification §2.2:
298
331
  `Harness.open/resume/suspend/close`, lifecycle gates, root/create/lookup
299
- `Conversation` objects, ordered `write` handles, send/input handles,
332
+ `Conversation` objects, typed input/write `submit()` and `Submission` objects,
300
333
  conversation-bound commits and history pagination, fork/collapse/reset/abort/idle,
301
- typed task wait/abort, generic document access, task/tool/section registries, and structural conversation watches. Do not restore Pico3's
334
+ typed task wait/abort, generic document access, task/tool/section registries, and
335
+ structural conversation watches. Do not restore Pico3's
302
336
  namespace router, fixed document accessors, semantic view events, or manual Chord
303
337
  view bridge.
304
338
 
305
339
  Expose service withdrawal/client detach and product wiring. Implement the §9.4
306
340
  agent-mode notification adapter directly from uncoalesced committed publication,
307
341
  without another tracker or persistence authority. Migrate TUI hydration to the
308
- structural conversation watch, make print await its own `InputHandle`, and expose
342
+ structural conversation watch, make print await its own input `Submission`, and expose
309
343
  JSON/RPC correlated commands plus ordered committed notifications. Test that
310
344
  watch reset compaction cannot erase a subscribed notification lifecycle, late
311
345
  clients use structural hydration rather than event replay, progress notifications
@@ -315,15 +349,15 @@ backpressure/disconnect policy stays in the mode adapter.
315
349
  Implement the v1 host-extension reload path as stop admission, close/join, dispose,
316
350
  rebuild with new document tokens and registered task/tool/section definitions, reopen/migrate live tasks, and resume. Ordinary documents migrate
317
351
  on later typed access. Test that closing seals commit and
318
- get-or-create admission, lets storage settlement for already-flushed admitted
352
+ mutation admission, lets storage settlement for already-prepared admitted
319
353
  commits finish despite caller cancellation, stops watches, joins in-flight watch
320
354
  callbacks and task/tool/hook invocations outside the Session line, writes no abort
321
355
  or terminal outcome, starts no fresh abort invocation, and does not run old and
322
356
  new generations concurrently. Include cancellation during watch acquisition and
323
357
  a non-cooperative watch callback in shutdown/extension-reload quiescence tests.
324
358
 
325
- Test stable persisted root identity; atomic conversation/config/section/input
326
- creation; default `"off"` thinking; every configuration getter/setter; explicit
359
+ Test stable persisted root identity; atomic conversation/config/section/input-
360
+ submission creation; default `"off"` thinking; every configuration getter/setter; explicit
327
361
  active-tool seed duplicate/unregistered rejection; default active registry
328
362
  snapshot; as-of fork inheritance including unavailable historical names; durable
329
363
  `missing_active_tool` settlement; fork seed overrides; concrete-entry forks;
@@ -334,8 +368,9 @@ quiescence with eligible work; listener initial/future delivery and isolation;
334
368
  and runtime registration between open and resume without resurrection of a task
335
369
  settled during open.
336
370
 
337
- Compile-test every §2.2 signature and the usage sequences in the normative
338
- specification and Chord guide. The erased registry test must include a concrete task with narrowed
371
+ Compile-test every §2.2 and §3 owner/key/seed overload plus the usage sequences
372
+ in the normative specification and Chord guide. Verify that a Chord root
373
+ replacement delta remains distinct from a Session-selected storage checkpoint. The erased registry test must include a concrete task with narrowed
339
374
  input, multiple checkpoint phases, and custom hooks. Run all package-specific
340
375
  tests and the repository check. Verify a local
341
376
  coding-agent turn and a reopened interrupted turn, then stop for final review.