@earendil-works/pi-durable 0.0.1 → 0.86.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 +13 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-storage.d.ts +37 -0
- package/dist/memory-storage.d.ts.map +1 -0
- package/dist/memory-storage.js +322 -0
- package/dist/memory-storage.js.map +1 -0
- package/dist/types.d.ts +312 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/docs/chord-delta-findings.md +358 -0
- package/docs/pico-v5-chord-usage.md +384 -0
- package/docs/pico-v5-handoff.md +341 -0
- package/docs/pico-v5.md +2031 -0
- package/package.json +50 -23
- package/index.js +0 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Pico5 documents through Chord
|
|
2
|
+
|
|
3
|
+
This guide uses the contracts in the [Pico5 specification](pico-v5.md).
|
|
4
|
+
|
|
5
|
+
- **Session:** a durable container for conversations, entries, tasks, and documents;
|
|
6
|
+
it serializes mutations on one commit line.
|
|
7
|
+
- **Facet:** a feature's setup unit in a Chord host. Setup declares services and
|
|
8
|
+
dependencies synchronously; `onActivate` runs after dependencies are ready.
|
|
9
|
+
- **Context:** explicitly passed cancellation and invocation values. Forward the caller's context.
|
|
10
|
+
- **Service:** a typed token and implementation acquired with `env.use`. Remote
|
|
11
|
+
services contain replicated state and async methods with JSON arguments plus a trailing `Context`.
|
|
12
|
+
- **Durable document:** JSON persisted as a complete base, Chord operations, and
|
|
13
|
+
checkpoints. Its definition token declares schema, version, scope, and initialization;
|
|
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.
|
|
17
|
+
- **DocumentSource:** an opaque handle to one document incarnation's committed changes,
|
|
18
|
+
not a mutable value or a public subscription API.
|
|
19
|
+
- **ReplicatedState:** Chord's immutable complete values through `value` and
|
|
20
|
+
`subscribe(listener)`. `value` is `undefined` before hydration or while disconnected.
|
|
21
|
+
The listener receives `(value, context, delivery)`; delivery contains `kind`
|
|
22
|
+
(`hydrate` or `update`) and a stream `sequence`, not a Session commit number.
|
|
23
|
+
|
|
24
|
+
Documents are scoped directly to a Session, conversation, or task. A task is
|
|
25
|
+
durable work attached to a conversation; its documents retire when it becomes
|
|
26
|
+
terminal and never participate in conversation forks.
|
|
27
|
+
|
|
28
|
+
## Imports and the adapter boundary
|
|
29
|
+
|
|
30
|
+
Examples build on one another. Pico5 names (`defineDoc`, `defineDocFamily`,
|
|
31
|
+
`Session`, `DocumentSource`, `Id`, `TaskRuntime`, `DocumentObserver`)
|
|
32
|
+
refer to normative contracts, without a specified import path or runnable Pico5
|
|
33
|
+
package. In those contracts, `ConversationRecord`, `EntryRecord`, and
|
|
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:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
createFacetHost, createRemoteServiceBinding, defineFacet, defineService,
|
|
41
|
+
type Context, type Facet, type JsonValue, type RemoteServiceTransport,
|
|
42
|
+
type ReplicatedState,
|
|
43
|
+
} from "@earendil-works/chord";
|
|
44
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
45
|
+
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
46
|
+
|
|
47
|
+
// The spec calls this JsonObject; current Chord exports JsonValue, not JsonObject.
|
|
48
|
+
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
|
+
```
|
|
55
|
+
|
|
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 a **mutable, in-memory** tracker
|
|
62
|
+
with `.state` and `.publish(context)`, not a durable source adapter. There is no
|
|
63
|
+
`replicatedState.own(source)`. Do not mirror documents into another tracker.
|
|
64
|
+
|
|
65
|
+
## 1. A Session-wide canvas
|
|
66
|
+
|
|
67
|
+
All conversations in this Session share one canvas. Session documents are
|
|
68
|
+
current-only and have no history or fork settings.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
type Stroke = { color: string; points: { x: number; y: number }[] };
|
|
72
|
+
type CanvasState = { strokes: Stroke[] };
|
|
73
|
+
|
|
74
|
+
const CanvasDoc = defineDoc<CanvasState>({
|
|
75
|
+
kind: "app.canvas", version: 1, scope: "session",
|
|
76
|
+
initial: () => ({ strokes: [] }),
|
|
77
|
+
// This append-only example adds one stroke per commit.
|
|
78
|
+
checkpointWhen: value => value.strokes.length % 100 === 0,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
interface CanvasService {
|
|
82
|
+
readonly state: ReplicatedState<CanvasState | null>;
|
|
83
|
+
addStroke(stroke: Stroke, context: Context): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
const Canvas = defineService<CanvasService>("app.canvas");
|
|
86
|
+
|
|
87
|
+
async function createCanvasFacet(session: Session, context: Context): Promise<Facet> {
|
|
88
|
+
const target = { scope: "session" } as const;
|
|
89
|
+
// Get-or-create: an absent canvas is initialized in a serialized commit.
|
|
90
|
+
const source = await session.documentSource(CanvasDoc, target, context);
|
|
91
|
+
const replica = await documentReplicatedState(source, context);
|
|
92
|
+
return defineFacet({
|
|
93
|
+
id: "app.canvas/session",
|
|
94
|
+
setup(env) {
|
|
95
|
+
env.own(() => replica.dispose());
|
|
96
|
+
env.provide(Canvas, {
|
|
97
|
+
state: replica.state,
|
|
98
|
+
async addStroke(stroke, context) {
|
|
99
|
+
await session.commit(async tx => {
|
|
100
|
+
const draft = await tx.doc(CanvasDoc, target);
|
|
101
|
+
draft.strokes.push(stroke); // The draft now owns stroke; caller must not mutate it.
|
|
102
|
+
}, context);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const CanvasConsumer = defineFacet({
|
|
110
|
+
id: "app.canvas/consumer",
|
|
111
|
+
setup(env) {
|
|
112
|
+
const canvas = env.use(Canvas); // Declare now; access only after activation.
|
|
113
|
+
env.onActivate(() => {
|
|
114
|
+
env.own(canvas.state.subscribe((value, _context, delivery) => {
|
|
115
|
+
console.log(delivery.kind, delivery.sequence, value?.strokes.length ?? "retired");
|
|
116
|
+
})); // subscribe delivers the current hydrated value, then updates.
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
async function runCanvasExample(session: Session): Promise<void> {
|
|
122
|
+
const context = BACKGROUND_CONTEXT;
|
|
123
|
+
const provider = await createCanvasFacet(session, context);
|
|
124
|
+
const host = await createFacetHost({ facets: [provider, CanvasConsumer] });
|
|
125
|
+
try {
|
|
126
|
+
await host.services.use(Canvas).addStroke({
|
|
127
|
+
color: "black", points: [{ x: 10, y: 20 }, { x: 30, y: 40 }],
|
|
128
|
+
}, context);
|
|
129
|
+
} finally {
|
|
130
|
+
await host.dispose(); // Unsubscribes; does not delete the canvas or close Session.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The application owns the open Session. Acquire state **before** synchronous
|
|
136
|
+
`setup`; `env.provide` cannot run in `onActivate`. Install one provider per
|
|
137
|
+
Session host. Chord may reload presentation facets independently, but v1 does
|
|
138
|
+
not use facet reload to replace Session-side task or hook implementations. A
|
|
139
|
+
host extension code change closes and reopens the Harness with the new
|
|
140
|
+
definition set.
|
|
141
|
+
|
|
142
|
+
Remote clients supply a `RemoteServiceTransport` connected to the host's `services`
|
|
143
|
+
provider; Chord prescribes no socket protocol. `CanvasConsumer` also works unchanged
|
|
144
|
+
in a UI host configured with a remote service source.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
async function connectCanvas(transport: RemoteServiceTransport, context: Context) {
|
|
148
|
+
const services = createRemoteServiceBinding({ services: [Canvas], transport });
|
|
149
|
+
const canvas = services.use(Canvas);
|
|
150
|
+
const stop = canvas.state.subscribe((value, _context, delivery) => {
|
|
151
|
+
console.log(delivery.kind, delivery.sequence, value?.strokes ?? "retired");
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
await services.ready(context); // Initial snapshot installed; not all future updates.
|
|
155
|
+
} catch (error) {
|
|
156
|
+
stop();
|
|
157
|
+
await services.dispose(BACKGROUND_CONTEXT);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
return async () => {
|
|
161
|
+
stop();
|
|
162
|
+
await services.dispose(BACKGROUND_CONTEXT);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
first acquisition -> commit base { strokes: [] }
|
|
169
|
+
addStroke(A) -> commit A -> local and remote subscribers see A
|
|
170
|
+
worker restarts -> open same durable storage; install canvas facet again
|
|
171
|
+
acquire source -> load base + committed deltas, without rerunning initial()
|
|
172
|
+
late client -> hydrate complete canvas including A, then ordered updates
|
|
173
|
+
conversation fork -> still uses this same Session canvas
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Use persistent storage for restart survival. The memory backend is not persistent.
|
|
177
|
+
A method's successful commit does not promise every remote callback has run yet.
|
|
178
|
+
|
|
179
|
+
## 2. Conversation-scoped diff reviews
|
|
180
|
+
|
|
181
|
+
A **document family** uses one definition for many instances. The logical key is
|
|
182
|
+
`(kind, conversationId, key)`; the persisted numeric document ID identifies an
|
|
183
|
+
incarnation and is never reused. Initializer input is not part of the key.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
type ReviewInput = { path: string; patch: string };
|
|
187
|
+
type ReviewComment = { id: string; line: number; text: string };
|
|
188
|
+
type ReviewState = ReviewInput & { comments: ReviewComment[] };
|
|
189
|
+
const ReviewDoc = defineDocFamily<ReviewState, ReviewInput>({
|
|
190
|
+
kind: "app.diff-review", version: 1, family: true, scope: "conversation",
|
|
191
|
+
history: "latest", fork: "current",
|
|
192
|
+
initial: input => ({ path: input.path, patch: input.patch, comments: [] }),
|
|
193
|
+
checkpointWhen: value => value.comments.length % 50 === 0,
|
|
194
|
+
});
|
|
195
|
+
interface DiffReviewService {
|
|
196
|
+
readonly state: ReplicatedState<ReviewState | null>;
|
|
197
|
+
identity(context: Context): Promise<{ conversationId: Id; key: string }>;
|
|
198
|
+
addComment(comment: ReviewComment, context: Context): Promise<void>;
|
|
199
|
+
}
|
|
200
|
+
const DiffReviews = defineService<DiffReviewService>("app.diff-reviews");
|
|
201
|
+
|
|
202
|
+
function reviewFacet(
|
|
203
|
+
session: Session, conversationId: Id,
|
|
204
|
+
reviews: readonly { key: string; initial: ReviewInput }[], context: Context,
|
|
205
|
+
): Facet {
|
|
206
|
+
return defineFacet({
|
|
207
|
+
id: "app.diff-reviews/session",
|
|
208
|
+
setup(env) {
|
|
209
|
+
const instances = env.provideMany(DiffReviews);
|
|
210
|
+
env.onActivate(async () => {
|
|
211
|
+
for (const review of reviews) {
|
|
212
|
+
const target = { scope: "conversation" as const, conversationId, ...review };
|
|
213
|
+
const source = await session.documentSource(ReviewDoc, target, context);
|
|
214
|
+
const replica = await documentReplicatedState(source, context);
|
|
215
|
+
env.own(() => replica.dispose());
|
|
216
|
+
// Chord instance keys route services; they are not numeric document incarnation IDs.
|
|
217
|
+
instances.spawn(JSON.stringify([conversationId, review.key]), {
|
|
218
|
+
state: replica.state,
|
|
219
|
+
async identity() { return { conversationId, key: review.key }; },
|
|
220
|
+
async addComment(comment, context) {
|
|
221
|
+
await session.commit(async tx => {
|
|
222
|
+
const draft = await tx.doc(ReviewDoc, target);
|
|
223
|
+
draft.comments.push(comment); // Do not mutate comment after this assignment.
|
|
224
|
+
}, context);
|
|
225
|
+
},
|
|
226
|
+
}); // The facet owns spawned service lifetimes automatically.
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Example input: `[{ key: "review-7", initial: { path: "a.ts", patch: "-old\n+new" } }]`.
|
|
235
|
+
Pass distinct family keys and a real conversation ID; install the facet as above.
|
|
236
|
+
Keyed consumers use `env.observe(DiffReviews, handler)`, not `env.use`. The handler
|
|
237
|
+
receives `(review, context)`; call `review.identity(context)` to identify it and
|
|
238
|
+
`review.state.subscribe` to observe comments.
|
|
239
|
+
|
|
240
|
+
These comments are conversation-scoped: ending the generating task or unloading
|
|
241
|
+
the facet does not retire them. `current` copies every logically
|
|
242
|
+
present review at fork-commit time into independent child instances with new
|
|
243
|
+
numeric IDs, retaining family keys. Parent and child then diverge.
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
parent review-7: comment A -> transcript entry E -> comment B
|
|
247
|
+
fork at E with current: child review-7 contains A and B
|
|
248
|
+
child adds C: parent still contains only A and B
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
With `fork: "initial"`, no instance copies; first child access uses supplied input.
|
|
252
|
+
`latest` cannot read history or fork `asOf`; use `history: "rewindable"` with `asOf`
|
|
253
|
+
to reflect E's commit. Reaccess ignores `initial`; updating the patch requires
|
|
254
|
+
an explicit mutation or a distinct review key.
|
|
255
|
+
|
|
256
|
+
## 3. Task-scoped output and a tool/task watch
|
|
257
|
+
|
|
258
|
+
This is a bounded progress window, not the permanent result. Scope itself gives
|
|
259
|
+
it task lifetime; no history, fork, owner, or conversation setting is needed.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
type JobInput = { command: string };
|
|
263
|
+
type JobOutput = { stdout: string; chunks: number };
|
|
264
|
+
const JobOutputDoc = defineDoc<JobOutput>({
|
|
265
|
+
kind: "app.job-output", version: 1, scope: "task",
|
|
266
|
+
initial: () => ({ stdout: "", chunks: 0 }),
|
|
267
|
+
checkpointWhen: value => value.chunks % 100 === 0,
|
|
268
|
+
});
|
|
269
|
+
function jobOutputTarget(taskId: Id) {
|
|
270
|
+
return { scope: "task" as const, taskId };
|
|
271
|
+
}
|
|
272
|
+
async function appendJobOutput(
|
|
273
|
+
runtime: TaskRuntime<JobInput, { phase: "running" }, null, {}>,
|
|
274
|
+
chunk: string, context: Context,
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
// Read process output outside this callback. The runtime gates the live task.
|
|
277
|
+
await runtime.commit(async tx => {
|
|
278
|
+
const draft = await tx.doc(JobOutputDoc, jobOutputTarget(runtime.taskId));
|
|
279
|
+
draft.stdout = (draft.stdout + chunk).slice(-50_000);
|
|
280
|
+
draft.chunks += 1;
|
|
281
|
+
}, context);
|
|
282
|
+
}
|
|
283
|
+
async function observeJob(
|
|
284
|
+
api: DocumentObserver, producerTaskId: Id,
|
|
285
|
+
finished: Promise<void>, context: Context,
|
|
286
|
+
): Promise<void> {
|
|
287
|
+
const target = jobOutputTarget(producerTaskId);
|
|
288
|
+
const watch = await api.watchDoc(JobOutputDoc, target, context);
|
|
289
|
+
let value = watch.value;
|
|
290
|
+
console.log(value === null ? "retired" : value.stdout);
|
|
291
|
+
try {
|
|
292
|
+
watch.start(async (ops, _context) => {
|
|
293
|
+
value = applyImmutable(value, ops);
|
|
294
|
+
await render(value === null ? "retired" : value.stdout);
|
|
295
|
+
}); // Serialized callbacks never overlap.
|
|
296
|
+
await finished; // Caller-supplied observation lifetime; outside any commit.
|
|
297
|
+
} finally {
|
|
298
|
+
watch.stop(); // Idempotent; prevents another callback from starting.
|
|
299
|
+
await watch.closed; // Wait for an in-flight callback to settle.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The running phase calls `appendJobOutput` and must also commit its next checkpoint
|
|
305
|
+
or terminal outcome. `api` is the invocation's `DocumentObserver`; `TaskRuntime`
|
|
306
|
+
includes that interface. Acquisition validates the target task is live and
|
|
307
|
+
derives its conversation from the task record.
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
watchDoc: capture immutable V0 + register for later committed operation batches
|
|
311
|
+
producer commits D1 and D2 before start: queue D1, D2
|
|
312
|
+
pending operation count exceeds its limit: replace suffix with [["r", V2]]
|
|
313
|
+
start: caller has initialized from V0; deliver the reset through the async listener
|
|
314
|
+
producer commits D3 while listener awaits: queue D3; never overlap callbacks
|
|
315
|
+
unrelated commit has no source ops: enqueue nothing
|
|
316
|
+
producer becomes terminal: queue [["r", null]], deliver it, then close as retired
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Watches automatically stop when their invocation ends. A Session-acquired watch
|
|
320
|
+
is caller-owned and stops on Session close. Previously delivered snapshots never
|
|
321
|
+
mutate. Slow or unstarted delivery may coalesce an undelivered suffix into a
|
|
322
|
+
complete reset, so a watch is convergent state observation rather than a
|
|
323
|
+
transition journal. Retirement ends the incarnation's stream; recreation
|
|
324
|
+
requires a new watch/source and a new numeric document ID. A terminal task cannot
|
|
325
|
+
create more output. Task-scoped documents
|
|
326
|
+
never copy into forks. Preserve required output in result entries or Session- or
|
|
327
|
+
conversation-scoped documents in the terminal commit before retirement. Use a
|
|
328
|
+
task-scoped family only when one task needs several independently keyed documents.
|
|
329
|
+
|
|
330
|
+
## 4. Built-in conversation views and commit delivery
|
|
331
|
+
|
|
332
|
+
`ConversationView` contains `conversation`, raw transcript `entries`, and
|
|
333
|
+
`docs: Readonly<Record<string, JsonObject>>`. Selected built-in singletons mount
|
|
334
|
+
under `docs` by stable document kind, not numeric incarnation ID. The spec's
|
|
335
|
+
illustrative path mapping is:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
document ["s", ["message"], value]
|
|
339
|
+
-> view ["s", ["docs", "pi.live", "message"], value]
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Built-in kinds and fields await approval; `pi.live` is illustrative, not an available API.
|
|
343
|
+
The mount publishes one batch per complete Session commit: entry/head changes and
|
|
344
|
+
changed mounted documents together, without a tracker or semantic projection.
|
|
345
|
+
Third-party documents are **not automatically mounted**; use their own Chord
|
|
346
|
+
services or trusted invocation watches. A retired source publishes `null`; a
|
|
347
|
+
dynamic service should then withdraw its instance rather than expose stale data.
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
addStroke -> hold Session mutation line -> await tx.doc -> mutate tracked draft
|
|
351
|
+
callback succeeds -> tracker flush: incremental ops + candidate value
|
|
352
|
+
Session checkpoint predicate selects a base or delta exactly once
|
|
353
|
+
atomic storage commit: persist selected document and record writes
|
|
354
|
+
storage succeeds -> materialize immutable published value + enqueue value/ops, still on line
|
|
355
|
+
release line -> deliver committed source ops -> adapter -> local/remote Chord consumers
|
|
356
|
+
late subscriber -> atomically capture committed value + adapter sequence + subscription
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
No visible-undurable path exists. Callback failure restores the unchanged
|
|
360
|
+
baseline. Failure after flush, including checkpoint/storage failure, publishes
|
|
361
|
+
nothing and poisons the open Session; close and reopen it instead of continuing.
|
|
362
|
+
|
|
363
|
+
## Footguns
|
|
364
|
+
|
|
365
|
+
- Never retain drafts, nested proxies, or bound array methods across commits.
|
|
366
|
+
Never mutate inserted aliases or insert one mutable object at multiple paths.
|
|
367
|
+
- Async commit holds the line through storage settlement and baseline adoption.
|
|
368
|
+
Await document access there, not models, processes, network calls, or humans.
|
|
369
|
+
- Normal `snapshot`, `documentSource`, and `watchDoc` reads are get-or-create and
|
|
370
|
+
lazily migrate through the supplied token. Harness open does not scan ordinary
|
|
371
|
+
documents. Family `initial` is first-creation input, not an update.
|
|
372
|
+
- Initialize from the fixed `watch.value` before `start()`. Slow or unstarted
|
|
373
|
+
delivery may coalesce an undelivered suffix into a root replacement when its
|
|
374
|
+
operation count exceeds the limit, omitting intermediate states. Queue
|
|
375
|
+
accounting never serializes operations to estimate bytes. Do not use a watch
|
|
376
|
+
as an audit log.
|
|
377
|
+
- A listener may call `stop()`, but must not await its own `closed` promise.
|
|
378
|
+
- Definitions own checkpoints, not storage heuristics. Revise the counting predicates
|
|
379
|
+
above if mutations change. Keep document kinds, versions, fork policies, and
|
|
380
|
+
public paths stable; schema changes require migration, not a source-only rename.
|
|
381
|
+
|
|
382
|
+
Chord sources: [types](../../chord/src/types.ts), [facet examples](../../chord/test/facets.test.ts),
|
|
383
|
+
[state](../../chord/src/services/state.ts), [provider](../../chord/src/services/provider.ts),
|
|
384
|
+
[Context](../../chord/src/context/index.ts), [Delta](../../chord/src/delta/README.md).
|