@cello-protocol/daemon 0.0.120 → 0.0.122
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/dist/content-park.d.ts +1 -1
- package/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +64 -12
- package/dist/content-park.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +46 -32
- package/dist/daemon.js.map +1 -1
- package/dist/document-delivery-transport.d.ts +78 -0
- package/dist/document-delivery-transport.d.ts.map +1 -0
- package/dist/document-delivery-transport.js +109 -0
- package/dist/document-delivery-transport.js.map +1 -0
- package/dist/document-delivery.d.ts +130 -0
- package/dist/document-delivery.d.ts.map +1 -0
- package/dist/document-delivery.js +246 -0
- package/dist/document-delivery.js.map +1 -0
- package/dist/document-engine.d.ts +134 -0
- package/dist/document-engine.d.ts.map +1 -0
- package/dist/document-engine.js +281 -0
- package/dist/document-engine.js.map +1 -0
- package/dist/document-gate.d.ts +139 -0
- package/dist/document-gate.d.ts.map +1 -0
- package/dist/document-gate.js +465 -0
- package/dist/document-gate.js.map +1 -0
- package/dist/document-handshake.d.ts +88 -0
- package/dist/document-handshake.d.ts.map +1 -0
- package/dist/document-handshake.js +239 -0
- package/dist/document-handshake.js.map +1 -0
- package/dist/document-lifecycle.d.ts +104 -0
- package/dist/document-lifecycle.d.ts.map +1 -0
- package/dist/document-lifecycle.js +363 -0
- package/dist/document-lifecycle.js.map +1 -0
- package/dist/document-notify.d.ts +130 -0
- package/dist/document-notify.d.ts.map +1 -0
- package/dist/document-notify.js +313 -0
- package/dist/document-notify.js.map +1 -0
- package/dist/document-reachability.d.ts +42 -0
- package/dist/document-reachability.d.ts.map +1 -0
- package/dist/document-reachability.js +72 -0
- package/dist/document-reachability.js.map +1 -0
- package/dist/document-rejection.d.ts +224 -0
- package/dist/document-rejection.d.ts.map +1 -0
- package/dist/document-rejection.js +374 -0
- package/dist/document-rejection.js.map +1 -0
- package/dist/document-store.d.ts +269 -0
- package/dist/document-store.d.ts.map +1 -0
- package/dist/document-store.js +752 -0
- package/dist/document-store.js.map +1 -0
- package/dist/document-write-path.d.ts +84 -0
- package/dist/document-write-path.d.ts.map +1 -0
- package/dist/document-write-path.js +412 -0
- package/dist/document-write-path.js.map +1 -0
- package/dist/line-lcs.d.ts +51 -0
- package/dist/line-lcs.d.ts.map +1 -0
- package/dist/line-lcs.js +71 -0
- package/dist/line-lcs.js.map +1 -0
- package/dist/reconnect-drain.d.ts +15 -0
- package/dist/reconnect-drain.d.ts.map +1 -0
- package/dist/reconnect-drain.js +51 -0
- package/dist/reconnect-drain.js.map +1 -0
- package/dist/session-node-manager.d.ts +39 -0
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +152 -12
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-relay-client.d.ts +2 -0
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +16 -1
- package/dist/session-relay-client.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-DOC-LIFECYCLE-1 — the verbs (§3.5 + §16.4).
|
|
3
|
+
*
|
|
4
|
+
* Three ways a document can end, and they are not interchangeable. Conflating any two of them
|
|
5
|
+
* makes a promise the protocol cannot keep:
|
|
6
|
+
*
|
|
7
|
+
* close BILATERAL. Both sides ack and the document is complete by agreement. One side's
|
|
8
|
+
* close is a REQUEST — treating it as a conclusion would tell this operator the
|
|
9
|
+
* collaboration ended while the peer is still writing into it.
|
|
10
|
+
* kill UNILATERAL. Stop accepting and publishing, notify the peer, and KEEP the local copy
|
|
11
|
+
* and the log. The peer keeps what it holds, and that is said out loud: it is the one
|
|
12
|
+
* thing an operator is most likely to assume a kill undoes, and the one thing it
|
|
13
|
+
* cannot.
|
|
14
|
+
* withdraw ONE UNDELIVERED update. A local rollback plus a withdrawal record BESIDE the
|
|
15
|
+
* original — marked, never deleted, because a hole in an append-only log is
|
|
16
|
+
* indistinguishable from tampering. Once the peer has it, withdrawal is refused rather
|
|
17
|
+
* than faked.
|
|
18
|
+
*
|
|
19
|
+
* ── THE KILL SWITCH (§16.7-11) ────────────────────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* A platform-paused agent refuses OUTBOUND publishes loudly, still admits INBOUND mechanically,
|
|
22
|
+
* and suppresses notifications. The asymmetry is deliberate. Refusing inbound would surface the
|
|
23
|
+
* pause to the peer as a protocol fault and force a rejection round for something that is not
|
|
24
|
+
* their doing; refusing outbound silently would leave the operator writing into a document that is
|
|
25
|
+
* going nowhere, with their work piling up locally and no sign anything is wrong.
|
|
26
|
+
*/
|
|
27
|
+
const CREATE_LIFECYCLE_SQL = `
|
|
28
|
+
CREATE TABLE IF NOT EXISTS document_closes (
|
|
29
|
+
owner_agent_id TEXT NOT NULL,
|
|
30
|
+
document_id TEXT NOT NULL,
|
|
31
|
+
-- WHO closed, not how many closes there were. Counting would let one party close a document
|
|
32
|
+
-- unilaterally by asking twice, which is precisely the bilateral guarantee gone.
|
|
33
|
+
closed_by TEXT NOT NULL,
|
|
34
|
+
created_at INTEGER NOT NULL,
|
|
35
|
+
PRIMARY KEY (owner_agent_id, document_id, closed_by)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- WITHDRAWALS live here, NOT in document_envelopes, and that placement is the fix for three
|
|
39
|
+
-- separate defects rather than a filing preference.
|
|
40
|
+
--
|
|
41
|
+
-- 1. CHAIN. A withdrawal is local-only by design: it is never delivered (the update it concerns
|
|
42
|
+
-- was never delivered, so the peer has nothing to act on). As an envelope it still advanced
|
|
43
|
+
-- our per-sender chain, so our NEXT update chained onto a node the peer will never hold and
|
|
44
|
+
-- the peer refused it with document_chain_broken — sending an operator to the chain layer for
|
|
45
|
+
-- a withdrawal-scoping bug, and leaving the document unopenable after their next restart.
|
|
46
|
+
-- Chaining it to the last DELIVERABLE envelope instead would fork our chain, since the next
|
|
47
|
+
-- update claims the same predecessor. It cannot be a node in that chain at all.
|
|
48
|
+
-- 2. CRYPTO. As an envelope it needed a signature and a state vector, and it had neither of its
|
|
49
|
+
-- own — the first version copied the ORIGINAL's, putting a real Ed25519 signature made over a
|
|
50
|
+
-- different record onto a permanent append-only row. document-rejection.ts states the rule
|
|
51
|
+
-- this violated in writing: required, never fabricated.
|
|
52
|
+
-- 3. REPLAY. An envelope row is something rebuildSnapshot must reason about; an audit row is not.
|
|
53
|
+
--
|
|
54
|
+
-- Same shape as document_quarantine, for the same reason: audit that must survive, must not be
|
|
55
|
+
-- replayed, and must not be chained.
|
|
56
|
+
CREATE TABLE IF NOT EXISTS document_withdrawals (
|
|
57
|
+
owner_agent_id TEXT NOT NULL,
|
|
58
|
+
document_id TEXT NOT NULL,
|
|
59
|
+
envelope_hash TEXT NOT NULL,
|
|
60
|
+
created_at INTEGER NOT NULL,
|
|
61
|
+
PRIMARY KEY (owner_agent_id, document_id, envelope_hash)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS agent_platform_pause (
|
|
65
|
+
agent_id TEXT NOT NULL PRIMARY KEY,
|
|
66
|
+
paused INTEGER NOT NULL,
|
|
67
|
+
updated_at INTEGER NOT NULL
|
|
68
|
+
);
|
|
69
|
+
`;
|
|
70
|
+
export class DocumentLifecycle {
|
|
71
|
+
#store;
|
|
72
|
+
#logger;
|
|
73
|
+
#notifier;
|
|
74
|
+
#rollback;
|
|
75
|
+
constructor(store, logger, notifier,
|
|
76
|
+
/**
|
|
77
|
+
* Undo one envelope's operations on the LIVE document, as inverses. Injected because the live
|
|
78
|
+
* `Y.Doc` and its UndoManager belong to the engine, not here — and REQUIRED, because a default
|
|
79
|
+
* that quietly did nothing would restore exactly the defect this argument exists to fix.
|
|
80
|
+
*/
|
|
81
|
+
rollback) {
|
|
82
|
+
this.#store = store;
|
|
83
|
+
this.#logger = logger;
|
|
84
|
+
this.#notifier = notifier;
|
|
85
|
+
this.#rollback = rollback;
|
|
86
|
+
this.#store.rawDb.exec(CREATE_LIFECYCLE_SQL);
|
|
87
|
+
}
|
|
88
|
+
list(ownerAgentId, nowMs) {
|
|
89
|
+
// Every pending envelope regardless of schedule: the operator is asking "what has not reached
|
|
90
|
+
// my peer", not "what is due for a retry in the next few seconds".
|
|
91
|
+
const pending = this.#store.pendingDeliveries(ownerAgentId, Number.MAX_SAFE_INTEGER);
|
|
92
|
+
const pendingByDocument = new Map();
|
|
93
|
+
for (const e of pending) {
|
|
94
|
+
pendingByDocument.set(e.documentId, (pendingByDocument.get(e.documentId) ?? 0) + 1);
|
|
95
|
+
}
|
|
96
|
+
void nowMs;
|
|
97
|
+
return this.#store.listDocuments(ownerAgentId).map((d) => ({
|
|
98
|
+
documentId: d.documentId,
|
|
99
|
+
peerAgentId: d.peerAgentId,
|
|
100
|
+
documentType: d.documentType,
|
|
101
|
+
assuranceTier: "authenticated",
|
|
102
|
+
epochId: 0,
|
|
103
|
+
status: d.status,
|
|
104
|
+
pendingDeliveries: pendingByDocument.get(d.documentId) ?? 0,
|
|
105
|
+
closePending: this.#hasClosed(ownerAgentId, d.documentId, ownerAgentId) &&
|
|
106
|
+
!this.#hasClosed(ownerAgentId, d.documentId, d.peerAgentId),
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
/** Our half of a bilateral close. Completes only when the peer's half is also on record. */
|
|
110
|
+
async close(ownerAgentId, documentId, nowMs) {
|
|
111
|
+
const doc = this.#store.getDocument(ownerAgentId, documentId);
|
|
112
|
+
if (!doc) {
|
|
113
|
+
return { ok: false, reason: "document_unknown", detail: `no document ${documentId.slice(0, 16)}…` };
|
|
114
|
+
}
|
|
115
|
+
this.#recordClose(ownerAgentId, documentId, ownerAgentId, nowMs);
|
|
116
|
+
const notified = await this.#notifier.notifyPeer(documentId, "close");
|
|
117
|
+
if (!notified.ok) {
|
|
118
|
+
this.#logger.warn("document.close.peer_not_notified", { documentId, reason: notified.reason });
|
|
119
|
+
}
|
|
120
|
+
this.#settleClose(ownerAgentId, documentId, doc.peerAgentId);
|
|
121
|
+
this.#logger.info("document.close.requested", { documentId });
|
|
122
|
+
return { ok: true };
|
|
123
|
+
}
|
|
124
|
+
/** The peer's half, arriving over the session. */
|
|
125
|
+
recordPeerClose(ownerAgentId, documentId, peerAgentId, nowMs) {
|
|
126
|
+
const doc = this.#store.getDocument(ownerAgentId, documentId);
|
|
127
|
+
if (!doc) {
|
|
128
|
+
// Without this the row is written for a document that does not exist — there is no foreign
|
|
129
|
+
// key on this table — and `setDocumentStatus` updates zero rows and returns silently.
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: "document_unknown",
|
|
133
|
+
detail: `no document ${documentId.slice(0, 16)}… for this agent`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (peerAgentId !== doc.peerAgentId) {
|
|
137
|
+
// THE BILATERAL GUARANTEE. The closer id came from the caller and was written as `closed_by`
|
|
138
|
+
// and settled against ITSELF, so any second distinct string — a stale contact id, a
|
|
139
|
+
// pubkey-vs-name mismatch, a hostile session — plus our own close flipped the document to
|
|
140
|
+
// closed. The whole point of recording WHO closed is defeated if who is never checked.
|
|
141
|
+
this.#logger.warn("document.close.not_peer", {
|
|
142
|
+
documentId,
|
|
143
|
+
claimedBy: peerAgentId,
|
|
144
|
+
peerAgentId: doc.peerAgentId,
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: "document_close_not_peer",
|
|
149
|
+
detail: `${peerAgentId} is not this document's peer (${doc.peerAgentId}), so their close is not ` +
|
|
150
|
+
`the other half of a bilateral close`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
this.#recordClose(ownerAgentId, documentId, doc.peerAgentId, nowMs);
|
|
154
|
+
this.#settleClose(ownerAgentId, documentId, doc.peerAgentId);
|
|
155
|
+
this.#logger.info("document.close.peer_requested", { documentId, peerAgentId: doc.peerAgentId });
|
|
156
|
+
return { ok: true };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Unilateral end. Local, and deliberately not contingent on the peer hearing about it — a
|
|
160
|
+
* decision to stop that depends on the other party being online is not a decision to stop.
|
|
161
|
+
*/
|
|
162
|
+
async kill(ownerAgentId, documentId, nowMs) {
|
|
163
|
+
const doc = this.#store.getDocument(ownerAgentId, documentId);
|
|
164
|
+
if (!doc) {
|
|
165
|
+
return { ok: false, reason: "document_unknown", detail: `no document ${documentId.slice(0, 16)}…` };
|
|
166
|
+
}
|
|
167
|
+
void nowMs;
|
|
168
|
+
this.#store.setDocumentStatus(ownerAgentId, documentId, "killed");
|
|
169
|
+
const notified = await this.#notifier.notifyPeer(documentId, "kill");
|
|
170
|
+
if (!notified.ok) {
|
|
171
|
+
// Reported, not swallowed: a kill the peer never heard about leaves them publishing into a
|
|
172
|
+
// document that will never answer, and the operator is the one who needs to know that.
|
|
173
|
+
this.#logger.error("document.kill.peer_not_notified", {
|
|
174
|
+
documentId,
|
|
175
|
+
peerAgentId: doc.peerAgentId,
|
|
176
|
+
reason: notified.reason,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
this.#logger.info("document.killed", { documentId, peerNotified: notified.ok });
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
peerNotified: notified.ok,
|
|
183
|
+
note: "this document no longer accepts or publishes updates, and your local copy and log are " +
|
|
184
|
+
"retained. Your peer keeps what it holds — a kill stops the collaboration, it does not " +
|
|
185
|
+
"retract content they already have.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Withdraw ONE undelivered update: a withdrawal record beside the original.
|
|
190
|
+
*
|
|
191
|
+
* Every refusal here is the same shape — say no rather than produce a record that claims
|
|
192
|
+
* something untrue about the log.
|
|
193
|
+
*/
|
|
194
|
+
withdraw(ownerAgentId, documentId, envelopeHash, nowMs) {
|
|
195
|
+
const log = this.#store.getEnvelopeLog(ownerAgentId, documentId);
|
|
196
|
+
const original = log.find((e) => e.envelopeHash === envelopeHash);
|
|
197
|
+
if (!original) {
|
|
198
|
+
// A withdrawal record pointing at nothing is worse than a refusal: it is a permanent claim
|
|
199
|
+
// in an append-only store about an envelope that never existed.
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
reason: "document_envelope_unknown",
|
|
203
|
+
detail: `no envelope ${envelopeHash.slice(0, 16)}… in this document's log`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (original.senderAgentId !== ownerAgentId) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
reason: "document_not_author",
|
|
210
|
+
detail: `envelope ${envelopeHash.slice(0, 16)}… was authored by ${original.senderAgentId}, not by you`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (original.ackedAtMs != null) {
|
|
214
|
+
// The honest refusal. Withdrawing a delivered update would tell the operator their content
|
|
215
|
+
// was retracted while the peer is holding it — the promise this module refuses to make.
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: "document_already_delivered",
|
|
219
|
+
detail: `envelope ${envelopeHash.slice(0, 16)}… has already been delivered and acknowledged — ` +
|
|
220
|
+
`your peer holds it, so it cannot be withdrawn. Publish a superseding update instead.`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// THE LOCAL ROLLBACK — the half that was missing. Writing only the record left the original in
|
|
224
|
+
// the log WITH its payload, and replay applies every update payload in order, so the withdrawn
|
|
225
|
+
// text stayed in the operator's own document and came back on every rebuild. The operator was
|
|
226
|
+
// told their update was withdrawn while their file still contained it.
|
|
227
|
+
//
|
|
228
|
+
// Rolled back as INVERSES through the same undo path a rejection uses, never by dropping the
|
|
229
|
+
// payload: our own later work may be causally stacked on these operations, and REJECT-1
|
|
230
|
+
// measured what removing them costs — everything after stays pending forever and the document
|
|
231
|
+
// silently loses the legitimate work. The inverse enters the log on the next ordinary publish,
|
|
232
|
+
// computed from the live document, which is also why neither the original nor the inverse is
|
|
233
|
+
// ever delivered: the peer holds neither, and the next publish carries the net effect.
|
|
234
|
+
const rolledBack = this.#rollback(ownerAgentId, documentId, envelopeHash);
|
|
235
|
+
if (!rolledBack.ok) {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
reason: "document_withdraw_rollback_failed",
|
|
239
|
+
detail: `the local rollback did not happen (${rolledBack.reason}), so nothing was withdrawn — ` +
|
|
240
|
+
`reporting success here would tell you your update was retracted while your file still ` +
|
|
241
|
+
`contains it`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const info = this.#store.rawDb
|
|
245
|
+
.prepare(`INSERT INTO document_withdrawals (owner_agent_id, document_id, envelope_hash, created_at)
|
|
246
|
+
VALUES (?, ?, ?, ?)
|
|
247
|
+
ON CONFLICT (owner_agent_id, document_id, envelope_hash) DO NOTHING`)
|
|
248
|
+
.run(ownerAgentId, documentId, envelopeHash, nowMs);
|
|
249
|
+
if (Number(info.changes) === 0) {
|
|
250
|
+
// Already withdrawn. Reported rather than inferred — the earlier version ignored the write's
|
|
251
|
+
// outcome and returned success for a no-op.
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
reason: "document_already_withdrawn",
|
|
255
|
+
detail: `envelope ${envelopeHash.slice(0, 16)}… was already withdrawn`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
this.#logger.info("document.withdrawn", { documentId, envelopeHash });
|
|
259
|
+
return { ok: true };
|
|
260
|
+
}
|
|
261
|
+
// ─── the kill switch (§16.7-11) ───────────────────────────────────────────
|
|
262
|
+
setPlatformPaused(agentId, paused, nowMs) {
|
|
263
|
+
this.#store.rawDb
|
|
264
|
+
.prepare(`INSERT INTO agent_platform_pause (agent_id, paused, updated_at) VALUES (?, ?, ?)
|
|
265
|
+
ON CONFLICT (agent_id) DO UPDATE SET paused = excluded.paused, updated_at = excluded.updated_at`)
|
|
266
|
+
// Hard-wired to 0 before, so every row read 1970 — on the KILL SWITCH, where "when was this
|
|
267
|
+
// agent paused by the platform" is the audit fact of the whole feature. The tell was that it
|
|
268
|
+
// was the only method in the class taking no clock.
|
|
269
|
+
.run(agentId, paused ? 1 : 0, nowMs);
|
|
270
|
+
this.#logger.warn("agent.platform_pause.changed", { agentId, paused });
|
|
271
|
+
}
|
|
272
|
+
isPlatformPaused(agentId) {
|
|
273
|
+
const r = this.#store.rawDb
|
|
274
|
+
.prepare("SELECT paused FROM agent_platform_pause WHERE agent_id = ?")
|
|
275
|
+
.get(agentId);
|
|
276
|
+
return (r?.paused ?? 0) === 1;
|
|
277
|
+
}
|
|
278
|
+
/** Outbound. Refused loudly while paused, and while the document has ended. */
|
|
279
|
+
canPublish(ownerAgentId, documentId) {
|
|
280
|
+
if (this.isPlatformPaused(ownerAgentId)) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
reason: "agent_platform_paused",
|
|
284
|
+
detail: `this agent is paused by the platform, so nothing can be published. Your work is kept ` +
|
|
285
|
+
`locally and will publish once the agent is unpaused.`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const doc = this.#store.getDocument(ownerAgentId, documentId);
|
|
289
|
+
if (!doc) {
|
|
290
|
+
return { ok: false, reason: "document_unknown", detail: `no document ${documentId.slice(0, 16)}…` };
|
|
291
|
+
}
|
|
292
|
+
if (doc.status === "closed") {
|
|
293
|
+
return { ok: false, reason: "document_closed", detail: "this document was closed by agreement" };
|
|
294
|
+
}
|
|
295
|
+
if (doc.status === "killed") {
|
|
296
|
+
return { ok: false, reason: "document_killed", detail: "this document was ended locally" };
|
|
297
|
+
}
|
|
298
|
+
if (doc.status === "stalled") {
|
|
299
|
+
// REJECT-1 stalls a document after its retry rounds. A stalled document has stopped
|
|
300
|
+
// converging, so publishing into it is the same lie as publishing into a killed one — and
|
|
301
|
+
// two partial gates that each know half the terminal states is how a caller ends up wrong
|
|
302
|
+
// about the other half.
|
|
303
|
+
return {
|
|
304
|
+
ok: false,
|
|
305
|
+
reason: "document_stalled",
|
|
306
|
+
detail: "this document stopped accepting updates after repeated rejections",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return { ok: true };
|
|
310
|
+
}
|
|
311
|
+
/** Inbound. A pause does NOT refuse it — see the header. */
|
|
312
|
+
canAdmit(ownerAgentId, documentId) {
|
|
313
|
+
const doc = this.#store.getDocument(ownerAgentId, documentId);
|
|
314
|
+
if (!doc) {
|
|
315
|
+
return { ok: false, reason: "document_unknown", detail: `no document ${documentId.slice(0, 16)}…` };
|
|
316
|
+
}
|
|
317
|
+
if (doc.status === "killed") {
|
|
318
|
+
return { ok: false, reason: "document_killed", detail: "this document was ended locally" };
|
|
319
|
+
}
|
|
320
|
+
if (doc.status === "closed") {
|
|
321
|
+
return { ok: false, reason: "document_closed", detail: "this document was closed by agreement" };
|
|
322
|
+
}
|
|
323
|
+
if (doc.status === "stalled") {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
reason: "document_stalled",
|
|
327
|
+
detail: "this document stopped accepting updates after repeated rejections",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { ok: true };
|
|
331
|
+
}
|
|
332
|
+
shouldNotify(agentId) {
|
|
333
|
+
return !this.isPlatformPaused(agentId);
|
|
334
|
+
}
|
|
335
|
+
// ─── internals ────────────────────────────────────────────────────────────
|
|
336
|
+
#recordClose(ownerAgentId, documentId, closedBy, nowMs) {
|
|
337
|
+
this.#store.rawDb
|
|
338
|
+
.prepare(`INSERT INTO document_closes (owner_agent_id, document_id, closed_by, created_at)
|
|
339
|
+
VALUES (?, ?, ?, ?)
|
|
340
|
+
ON CONFLICT (owner_agent_id, document_id, closed_by) DO NOTHING`)
|
|
341
|
+
.run(ownerAgentId, documentId, closedBy, nowMs);
|
|
342
|
+
}
|
|
343
|
+
#hasClosed(ownerAgentId, documentId, who) {
|
|
344
|
+
const r = this.#store.rawDb
|
|
345
|
+
.prepare(`SELECT 1 AS present FROM document_closes
|
|
346
|
+
WHERE owner_agent_id = ? AND document_id = ? AND closed_by = ?`)
|
|
347
|
+
.get(ownerAgentId, documentId, who);
|
|
348
|
+
return r?.present === 1;
|
|
349
|
+
}
|
|
350
|
+
#settleClose(ownerAgentId, documentId, peerAgentId) {
|
|
351
|
+
// ONLY FROM ACTIVE. Unconditional, a peer close arriving after a unilateral kill overwrote
|
|
352
|
+
// `killed` with `closed` — the operator's own decision replaced by "closed by agreement", which
|
|
353
|
+
// is a different fact and the one they did not choose. Same for `stalled`.
|
|
354
|
+
if (this.#store.getDocument(ownerAgentId, documentId)?.status !== "active")
|
|
355
|
+
return;
|
|
356
|
+
if (this.#hasClosed(ownerAgentId, documentId, ownerAgentId) &&
|
|
357
|
+
this.#hasClosed(ownerAgentId, documentId, peerAgentId)) {
|
|
358
|
+
this.#store.setDocumentStatus(ownerAgentId, documentId, "closed");
|
|
359
|
+
this.#logger.info("document.closed", { documentId });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
//# sourceMappingURL=document-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-lifecycle.js","sourceRoot":"","sources":["../src/document-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAaH,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C5B,CAAC;AAiBF,MAAM,OAAO,iBAAiB;IACnB,MAAM,CAAgB;IACtB,OAAO,CAAS;IAChB,SAAS,CAAoB;IAC7B,SAAS,CAIgC;IAElD,YACE,KAAoB,EACpB,MAAc,EACd,QAA2B;IAC3B;;;;OAIG;IACH,QAC8C;QAE9C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,CAAC,YAAoB,EAAE,KAAa;QACtC,8FAA8F;QAC9F,mEAAmE;QACnE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACrF,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;QACpD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtF,CAAC;QACD,KAAK,KAAK,CAAC;QAEX,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzD,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,aAAa,EAAE,eAAe;YAC9B,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,iBAAiB,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAC3D,YAAY,EACV,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU,EAAE,YAAY,CAAC;gBACzD,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC;SAC9D,CAAC,CAAC,CAAC;IACN,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,KAAK,CAAC,YAAoB,EAAE,UAAkB,EAAE,KAAa;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,kCAAkC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,kDAAkD;IAClD,eAAe,CACb,YAAoB,EACpB,UAAkB,EAClB,WAAmB,EACnB,KAAa;QAEb,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,2FAA2F;YAC3F,sFAAsF;YACtF,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,kBAAkB;gBAC1B,MAAM,EAAE,eAAe,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB;aACjE,CAAC;QACJ,CAAC;QACD,IAAI,WAAW,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC;YACpC,6FAA6F;YAC7F,oFAAoF;YACpF,0FAA0F;YAC1F,uFAAuF;YACvF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBAC3C,UAAU;gBACV,SAAS,EAAE,WAAW;gBACtB,WAAW,EAAE,GAAG,CAAC,WAAW;aAC7B,CAAC,CAAC;YACH,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,yBAAyB;gBACjC,MAAM,EACJ,GAAG,WAAW,iCAAiC,GAAG,CAAC,WAAW,2BAA2B;oBACzF,qCAAqC;aACxC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QACjG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CACR,YAAoB,EACpB,UAAkB,EAClB,KAAa;QAKb,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;QACtG,CAAC;QACD,KAAK,KAAK,CAAC;QAEX,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,2FAA2F;YAC3F,uFAAuF;YACvF,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE;gBACpD,UAAU;gBACV,WAAW,EAAE,GAAG,CAAC,WAAW;gBAC5B,MAAM,EAAE,QAAQ,CAAC,MAAM;aACxB,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEhF,OAAO;YACL,EAAE,EAAE,IAAI;YACR,YAAY,EAAE,QAAQ,CAAC,EAAE;YACzB,IAAI,EACF,wFAAwF;gBACxF,wFAAwF;gBACxF,oCAAoC;SACvC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CACN,YAAoB,EACpB,UAAkB,EAClB,YAAoB,EACpB,KAAa;QAEb,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;QAClE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,2FAA2F;YAC3F,gEAAgE;YAChE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,2BAA2B;gBACnC,MAAM,EAAE,eAAe,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,0BAA0B;aAC3E,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,aAAa,KAAK,YAAY,EAAE,CAAC;YAC5C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,qBAAqB;gBAC7B,MAAM,EAAE,YAAY,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,QAAQ,CAAC,aAAa,cAAc;aACvG,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;YAC/B,2FAA2F;YAC3F,wFAAwF;YACxF,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,4BAA4B;gBACpC,MAAM,EACJ,YAAY,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,kDAAkD;oBACvF,sFAAsF;aACzF,CAAC;QACJ,CAAC;QAED,+FAA+F;QAC/F,+FAA+F;QAC/F,8FAA8F;QAC9F,uEAAuE;QACvE,EAAE;QACF,6FAA6F;QAC7F,wFAAwF;QACxF,8FAA8F;QAC9F,+FAA+F;QAC/F,6FAA6F;QAC7F,uFAAuF;QACvF,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;QAC1E,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,mCAAmC;gBAC3C,MAAM,EACJ,sCAAsC,UAAU,CAAC,MAAM,gCAAgC;oBACvF,wFAAwF;oBACxF,aAAa;aAChB,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK;aAC3B,OAAO,CACN;;6EAEqE,CACtE;aACA,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,6FAA6F;YAC7F,4CAA4C;YAC5C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,4BAA4B;gBACpC,MAAM,EAAE,YAAY,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,yBAAyB;aACvE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC;QACtE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,6EAA6E;IAE7E,iBAAiB,CAAC,OAAe,EAAE,MAAe,EAAE,KAAa;QAC/D,IAAI,CAAC,MAAM,CAAC,KAAK;aACd,OAAO,CACN;yGACiG,CAClG;YACD,4FAA4F;YAC5F,6FAA6F;YAC7F,oDAAoD;aACnD,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,gBAAgB,CAAC,OAAe;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK;aACxB,OAAO,CAAC,4DAA4D,CAAC;aACrE,GAAG,CAAC,OAAO,CAAoC,CAAC;QACnD,OAAO,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,+EAA+E;IAC/E,UAAU,CAAC,YAAoB,EAAE,UAAkB;QACjD,IAAI,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,uBAAuB;gBAC/B,MAAM,EACJ,uFAAuF;oBACvF,sDAAsD;aACzD,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;QACnG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;QAC7F,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,oFAAoF;YACpF,0FAA0F;YAC1F,0FAA0F;YAC1F,wBAAwB;YACxB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,kBAAkB;gBAC1B,MAAM,EAAE,mEAAmE;aAC5E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,4DAA4D;IAC5D,QAAQ,CAAC,YAAoB,EAAE,UAAkB;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;QAC7F,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;QACnG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,kBAAkB;gBAC1B,MAAM,EAAE,mEAAmE;aAC5E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,YAAY,CAAC,OAAe;QAC1B,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,6EAA6E;IAE7E,YAAY,CAAC,YAAoB,EAAE,UAAkB,EAAE,QAAgB,EAAE,KAAa;QACpF,IAAI,CAAC,MAAM,CAAC,KAAK;aACd,OAAO,CACN;;yEAEiE,CAClE;aACA,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IAED,UAAU,CAAC,YAAoB,EAAE,UAAkB,EAAE,GAAW;QAC9D,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK;aACxB,OAAO,CACN;yEACiE,CAClE;aACA,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAqC,CAAC;QAC1E,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,YAAY,CAAC,YAAoB,EAAE,UAAkB,EAAE,WAAmB;QACxE,2FAA2F;QAC3F,gGAAgG;QAChG,2EAA2E;QAC3E,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,QAAQ;YAAE,OAAO;QACnF,IACE,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC;YACvD,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,EAAE,WAAW,CAAC,EACtD,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;YAClE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-DOC-NOTIFY-1 — passive notification and the two read calls (§16.5, §4.1).
|
|
3
|
+
*
|
|
4
|
+
* ── WHY NO DOORBELL ───────────────────────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* A document update raises NO doorbell (§11.3 — doorbell-on-update is parked). The notice sits in
|
|
7
|
+
* the inbox aggregation and the agent finds it the next time it looks. That is the whole design:
|
|
8
|
+
* a collaborator typing produces a stream of updates, and a doorbell per update would interrupt
|
|
9
|
+
* the operator's agent continuously for something that has no deadline. The one-line summary the
|
|
10
|
+
* agent eventually reads carries the same information at none of the cost.
|
|
11
|
+
*
|
|
12
|
+
* Following the `contact_rename_notices` precedent: there is no inbox store — the inbox is computed
|
|
13
|
+
* per call — so a derived section needs its own table and its own getter.
|
|
14
|
+
*
|
|
15
|
+
* The notice carries `document_id` and a PENDING COUNT, and nothing else. Not a preview, not the
|
|
16
|
+
* first line, not the author's words. Content reaches the agent only through the two read calls
|
|
17
|
+
* below, which are screened; a notification that carried content would be an unscreened path into
|
|
18
|
+
* the agent's context, which is the whole prompt-injection surface §3.1 exists to close.
|
|
19
|
+
*
|
|
20
|
+
* ── THE TWO READ CALLS (§4.1) ─────────────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* `diffStats` is STRUCTURAL ONLY — counts, ranges, and the overlap flag. It exists so an agent can
|
|
23
|
+
* decide whether to read without reading, which is exactly what a cautious agent should be able to
|
|
24
|
+
* do. `diff` is the git-like diff itself and is an ordinary screened read.
|
|
25
|
+
*
|
|
26
|
+
* ── SUPPORTED TYPES FOR `diff`, DECIDED IN-UNIT ───────────────────────────────────────────────
|
|
27
|
+
*
|
|
28
|
+
* Markdown, plain text, and JSON. Markdown and plain text share one line-diff implementation (a
|
|
29
|
+
* markdown file IS lines); JSON gets a key-path diff because a line diff over re-serialized JSON
|
|
30
|
+
* reports the whole document as changed whenever a formatter touches it. Anything else returns a
|
|
31
|
+
* REFUSAL naming the type rather than a line diff over bytes that are not lines — a diff that is
|
|
32
|
+
* wrong is worse than no diff, because the agent cannot tell.
|
|
33
|
+
*/
|
|
34
|
+
import type { DocumentStore } from "./document-store.js";
|
|
35
|
+
import type { Logger } from "./types.js";
|
|
36
|
+
/** Types `diff` can render. Decided in-unit; see the header for why the list is closed. */
|
|
37
|
+
export declare const DIFFABLE_DOCUMENT_TYPES: readonly ["markdown", "text", "json"];
|
|
38
|
+
export type DiffableDocumentType = (typeof DIFFABLE_DOCUMENT_TYPES)[number];
|
|
39
|
+
export interface DocumentNotice {
|
|
40
|
+
documentId: string;
|
|
41
|
+
pending: number;
|
|
42
|
+
noticedAtMs: number;
|
|
43
|
+
}
|
|
44
|
+
export interface DiffStats {
|
|
45
|
+
documentId: string;
|
|
46
|
+
linesAdded: number;
|
|
47
|
+
linesRemoved: number;
|
|
48
|
+
/** 1-based inclusive line ranges the change touches. Ranges, never the lines themselves. */
|
|
49
|
+
ranges: Array<{
|
|
50
|
+
start: number;
|
|
51
|
+
end: number;
|
|
52
|
+
}>;
|
|
53
|
+
/** For JSON: the key paths touched. Empty for line-oriented types. */
|
|
54
|
+
keyPaths: string[];
|
|
55
|
+
/**
|
|
56
|
+
* Whether the peer's change touches a region this operator also edited. The one field here that
|
|
57
|
+
* is a judgement rather than a count, and the reason an agent reads stats before deciding.
|
|
58
|
+
*/
|
|
59
|
+
overlap: boolean | null;
|
|
60
|
+
/** Whether `keyPaths` was computed at all — an empty list is otherwise read as "none changed". */
|
|
61
|
+
keyPathsComputed: boolean;
|
|
62
|
+
/** The document exceeded the LCS limit, so the numbers above are bounds, not a measurement. */
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
}
|
|
65
|
+
export type DiffResult = {
|
|
66
|
+
ok: true;
|
|
67
|
+
documentType: DiffableDocumentType;
|
|
68
|
+
diff: string;
|
|
69
|
+
/**
|
|
70
|
+
* Set when the renderer could not do what the type asked for. A notice inside the diff STRING
|
|
71
|
+
* is readable by a human and invisible to a caller branching on the result — and it travels
|
|
72
|
+
* as screened content, which is the wrong channel for a fact about the renderer.
|
|
73
|
+
*/
|
|
74
|
+
fallback?: "unparseable_json_line_diff";
|
|
75
|
+
} | {
|
|
76
|
+
ok: false;
|
|
77
|
+
reason: string;
|
|
78
|
+
detail: string;
|
|
79
|
+
};
|
|
80
|
+
export declare class DocumentNotifications {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(store: DocumentStore, logger: Logger);
|
|
83
|
+
/**
|
|
84
|
+
* Note that a document has unread updates. Passive — this never pushes anything.
|
|
85
|
+
*
|
|
86
|
+
* The count is REPLACED rather than incremented, and is passed in by the caller from the log,
|
|
87
|
+
* so the notice cannot drift from the document. An incremented counter is a second tally of a
|
|
88
|
+
* fact the log already holds, and the two disagree the first time a notice write is missed.
|
|
89
|
+
*/
|
|
90
|
+
notice(agentId: string, documentId: string, pending: number, nowMs: number): void;
|
|
91
|
+
/** The derived inbox section. Read-only — fetching is what clears, and that is explicit. */
|
|
92
|
+
pending(agentId: string): DocumentNotice[];
|
|
93
|
+
/**
|
|
94
|
+
* Cleared by the agent's EXPLICIT fetch, never by the aggregation that displays it.
|
|
95
|
+
*
|
|
96
|
+
* Reading the inbox and clearing the notice are different acts: an inbox render that cleared
|
|
97
|
+
* would drop the notice for an agent that merely glanced at a summary, and the update would then
|
|
98
|
+
* be unread and unannounced — invisible in both directions.
|
|
99
|
+
*/
|
|
100
|
+
clear(agentId: string, documentId: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Structural counts and ranges — NO content. Lets an agent decide whether to read.
|
|
103
|
+
*
|
|
104
|
+
* `before` and `after` are the materialized text of the document at the two points; the caller
|
|
105
|
+
* holds the engine and therefore the materialization.
|
|
106
|
+
*
|
|
107
|
+
* Uses the SHARED line LCS, not a positional walk. The first version compared line i to line i,
|
|
108
|
+
* which is correct only while the line count is unchanged: inserting one line at the top of a
|
|
109
|
+
* three-line file reported `+4 -3` with a single range covering the whole document. The counts
|
|
110
|
+
* being wrong is the smaller half — `overlap` is derived from those ranges, so it became
|
|
111
|
+
* permanently true, and a flag that always says yes trains the agent to ignore it. Same defect
|
|
112
|
+
* class WRITE-1 measured, which is why there is now one LCS rather than two.
|
|
113
|
+
*/
|
|
114
|
+
diffStats(documentId: string, before: string, after: string,
|
|
115
|
+
/**
|
|
116
|
+
* The operator's own edited line numbers. REQUIRED — pass `null` to say "not computed".
|
|
117
|
+
* Defaulting to `[]` made a caller that simply forgot receive `overlap: false`, the reassuring
|
|
118
|
+
* answer, indistinguishable from "checked, and there is no conflict". This is the one
|
|
119
|
+
* judgement field in an otherwise structural result.
|
|
120
|
+
*/
|
|
121
|
+
myEdits: readonly number[] | null, documentType?: string): DiffStats;
|
|
122
|
+
/**
|
|
123
|
+
* The git-like diff. An ordinary screened read — the caller screens it; this renders it.
|
|
124
|
+
*
|
|
125
|
+
* An unsupported type is REFUSED by name rather than line-diffed anyway. A diff that is silently
|
|
126
|
+
* wrong is worse than no diff, because the agent has no way to tell and will act on it.
|
|
127
|
+
*/
|
|
128
|
+
diff(documentType: string, before: string, after: string, documentId: string): DiffResult;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=document-notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-notify.d.ts","sourceRoot":"","sources":["../src/document-notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAazC,2FAA2F;AAC3F,eAAO,MAAM,uBAAuB,uCAAwC,CAAC;AAC7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,sEAAsE;IACtE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,kGAAkG;IAClG,gBAAgB,EAAE,OAAO,CAAC;IAC1B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAClB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,oBAAoB,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,4BAA4B,CAAC;CACzC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,qBAAa,qBAAqB;;gBAIpB,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM;IAMhD;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAsBjF,4FAA4F;IAC5F,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE;IAc1C;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAWnD;;;;;;;;;;;;OAYG;IACH,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM;IACb;;;;;OAKG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,EACjC,YAAY,CAAC,EAAE,MAAM,GACpB,SAAS;IA2DZ;;;;;OAKG;IACH,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU;CA8B1F"}
|