@cello-protocol/daemon 0.0.121 → 0.0.123

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.
@@ -0,0 +1,465 @@
1
+ /**
2
+ * DOD-DOC-GATE-1 — the validation gate (§3.2).
3
+ *
4
+ * arrive → shadow-apply → validate the PROJECTED DIFF → admit or quarantine
5
+ *
6
+ * The shadow document is rebuilt from ACCEPTED state and discarded. Validation asks a question;
7
+ * admitting is a separate, explicit act, so a refused update never touches the live document.
8
+ *
9
+ * ── WHY THE RULES ARE WHAT THEY ARE ───────────────────────────────────────────────────────────
10
+ *
11
+ * Every rule here answers something DOD-DOC-FUZZ-1 MEASURED against real Yjs, and six of the nine
12
+ * are the ACCEPT class — input `Y.applyUpdate` returns SUCCESS for. That matters because the V1
13
+ * posture is "cap, catch, contain" (§16.7-7), and none of its three legs catches an accept: the
14
+ * size cap sees a small update, the try/catch sees success, and "structural limits on the shadow"
15
+ * has nothing to measure because the shadow looks fine. Measured, in order:
16
+ *
17
+ * (a) an update whose dependencies never arrive is ACCEPTED and RETAINED forever — a peer
18
+ * streams those until the daemon dies, and a try/catch sees only success;
19
+ * (b) an update carries NO document identity, so one built on a different document merges
20
+ * silently — binding is out-of-band work the gate must do;
21
+ * (c) V2-format bytes are accepted by the v1 decoder and silently drop all content;
22
+ * (d) trailing bytes past the decoder's cursor are ignored, so unlimited byte strings decode to
23
+ * identical state — which makes an update's hash a poor identifier for what it says, and
24
+ * that hash becomes a `0x04` leaf;
25
+ * (h) authorship IS the clientID, so a colliding one silently wins and the honest client's
26
+ * update is then accepted-and-dropped, leaving a splice of two authors with an EMPTY
27
+ * pending set — the (a) rule cannot see this, which is why (h) is separate;
28
+ * (i) a ten-byte well-formed update deletes a document's entire content. Structural limits are
29
+ * UPPER bounds, so a shrinking update passes every one of them.
30
+ *
31
+ * The remaining three are the throw class: a size floor (e) because an empty update throws a lib0
32
+ * decoder string rather than a protocol fault, one typed reason per throw (g), and a nesting-depth
33
+ * limit (f) because Yjs bounds depth not at all and the size cap bounds it poorly — ~16 bytes per
34
+ * level, so roughly 65,000 levels fit inside 1 MiB.
35
+ *
36
+ * ── QUARANTINE, NEVER DISCARD ─────────────────────────────────────────────────────────────────
37
+ *
38
+ * A refused update is HELD. Every path out of this gate — including an unexpected failure inside
39
+ * a rule — produces a verdict and an event. A gate that admitted on internal error would be
40
+ * strictly worse than one with no rules at all, and one that dropped silently would diverge the
41
+ * two copies permanently and invisibly (§3.2).
42
+ */
43
+ import * as Y from "yjs";
44
+ /** The one accepted update encoding (§16.7-8 pins it in the protocol types). */
45
+ export const UPDATE_ENCODING_V1 = "yjs-v1";
46
+ /** Published so a peer can discover them — a receiver-local limit nobody can learn is not a protocol. */
47
+ export const DEFAULT_GATE_LIMITS = {
48
+ // Deliberately the same number the engine enforces, taken from the engine at construction so
49
+ // the two cannot drift into two sources of truth.
50
+ maxUpdateBytes: 1024 * 1024,
51
+ maxDocumentBytes: 8 * 1024 * 1024,
52
+ maxNestingDepth: 64,
53
+ maxUpdatesPerMinute: 120,
54
+ };
55
+ export class DocumentGate {
56
+ #engine;
57
+ #limits;
58
+ #logger;
59
+ #rules = new Map();
60
+ /** senderAgentId → recent admission timestamps, for the rate limit. */
61
+ #recent = new Map();
62
+ constructor(engine, limits, logger) {
63
+ this.#engine = engine;
64
+ // The engine already enforces a pre-parse cap; take it rather than repeat the constant, so a
65
+ // change in one place cannot leave the gate admitting what the engine will refuse.
66
+ this.#limits = { ...DEFAULT_GATE_LIMITS, maxUpdateBytes: engine.maxUpdateBytes, ...limits };
67
+ this.#logger = logger;
68
+ }
69
+ limits() {
70
+ return { ...this.#limits };
71
+ }
72
+ /** Register a pluggable rule. The screening rule (DOD-DOC-SCREEN-1) plugs in here. */
73
+ addRule(name, rule) {
74
+ this.#rules.set(name, rule);
75
+ }
76
+ /**
77
+ * Validate an incoming update against the accepted state.
78
+ *
79
+ * Never mutates `accepted`. Never throws — every failure, including an unexpected one inside a
80
+ * rule, becomes a quarantine verdict.
81
+ */
82
+ validate(accepted, update, context, now = Date.now()) {
83
+ try {
84
+ return this.#validate(accepted, update, context, now);
85
+ }
86
+ catch (err) {
87
+ // The catch-all is the no-silent-drop invariant made structural: a rule with a bug in it
88
+ // must not become an admission.
89
+ return this.#quarantine(context, update, {
90
+ admit: false,
91
+ reason: "document_gate_rule_failed",
92
+ detail: err instanceof Error ? err.message : String(err),
93
+ });
94
+ }
95
+ }
96
+ #validate(accepted, update, context, now) {
97
+ // ── Pre-parse, before Yjs sees the bytes ────────────────────────────────
98
+ if (update.length > this.#limits.maxUpdateBytes) {
99
+ return this.#quarantine(context, update, {
100
+ admit: false,
101
+ reason: "document_update_too_large",
102
+ limit: { name: "maxUpdateBytes", limit: this.#limits.maxUpdateBytes, actual: update.length },
103
+ });
104
+ }
105
+ if (update.length < 2) {
106
+ // (e) The floor. Below it Yjs throws "Unexpected end of array" — a decoder string naming
107
+ // lib0 internals rather than a protocol fault the peer could act on.
108
+ return this.#quarantine(context, update, {
109
+ admit: false,
110
+ reason: "document_update_too_small",
111
+ detail: `${update.length} bytes is below the 2-byte minimum for a Yjs update`,
112
+ });
113
+ }
114
+ // (c) V2 bytes are ACCEPTED by the v1 decoder and silently drop all content, so the encoding
115
+ // is pinned. Declared, not sniffed — see GateContext.
116
+ if (context.declaredEncoding !== UPDATE_ENCODING_V1) {
117
+ return this.#quarantine(context, update, {
118
+ admit: false,
119
+ reason: "document_update_encoding_unsupported",
120
+ detail: `envelope declares encoding ${JSON.stringify(context.declaredEncoding ?? null)}; ` +
121
+ `only ${UPDATE_ENCODING_V1} is accepted, and v2 bytes decode to an empty document without error`,
122
+ });
123
+ }
124
+ // (b) An update carries NO document identity, so the binding is the envelope's and the check
125
+ // is the receiver's. ABSENT IS NOT FINE: an unbound update is refused, not trusted.
126
+ if (context.declaredDocumentId !== context.documentId) {
127
+ return this.#quarantine(context, update, {
128
+ admit: false,
129
+ reason: "document_update_foreign_origin",
130
+ detail: `envelope declares document ${String(context.declaredDocumentId ?? "(none)").slice(0, 16)}… ` +
131
+ `but this is ${context.documentId.slice(0, 16)}… — updates carry no document identity, so ` +
132
+ `a well-formed update built on another document would otherwise merge silently`,
133
+ });
134
+ }
135
+ // ── Rate, per sender ────────────────────────────────────────────────────
136
+ // Counted on ARRIVAL, after the cheap pre-parse checks and BEFORE the shadow rebuild.
137
+ // Counting admissions instead left a peer who sends only invalid updates un-rate-limited,
138
+ // and everything past this point costs a full encode+apply of the whole document per attempt.
139
+ const window = (this.#recent.get(context.senderAgentId) ?? []).filter((t) => now - t < 60_000);
140
+ if (window.length >= this.#limits.maxUpdatesPerMinute) {
141
+ this.#recent.set(context.senderAgentId, window);
142
+ return this.#quarantine(context, update, {
143
+ admit: false,
144
+ reason: "document_update_rate_exceeded",
145
+ limit: {
146
+ name: "maxUpdatesPerMinute",
147
+ limit: this.#limits.maxUpdatesPerMinute,
148
+ actual: window.length + 1,
149
+ },
150
+ });
151
+ }
152
+ window.push(now);
153
+ this.#recent.set(context.senderAgentId, window);
154
+ // Prune senders whose window has emptied, so the map does not accumulate one entry per peer
155
+ // for the life of the process.
156
+ for (const [sender, times] of this.#recent) {
157
+ if (times.length > 0 && now - times[times.length - 1] > 60_000)
158
+ this.#recent.delete(sender);
159
+ }
160
+ // ── Shadow apply, on state rebuilt from ACCEPTED — never long-lived ─────
161
+ const shadow = new Y.Doc();
162
+ try {
163
+ Y.applyUpdate(shadow, Y.encodeStateAsUpdate(accepted));
164
+ }
165
+ catch (err) {
166
+ return this.#quarantine(context, update, {
167
+ admit: false,
168
+ reason: "document_accepted_state_unreadable",
169
+ detail: err instanceof Error ? err.message : String(err),
170
+ });
171
+ }
172
+ const beforeText = shadow.getText("content").toString();
173
+ const beforeKeys = new Map(Object.entries(shadow.getMap("data").toJSON()));
174
+ try {
175
+ Y.applyUpdate(shadow, update);
176
+ }
177
+ catch (err) {
178
+ // (g) ONE typed reason per throw. The decoder string is useful and travels as detail —
179
+ // it is simply not a reason an operator or a policy log can key on.
180
+ return this.#quarantine(context, update, {
181
+ admit: false,
182
+ reason: "document_update_malformed",
183
+ detail: err instanceof Error ? err.message : String(err),
184
+ });
185
+ }
186
+ // (a) Yjs returned success — that is not evidence the update integrated. BOTH pending sets
187
+ // count: an update carrying a DELETE SET for structs the receiver has never seen leaves
188
+ // pendingStructs null and pendingDs populated, and that retains forever exactly as the
189
+ // struct case does. Reading one field was coding the mechanism; the rule is about whether
190
+ // the update actually integrated.
191
+ const pendingStructs = shadow.store.pendingStructs;
192
+ const pendingDs = shadow.store.pendingDs;
193
+ if (pendingStructs !== null || pendingDs !== null) {
194
+ return this.#quarantine(context, update, {
195
+ admit: false,
196
+ reason: "document_update_unresolved_dependencies",
197
+ detail: pendingStructs
198
+ ? `depends on ${pendingStructs.missing.size} client(s) whose earlier operations are absent`
199
+ : `carries a delete set referring to operations this document has never seen`,
200
+ });
201
+ }
202
+ // (d) Re-encode THE UPDATE ITSELF and compare. Comparing against the canonical DELTA looked
203
+ // equivalent and is not: a peer that batches several transactions (the normal shape for an
204
+ // append-only log) or sends full state (what y-protocols sync step 2 does with no state
205
+ // vector) produces an update legitimately LARGER than the delta, and both were measured
206
+ // being refused as "trailing bytes" — an attack label on a benign encoding, which sends an
207
+ // operator hunting a malicious peer while the document stops converging.
208
+ //
209
+ // `Y.diffUpdate` against an empty state vector re-encodes the update's own content and drops
210
+ // slack. `Y.mergeUpdates([padded])` does NOT — it preserves the padding — so it is not a
211
+ // usable detector here.
212
+ let canonicalUpdate;
213
+ try {
214
+ canonicalUpdate = Y.diffUpdate(update, Y.encodeStateVector(new Y.Doc()));
215
+ }
216
+ catch (err) {
217
+ // A throw here is a malformed update, not an internal rule failure — labelling it
218
+ // document_gate_rule_failed would name this gate's exit point rather than the peer's fault.
219
+ return this.#quarantine(context, update, {
220
+ admit: false,
221
+ reason: "document_update_malformed",
222
+ detail: err instanceof Error ? err.message : String(err),
223
+ });
224
+ }
225
+ const reEncoded = Y.encodeStateAsUpdate(shadow);
226
+ if (update.length > canonicalUpdate.length) {
227
+ return this.#quarantine(context, update, {
228
+ admit: false,
229
+ reason: "document_update_trailing_bytes",
230
+ detail: `${update.length - canonicalUpdate.length} bytes past the decoder's cursor — the encoding ` +
231
+ `would otherwise be malleable, and this update's hash becomes a 0x04 leaf`,
232
+ });
233
+ }
234
+ // (b) and (h): who wrote this, and on what.
235
+ const authorship = this.#checkAuthorship(accepted, shadow, context);
236
+ if (authorship)
237
+ return this.#quarantine(context, update, authorship);
238
+ // ── The PROJECTED DIFF — what the update would do ───────────────────────
239
+ const afterText = shadow.getText("content").toString();
240
+ const afterKeys = new Map(Object.entries(shadow.getMap("data").toJSON()));
241
+ const diff = {
242
+ inserted: insertedText(beforeText, afterText),
243
+ deletedChars: deletedCount(beforeText, afterText),
244
+ changedKeys: [...new Set([...beforeKeys.keys(), ...afterKeys.keys()])].filter((k) => JSON.stringify(beforeKeys.get(k)) !== JSON.stringify(afterKeys.get(k))),
245
+ resultingBytes: reEncoded.length,
246
+ maxDepth: deepestRoot(shadow, this.#limits.maxNestingDepth),
247
+ };
248
+ // ── Structural limits, on the projected result ──────────────────────────
249
+ if (diff.resultingBytes > this.#limits.maxDocumentBytes) {
250
+ return this.#quarantine(context, update, {
251
+ admit: false,
252
+ reason: "document_too_large",
253
+ limit: { name: "maxDocumentBytes", limit: this.#limits.maxDocumentBytes, actual: diff.resultingBytes },
254
+ });
255
+ }
256
+ if (diff.maxDepth > this.#limits.maxNestingDepth) {
257
+ return this.#quarantine(context, update, {
258
+ admit: false,
259
+ reason: "document_nesting_too_deep",
260
+ limit: { name: "maxNestingDepth", limit: this.#limits.maxNestingDepth, actual: diff.maxDepth },
261
+ });
262
+ }
263
+ // (i) append_only, judged on the PROJECTED DIFF rather than the update's size — a ten-byte
264
+ // update deletes everything and every structural limit passes it.
265
+ //
266
+ // Measured in BOTH coordinate spaces the write path projects from. Diffing only the text root
267
+ // made append_only a COMPLETE NO-OP for json documents, whose content lives in the map: an
268
+ // 8-byte update emptied `data` with deletedChars = 0 and was admitted.
269
+ if (context.appendOnly === true) {
270
+ // The UPDATE'S OWN DELETE SET, not a projection diff. Measured, it is exactly the right
271
+ // question and the projection was not:
272
+ // - it is root- and type-agnostic, so an append-only document is protected whatever roots
273
+ // a peer chooses — the previous version protected `content` and `data` and nothing else,
274
+ // which is no protection at all when the peer names the roots;
275
+ // - a map key REWRITE deletes the old item, so §16.7-1's "deletes OR EDITS" falls out;
276
+ // - and growing a NESTED entry deletes nothing, so it is admitted. The projection diff
277
+ // compared top-level keys with JSON.stringify and called that a removal, refusing the
278
+ // very append the feature is named for and telling the operator content was removed
279
+ // when nothing was.
280
+ const deletions = countDeletions(update);
281
+ if (deletions > 0) {
282
+ return this.#quarantine(context, update, {
283
+ admit: false,
284
+ reason: "document_append_only_violation",
285
+ detail: `the update deletes or rewrites ${deletions} existing range(s); this document ` +
286
+ `accepts appends only`,
287
+ });
288
+ }
289
+ }
290
+ // ── Pluggable rules (DOD-DOC-SCREEN-1 registers here) ───────────────────
291
+ for (const [name, rule] of this.#rules) {
292
+ const refusal = rule(diff, context);
293
+ if (refusal) {
294
+ return this.#quarantine(context, update, { admit: false, ...refusal, rule: name });
295
+ }
296
+ }
297
+ this.#logger.info("document.update.admitted", {
298
+ documentId: context.documentId,
299
+ senderAgentId: context.senderAgentId,
300
+ insertedChars: diff.inserted.length,
301
+ deletedChars: diff.deletedChars,
302
+ });
303
+ return { admit: true, projectedDiff: diff };
304
+ }
305
+ /**
306
+ * (h) Every client whose CLOCK ADVANCED must be one this peer is bound to.
307
+ *
308
+ * The first version asked whether a clientID was NEW — exempting any already present in the
309
+ * accepted state, including the document owner's own. That is exactly the attack AC (h) was
310
+ * written from, and it was measured working: integrate the accepted state, THEN set clientID to
311
+ * the owner's, and the forged update is admitted and attributed to the owner ("FORGED honest
312
+ * content"), with an empty pending set so rule (a) cannot see it either. Setting the clientID
313
+ * after integration sidesteps Yjs's own "Changed the client-id" guard; a hand-rolled encoder
314
+ * needs no trick at all.
315
+ *
316
+ * Advancement is the right question for INSERTIONS: a peer never legitimately advances another
317
+ * client's clock. Reusing a clock position below the owner's is deduped by Yjs, and one above
318
+ * leaves a gap that rule (a) catches.
319
+ *
320
+ * IT DOES NOT COVER DELETIONS. A Yjs delete set carries no clientID and advances no clock, so a
321
+ * BOUND peer can delete the owner's content and this rule sees nothing — which is legitimate
322
+ * CRDT behaviour for an authorized writer, not a forgery, but it means `append_only` is the only
323
+ * thing standing between a bound peer and erasure, and it defaults off. Recorded rather than
324
+ * left for a reader to infer, and carried as an AC on DOD-DOC-REJECT-1.
325
+ */
326
+ #checkAuthorship(accepted, shadow, context) {
327
+ const before = Y.decodeStateVector(Y.encodeStateVector(accepted));
328
+ const after = Y.decodeStateVector(Y.encodeStateVector(shadow));
329
+ const bound = new Set(context.senderClientIds);
330
+ const advanced = [];
331
+ for (const [client, clock] of after) {
332
+ if (clock > (before.get(client) ?? 0))
333
+ advanced.push(client);
334
+ }
335
+ const unbound = advanced.filter((c) => !bound.has(c));
336
+ if (unbound.length > 0) {
337
+ return {
338
+ admit: false,
339
+ reason: "document_update_unbound_client",
340
+ detail: `clientID(s) ${unbound.join(", ")} advanced but are not bound to sender ` +
341
+ `${context.senderAgentId.slice(0, 16)}… — either an update authored under another ` +
342
+ `party's identity, or relayed third-party operations, which V1 does not support ` +
343
+ `(hub-and-spoke is deferred, §11.1)`,
344
+ };
345
+ }
346
+ return null;
347
+ }
348
+ #quarantine(context, update, verdict) {
349
+ // A quarantined update is HELD — never admitted, never discarded — so the record carries what
350
+ // is needed to act on it: which document, which peer, and why (§3.2).
351
+ this.#logger.warn("document.update.quarantined", {
352
+ documentId: context.documentId,
353
+ senderAgentId: context.senderAgentId,
354
+ reason: verdict.reason,
355
+ detail: verdict.detail,
356
+ limit: verdict.limit,
357
+ rule: verdict.rule,
358
+ });
359
+ // COPY. Storing the caller's reference would let a pooled network read buffer be reused
360
+ // underneath us, so DOD-DOC-REJECT-1 would hash bytes that are no longer the ones refused —
361
+ // into a 0x05 leaf, silently.
362
+ return { ...verdict, quarantined: new Uint8Array(update) };
363
+ }
364
+ }
365
+ function insertedText(before, after) {
366
+ if (after.length <= before.length)
367
+ return "";
368
+ let from = 0;
369
+ while (from < before.length && before[from] === after[from])
370
+ from++;
371
+ const tail = before.length - from;
372
+ return after.slice(from, after.length - tail);
373
+ }
374
+ function deletedCount(before, after) {
375
+ let from = 0;
376
+ const max = Math.min(before.length, after.length);
377
+ while (from < max && before[from] === after[from])
378
+ from++;
379
+ let suffix = 0;
380
+ while (suffix < before.length - from &&
381
+ suffix < after.length - from &&
382
+ before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
383
+ suffix++;
384
+ }
385
+ return Math.max(0, before.length - from - suffix);
386
+ }
387
+ /**
388
+ * The deepest nesting across EVERY root, whatever shape that root is.
389
+ *
390
+ * Two things this must not do, both measured going wrong:
391
+ *
392
+ * 1. **It must not recurse.** `YMap.toJSON()` and a recursive depth walk are mutually recursive,
393
+ * and past roughly a thousand levels they throw `RangeError: Maximum call stack size exceeded`.
394
+ * A `catch` around that returned 0, which INVERTED the rule — 30 levels were caught and 5,000
395
+ * sailed through, so the deeper the attack the more certain it passed. Worse, it converted the
396
+ * gate's own fail-safe catch-all into an admission. This walk is iterative and bails the moment
397
+ * it passes the limit it is enforcing, so it never goes deeper than it has to.
398
+ * 2. **It must not instantiate roots by a guessed type.** `doc.getMap(name)` MIGRATES an
399
+ * uninstantiated root to a map in place, so an Array- or Text-shaped root became an empty map
400
+ * and reported depth 0 — the same bypass, reached with a different type. Walking Yjs's own item
401
+ * graph is shape-agnostic: a nested type is a nested type whether it hangs off `_map` or the
402
+ * `_start` list.
403
+ */
404
+ function deepestRoot(doc, bailAt) {
405
+ let deepest = 0;
406
+ const stack = [];
407
+ for (const root of doc.share.values()) {
408
+ stack.push({ type: root, depth: 0 });
409
+ }
410
+ while (stack.length > 0) {
411
+ const { type, depth } = stack.pop();
412
+ if (depth > deepest)
413
+ deepest = depth;
414
+ // Never walk deeper than the limit under enforcement — the answer cannot change.
415
+ if (deepest > bailAt)
416
+ return deepest;
417
+ const internals = type;
418
+ if (internals._map) {
419
+ for (const item of internals._map.values()) {
420
+ const nested = nestedType(item);
421
+ if (nested)
422
+ stack.push({ type: nested, depth: depth + 1 });
423
+ }
424
+ }
425
+ let item = internals._start;
426
+ while (item) {
427
+ const nested = nestedType(item);
428
+ if (nested)
429
+ stack.push({ type: nested, depth: depth + 1 });
430
+ item = item.right;
431
+ }
432
+ }
433
+ return deepest;
434
+ }
435
+ /** The type nested inside an item, or null when the item holds a plain value. */
436
+ function nestedType(item) {
437
+ const content = item?.content;
438
+ if (content instanceof Y.ContentType) {
439
+ return content.type;
440
+ }
441
+ return null;
442
+ }
443
+ /**
444
+ * How many ranges this update deletes — the basis for `append_only`.
445
+ *
446
+ * The update's own delete set, rather than a diff of the projected document. Measured, that is
447
+ * the right question and a projection diff was not: this is root- and type-agnostic (so a peer
448
+ * cannot escape by naming its own roots), a map-key rewrite deletes the old item so §16.7-1's
449
+ * "deletes OR EDITS" falls out, and growing a nested entry deletes nothing so it is admitted.
450
+ */
451
+ function countDeletions(update) {
452
+ try {
453
+ const { ds } = Y.decodeUpdate(update);
454
+ let total = 0;
455
+ for (const ranges of ds.clients.values())
456
+ total += ranges.length;
457
+ return total;
458
+ }
459
+ catch {
460
+ // Undecodable here means the malformed check upstream already refused it. Inventing an
461
+ // append_only violation from a decode failure would name the wrong cause.
462
+ return 0;
463
+ }
464
+ }
465
+ //# sourceMappingURL=document-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-gate.js","sourceRoot":"","sources":["../src/document-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * DOD-DOC-WRITE-1 — the write path (§16.2).
3
+ *
4
+ * **The document is a real file.** The agent edits it with Read/Edit like any other file, and the
5
+ * human can open the same file; the daemon diffs it at publish and converts the diff into Yjs
6
+ * operations. No new editing surface exists — that is the design, not a shortcut.
7
+ *
8
+ * ── THE FOLD ORDER IS THE DESIGN ──────────────────────────────────────────────────────────────
9
+ *
10
+ * On admission (§16.2): fold the agent's UNPUBLISHED file edits into the `Y.Doc` as local
11
+ * operations FIRST, then merge the incoming update, then rewrite the file. Reversing the first
12
+ * two silently destroys the agent's unpublished work, and §4.1's overlap flag — which IS whether
13
+ * the merge touched what the fold just wrote — disappears with it.
14
+ *
15
+ * ── WHY THE PROJECTION IS PERSISTED ───────────────────────────────────────────────────────────
16
+ *
17
+ * §16.2 says publish diffs the file against "the last-known projection", and that baseline has to
18
+ * outlive the process. Diffing against the DOC's current text instead looks equivalent and is not:
19
+ * `admit` merges into the doc and rewrites the file as two separate steps, so a crash between them
20
+ * leaves a stale file beside an advanced doc. Diffing that against the doc reads the peer's
21
+ * admitted content as something the agent deleted, and publishes the deletion as deliberate
22
+ * intent. Measured, so the projection is written to disk beside the document and read back.
23
+ */
24
+ import * as Y from "yjs";
25
+ import type { DocumentEngine } from "./document-engine.js";
26
+ import type { Logger } from "./types.js";
27
+ export type DocumentWriteFailure = "document_file_missing" | "document_file_unparseable" | "document_file_stale" | "document_file_changed_during_merge" | "document_projection_missing" | "document_file_unreadable" | "document_type_unsupported" | "document_id_invalid";
28
+ export declare class DocumentWriteError extends Error {
29
+ readonly reason: DocumentWriteFailure;
30
+ readonly detail: string;
31
+ constructor(reason: DocumentWriteFailure, detail: string);
32
+ }
33
+ export interface AdmitResult {
34
+ /**
35
+ * §4.1's overlap flag: the merge touched a region (or, for JSON, a key) holding UNPUBLISHED
36
+ * local edits. Not a conflict warning — Yjs resolved it — but a signal that the agent should
37
+ * re-read the merged projection before building on what it thought it had written.
38
+ */
39
+ overlap: boolean;
40
+ }
41
+ export declare class DocumentWritePath {
42
+ #private;
43
+ readonly engine: DocumentEngine;
44
+ constructor(engine: DocumentEngine, workspaceRoot: string, logger: Logger);
45
+ /**
46
+ * The file a document lives at. Keyed on the FULL `agent_id`/`document_id` — truncating them
47
+ * would let two documents sharing a prefix collide onto one file and destroy each other, and
48
+ * both ids are validated because they become path components.
49
+ */
50
+ filePath(agentId: string, documentId: string, documentType: string): string;
51
+ /** Write the document's projection to disk and record it as the diff baseline. */
52
+ materialize(agentId: string, documentId: string, documentType: string, doc: Y.Doc): Promise<string>;
53
+ /**
54
+ * Diff the file against the LAST-KNOWN PROJECTION and apply the difference as local operations.
55
+ * Returns the resulting update, or null when the file is unchanged.
56
+ *
57
+ * Null is a real answer: publish is an intent, so a publish with nothing to say must produce no
58
+ * envelope rather than an empty one that still costs a leaf and a round trip.
59
+ */
60
+ publish(agentId: string, documentId: string, documentType: string, doc: Y.Doc): Promise<Uint8Array | null>;
61
+ /** Admit a peer's update: fold local edits, merge, rewrite, report overlap. */
62
+ admit(agentId: string, documentId: string, documentType: string, doc: Y.Doc, incoming: Uint8Array): Promise<AdmitResult>;
63
+ }
64
+ /**
65
+ * Line-level hunks between two texts, as character offsets into `before`.
66
+ *
67
+ * Lines are represented as CHUNKS — each line carries its own trailing newline, except the last,
68
+ * which does not. That representation is what makes newline handling fall out instead of being
69
+ * special-cased: appending a line to "a\nb" turns the chunk "b" into "b\n" + "c", so the LCS
70
+ * sees the last line as changed and the hunk replaces "b" with "b\nc". A hand-rolled
71
+ * prefix/suffix trim got this wrong for every edit that changed the line count — measured, four
72
+ * of six ordinary markdown edits published text that was not what the file said.
73
+ *
74
+ * A real LCS (not a prefix/suffix trim) is required because the whole point is to emit SEPARATE
75
+ * hunks for separated edits: one span from the first change to the last would delete and
76
+ * re-insert everything between them as new operations, resurrecting text a peer concurrently
77
+ * deleted. Under publish-on-intent, multi-edit publishes are the modal case.
78
+ */
79
+ export declare function lineHunks(before: string, after: string): Array<{
80
+ from: number;
81
+ to: number;
82
+ insert: string;
83
+ }>;
84
+ //# sourceMappingURL=document-write-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-write-path.d.ts","sourceRoot":"","sources":["../src/document-write-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAYzC,MAAM,MAAM,oBAAoB,GAC5B,uBAAuB,GACvB,2BAA2B,GAC3B,qBAAqB,GACrB,oCAAoC,GACpC,6BAA6B,GAC7B,0BAA0B,GAC1B,2BAA2B,GAC3B,qBAAqB,CAAC;AAE1B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM;CAMzD;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,iBAAiB;;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBAIpB,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAMzE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAO3E,kFAAkF;IAC5E,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IAUzG;;;;;;OAMG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA0ChH,+EAA+E;IACzE,KAAK,CACT,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,CAAC,CAAC,GAAG,EACV,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,WAAW,CAAC;CAySxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAiDrD"}