@plurnk/plurnk-service 1.5.0 → 1.6.0
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/.env.defaults +21 -12
- package/INSTALL.md +11 -7
- package/SPEC.md +437 -304
- package/dist/build-info.json +1 -1
- package/dist/content/edit-collision.d.ts +5 -0
- package/dist/content/edit-collision.d.ts.map +1 -0
- package/dist/content/edit-collision.js +14 -0
- package/dist/content/edit-collision.js.map +1 -0
- package/dist/content/edit-receipt.d.ts +3 -2
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +58 -13
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/index.d.ts +4 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +3 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/line-anchors.d.ts +41 -0
- package/dist/content/line-anchors.d.ts.map +1 -0
- package/dist/content/line-anchors.js +191 -0
- package/dist/content/line-anchors.js.map +1 -0
- package/dist/content/read-projector.d.ts +7 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +46 -10
- package/dist/content/read-projector.js.map +1 -1
- package/dist/content/read-resolve.js +3 -3
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +9 -2
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/ChannelWrite.js +1 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +13 -3
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +391 -113
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/DurableStatement.d.ts.map +1 -1
- package/dist/core/DurableStatement.js +2 -0
- package/dist/core/DurableStatement.js.map +1 -1
- package/dist/core/Engine.d.ts +13 -14
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +434 -300
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +156 -90
- package/dist/core/ExecutableTools.d.ts +10 -0
- package/dist/core/ExecutableTools.d.ts.map +1 -0
- package/dist/core/ExecutableTools.js +41 -0
- package/dist/core/ExecutableTools.js.map +1 -0
- package/dist/core/ExecutorRegistry.d.ts +3 -3
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +2 -2
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/LogBody.d.ts +2 -0
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +17 -5
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogEntryProjection.d.ts +14 -0
- package/dist/core/LogEntryProjection.d.ts.map +1 -0
- package/dist/core/LogEntryProjection.js +38 -0
- package/dist/core/LogEntryProjection.js.map +1 -0
- package/dist/core/ModelCall.d.ts +27 -0
- package/dist/core/ModelCall.d.ts.map +1 -0
- package/dist/core/ModelCall.js +125 -0
- package/dist/core/ModelCall.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts +10 -10
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +131 -103
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +1 -0
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +3 -4
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +23 -6
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.js +7 -7
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +0 -1
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +6 -4
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/caps/CapsResolve.js +1 -1
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +2 -1
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js +8 -5
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbProjectionCaps.d.ts +1 -0
- package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbProjectionCaps.js +29 -0
- package/dist/core/caps/DbProjectionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +5 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +6 -4
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +23 -7
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +33 -12
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +1 -2
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.js +1 -1
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/packet-wire.d.ts +4 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +248 -90
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/provider-accounting.d.ts +24 -0
- package/dist/core/provider-accounting.d.ts.map +1 -0
- package/dist/core/provider-accounting.js +76 -0
- package/dist/core/provider-accounting.js.map +1 -0
- package/dist/core/search-gate.d.ts +2 -2
- package/dist/core/search-gate.d.ts.map +1 -1
- package/dist/core/search-gate.js +8 -8
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/token-ruler.js +1 -1
- package/dist/core/token-ruler.js.map +1 -1
- package/dist/core/turn-scheduler.js +1 -1
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +220 -109
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +40 -18
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +111 -101
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +5 -4
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +24 -22
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +11 -0
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +171 -106
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +48 -32
- package/dist/schemes/Prompt.d.ts +3 -3
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +4 -1
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Skill.d.ts +4 -4
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +3 -0
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts +4 -4
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +9 -5
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +0 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +15 -9
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +2 -14
- package/dist/schemes/_entry-find.d.ts +13 -12
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +33 -28
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +2 -21
- package/dist/schemes/_entry-graph.sql +1 -1
- package/dist/schemes/_entry-manifest.d.ts +5 -17
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +28 -24
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +5 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +87 -47
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +30 -4
- package/dist/schemes/_entry-send.js +4 -4
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +10 -2
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/exec-abort.js +2 -2
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +25 -23
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/drain.sql +2 -2
- package/dist/server/envelope.d.ts +0 -2
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +2 -2
- package/dist/server/lifecycle-recovery.sql +25 -15
- package/dist/server/logEntry.d.ts +1 -0
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +13 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +5 -1
- package/migrations/001_schema.sql +603 -207
- package/package.json +39 -34
- package/dist/core/BudgetOverflow.d.ts +0 -20
- package/dist/core/BudgetOverflow.d.ts.map +0 -1
- package/dist/core/BudgetOverflow.js +0 -49
- package/dist/core/BudgetOverflow.js.map +0 -1
- package/dist/core/caps/DbTagCaps.d.ts +0 -10
- package/dist/core/caps/DbTagCaps.d.ts.map +0 -1
- package/dist/core/caps/DbTagCaps.js +0 -46
- package/dist/core/caps/DbTagCaps.js.map +0 -1
package/dist/core/Dispatcher.js
CHANGED
|
@@ -5,11 +5,12 @@ import WorkerControlAddress from "./WorkerControlAddress.js";
|
|
|
5
5
|
import { entryPathnameOf, foldAuthorityIntoPath, renderAddress, renderTarget, schemeNameOf } from "./plurnk-uri.js";
|
|
6
6
|
import Fork from "./fork.js";
|
|
7
7
|
import WorkerCap from "./worker-cap.js";
|
|
8
|
-
import { PathSyntax } from "@plurnk/plurnk-contracts";
|
|
8
|
+
import { PathSyntax, TagSignal } from "@plurnk/plurnk-contracts";
|
|
9
9
|
import Namespace from "./namespace.js";
|
|
10
10
|
import LoopFlagsReader from "./LoopFlagsReader.js";
|
|
11
11
|
import ChannelWrite from "./ChannelWrite.js";
|
|
12
|
-
import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, editReceipt, LineMarkerOps, MimetypeBinary, PathMimetype, ReadProjector, projectEditReceipt, } from "../content/index.js";
|
|
12
|
+
import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, EditCollision, editReceipt, LineAnchors, LineMarkerOps, MimetypeBinary, PathMimetype, ReadProjector, projectEditReceipt, } from "../content/index.js";
|
|
13
|
+
import DbProjectionCaps from "./caps/DbProjectionCaps.js";
|
|
13
14
|
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
14
15
|
import EntryCrud from "../schemes/_entry-crud.js";
|
|
15
16
|
import EntryOps from "../schemes/_entry-ops.js";
|
|
@@ -26,6 +27,16 @@ import DurableStatement from "./DurableStatement.js";
|
|
|
26
27
|
// SPEC {§scheme-surface}: writer must be in target scheme's manifest.writableBy.
|
|
27
28
|
// OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
|
|
28
29
|
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL", "FORK", "WORK"]);
|
|
30
|
+
const assertClassifyingSignal = (statement) => {
|
|
31
|
+
if (statement.op === "FIND"
|
|
32
|
+
|| statement.op === "READ"
|
|
33
|
+
|| statement.op === "EDIT"
|
|
34
|
+
|| statement.op === "COPY"
|
|
35
|
+
|| statement.op === "MOVE"
|
|
36
|
+
|| statement.op === "BARE") {
|
|
37
|
+
TagSignal.applied(statement.signal);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
29
40
|
// Op dispatch ({§op-methods-op-dispatch}): gates (writableBy, loop flags), the
|
|
30
41
|
// engine-owned op orchestrations (COPY/MOVE/KILL/SEND), scheme
|
|
31
42
|
// routing, the durable log write, and the proposal pause.
|
|
@@ -55,7 +66,7 @@ class Dispatcher {
|
|
|
55
66
|
#branchCompletionGate;
|
|
56
67
|
#cancelWorker;
|
|
57
68
|
#cancelDescendants;
|
|
58
|
-
// {§send-premature-terminate}/SEND
|
|
69
|
+
// {§send-premature-terminate}/SEND signal 202 with scope — the engine-owned park-deadline registry (loopId → seconds;
|
|
59
70
|
// -1 = indefinite). The dispatcher WRITES at park; the daemon's drain park-exit consumes.
|
|
60
71
|
#parkDeadlines;
|
|
61
72
|
#searchGate;
|
|
@@ -124,7 +135,12 @@ class Dispatcher {
|
|
|
124
135
|
status: result.status,
|
|
125
136
|
entry: result.entry === null
|
|
126
137
|
? null
|
|
127
|
-
: {
|
|
138
|
+
: {
|
|
139
|
+
channels: { ...result.entry.channels },
|
|
140
|
+
...(result.entry.attributes === undefined
|
|
141
|
+
? {}
|
|
142
|
+
: { attributes: { ...result.entry.attributes } }),
|
|
143
|
+
},
|
|
128
144
|
});
|
|
129
145
|
}
|
|
130
146
|
async #writeEntry(scheme, pathname, entry, ctx) {
|
|
@@ -197,11 +213,114 @@ class Dispatcher {
|
|
|
197
213
|
if (key !== null)
|
|
198
214
|
target.pathname = key;
|
|
199
215
|
}
|
|
216
|
+
async #resolveEditAnchors(statements, identity, schemeName, manifest, ctx) {
|
|
217
|
+
const anchored = statements.filter(({ lineMarker }) => LineAnchors.hasAnchor(lineMarker));
|
|
218
|
+
if (anchored.length === 0) {
|
|
219
|
+
return { statements: statements, precondition: null };
|
|
220
|
+
}
|
|
221
|
+
if (manifest.textEditScopes !== true || !manifest.writableBy.includes("model")) {
|
|
222
|
+
return {
|
|
223
|
+
result: _a.#failure("line-anchor-unsupported", 400, `Scheme '${schemeName}' does not support textual EDIT scopes.`, {}, {
|
|
224
|
+
scheme: schemeName,
|
|
225
|
+
operation: "EDIT",
|
|
226
|
+
recovery: "Remove the scope and submit the scheme's complete editable unit.",
|
|
227
|
+
retryable: false,
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (identity === null) {
|
|
232
|
+
return {
|
|
233
|
+
result: _a.#failure("edit-target-required", 400, "A line-anchored EDIT requires a target resource.", {}, { recovery: "Provide the target that rendered the line anchor.", retryable: false }),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const first = statements[0];
|
|
237
|
+
if (first === undefined)
|
|
238
|
+
return { statements: [], precondition: null };
|
|
239
|
+
const current = await this.#run(schemeName, {
|
|
240
|
+
op: "READ",
|
|
241
|
+
suffix: first.suffix,
|
|
242
|
+
signal: null,
|
|
243
|
+
target: first.target,
|
|
244
|
+
lineMarker: { marks: [1, -1] },
|
|
245
|
+
body: null,
|
|
246
|
+
position: first.position,
|
|
247
|
+
}, ctx);
|
|
248
|
+
if (current.status === 204 || current.status === 404) {
|
|
249
|
+
return { result: EditCollision.result(identity) };
|
|
250
|
+
}
|
|
251
|
+
if (current.status >= 300) {
|
|
252
|
+
return {
|
|
253
|
+
result: _a.#failure("line-anchor-validation-failed", current.status, `EDIT could not validate its line anchor at ${identity}: ${current.problem?.detail ?? `READ returned ${current.status}`}`, {}, {
|
|
254
|
+
target: identity,
|
|
255
|
+
upstreamStatus: current.status,
|
|
256
|
+
stage: "mutation-precondition",
|
|
257
|
+
retryable: false,
|
|
258
|
+
}),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (current.status !== 200) {
|
|
262
|
+
return { result: EditCollision.result(identity) };
|
|
263
|
+
}
|
|
264
|
+
const content = current.content;
|
|
265
|
+
if (typeof content !== "string") {
|
|
266
|
+
throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ ${current.status} without textual content while validating an EDIT anchor.`);
|
|
267
|
+
}
|
|
268
|
+
const lineAnchors = current.lineAnchors;
|
|
269
|
+
const lineAnchorIdentity = current.lineAnchorIdentity;
|
|
270
|
+
try {
|
|
271
|
+
LineAnchors.assertProjection(content, lineAnchors);
|
|
272
|
+
if (typeof lineAnchorIdentity !== "string" || lineAnchorIdentity.length === 0) {
|
|
273
|
+
throw new TypeError("READ line anchors require their canonical derivation identity.");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch (cause) {
|
|
277
|
+
throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ 200 without its core-owned line-anchor projection.`, { cause });
|
|
278
|
+
}
|
|
279
|
+
const resolved = [];
|
|
280
|
+
const checks = [];
|
|
281
|
+
for (const statement of statements) {
|
|
282
|
+
if (statement.lineMarker === null || !LineAnchors.hasAnchor(statement.lineMarker)) {
|
|
283
|
+
resolved.push(statement);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const resolution = LineAnchors.resolve(lineAnchors, statement.lineMarker);
|
|
287
|
+
if (!resolution.ok) {
|
|
288
|
+
const { anchor, kind } = resolution.failure;
|
|
289
|
+
const invalid = kind === "invalid";
|
|
290
|
+
if (!invalid)
|
|
291
|
+
return { result: EditCollision.result(lineAnchorIdentity) };
|
|
292
|
+
return {
|
|
293
|
+
result: _a.#failure("line-anchor-invalid", 400, `${anchor} is not valid in that EDIT line-coordinate position.`, {}, {
|
|
294
|
+
anchor,
|
|
295
|
+
target: identity,
|
|
296
|
+
recovery: "Use anchors only where the EDIT scope denotes a line.",
|
|
297
|
+
retryable: false,
|
|
298
|
+
}),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
for (const [index, anchor] of statement.lineMarker.marks.entries()) {
|
|
302
|
+
if (typeof anchor !== "string")
|
|
303
|
+
continue;
|
|
304
|
+
const line = resolution.marker.marks[index];
|
|
305
|
+
if (typeof line !== "number") {
|
|
306
|
+
throw new InvalidOperationResultError("An EDIT line anchor did not lower to a numeric line.");
|
|
307
|
+
}
|
|
308
|
+
checks.push({ anchor, line });
|
|
309
|
+
}
|
|
310
|
+
resolved.push({ ...statement, lineMarker: resolution.marker });
|
|
311
|
+
}
|
|
312
|
+
const uniqueChecks = [...new Map(checks.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
|
|
313
|
+
return {
|
|
314
|
+
statements: resolved,
|
|
315
|
+
precondition: { identity: lineAnchorIdentity, checks: uniqueChecks },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
200
318
|
async prepareEditBatches(statements, context) {
|
|
201
319
|
const { workspaceId, workerId, loopId, turnId, origin } = context;
|
|
202
320
|
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
203
321
|
const groups = new Map();
|
|
204
322
|
for (const statement of statements) {
|
|
323
|
+
assertClassifyingSignal(statement); // {§log-tag-signal}
|
|
205
324
|
const target = this.#extractTarget(statement.target);
|
|
206
325
|
await this.#canonColumns(target, workspaceId);
|
|
207
326
|
const key = JSON.stringify([
|
|
@@ -213,11 +332,15 @@ class Dispatcher {
|
|
|
213
332
|
]);
|
|
214
333
|
const group = groups.get(key);
|
|
215
334
|
if (group === undefined)
|
|
216
|
-
groups.set(key,
|
|
335
|
+
groups.set(key, {
|
|
336
|
+
identity: renderTarget(target),
|
|
337
|
+
statements: [statement],
|
|
338
|
+
});
|
|
217
339
|
else
|
|
218
|
-
group.push(statement);
|
|
340
|
+
group.statements.push(statement);
|
|
219
341
|
}
|
|
220
|
-
for (const
|
|
342
|
+
for (const preparedGroup of groups.values()) {
|
|
343
|
+
const group = preparedGroup.statements;
|
|
221
344
|
const first = group[0];
|
|
222
345
|
const schemeName = schemeNameOf(first.target);
|
|
223
346
|
let initial;
|
|
@@ -248,11 +371,20 @@ class Dispatcher {
|
|
|
248
371
|
}
|
|
249
372
|
else {
|
|
250
373
|
try {
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
374
|
+
const resolved = await this.#resolveEditAnchors(group, preparedGroup.identity, schemeName, manifest, schemeCtx);
|
|
375
|
+
if ("result" in resolved) {
|
|
376
|
+
initial = resolved.result;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
|
|
380
|
+
const publishedChannel = first.target?.kind === "url"
|
|
381
|
+
? first.target.fragment ?? manifest.defaultChannel
|
|
382
|
+
: manifest.defaultChannel;
|
|
383
|
+
initial = Results.assert(await method.call(handler, resolved.statements, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, {
|
|
384
|
+
publishedChannel,
|
|
385
|
+
editPrecondition: resolved.precondition,
|
|
386
|
+
})));
|
|
387
|
+
}
|
|
256
388
|
}
|
|
257
389
|
catch (err) {
|
|
258
390
|
if (err instanceof InvalidOperationResultError)
|
|
@@ -295,6 +427,7 @@ class Dispatcher {
|
|
|
295
427
|
}
|
|
296
428
|
}
|
|
297
429
|
async dispatch(context) {
|
|
430
|
+
assertClassifyingSignal(context.statement); // {§log-tag-signal}
|
|
298
431
|
const result = await this.#dispatchOne(context);
|
|
299
432
|
if (context.statement.op === "EDIT") {
|
|
300
433
|
const prepared = this.#preparedEdits.get(context.statement);
|
|
@@ -311,6 +444,7 @@ class Dispatcher {
|
|
|
311
444
|
const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, } = context;
|
|
312
445
|
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
313
446
|
let result;
|
|
447
|
+
let curationPlan = null;
|
|
314
448
|
let denial = this.#checkWritable(statement, origin);
|
|
315
449
|
if (denial === null)
|
|
316
450
|
denial = await this.#checkFlagsGate(statement, loopId);
|
|
@@ -358,7 +492,9 @@ class Dispatcher {
|
|
|
358
492
|
result = await this.#handleSendBroadcast(statement, { workspaceId, workerId, loopId, turnId, sequence });
|
|
359
493
|
}
|
|
360
494
|
else if (statement.op === "OPEN" || statement.op === "FOLD") {
|
|
361
|
-
|
|
495
|
+
const curation = await this.#runLogCuration(statement, schemeCtx);
|
|
496
|
+
result = curation.result;
|
|
497
|
+
curationPlan = curation.plan;
|
|
362
498
|
}
|
|
363
499
|
else if (statement.op === "FORK" || statement.op === "WORK") {
|
|
364
500
|
result = await this.#handleWorkerControl(statement, schemeCtx);
|
|
@@ -376,10 +512,8 @@ class Dispatcher {
|
|
|
376
512
|
result = this.#handlePlan(statement);
|
|
377
513
|
}
|
|
378
514
|
else if (statement.op === "EXEC") {
|
|
379
|
-
// EXEC
|
|
380
|
-
//
|
|
381
|
-
// exec scheme; the scheme handler reads runtime
|
|
382
|
-
// (signal), cwd (target), and command (body).
|
|
515
|
+
// EXEC routes unconditionally to its operation owner. The
|
|
516
|
+
// resolved runtime declaration owns body/target semantics.
|
|
383
517
|
result = await this.#gatedExec(statement, schemeCtx, loopId, turnId);
|
|
384
518
|
}
|
|
385
519
|
else {
|
|
@@ -406,19 +540,30 @@ class Dispatcher {
|
|
|
406
540
|
// {§join-blocking-collect}
|
|
407
541
|
if (typeof result.awaitWorker === "string")
|
|
408
542
|
this.#joinTargets.add(loopId);
|
|
409
|
-
const logEntryId = await this.#writeLog({
|
|
543
|
+
const logEntryId = await this.#writeLog({
|
|
544
|
+
statement,
|
|
545
|
+
result,
|
|
546
|
+
workspaceId,
|
|
547
|
+
workerId,
|
|
548
|
+
loopId,
|
|
549
|
+
turnId,
|
|
550
|
+
sequence,
|
|
551
|
+
origin,
|
|
552
|
+
curationPlan,
|
|
553
|
+
modelCallId: null,
|
|
554
|
+
});
|
|
410
555
|
// {§search-gate} — register successful searches AFTER #writeLog stamps the runtime
|
|
411
556
|
// entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
|
|
412
557
|
if (statement.op === "EXEC" && result.status < 400) {
|
|
413
558
|
const rt = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
|
|
414
|
-
const
|
|
559
|
+
const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
415
560
|
const attrPath = result.attrs?.pathname;
|
|
416
561
|
if (typeof attrPath === "string")
|
|
417
|
-
this.#searchGate?.registerPending(loopId, turnId, rt,
|
|
562
|
+
this.#searchGate?.registerPending(loopId, turnId, rt, body, attrPath);
|
|
418
563
|
}
|
|
419
564
|
onDispatch?.(logEntryId);
|
|
420
565
|
// Proposal lifecycle (SPEC.md {§engine-rails} + {§methods-proposal-resolve}; {§proposal-202-pauses}). When a
|
|
421
|
-
// side-effecting op returns status 202 (a broadcast SEND
|
|
566
|
+
// side-effecting op returns status 202 (a broadcast SEND signal 202 park is model
|
|
422
567
|
// speech, not a proposal — #isProposal), the entry is written
|
|
423
568
|
// state='proposed'; dispatch then PAUSES on a per-entry waiter until
|
|
424
569
|
// resolution arrives via Engine.resolveProposal (from a client-interface resume,
|
|
@@ -676,8 +821,8 @@ class Dispatcher {
|
|
|
676
821
|
return null;
|
|
677
822
|
if (statement.op === "SEND" && statement.target === null)
|
|
678
823
|
return null;
|
|
679
|
-
// EXEC's
|
|
680
|
-
//
|
|
824
|
+
// EXEC's operation authority always belongs to the exec scheme;
|
|
825
|
+
// runtime-specific resource authority is gated separately below.
|
|
681
826
|
if (statement.op === "EXEC") {
|
|
682
827
|
return this.#denyIfDisallowed("exec", origin);
|
|
683
828
|
}
|
|
@@ -709,8 +854,8 @@ class Dispatcher {
|
|
|
709
854
|
// the prior durable digest and the per-turn cap refuses without execution.
|
|
710
855
|
async #gatedExec(statement, ctx, loopId, turnId) {
|
|
711
856
|
const runtime = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
|
|
712
|
-
const
|
|
713
|
-
const verdict = this.#searchGate?.check(loopId, turnId, runtime,
|
|
857
|
+
const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
858
|
+
const verdict = this.#searchGate?.check(loopId, turnId, runtime, body) ?? { verdict: "pass" };
|
|
714
859
|
if (verdict.verdict === "capped") {
|
|
715
860
|
return _a.#failure("search-limit-reached", 429, `This turn already used its ${verdict.cap} permitted searches.`, {}, {
|
|
716
861
|
searchLimit: verdict.cap,
|
|
@@ -817,12 +962,17 @@ class Dispatcher {
|
|
|
817
962
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
818
963
|
return check(statement.target) ?? check(statement.body?.target ?? null);
|
|
819
964
|
}
|
|
820
|
-
// {§exec-target-routing} —
|
|
821
|
-
//
|
|
965
|
+
// {§exec-target-routing} — only a runtime-declared resource target adds
|
|
966
|
+
// source authority. Literal identifiers and path targets stay executor-local.
|
|
822
967
|
if (statement.op === "EXEC") {
|
|
823
968
|
const operationDenial = checkScheme("exec");
|
|
824
969
|
if (operationDenial !== null)
|
|
825
970
|
return operationDenial;
|
|
971
|
+
const requested = typeof statement.signal === "string" ? statement.signal : "";
|
|
972
|
+
const runtime = requested === "" ? "sh" : requested;
|
|
973
|
+
const targetKind = this.#executors()?.entry(runtime)?.invocation.target?.kind;
|
|
974
|
+
if (targetKind !== "resource")
|
|
975
|
+
return null;
|
|
826
976
|
const sourceScheme = schemeNameOf(statement.target);
|
|
827
977
|
return sourceScheme === null || sourceScheme === "file" ? null : checkScheme(sourceScheme);
|
|
828
978
|
}
|
|
@@ -935,11 +1085,19 @@ class Dispatcher {
|
|
|
935
1085
|
retryable: false,
|
|
936
1086
|
});
|
|
937
1087
|
}
|
|
1088
|
+
const sourceMarks = statement.lineMarker?.marks;
|
|
1089
|
+
const sourceLineMarker = sourceMarks?.length === 2
|
|
1090
|
+
&& sourceMarks[0] === 1
|
|
1091
|
+
&& sourceMarks[1] === -1
|
|
1092
|
+
? null
|
|
1093
|
+
: statement.lineMarker;
|
|
938
1094
|
return this.#moveOrchestration({
|
|
939
1095
|
statement,
|
|
940
1096
|
source: {
|
|
941
1097
|
target: statement.target,
|
|
942
|
-
|
|
1098
|
+
// Canonicalize only the execution selection. #writeLog retains
|
|
1099
|
+
// the authored marker as operation evidence. {§move-canonical-whole-source}
|
|
1100
|
+
lineMarker: sourceLineMarker,
|
|
943
1101
|
},
|
|
944
1102
|
destination: statement.body,
|
|
945
1103
|
ctx,
|
|
@@ -1014,35 +1172,48 @@ class Dispatcher {
|
|
|
1014
1172
|
// carries the delete target to review (#isProposal fires on 202). Plurnk-internal deletes execute inline.
|
|
1015
1173
|
return this.#deleteEntry(schemeName, entryPathnameOf(path), ctx);
|
|
1016
1174
|
}
|
|
1017
|
-
|
|
1018
|
-
// the model can inspect and curate its own prior behavior. Ordinary admitted
|
|
1019
|
-
// emissions are folded; the bounded invalid-emission lifeline is the one
|
|
1020
|
-
// born-open rejected item under {§invalid-emission-attempts} and labels
|
|
1021
|
-
// that admission fact structurally.
|
|
1022
|
-
// Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
|
|
1023
|
-
// (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
|
|
1024
|
-
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model", admission = "accepted", reasoningItems }) {
|
|
1175
|
+
async #writeActionlessEntry({ verbatim, workerId, loopId, turnId, sequence, origin, kind, folded, modelCallId = null, attrs = {} }) {
|
|
1025
1176
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
1026
1177
|
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
|
|
1027
|
-
origin, source: null,
|
|
1178
|
+
origin, source: null, model_call_id: modelCallId,
|
|
1179
|
+
op: null, suffix: "", signal: null,
|
|
1028
1180
|
scheme: null, username: null, password: null, hostname: null, port: null,
|
|
1029
1181
|
pathname: null, query: null, fragment: null, lineMarker: null,
|
|
1030
1182
|
tx: "", mimetype_tx: "text/vnd.plurnk",
|
|
1031
1183
|
rx: JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" }),
|
|
1032
1184
|
mimetype_rx: "application/json",
|
|
1033
1185
|
status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
|
|
1034
|
-
attrs: JSON.stringify({
|
|
1035
|
-
kind: "model_emission",
|
|
1036
|
-
...(admission === "rejected" ? { admission } : {}),
|
|
1037
|
-
...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
|
|
1038
|
-
}),
|
|
1186
|
+
attrs: JSON.stringify({ ...attrs, kind }),
|
|
1039
1187
|
});
|
|
1040
1188
|
if (row === undefined)
|
|
1041
|
-
throw new Error("Dispatcher
|
|
1189
|
+
throw new Error("Dispatcher.#writeActionlessEntry: insert returned no row");
|
|
1042
1190
|
if (folded)
|
|
1043
1191
|
await this.#db.engine_fold_log_entry.run({ id: row.id });
|
|
1044
1192
|
return row.id;
|
|
1045
1193
|
}
|
|
1194
|
+
// {§worker-initialization-entry} — the kernel-authored, born-open worked
|
|
1195
|
+
// turn that initializes a worker's log without impersonating model output.
|
|
1196
|
+
async writeInitializationEntry({ verbatim, workerId, loopId, turnId, sequence }) {
|
|
1197
|
+
return this.#writeActionlessEntry({
|
|
1198
|
+
verbatim, workerId, loopId, turnId, sequence,
|
|
1199
|
+
origin: "plurnk", kind: "initialization", folded: false,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
// {§model-entry} — mirror a model emission back as an actionless log row, so
|
|
1203
|
+
// the model can inspect and curate its own prior behavior. Ordinary admitted
|
|
1204
|
+
// emissions are folded; the bounded invalid-emission lifeline is the one
|
|
1205
|
+
// born-open rejected item under {§invalid-emission-attempts} and labels
|
|
1206
|
+
// that admission fact structurally.
|
|
1207
|
+
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, modelCallId, admission = "accepted", reasoningItems }) {
|
|
1208
|
+
return this.#writeActionlessEntry({
|
|
1209
|
+
verbatim, workerId, loopId, turnId, sequence,
|
|
1210
|
+
origin: "model", kind: "model_emission", folded, modelCallId,
|
|
1211
|
+
attrs: {
|
|
1212
|
+
...(admission === "rejected" ? { admission } : {}),
|
|
1213
|
+
...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
|
|
1214
|
+
},
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1046
1217
|
// PLAN — the model's intended-goals op. An ordinary op: dispatched like any
|
|
1047
1218
|
// other, logged, and broadcast to the client as a log entry — but a pure no-op for
|
|
1048
1219
|
// state (PLAN ∉ MUTATING_OPS); its body serializes into the log row's tx, no effect.
|
|
@@ -1051,6 +1222,20 @@ class Dispatcher {
|
|
|
1051
1222
|
throw new Error("unreachable");
|
|
1052
1223
|
return { status: 200 };
|
|
1053
1224
|
}
|
|
1225
|
+
// {§bare-inference} Provider work is prepared concurrently by Engine; the
|
|
1226
|
+
// dispatcher owns only the ordinary operation-result commit and notification.
|
|
1227
|
+
async recordBareResult(context, result, modelCallId) {
|
|
1228
|
+
assertClassifyingSignal(context.statement);
|
|
1229
|
+
Results.assert(result);
|
|
1230
|
+
const logEntryId = await this.#writeLog({
|
|
1231
|
+
...context,
|
|
1232
|
+
result,
|
|
1233
|
+
curationPlan: null,
|
|
1234
|
+
modelCallId,
|
|
1235
|
+
});
|
|
1236
|
+
context.onDispatch?.(logEntryId);
|
|
1237
|
+
return result;
|
|
1238
|
+
}
|
|
1054
1239
|
static #isDispatchResult(value) {
|
|
1055
1240
|
return "status" in value;
|
|
1056
1241
|
}
|
|
@@ -1116,7 +1301,57 @@ class Dispatcher {
|
|
|
1116
1301
|
manifest,
|
|
1117
1302
|
};
|
|
1118
1303
|
}
|
|
1119
|
-
|
|
1304
|
+
#resolveResourceLineMarker(selection, content, operation) {
|
|
1305
|
+
if (!LineAnchors.hasAnchor(selection.lineMarker)) {
|
|
1306
|
+
return {
|
|
1307
|
+
selection: {
|
|
1308
|
+
...selection,
|
|
1309
|
+
lineMarker: selection.lineMarker,
|
|
1310
|
+
},
|
|
1311
|
+
precondition: null,
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
const target = this.#resourceAddress(selection);
|
|
1315
|
+
if (selection.manifest.textEditScopes !== true || !selection.manifest.writableBy.includes("model")) {
|
|
1316
|
+
return {
|
|
1317
|
+
result: _a.#failure("line-anchor-unsupported", 400, `The representation at ${target} does not publish line anchors.`, {}, {
|
|
1318
|
+
operation,
|
|
1319
|
+
target,
|
|
1320
|
+
recovery: "Use numeric text coordinates.",
|
|
1321
|
+
retryable: false,
|
|
1322
|
+
}),
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
const resolution = LineAnchors.resolve(LineAnchors.tokens(target, content), selection.lineMarker);
|
|
1326
|
+
if (!resolution.ok) {
|
|
1327
|
+
if (resolution.failure.kind === "invalid") {
|
|
1328
|
+
return {
|
|
1329
|
+
result: _a.#failure("line-anchor-invalid", 400, "A line anchor appeared outside a line-coordinate position.", {}, {
|
|
1330
|
+
operation,
|
|
1331
|
+
target,
|
|
1332
|
+
recovery: "Use anchors only for L, SL, or EL; columns remain numeric.",
|
|
1333
|
+
retryable: false,
|
|
1334
|
+
}),
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
result: _a.#failure("line-anchor-collision", 409, `${operation} coordinates collided with current content at ${target}.`, {}, {
|
|
1339
|
+
operation,
|
|
1340
|
+
target,
|
|
1341
|
+
recovery: `READ ${target} again and retry against its current coordinates.`,
|
|
1342
|
+
retryable: true,
|
|
1343
|
+
}),
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
return {
|
|
1347
|
+
selection: { ...selection, lineMarker: resolution.marker },
|
|
1348
|
+
precondition: {
|
|
1349
|
+
identity: target,
|
|
1350
|
+
checks: LineAnchors.checks(selection.lineMarker, resolution.marker),
|
|
1351
|
+
},
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
async #selectSource(selection, ctx, operation) {
|
|
1120
1355
|
const handler = this.#schemes.get(selection.scheme);
|
|
1121
1356
|
if (handler === undefined) {
|
|
1122
1357
|
throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
|
|
@@ -1155,6 +1390,9 @@ class Dispatcher {
|
|
|
1155
1390
|
retryable: false,
|
|
1156
1391
|
});
|
|
1157
1392
|
}
|
|
1393
|
+
const resolvedMarker = this.#resolveResourceLineMarker(selection, selected.content, operation);
|
|
1394
|
+
if ("result" in resolvedMarker)
|
|
1395
|
+
return resolvedMarker.result;
|
|
1158
1396
|
let content = selected.content;
|
|
1159
1397
|
let scopeNormalizations;
|
|
1160
1398
|
if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
|
|
@@ -1165,8 +1403,8 @@ class Dispatcher {
|
|
|
1165
1403
|
retryable: false,
|
|
1166
1404
|
});
|
|
1167
1405
|
}
|
|
1168
|
-
if (selection.lineMarker !== null) {
|
|
1169
|
-
const sliced = LineMarkerOps.sliceLinesRaw(content, selection.lineMarker);
|
|
1406
|
+
if (resolvedMarker.selection.lineMarker !== null) {
|
|
1407
|
+
const sliced = LineMarkerOps.sliceLinesRaw(content, resolvedMarker.selection.lineMarker);
|
|
1170
1408
|
if (sliced.status !== 200)
|
|
1171
1409
|
return Results.assert(sliced);
|
|
1172
1410
|
content = sliced.text ?? "";
|
|
@@ -1176,10 +1414,12 @@ class Dispatcher {
|
|
|
1176
1414
|
return Results.assert(selected.producerResult);
|
|
1177
1415
|
}
|
|
1178
1416
|
return {
|
|
1179
|
-
...selection,
|
|
1417
|
+
...resolvedMarker.selection,
|
|
1180
1418
|
storageAddress,
|
|
1181
1419
|
content,
|
|
1420
|
+
completeContent: selected.content,
|
|
1182
1421
|
mimetype: selected.mimetype,
|
|
1422
|
+
lineAnchorPrecondition: resolvedMarker.precondition,
|
|
1183
1423
|
...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
|
|
1184
1424
|
};
|
|
1185
1425
|
}
|
|
@@ -1194,6 +1434,18 @@ class Dispatcher {
|
|
|
1194
1434
|
],
|
|
1195
1435
|
});
|
|
1196
1436
|
}
|
|
1437
|
+
static #mergeLineAnchorPreconditions(...values) {
|
|
1438
|
+
const present = values.filter((value) => value !== null);
|
|
1439
|
+
if (present.length === 0)
|
|
1440
|
+
return null;
|
|
1441
|
+
const identity = present[0].identity;
|
|
1442
|
+
if (present.some((value) => value.identity !== identity)) {
|
|
1443
|
+
throw new TypeError("Line-anchor preconditions for one edit batch must share a resource identity.");
|
|
1444
|
+
}
|
|
1445
|
+
const checks = [...new Map(present.flatMap(({ checks: valueChecks }) => valueChecks)
|
|
1446
|
+
.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
|
|
1447
|
+
return { identity, checks };
|
|
1448
|
+
}
|
|
1197
1449
|
#resourceAddress(selection) {
|
|
1198
1450
|
const address = renderTarget({
|
|
1199
1451
|
scheme: selection.scheme === "file" ? null : selection.scheme,
|
|
@@ -1413,7 +1665,7 @@ class Dispatcher {
|
|
|
1413
1665
|
},
|
|
1414
1666
|
};
|
|
1415
1667
|
}
|
|
1416
|
-
async #invokeEditBatch(selection, edits, ctx) {
|
|
1668
|
+
async #invokeEditBatch(selection, edits, ctx, precondition = null) {
|
|
1417
1669
|
const handler = this.#schemes.get(selection.scheme);
|
|
1418
1670
|
if (typeof handler?.editBatch !== "function") {
|
|
1419
1671
|
return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
|
|
@@ -1422,10 +1674,10 @@ class Dispatcher {
|
|
|
1422
1674
|
retryable: false,
|
|
1423
1675
|
});
|
|
1424
1676
|
}
|
|
1425
|
-
const statements = edits.map(({ marker, body,
|
|
1677
|
+
const statements = edits.map(({ marker, body, position }) => ({
|
|
1426
1678
|
op: "EDIT",
|
|
1427
1679
|
suffix: "",
|
|
1428
|
-
signal:
|
|
1680
|
+
signal: null,
|
|
1429
1681
|
target: selection.target,
|
|
1430
1682
|
lineMarker: marker,
|
|
1431
1683
|
body,
|
|
@@ -1435,7 +1687,10 @@ class Dispatcher {
|
|
|
1435
1687
|
? selection.target.scheme
|
|
1436
1688
|
: selection.scheme;
|
|
1437
1689
|
try {
|
|
1438
|
-
const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions
|
|
1690
|
+
const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, {
|
|
1691
|
+
publishedChannel: selection.channel,
|
|
1692
|
+
editPrecondition: precondition,
|
|
1693
|
+
})));
|
|
1439
1694
|
return this.#withProposalRoute(result, selection);
|
|
1440
1695
|
}
|
|
1441
1696
|
catch (err) {
|
|
@@ -1471,7 +1726,6 @@ class Dispatcher {
|
|
|
1471
1726
|
retryable: false,
|
|
1472
1727
|
});
|
|
1473
1728
|
}
|
|
1474
|
-
const tags = Array.isArray(statement.signal) ? statement.signal : [];
|
|
1475
1729
|
const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
|
|
1476
1730
|
if (destination.lineMarker !== null) {
|
|
1477
1731
|
if (destinationChannel === undefined) {
|
|
@@ -1487,13 +1741,15 @@ class Dispatcher {
|
|
|
1487
1741
|
retryable: false,
|
|
1488
1742
|
});
|
|
1489
1743
|
}
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1744
|
+
const resolvedMarker = this.#resolveResourceLineMarker(destination, destinationChannel.content, statement.op);
|
|
1745
|
+
if ("result" in resolvedMarker)
|
|
1746
|
+
return resolvedMarker.result;
|
|
1747
|
+
const edited = await this.#invokeEditBatch(resolvedMarker.selection, [{
|
|
1748
|
+
marker: resolvedMarker.selection.lineMarker,
|
|
1492
1749
|
body: source.content,
|
|
1493
|
-
tags,
|
|
1494
1750
|
position: statement.position,
|
|
1495
|
-
}], ctx);
|
|
1496
|
-
return this.#finalizeEffects(edited,
|
|
1751
|
+
}], ctx, resolvedMarker.precondition);
|
|
1752
|
+
return this.#finalizeEffects(edited, resolvedMarker.selection, [destinationEffect]);
|
|
1497
1753
|
}
|
|
1498
1754
|
if (destinationChannel !== undefined
|
|
1499
1755
|
&& destinationChannel.content !== source.content) {
|
|
@@ -1502,10 +1758,7 @@ class Dispatcher {
|
|
|
1502
1758
|
retryable: false,
|
|
1503
1759
|
});
|
|
1504
1760
|
}
|
|
1505
|
-
|
|
1506
|
-
const mergedTags = [...new Set([...priorTags, ...tags])];
|
|
1507
|
-
const addsTags = mergedTags.length !== priorTags.length;
|
|
1508
|
-
if (destinationChannel !== undefined && !addsTags)
|
|
1761
|
+
if (destinationChannel !== undefined)
|
|
1509
1762
|
return { status: 304 };
|
|
1510
1763
|
const channels = {
|
|
1511
1764
|
...(existing?.channels ?? {}),
|
|
@@ -1514,15 +1767,18 @@ class Dispatcher {
|
|
|
1514
1767
|
mimetype: source.mimetype,
|
|
1515
1768
|
},
|
|
1516
1769
|
};
|
|
1517
|
-
const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels
|
|
1770
|
+
const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels }, ctx);
|
|
1518
1771
|
const exactWritten = Results.assert(written);
|
|
1772
|
+
const parseIssues = exactWritten.status === 200 || exactWritten.status === 201
|
|
1773
|
+
? await new DbProjectionCaps(ctx).parseIssues(source.content, source.mimetype)
|
|
1774
|
+
: undefined;
|
|
1519
1775
|
const materialized = source.lineMarker === null
|
|
1520
1776
|
|| (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
|
|
1521
1777
|
? exactWritten
|
|
1522
|
-
: _a.#withEditMaterialization(exactWritten, editReceipt(
|
|
1778
|
+
: _a.#withEditMaterialization(exactWritten, editReceipt("", source.content, [{
|
|
1523
1779
|
marker: { marks: [1, -1] },
|
|
1524
1780
|
body: source.content,
|
|
1525
|
-
}]));
|
|
1781
|
+
}], parseIssues));
|
|
1526
1782
|
return this.#finalizeEffects(materialized, destination, [destinationEffect]);
|
|
1527
1783
|
}
|
|
1528
1784
|
async #copyOrchestration({ statement, source, destination, ctx, }) {
|
|
@@ -1532,13 +1788,13 @@ class Dispatcher {
|
|
|
1532
1788
|
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1533
1789
|
if (_a.#isDispatchResult(resolvedDestination))
|
|
1534
1790
|
return resolvedDestination;
|
|
1535
|
-
const selected = await this.#selectSource(resolvedSource, ctx);
|
|
1791
|
+
const selected = await this.#selectSource(resolvedSource, ctx, "COPY");
|
|
1536
1792
|
if (_a.#isDispatchResult(selected))
|
|
1537
1793
|
return selected;
|
|
1538
1794
|
const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
|
|
1539
1795
|
return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
|
|
1540
1796
|
}
|
|
1541
|
-
#deferredMoveSource(source, destination) {
|
|
1797
|
+
#deferredMoveSource(source, destination, lineAnchorPrecondition) {
|
|
1542
1798
|
return {
|
|
1543
1799
|
target: source.target,
|
|
1544
1800
|
lineMarker: source.lineMarker,
|
|
@@ -1546,6 +1802,7 @@ class Dispatcher {
|
|
|
1546
1802
|
pathname: source.pathname,
|
|
1547
1803
|
channel: source.channel,
|
|
1548
1804
|
destination: this.#resourceAddress(destination),
|
|
1805
|
+
lineAnchorPrecondition,
|
|
1549
1806
|
};
|
|
1550
1807
|
}
|
|
1551
1808
|
#moveFailureAfterDestination(result, destination, destinationEffects) {
|
|
@@ -1567,7 +1824,7 @@ class Dispatcher {
|
|
|
1567
1824
|
});
|
|
1568
1825
|
return _a.#withCombinedEffects(failed, destinationEffects);
|
|
1569
1826
|
}
|
|
1570
|
-
async #removeMoveSource(statement, source, ctx) {
|
|
1827
|
+
async #removeMoveSource(statement, source, ctx, lineAnchorPrecondition = null) {
|
|
1571
1828
|
const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
|
|
1572
1829
|
if (source.lineMarker === null) {
|
|
1573
1830
|
const deleted = await this.#deleteChannel(source.scheme, source.pathname, source.channel, ctx);
|
|
@@ -1576,9 +1833,8 @@ class Dispatcher {
|
|
|
1576
1833
|
const edited = await this.#invokeEditBatch(source, [{
|
|
1577
1834
|
marker: source.lineMarker,
|
|
1578
1835
|
body: "",
|
|
1579
|
-
tags: null,
|
|
1580
1836
|
position: statement.position,
|
|
1581
|
-
}], ctx);
|
|
1837
|
+
}], ctx, lineAnchorPrecondition);
|
|
1582
1838
|
return this.#finalizeEffects(edited, source, [effect]);
|
|
1583
1839
|
}
|
|
1584
1840
|
async #moveWithinChannel(statement, source, destination, ctx) {
|
|
@@ -1595,22 +1851,24 @@ class Dispatcher {
|
|
|
1595
1851
|
if (destination.lineMarker === null) {
|
|
1596
1852
|
return this.#writeDestination(statement, source, destination, ctx);
|
|
1597
1853
|
}
|
|
1598
|
-
const
|
|
1854
|
+
const resolvedDestination = this.#resolveResourceLineMarker(destination, source.completeContent, "MOVE");
|
|
1855
|
+
if ("result" in resolvedDestination)
|
|
1856
|
+
return resolvedDestination.result;
|
|
1857
|
+
const precondition = _a.#mergeLineAnchorPreconditions(source.lineAnchorPrecondition, resolvedDestination.precondition);
|
|
1858
|
+
const moved = await this.#invokeEditBatch(resolvedDestination.selection, [
|
|
1599
1859
|
{
|
|
1600
|
-
marker:
|
|
1860
|
+
marker: resolvedDestination.selection.lineMarker,
|
|
1601
1861
|
body: source.content,
|
|
1602
|
-
tags: Array.isArray(statement.signal) ? statement.signal : [],
|
|
1603
1862
|
position: statement.position,
|
|
1604
1863
|
},
|
|
1605
1864
|
{
|
|
1606
1865
|
marker: source.lineMarker,
|
|
1607
1866
|
body: "",
|
|
1608
|
-
tags: null,
|
|
1609
1867
|
position: statement.position,
|
|
1610
1868
|
},
|
|
1611
|
-
], ctx);
|
|
1612
|
-
const effect = this.#pendingEffect(
|
|
1613
|
-
return this.#finalizeEffects(moved,
|
|
1869
|
+
], ctx, precondition);
|
|
1870
|
+
const effect = this.#pendingEffect(resolvedDestination.selection, "update");
|
|
1871
|
+
return this.#finalizeEffects(moved, resolvedDestination.selection, [effect, effect]);
|
|
1614
1872
|
}
|
|
1615
1873
|
async #moveOrchestration({ statement, source, destination, ctx, }) {
|
|
1616
1874
|
const resolvedSource = await this.#resolveResourceSelection(source, ctx);
|
|
@@ -1619,7 +1877,7 @@ class Dispatcher {
|
|
|
1619
1877
|
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1620
1878
|
if (_a.#isDispatchResult(resolvedDestination))
|
|
1621
1879
|
return resolvedDestination;
|
|
1622
|
-
const selected = await this.#selectSource(resolvedSource, ctx);
|
|
1880
|
+
const selected = await this.#selectSource(resolvedSource, ctx, "MOVE");
|
|
1623
1881
|
if (_a.#isDispatchResult(selected))
|
|
1624
1882
|
return selected;
|
|
1625
1883
|
if (this.#sameChannel(resolvedSource, resolvedDestination)) {
|
|
@@ -1638,11 +1896,11 @@ class Dispatcher {
|
|
|
1638
1896
|
...destinationResult,
|
|
1639
1897
|
attrs: {
|
|
1640
1898
|
...destinationResult.attrs,
|
|
1641
|
-
moveSource: this.#deferredMoveSource(
|
|
1899
|
+
moveSource: this.#deferredMoveSource(selected, resolvedDestination, selected.lineAnchorPrecondition),
|
|
1642
1900
|
},
|
|
1643
1901
|
};
|
|
1644
1902
|
}
|
|
1645
|
-
const sourceResult = await this.#removeMoveSource(statement,
|
|
1903
|
+
const sourceResult = await this.#removeMoveSource(statement, selected, ctx, selected.lineAnchorPrecondition);
|
|
1646
1904
|
if (sourceResult.status === 202) {
|
|
1647
1905
|
return {
|
|
1648
1906
|
...sourceResult,
|
|
@@ -1738,7 +1996,10 @@ class Dispatcher {
|
|
|
1738
1996
|
|| resolvedSource.channel !== deferred.channel) {
|
|
1739
1997
|
throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
|
|
1740
1998
|
}
|
|
1741
|
-
const removed = await this.#removeMoveSource(statement,
|
|
1999
|
+
const removed = await this.#removeMoveSource(statement, {
|
|
2000
|
+
...resolvedSource,
|
|
2001
|
+
lineMarker: resolvedSource.lineMarker,
|
|
2002
|
+
}, ctx, deferred.lineAnchorPrecondition);
|
|
1742
2003
|
if (removed.status >= 400) {
|
|
1743
2004
|
return {
|
|
1744
2005
|
resolution: settlement.resolution,
|
|
@@ -1776,7 +2037,7 @@ class Dispatcher {
|
|
|
1776
2037
|
// {§send-premature-terminate} — the unified PENDING SET, judged at the terminal's OWN dispatch
|
|
1777
2038
|
// (post-batch: the emission's earlier ops already executed, so a same-turn KILL+[200] repairs in
|
|
1778
2039
|
// ONE turn, and a same-turn WORK+[200] is caught — the spawn is live by the time the SEND lands).
|
|
1779
|
-
// pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
|
|
2040
|
+
// pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN/BARE, results unseen
|
|
1780
2041
|
// until next packet). Failed operations are the separate next-packet leg shared by explicit
|
|
1781
2042
|
// completion and empty-join completion. Nothing pending may be silently discarded; 499 discards
|
|
1782
2043
|
// BY STATED INTENT and is never gated.
|
|
@@ -1789,30 +2050,32 @@ class Dispatcher {
|
|
|
1789
2050
|
const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
|
|
1790
2051
|
if (liveChild !== undefined)
|
|
1791
2052
|
pending.push("surviving workers");
|
|
1792
|
-
const
|
|
1793
|
-
if (
|
|
2053
|
+
const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
|
|
2054
|
+
if (boundaries.retrievals)
|
|
1794
2055
|
pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
|
|
1795
|
-
if (
|
|
2056
|
+
if (boundaries.streamTerminations) {
|
|
1796
2057
|
pending.push("completed stream results that land in the NEXT packet's Log");
|
|
1797
2058
|
}
|
|
1798
|
-
if (
|
|
2059
|
+
if (boundaries.childTerminations)
|
|
1799
2060
|
pending.push("worker results that arrived during this turn (they land NEXT turn)");
|
|
1800
2061
|
return pending;
|
|
1801
2062
|
}
|
|
1802
|
-
// Results cross an observation boundary only when they have appeared in a
|
|
1803
|
-
//
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2063
|
+
// Results cross an observation boundary only when they have appeared in a packet;
|
|
2064
|
+
// successful FOLD effects likewise become useful through the curated next packet.
|
|
2065
|
+
// Keep every next-packet boundary in one classifier while letting the callers apply
|
|
2066
|
+
// their distinct contracts: retrievals block explicit completion, whereas FOLD only
|
|
2067
|
+
// prevents an empty wait from being inferred as completion.
|
|
2068
|
+
async #nextPacketBoundaries(workerId, turnId) {
|
|
2069
|
+
const [turnBoundaries, streamTermination, childTermination] = await Promise.all([
|
|
2070
|
+
this.#db.engine_turn_packet_boundaries.all({ turn_id: turnId }),
|
|
1809
2071
|
this.#db.engine_worker_has_undelivered_stream_term
|
|
1810
2072
|
.get({ worker_id: workerId }),
|
|
1811
2073
|
this.#db.engine_worker_has_undelivered_child_term
|
|
1812
2074
|
.get({ worker_id: workerId }),
|
|
1813
2075
|
]);
|
|
1814
2076
|
return {
|
|
1815
|
-
retrievals:
|
|
2077
|
+
retrievals: turnBoundaries.some(({ op }) => op !== "FOLD"),
|
|
2078
|
+
folds: turnBoundaries.some(({ op }) => op === "FOLD"),
|
|
1816
2079
|
streamTerminations: streamTermination !== undefined,
|
|
1817
2080
|
childTerminations: childTermination !== undefined,
|
|
1818
2081
|
};
|
|
@@ -1854,14 +2117,14 @@ class Dispatcher {
|
|
|
1854
2117
|
return _a.#failure("send-status-required", 400, "SEND requires a numeric status.", {}, { retryable: false });
|
|
1855
2118
|
}
|
|
1856
2119
|
const raw = statement.body === null ? "" : statement.body.raw;
|
|
1857
|
-
// The park rides SEND
|
|
2120
|
+
// The park rides SEND signal 202 only ({§park-202-only}). A scoped signal 102 is neither
|
|
1858
2121
|
// a wait nor a meaningful continuation, so reject it instead of preserving the
|
|
1859
2122
|
// retired dual spelling.
|
|
1860
2123
|
if (status === 102 && statement.lineMarker !== null) {
|
|
1861
|
-
return _a.#failure("send-scope-invalid", 400, "
|
|
2124
|
+
return _a.#failure("send-scope-invalid", 400, "`## SEND0 [102]` does not accept a scope.", {}, {
|
|
1862
2125
|
requestedStatus: 102,
|
|
1863
2126
|
scope: statement.lineMarker,
|
|
1864
|
-
recovery: "Use
|
|
2127
|
+
recovery: "Use `## SEND0 [202] <scope>` to wait, or remove the scope to continue.",
|
|
1865
2128
|
retryable: false,
|
|
1866
2129
|
});
|
|
1867
2130
|
}
|
|
@@ -1875,7 +2138,7 @@ class Dispatcher {
|
|
|
1875
2138
|
this.#parkDeadlines.set(loopId, -1); // indefinite: the bounded child's terminal is the wake edge
|
|
1876
2139
|
return { status: 102, attrs: { parked: -1, join: true } };
|
|
1877
2140
|
}
|
|
1878
|
-
// {§wait-obligation-matrix} — SEND
|
|
2141
|
+
// {§wait-obligation-matrix} — SEND signal 202 is the obligation-checked join. A live
|
|
1879
2142
|
// obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
|
|
1880
2143
|
// reawakens it ({§worker-lifecycle-child-wake}); a wait on nothing (∅) is already satisfied and
|
|
1881
2144
|
// resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
|
|
@@ -1894,8 +2157,8 @@ class Dispatcher {
|
|
|
1894
2157
|
// all complete-but-unobserved. Their wake edge may already have
|
|
1895
2158
|
// fired, so do not park; continue directly to the packet that
|
|
1896
2159
|
// materializes them.
|
|
1897
|
-
const
|
|
1898
|
-
if (
|
|
2160
|
+
const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
|
|
2161
|
+
if (boundaries.retrievals || boundaries.folds || boundaries.streamTerminations || boundaries.childTerminations) {
|
|
1899
2162
|
return { status: 102 };
|
|
1900
2163
|
}
|
|
1901
2164
|
const failCount = await this.#unobservedFailureCount(turnId);
|
|
@@ -1950,7 +2213,7 @@ class Dispatcher {
|
|
|
1950
2213
|
return _a.#failure("retrieval-results-unobserved", 409, "Last turn both performed retrieval operations and attempted to terminate. Retrieval operations force an additional turn so their results can be reviewed.", {}, {
|
|
1951
2214
|
pending: [...pending],
|
|
1952
2215
|
stage: "completion",
|
|
1953
|
-
recovery: "Review the results, then use only
|
|
2216
|
+
recovery: "Review the results, then use only `# PLAN0` and `## SEND0 [200]` to conclude.",
|
|
1954
2217
|
retryable: false,
|
|
1955
2218
|
});
|
|
1956
2219
|
}
|
|
@@ -1977,7 +2240,7 @@ class Dispatcher {
|
|
|
1977
2240
|
turn_id: turnId,
|
|
1978
2241
|
});
|
|
1979
2242
|
if (seqs === undefined) {
|
|
1980
|
-
throw new Error(`SEND
|
|
2243
|
+
throw new Error(`SEND signal 499: no coordinate for loop=${loopId} turn=${turnId}`);
|
|
1981
2244
|
}
|
|
1982
2245
|
Results.attachInstance(failure, `log:///${seqs.loop_seq}/${seqs.turn_seq}/${ctx.sequence}/SEND`);
|
|
1983
2246
|
const finished = await this.#lifecycle.finish(loopId, failure);
|
|
@@ -2070,6 +2333,7 @@ class Dispatcher {
|
|
|
2070
2333
|
statement,
|
|
2071
2334
|
manifest,
|
|
2072
2335
|
target,
|
|
2336
|
+
identity: target,
|
|
2073
2337
|
representation: resolved.representation,
|
|
2074
2338
|
mimetypes: ctx.mimetypes,
|
|
2075
2339
|
}));
|
|
@@ -2171,11 +2435,14 @@ class Dispatcher {
|
|
|
2171
2435
|
async #runLogCuration(statement, ctx) {
|
|
2172
2436
|
const addressedScheme = schemeNameOf(statement.target);
|
|
2173
2437
|
if (addressedScheme !== null && addressedScheme !== "log") {
|
|
2174
|
-
return
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2438
|
+
return {
|
|
2439
|
+
result: _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
|
|
2440
|
+
scheme: addressedScheme,
|
|
2441
|
+
operation: statement.op,
|
|
2442
|
+
retryable: false,
|
|
2443
|
+
}),
|
|
2444
|
+
plan: null,
|
|
2445
|
+
};
|
|
2179
2446
|
}
|
|
2180
2447
|
const handler = this.#schemes.get("log");
|
|
2181
2448
|
const manifest = this.#schemes.manifestFor("log");
|
|
@@ -2183,21 +2450,20 @@ class Dispatcher {
|
|
|
2183
2450
|
throw new Error("the core log curation owner is not registered");
|
|
2184
2451
|
}
|
|
2185
2452
|
const schemeCtx = new SchemeCtxImpl(ctx, "log", manifest, this.#liveSubscriptions);
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
: Results.assert(await handler.fold(statement, schemeCtx));
|
|
2453
|
+
const outcome = await handler.curate(statement, schemeCtx);
|
|
2454
|
+
return { result: Results.assert(outcome.result), plan: outcome.plan };
|
|
2189
2455
|
}
|
|
2190
2456
|
// {§proposal}/{§send} — status 202 is a proposal except for broadcast
|
|
2191
|
-
// SEND
|
|
2457
|
+
// SEND signal 202, which parks the loop. The operation disambiguates the status.
|
|
2192
2458
|
static #isProposal(statement, result) {
|
|
2193
|
-
// {§send-300-choices} — SEND
|
|
2459
|
+
// {§send-300-choices} — SEND signal 300 is the proposal exception.
|
|
2194
2460
|
if (result.status !== 202)
|
|
2195
2461
|
return false;
|
|
2196
2462
|
if (statement.op === "SEND" && statement.signal === 300)
|
|
2197
2463
|
return true;
|
|
2198
2464
|
return !(statement.op === "SEND" && statement.target === null);
|
|
2199
2465
|
}
|
|
2200
|
-
async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, }) {
|
|
2466
|
+
async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, curationPlan, modelCallId, }) {
|
|
2201
2467
|
const durableStatement = DurableStatement.project(statement);
|
|
2202
2468
|
const target = this.#extractTarget(durableStatement.target);
|
|
2203
2469
|
await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon}
|
|
@@ -2207,12 +2473,23 @@ class Dispatcher {
|
|
|
2207
2473
|
// A proposal (status 202 from a side-effecting op) is written to the log in
|
|
2208
2474
|
// state='proposed' until the proposal lifecycle resolves it; attrs holds the
|
|
2209
2475
|
// scheme-supplied payload (file diff, exec command, etc.) the client renders
|
|
2210
|
-
// for review and the scheme consumes on accept. A broadcast SEND
|
|
2476
|
+
// for review and the scheme consumes on accept. A broadcast SEND signal 202 is a
|
|
2211
2477
|
// parked-terminal, not a proposal (#isProposal) → state='resolved'.
|
|
2212
2478
|
const isProposed = _a.#isProposal(statement, result);
|
|
2213
2479
|
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
2214
2480
|
? { ...result.attrs }
|
|
2215
2481
|
: {};
|
|
2482
|
+
if (curationPlan !== null) {
|
|
2483
|
+
if (Object.hasOwn(attrsObj, "__plurnk_curation")) {
|
|
2484
|
+
throw new Error("Dispatcher.#writeLog: result attrs collide with private log curation state");
|
|
2485
|
+
}
|
|
2486
|
+
attrsObj.__plurnk_curation = {
|
|
2487
|
+
ids: curationPlan.targetLogEntryIds,
|
|
2488
|
+
expanded: curationPlan.expanded,
|
|
2489
|
+
add: curationPlan.add,
|
|
2490
|
+
remove: curationPlan.remove,
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2216
2493
|
const seqs = statement.op === "EXEC" || result.problem !== undefined
|
|
2217
2494
|
? await this.#db.engine_loop_turn_seqs.get({
|
|
2218
2495
|
loop_id: loopId,
|
|
@@ -2263,6 +2540,7 @@ class Dispatcher {
|
|
|
2263
2540
|
sequence: sequence,
|
|
2264
2541
|
origin,
|
|
2265
2542
|
source: null, // dispatch entries are self-authored; {§env-delta} deltas set this
|
|
2543
|
+
model_call_id: modelCallId,
|
|
2266
2544
|
op: durableStatement.op,
|
|
2267
2545
|
suffix: durableStatement.suffix,
|
|
2268
2546
|
signal: this.#signalToJson(durableStatement.signal),
|