@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.
- package/README.md +29 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{memory-storage.d.ts → storage/memory.d.ts} +12 -6
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/{memory-storage.js → storage/memory.js} +236 -24
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/sqlite/database.d.ts +26 -0
- package/dist/storage/sqlite/database.d.ts.map +1 -0
- package/dist/storage/sqlite/database.js +2 -0
- package/dist/storage/sqlite/database.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +4 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +3 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -0
- package/dist/storage/sqlite/migrations.js +104 -0
- package/dist/storage/sqlite/migrations.js.map +1 -0
- package/dist/storage/sqlite/node.d.ts +25 -0
- package/dist/storage/sqlite/node.d.ts.map +1 -0
- package/dist/storage/sqlite/node.js +97 -0
- package/dist/storage/sqlite/node.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +45 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/storage/sqlite/storage.js +524 -0
- package/dist/storage/sqlite/storage.js.map +1 -0
- package/dist/types.d.ts +113 -34
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/pico-v5-chord-usage.md +64 -63
- package/docs/pico-v5-handoff.md +92 -57
- package/docs/pico-v5.md +519 -350
- package/package.json +20 -3
- package/dist/memory-storage.d.ts.map +0 -1
- 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
|
|
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.
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
92
|
+
const state = replicatedState(source);
|
|
93
|
+
env.own(() => state.dispose());
|
|
97
94
|
env.provide(Canvas, {
|
|
98
|
-
state
|
|
95
|
+
state,
|
|
99
96
|
async addStroke(stroke, context) {
|
|
100
97
|
await session.commit(async tx => {
|
|
101
|
-
const draft = await tx.doc(CanvasDoc
|
|
102
|
-
draft.strokes.push(stroke); //
|
|
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
|
|
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
|
|
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.
|
|
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:
|
|
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;
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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,
|
|
224
|
-
draft.comments.push(comment); //
|
|
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",
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
289
|
-
|
|
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.
|
|
308
|
-
|
|
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;
|
|
325
|
-
|
|
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
|
|
352
|
-
callback succeeds -> tracker
|
|
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 ->
|
|
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
|
|
361
|
-
|
|
362
|
-
nothing and poisons the open Session; close and reopen it instead of
|
|
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
|
-
|
|
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
|
-
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
package/docs/pico-v5-handoff.md
CHANGED
|
@@ -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
|
-
-
|
|
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`,
|
|
20
|
-
values, document
|
|
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,
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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/
|
|
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
|
|
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
|
|
108
|
-
with later edits, rewindable migration on current/historical read, first
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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.
|
|
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
|
-
|
|
134
|
-
|
|
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
|
|
137
|
-
|
|
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
|
|
144
|
-
atomically captures one fixed immutable value
|
|
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
|
|
151
|
-
|
|
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.
|
|
245
|
+
## 18. Submissions and positional inbox
|
|
218
246
|
|
|
219
|
-
Define the initial inbox and turn-control documents, then implement
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
224
|
-
self-head cuts, stale targets,
|
|
225
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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
|
|
338
|
-
specification and Chord guide.
|
|
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.
|