@plurnk/plurnk-service 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +22 -27
- package/INSTALL.md +22 -17
- package/README.md +1 -1
- package/SPEC.md +505 -362
- 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/ChannelWrite.sql +3 -3
- package/dist/core/CoreSchemeServices.d.ts +1 -1
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +1 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +15 -5
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +220 -978
- 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 +18 -43
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +90 -1554
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +192 -112
- 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 +3 -0
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +21 -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 +127 -0
- package/dist/core/ModelCall.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts +14 -25
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +170 -244
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts +1 -1
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +15 -3
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +2 -2
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +17 -9
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +25 -8
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +73 -0
- package/dist/core/ResourceMutations.d.ts.map +1 -0
- package/dist/core/ResourceMutations.js +1142 -0
- package/dist/core/ResourceMutations.js.map +1 -0
- package/dist/core/SchemeRegistry.js +10 -10
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StoredPacket.d.ts +2 -2
- package/dist/core/StoredPacket.js +7 -7
- 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/TurnRunner.d.ts +72 -0
- package/dist/core/TurnRunner.d.ts.map +1 -0
- package/dist/core/TurnRunner.js +1730 -0
- package/dist/core/TurnRunner.js.map +1 -0
- package/dist/core/caps/CapsResolve.js +1 -1
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +4 -4
- package/dist/core/caps/DbChannelCaps.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/content-hash.js +2 -2
- package/dist/core/content-hash.js.map +1 -1
- package/dist/core/content-weight.d.ts +2 -0
- package/dist/core/content-weight.d.ts.map +1 -0
- package/dist/core/content-weight.js +4 -0
- package/dist/core/content-weight.js.map +1 -0
- package/dist/core/content_weight.d.ts +4 -0
- package/dist/core/content_weight.d.ts.map +1 -0
- package/dist/core/content_weight.js +6 -0
- package/dist/core/content_weight.js.map +1 -0
- 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 +38 -17
- 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 +6 -3
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +257 -99
- 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/scheme-types.d.ts +1 -1
- package/dist/core/scheme-types.d.ts.map +1 -1
- 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/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 +221 -110
- 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 +136 -110
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +1 -1
- package/dist/schemes/ExecOutputScheme.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 +25 -23
- 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 +172 -107
- 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 +11 -7
- 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 +19 -13
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +4 -16
- package/dist/schemes/_entry-find.d.ts +17 -16
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +58 -52
- 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 +32 -28
- 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 +89 -49
- 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 +7 -42
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +139 -900
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +117 -0
- package/dist/server/DrainSupervisor.d.ts.map +1 -0
- package/dist/server/DrainSupervisor.js +802 -0
- package/dist/server/DrainSupervisor.js.map +1 -0
- 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 +28 -15
- package/dist/server/logEntry.d.ts +2 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +14 -2
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +5 -1
- package/dist/server/seam-entry-read.sql +2 -2
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +622 -216
- 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/ruler_count.d.ts +0 -4
- package/dist/core/ruler_count.d.ts.map +0 -1
- package/dist/core/ruler_count.js +0 -7
- package/dist/core/ruler_count.js.map +0 -1
- package/dist/core/token-ruler.d.ts +0 -2
- package/dist/core/token-ruler.d.ts.map +0 -1
- package/dist/core/token-ruler.js +0 -5
- package/dist/core/token-ruler.js.map +0 -1
package/dist/core/Dispatcher.js
CHANGED
|
@@ -5,11 +5,11 @@ 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 {
|
|
12
|
+
import { ReadProjector } from "../content/index.js";
|
|
13
13
|
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
14
14
|
import EntryCrud from "../schemes/_entry-crud.js";
|
|
15
15
|
import EntryOps from "../schemes/_entry-ops.js";
|
|
@@ -23,12 +23,23 @@ import EffectPolicy from "../schemes/EffectPolicy.js";
|
|
|
23
23
|
import { CoreSchemeAdapterBase, } from "./CoreSchemeServices.js";
|
|
24
24
|
import { InvalidOperationResultError, NetworkAddress, } from "@plurnk/plurnk-schemes";
|
|
25
25
|
import DurableStatement from "./DurableStatement.js";
|
|
26
|
+
import ResourceMutations from "./ResourceMutations.js";
|
|
27
|
+
import LogBody from "./LogBody.js";
|
|
26
28
|
// SPEC {§scheme-surface}: writer must be in target scheme's manifest.writableBy.
|
|
27
29
|
// OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
|
|
28
30
|
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL", "FORK", "WORK"]);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const assertClassifyingSignal = (statement) => {
|
|
32
|
+
if (statement.op === "FIND"
|
|
33
|
+
|| statement.op === "READ"
|
|
34
|
+
|| statement.op === "EDIT"
|
|
35
|
+
|| statement.op === "COPY"
|
|
36
|
+
|| statement.op === "MOVE"
|
|
37
|
+
|| statement.op === "BARE") {
|
|
38
|
+
TagSignal.applied(statement.signal);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Op dispatch ({§op-methods-op-dispatch}): admission, operation-owner routing,
|
|
42
|
+
// durable log writing, and proposal lifecycle.
|
|
32
43
|
class Dispatcher {
|
|
33
44
|
static #failure(code, status, detail, fields = {}, extensions = {}) {
|
|
34
45
|
return Results.failure("engine:dispatcher", code, status, detail, fields, extensions);
|
|
@@ -41,7 +52,7 @@ class Dispatcher {
|
|
|
41
52
|
#db;
|
|
42
53
|
#schemes;
|
|
43
54
|
#mimetypes;
|
|
44
|
-
#
|
|
55
|
+
#weighContent;
|
|
45
56
|
#notices;
|
|
46
57
|
#proposals;
|
|
47
58
|
// Boot-discovered runtime executors, late-injected on Engine — thunked.
|
|
@@ -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;
|
|
@@ -63,12 +74,12 @@ class Dispatcher {
|
|
|
63
74
|
#joinTargets;
|
|
64
75
|
#liveSubscriptions;
|
|
65
76
|
#lifecycle;
|
|
66
|
-
#
|
|
67
|
-
constructor({ db, schemes, mimetypes,
|
|
77
|
+
#resourceMutations;
|
|
78
|
+
constructor({ db, schemes, mimetypes, weigh, notices, proposals, executors, loopSignal, streamEventNotify, wakeWorkerNotify, injectWorker, branchWorker, branchCompletionGate, cancelWorker, cancelDescendants, searchGate, parkDeadlines, joinTargets, liveSubscriptions }) {
|
|
68
79
|
this.#db = db;
|
|
69
80
|
this.#schemes = schemes;
|
|
70
81
|
this.#mimetypes = mimetypes;
|
|
71
|
-
this.#
|
|
82
|
+
this.#weighContent = weigh;
|
|
72
83
|
this.#notices = notices;
|
|
73
84
|
this.#proposals = proposals;
|
|
74
85
|
this.#executors = executors;
|
|
@@ -85,6 +96,23 @@ class Dispatcher {
|
|
|
85
96
|
this.#joinTargets = joinTargets ?? new Set();
|
|
86
97
|
this.#liveSubscriptions = liveSubscriptions;
|
|
87
98
|
this.#lifecycle = new LoopLifecycle(db);
|
|
99
|
+
this.#resourceMutations = new ResourceMutations({
|
|
100
|
+
schemes,
|
|
101
|
+
liveSubscriptions,
|
|
102
|
+
run: (schemeName, statement, ctx) => this.#run(schemeName, statement, ctx),
|
|
103
|
+
checkWritable: (statement, origin) => this.#checkWritable(statement, origin),
|
|
104
|
+
checkFlagsGate: (statement, loopId) => this.#checkFlagsGate(statement, loopId),
|
|
105
|
+
editTargetIdentity: (statement, workspaceId) => this.#editTargetIdentity(statement, workspaceId),
|
|
106
|
+
canonicalFilePath: (pathname, workspaceId) => this.#canonicalFilePath(pathname, workspaceId),
|
|
107
|
+
prepareDataRepresentation: (args) => this.#prepareDataRepresentation({
|
|
108
|
+
...args,
|
|
109
|
+
handler: args.handler,
|
|
110
|
+
}),
|
|
111
|
+
readEntry: (scheme, pathname, ctx) => this.#readEntry(scheme, pathname, ctx),
|
|
112
|
+
writeEntry: (scheme, pathname, entry, ctx) => this.#writeEntry(scheme, pathname, entry, ctx),
|
|
113
|
+
deleteChannel: (scheme, pathname, channel, ctx) => this.#deleteChannel(scheme, pathname, channel, ctx),
|
|
114
|
+
applyProposal: (statement, result, resolution, ids) => this.#proposals.workerApply(statement, result, resolution, ids),
|
|
115
|
+
});
|
|
88
116
|
}
|
|
89
117
|
// workspace → project_root, memoized: {§fs-namespace} fixes the root immutably at
|
|
90
118
|
// workspace creation, so a process-lifetime cache can never go stale.
|
|
@@ -124,7 +152,12 @@ class Dispatcher {
|
|
|
124
152
|
status: result.status,
|
|
125
153
|
entry: result.entry === null
|
|
126
154
|
? null
|
|
127
|
-
: {
|
|
155
|
+
: {
|
|
156
|
+
channels: { ...result.entry.channels },
|
|
157
|
+
...(result.entry.attributes === undefined
|
|
158
|
+
? {}
|
|
159
|
+
: { attributes: { ...result.entry.attributes } }),
|
|
160
|
+
},
|
|
128
161
|
});
|
|
129
162
|
}
|
|
130
163
|
async #writeEntry(scheme, pathname, entry, ctx) {
|
|
@@ -197,113 +230,35 @@ class Dispatcher {
|
|
|
197
230
|
if (key !== null)
|
|
198
231
|
target.pathname = key;
|
|
199
232
|
}
|
|
200
|
-
async
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
233
|
+
async #canonicalFilePath(pathname, workspaceId) {
|
|
234
|
+
return Namespace.canonicalizeSpelling(pathname, await this.#workspaceRoot(workspaceId));
|
|
235
|
+
}
|
|
236
|
+
async #editTargetIdentity(statement, workspaceId) {
|
|
237
|
+
const target = this.#extractTarget(statement.target);
|
|
238
|
+
await this.#canonColumns(target, workspaceId);
|
|
239
|
+
return {
|
|
240
|
+
key: JSON.stringify([
|
|
208
241
|
schemeNameOf(statement.target),
|
|
209
242
|
target.scheme,
|
|
210
243
|
target.hostname,
|
|
211
244
|
target.pathname,
|
|
212
245
|
target.fragment,
|
|
213
|
-
])
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let initial;
|
|
224
|
-
let denial = group.map((statement) => this.#checkWritable(statement, origin)).find((result) => result !== null) ?? null;
|
|
225
|
-
if (denial === null) {
|
|
226
|
-
for (const statement of group) {
|
|
227
|
-
denial = await this.#checkFlagsGate(statement, loopId);
|
|
228
|
-
if (denial !== null)
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (denial !== null) {
|
|
233
|
-
initial = denial;
|
|
234
|
-
}
|
|
235
|
-
else if (schemeName === null) {
|
|
236
|
-
initial = _a.#failure("target-required", 400, "EDIT requires a target scheme.", {}, { retryable: false });
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
const handler = this.#schemes.get(schemeName);
|
|
240
|
-
const method = handler?.editBatch;
|
|
241
|
-
const manifest = this.#schemes.manifestFor(schemeName);
|
|
242
|
-
if (typeof method !== "function" || manifest === undefined) {
|
|
243
|
-
initial = _a.#failure("operation-not-implemented", 501, `Scheme '${schemeName}' does not implement EDIT batches.`, {}, {
|
|
244
|
-
scheme: schemeName,
|
|
245
|
-
operation: "EDIT",
|
|
246
|
-
retryable: false,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
try {
|
|
251
|
-
const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
|
|
252
|
-
const publishedChannel = first.target?.kind === "url"
|
|
253
|
-
? first.target.fragment ?? manifest.defaultChannel
|
|
254
|
-
: manifest.defaultChannel;
|
|
255
|
-
initial = Results.assert(await method.call(handler, group, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, { publishedChannel })));
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
if (err instanceof InvalidOperationResultError)
|
|
259
|
-
throw err;
|
|
260
|
-
console.error(`Scheme '${schemeName}' EDIT batch threw outside its operation result contract:`, err);
|
|
261
|
-
initial = _a.#failure("scheme-handler-threw", 500, `The '${schemeName}' scheme did not produce an EDIT result.`, {}, {
|
|
262
|
-
stage: "scheme-dispatch",
|
|
263
|
-
scheme: schemeName,
|
|
264
|
-
operation: "EDIT",
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
let resolveSettled;
|
|
270
|
-
const settled = new Promise((resolve) => { resolveSettled = resolve; });
|
|
271
|
-
const candidate = initial.editReceipt;
|
|
272
|
-
const expectedNormalizations = group.filter(({ lineMarker }) => lineMarker?.marks.length === 3).length;
|
|
273
|
-
if (initial.status < 400
|
|
274
|
-
&& (initial.scopeNormalizations?.length ?? 0) !== expectedNormalizations) {
|
|
275
|
-
throw new InvalidOperationResultError(`EDIT batch normalized ${initial.scopeNormalizations?.length ?? 0} scope(s), expected ${expectedNormalizations}.`);
|
|
276
|
-
}
|
|
277
|
-
const batch = {
|
|
278
|
-
initial,
|
|
279
|
-
settled,
|
|
280
|
-
aggregate: candidate === undefined || candidate === null
|
|
281
|
-
? undefined
|
|
282
|
-
: assertEditBatchReceipt(candidate),
|
|
283
|
-
settle: resolveSettled,
|
|
284
|
-
};
|
|
285
|
-
let normalizationIndex = 0;
|
|
286
|
-
for (const [index, statement] of group.entries()) {
|
|
287
|
-
const ownsNormalization = statement.lineMarker?.marks.length === 3 && initial.status < 400;
|
|
288
|
-
this.#preparedEdits.set(statement, {
|
|
289
|
-
first: index === 0,
|
|
290
|
-
index,
|
|
291
|
-
normalizationIndex: ownsNormalization ? normalizationIndex++ : null,
|
|
292
|
-
batch,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
246
|
+
]),
|
|
247
|
+
identity: renderTarget(target),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async prepareEditBatches(statements, context) {
|
|
251
|
+
for (const statement of statements)
|
|
252
|
+
assertClassifyingSignal(statement); // {§log-tag-signal}
|
|
253
|
+
const { workspaceId, workerId, loopId, turnId, origin } = context;
|
|
254
|
+
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
255
|
+
await this.#resourceMutations.prepareEditBatches(statements, context, schemeCtx);
|
|
296
256
|
}
|
|
297
257
|
async dispatch(context) {
|
|
258
|
+
assertClassifyingSignal(context.statement); // {§log-tag-signal}
|
|
298
259
|
const result = await this.#dispatchOne(context);
|
|
299
260
|
if (context.statement.op === "EDIT") {
|
|
300
|
-
|
|
301
|
-
if (prepared?.first === true) {
|
|
302
|
-
const normalizations = prepared.batch.initial.scopeNormalizations;
|
|
303
|
-
prepared.batch.settle(normalizations === undefined
|
|
304
|
-
? result
|
|
305
|
-
: Results.assert({ ...result, scopeNormalizations: normalizations }));
|
|
306
|
-
}
|
|
261
|
+
this.#resourceMutations.settleEdit(context.statement, result);
|
|
307
262
|
}
|
|
308
263
|
return result;
|
|
309
264
|
}
|
|
@@ -311,6 +266,7 @@ class Dispatcher {
|
|
|
311
266
|
const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, } = context;
|
|
312
267
|
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
313
268
|
let result;
|
|
269
|
+
let curationPlan = null;
|
|
314
270
|
let denial = this.#checkWritable(statement, origin);
|
|
315
271
|
if (denial === null)
|
|
316
272
|
denial = await this.#checkFlagsGate(statement, loopId);
|
|
@@ -325,49 +281,24 @@ class Dispatcher {
|
|
|
325
281
|
// those are system failures.
|
|
326
282
|
try {
|
|
327
283
|
if (statement.op === "EDIT") {
|
|
328
|
-
|
|
329
|
-
if (prepared === undefined) {
|
|
330
|
-
throw new InvalidOperationResultError("EDIT reached dispatch without a prepared resource batch.");
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
const settled = prepared.first
|
|
334
|
-
? prepared.batch.initial
|
|
335
|
-
: await prepared.batch.settled;
|
|
336
|
-
const normalization = prepared.normalizationIndex === null
|
|
337
|
-
? undefined
|
|
338
|
-
: settled.scopeNormalizations?.[prepared.normalizationIndex];
|
|
339
|
-
const { scopeNormalizations: _scopeNormalizations, ...projectedSettlement } = settled;
|
|
340
|
-
const statementResult = normalization === undefined
|
|
341
|
-
? projectedSettlement
|
|
342
|
-
: { ...projectedSettlement, scopeNormalizations: [normalization] };
|
|
343
|
-
const aggregate = prepared.batch.aggregate;
|
|
344
|
-
if (aggregate !== undefined) {
|
|
345
|
-
const receipt = assertEditBatchReceipt(aggregate);
|
|
346
|
-
const { editReceipt: _editReceipt, ...withoutAggregate } = statementResult;
|
|
347
|
-
result = {
|
|
348
|
-
...withoutAggregate,
|
|
349
|
-
receipt: projectEditReceipt(receipt, prepared.index),
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
result = statementResult;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
284
|
+
result = await this.#resourceMutations.preparedEditResult(statement);
|
|
356
285
|
}
|
|
357
286
|
else if (statement.op === "SEND" && statement.target === null) {
|
|
358
287
|
result = await this.#handleSendBroadcast(statement, { workspaceId, workerId, loopId, turnId, sequence });
|
|
359
288
|
}
|
|
360
289
|
else if (statement.op === "OPEN" || statement.op === "FOLD") {
|
|
361
|
-
|
|
290
|
+
const curation = await this.#runLogCuration(statement, schemeCtx);
|
|
291
|
+
result = curation.result;
|
|
292
|
+
curationPlan = curation.plan;
|
|
362
293
|
}
|
|
363
294
|
else if (statement.op === "FORK" || statement.op === "WORK") {
|
|
364
295
|
result = await this.#handleWorkerControl(statement, schemeCtx);
|
|
365
296
|
}
|
|
366
297
|
else if (statement.op === "COPY") {
|
|
367
|
-
result = await this.#handleCopy(statement, schemeCtx);
|
|
298
|
+
result = await this.#resourceMutations.handleCopy(statement, schemeCtx);
|
|
368
299
|
}
|
|
369
300
|
else if (statement.op === "MOVE") {
|
|
370
|
-
result = await this.#handleMove(statement, schemeCtx);
|
|
301
|
+
result = await this.#resourceMutations.handleMove(statement, schemeCtx);
|
|
371
302
|
}
|
|
372
303
|
else if (statement.op === "KILL") {
|
|
373
304
|
result = await this.#handleKill(statement, schemeCtx);
|
|
@@ -376,10 +307,8 @@ class Dispatcher {
|
|
|
376
307
|
result = this.#handlePlan(statement);
|
|
377
308
|
}
|
|
378
309
|
else if (statement.op === "EXEC") {
|
|
379
|
-
// EXEC
|
|
380
|
-
//
|
|
381
|
-
// exec scheme; the scheme handler reads runtime
|
|
382
|
-
// (signal), cwd (target), and command (body).
|
|
310
|
+
// EXEC routes unconditionally to its operation owner. The
|
|
311
|
+
// resolved runtime declaration owns body/target semantics.
|
|
383
312
|
result = await this.#gatedExec(statement, schemeCtx, loopId, turnId);
|
|
384
313
|
}
|
|
385
314
|
else {
|
|
@@ -406,19 +335,30 @@ class Dispatcher {
|
|
|
406
335
|
// {§join-blocking-collect}
|
|
407
336
|
if (typeof result.awaitWorker === "string")
|
|
408
337
|
this.#joinTargets.add(loopId);
|
|
409
|
-
const logEntryId = await this.#writeLog({
|
|
338
|
+
const logEntryId = await this.#writeLog({
|
|
339
|
+
statement,
|
|
340
|
+
result,
|
|
341
|
+
workspaceId,
|
|
342
|
+
workerId,
|
|
343
|
+
loopId,
|
|
344
|
+
turnId,
|
|
345
|
+
sequence,
|
|
346
|
+
origin,
|
|
347
|
+
curationPlan,
|
|
348
|
+
modelCallId: null,
|
|
349
|
+
});
|
|
410
350
|
// {§search-gate} — register successful searches AFTER #writeLog stamps the runtime
|
|
411
351
|
// entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
|
|
412
352
|
if (statement.op === "EXEC" && result.status < 400) {
|
|
413
353
|
const rt = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
|
|
414
|
-
const
|
|
354
|
+
const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
415
355
|
const attrPath = result.attrs?.pathname;
|
|
416
356
|
if (typeof attrPath === "string")
|
|
417
|
-
this.#searchGate?.registerPending(loopId, turnId, rt,
|
|
357
|
+
this.#searchGate?.registerPending(loopId, turnId, rt, body, attrPath);
|
|
418
358
|
}
|
|
419
359
|
onDispatch?.(logEntryId);
|
|
420
360
|
// Proposal lifecycle (SPEC.md {§engine-rails} + {§methods-proposal-resolve}; {§proposal-202-pauses}). When a
|
|
421
|
-
// side-effecting op returns status 202 (a broadcast SEND
|
|
361
|
+
// side-effecting op returns status 202 (a broadcast SEND signal 202 park is model
|
|
422
362
|
// speech, not a proposal — #isProposal), the entry is written
|
|
423
363
|
// state='proposed'; dispatch then PAUSES on a per-entry waiter until
|
|
424
364
|
// resolution arrives via Engine.resolveProposal (from a client-interface resume,
|
|
@@ -438,15 +378,13 @@ class Dispatcher {
|
|
|
438
378
|
}
|
|
439
379
|
if (autoAccept) {
|
|
440
380
|
const initialSettlement = await this.#proposals.workerApply(statement, result, { decision: "accept" }, { workspaceId, workerId, loopId, turnId });
|
|
441
|
-
const
|
|
442
|
-
const effective = await this.#settleMoveProposal({
|
|
381
|
+
const effective = await this.#resourceMutations.settleProposal({
|
|
443
382
|
statement,
|
|
444
383
|
result,
|
|
445
|
-
settlement,
|
|
384
|
+
settlement: initialSettlement,
|
|
446
385
|
ctx: schemeCtx,
|
|
447
386
|
ids: { workspaceId, workerId, loopId, turnId },
|
|
448
387
|
});
|
|
449
|
-
this.#recordEditSettlement(statement, effective);
|
|
450
388
|
return this.#proposals.applyResolution(logEntryId, effective);
|
|
451
389
|
}
|
|
452
390
|
// Register the resolution waiter SYNCHRONOUSLY before any await
|
|
@@ -475,15 +413,13 @@ class Dispatcher {
|
|
|
475
413
|
// preserved: an apply failure keeps its original status and
|
|
476
414
|
// Problem Details instead of masquerading as a client rejection.
|
|
477
415
|
const initialSettlement = await this.#proposals.workerApply(statement, result, resolution, { workspaceId, workerId, loopId, turnId });
|
|
478
|
-
const
|
|
479
|
-
const effective = await this.#settleMoveProposal({
|
|
416
|
+
const effective = await this.#resourceMutations.settleProposal({
|
|
480
417
|
statement,
|
|
481
418
|
result,
|
|
482
|
-
settlement,
|
|
419
|
+
settlement: initialSettlement,
|
|
483
420
|
ctx: schemeCtx,
|
|
484
421
|
ids: { workspaceId, workerId, loopId, turnId },
|
|
485
422
|
});
|
|
486
|
-
this.#recordEditSettlement(statement, effective);
|
|
487
423
|
const post = await this.#proposals.applyResolution(logEntryId, effective);
|
|
488
424
|
return post;
|
|
489
425
|
}
|
|
@@ -660,7 +596,7 @@ class Dispatcher {
|
|
|
660
596
|
wakeWorkerNotify: this.#wakeWorkerNotify,
|
|
661
597
|
injectWorker: this.#injectWorker,
|
|
662
598
|
mimetypes: this.#mimetypes,
|
|
663
|
-
|
|
599
|
+
weigh: this.#weighContent,
|
|
664
600
|
pushNotice: (notice) => this.#notices.push(workspaceId, loopId, notice),
|
|
665
601
|
executors: this.#executors(),
|
|
666
602
|
};
|
|
@@ -676,8 +612,8 @@ class Dispatcher {
|
|
|
676
612
|
return null;
|
|
677
613
|
if (statement.op === "SEND" && statement.target === null)
|
|
678
614
|
return null;
|
|
679
|
-
// EXEC's
|
|
680
|
-
//
|
|
615
|
+
// EXEC's operation authority always belongs to the exec scheme;
|
|
616
|
+
// runtime-specific resource authority is gated separately below.
|
|
681
617
|
if (statement.op === "EXEC") {
|
|
682
618
|
return this.#denyIfDisallowed("exec", origin);
|
|
683
619
|
}
|
|
@@ -709,8 +645,8 @@ class Dispatcher {
|
|
|
709
645
|
// the prior durable digest and the per-turn cap refuses without execution.
|
|
710
646
|
async #gatedExec(statement, ctx, loopId, turnId) {
|
|
711
647
|
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,
|
|
648
|
+
const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
649
|
+
const verdict = this.#searchGate?.check(loopId, turnId, runtime, body) ?? { verdict: "pass" };
|
|
714
650
|
if (verdict.verdict === "capped") {
|
|
715
651
|
return _a.#failure("search-limit-reached", 429, `This turn already used its ${verdict.cap} permitted searches.`, {}, {
|
|
716
652
|
searchLimit: verdict.cap,
|
|
@@ -817,19 +753,24 @@ class Dispatcher {
|
|
|
817
753
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
818
754
|
return check(statement.target) ?? check(statement.body?.target ?? null);
|
|
819
755
|
}
|
|
820
|
-
// {§exec-target-routing} —
|
|
821
|
-
//
|
|
756
|
+
// {§exec-target-routing} — only a runtime-declared resource target adds
|
|
757
|
+
// source authority. Literal identifiers and path targets stay executor-local.
|
|
822
758
|
if (statement.op === "EXEC") {
|
|
823
759
|
const operationDenial = checkScheme("exec");
|
|
824
760
|
if (operationDenial !== null)
|
|
825
761
|
return operationDenial;
|
|
762
|
+
const requested = typeof statement.signal === "string" ? statement.signal : "";
|
|
763
|
+
const runtime = requested === "" ? "sh" : requested;
|
|
764
|
+
const targetKind = this.#executors()?.entry(runtime)?.invocation.target?.kind;
|
|
765
|
+
if (targetKind !== "resource")
|
|
766
|
+
return null;
|
|
826
767
|
const sourceScheme = schemeNameOf(statement.target);
|
|
827
768
|
return sourceScheme === null || sourceScheme === "file" ? null : checkScheme(sourceScheme);
|
|
828
769
|
}
|
|
829
770
|
return check(statement.target);
|
|
830
771
|
}
|
|
831
772
|
// Worker control is FORK/WORK (grammar 0.74.55), not COPY — its body
|
|
832
|
-
// is the new worker's seed prompt, not a destination path. The COPY gates and
|
|
773
|
+
// is the new worker's seed prompt, not a destination path. The COPY gates and ResourceMutations.handleCopy
|
|
833
774
|
// branch on this so they never parse the prompt as a dst path.
|
|
834
775
|
#isWorkerControl(statement) {
|
|
835
776
|
return statement.op === "FORK" || statement.op === "WORK"; // worker control targets worker://<name> (grammar 0.74.55)
|
|
@@ -906,45 +847,6 @@ class Dispatcher {
|
|
|
906
847
|
await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags, parentLoopId: ctx.loopId });
|
|
907
848
|
return { status: 200, body: name };
|
|
908
849
|
}
|
|
909
|
-
async #handleCopy(statement, ctx) {
|
|
910
|
-
if (statement.target === null) {
|
|
911
|
-
return _a.#failure("copy-source-required", 400, "COPY requires a source path.", {}, { retryable: false });
|
|
912
|
-
}
|
|
913
|
-
if (statement.body === null) {
|
|
914
|
-
return _a.#failure("copy-destination-required", 400, "COPY requires a destination.", {}, { retryable: false });
|
|
915
|
-
}
|
|
916
|
-
return this.#copyOrchestration({
|
|
917
|
-
statement,
|
|
918
|
-
source: {
|
|
919
|
-
target: statement.target,
|
|
920
|
-
lineMarker: statement.lineMarker,
|
|
921
|
-
},
|
|
922
|
-
destination: statement.body,
|
|
923
|
-
ctx,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
async #handleMove(statement, ctx) {
|
|
927
|
-
if (statement.target === null) {
|
|
928
|
-
return _a.#failure("move-source-required", 400, "MOVE requires a source path.", {}, { retryable: false });
|
|
929
|
-
}
|
|
930
|
-
// MOVE is relocation only - deletion is KILL's job ({§move}, {§move-dev-null-not-special}). The /dev/null
|
|
931
|
-
// and null-body delete-by-MOVE has no alternate meaning.
|
|
932
|
-
if (statement.body === null) {
|
|
933
|
-
return _a.#failure("move-destination-required", 400, "MOVE requires a destination.", {}, {
|
|
934
|
-
recovery: "Use KILL when the intended operation is deletion.",
|
|
935
|
-
retryable: false,
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
return this.#moveOrchestration({
|
|
939
|
-
statement,
|
|
940
|
-
source: {
|
|
941
|
-
target: statement.target,
|
|
942
|
-
lineMarker: statement.lineMarker,
|
|
943
|
-
},
|
|
944
|
-
destination: statement.body,
|
|
945
|
-
ctx,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
850
|
// KILL is target-polymorphic. Scheme handlers own the optional numeric code's
|
|
949
851
|
// meaning; core retains worker and entry dispatch. {§operation-code-polymorphism}
|
|
950
852
|
async #handleKill(statement, ctx) {
|
|
@@ -1014,35 +916,59 @@ class Dispatcher {
|
|
|
1014
916
|
// carries the delete target to review (#isProposal fires on 202). Plurnk-internal deletes execute inline.
|
|
1015
917
|
return this.#deleteEntry(schemeName, entryPathnameOf(path), ctx);
|
|
1016
918
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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 }) {
|
|
919
|
+
async #writeActionlessEntry({ verbatim, workerId, loopId, turnId, sequence, origin, kind, folded, modelCallId = null, attrs = {} }) {
|
|
920
|
+
const durableAttrs = { ...attrs, kind };
|
|
921
|
+
const rx = JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" });
|
|
1025
922
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
1026
923
|
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
|
|
1027
|
-
origin, source: null,
|
|
924
|
+
origin, source: null, model_call_id: modelCallId,
|
|
925
|
+
op: null, suffix: "", signal: null,
|
|
1028
926
|
scheme: null, username: null, password: null, hostname: null, port: null,
|
|
1029
927
|
pathname: null, query: null, fragment: null, lineMarker: null,
|
|
1030
928
|
tx: "", mimetype_tx: "text/vnd.plurnk",
|
|
1031
|
-
rx
|
|
929
|
+
rx,
|
|
1032
930
|
mimetype_rx: "application/json",
|
|
1033
|
-
status_rx: 200,
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
931
|
+
status_rx: 200,
|
|
932
|
+
weight: LogBody.weight({
|
|
933
|
+
op: null,
|
|
934
|
+
attrs: durableAttrs,
|
|
935
|
+
tx: "",
|
|
936
|
+
rx,
|
|
937
|
+
mimetypeTx: "text/vnd.plurnk",
|
|
938
|
+
mimetypeRx: "application/json",
|
|
939
|
+
}, this.#weighContent),
|
|
940
|
+
state: "resolved", outcome: null,
|
|
941
|
+
attrs: JSON.stringify(durableAttrs),
|
|
1039
942
|
});
|
|
1040
943
|
if (row === undefined)
|
|
1041
|
-
throw new Error("Dispatcher
|
|
944
|
+
throw new Error("Dispatcher.#writeActionlessEntry: insert returned no row");
|
|
1042
945
|
if (folded)
|
|
1043
946
|
await this.#db.engine_fold_log_entry.run({ id: row.id });
|
|
1044
947
|
return row.id;
|
|
1045
948
|
}
|
|
949
|
+
// {§worker-initialization-entry} — the kernel-authored, born-open worked
|
|
950
|
+
// turn that initializes a worker's log without impersonating model output.
|
|
951
|
+
async writeInitializationEntry({ verbatim, workerId, loopId, turnId, sequence }) {
|
|
952
|
+
return this.#writeActionlessEntry({
|
|
953
|
+
verbatim, workerId, loopId, turnId, sequence,
|
|
954
|
+
origin: "plurnk", kind: "initialization", folded: false,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
// {§model-entry} — mirror a model emission back as an actionless log row, so
|
|
958
|
+
// the model can inspect and curate its own prior behavior. Ordinary admitted
|
|
959
|
+
// emissions are folded; the bounded invalid-emission lifeline is the one
|
|
960
|
+
// born-open rejected item under {§invalid-emission-attempts} and labels
|
|
961
|
+
// that admission fact structurally.
|
|
962
|
+
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, modelCallId, admission = "accepted", reasoningItems }) {
|
|
963
|
+
return this.#writeActionlessEntry({
|
|
964
|
+
verbatim, workerId, loopId, turnId, sequence,
|
|
965
|
+
origin: "model", kind: "model_emission", folded, modelCallId,
|
|
966
|
+
attrs: {
|
|
967
|
+
...(admission === "rejected" ? { admission } : {}),
|
|
968
|
+
...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
|
|
969
|
+
},
|
|
970
|
+
});
|
|
971
|
+
}
|
|
1046
972
|
// PLAN — the model's intended-goals op. An ordinary op: dispatched like any
|
|
1047
973
|
// other, logged, and broadcast to the client as a log entry — but a pure no-op for
|
|
1048
974
|
// state (PLAN ∉ MUTATING_OPS); its body serializes into the log row's tx, no effect.
|
|
@@ -1051,732 +977,24 @@ class Dispatcher {
|
|
|
1051
977
|
throw new Error("unreachable");
|
|
1052
978
|
return { status: 200 };
|
|
1053
979
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
return _a.#failure("worker-copy-address-invalid", 400, "COPY and MOVE do not address named worker spaces.", {}, {
|
|
1065
|
-
recovery: "Move worker-space content with READ and EDIT.",
|
|
1066
|
-
retryable: false,
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
const handler = this.#schemes.get(scheme);
|
|
1070
|
-
const manifest = this.#schemes.manifestFor(scheme);
|
|
1071
|
-
if (handler === undefined || manifest === undefined) {
|
|
1072
|
-
return _a.#failure("scheme-not-found", 501, `COPY or MOVE addressed the unregistered scheme '${scheme}'.`, {}, {
|
|
1073
|
-
scheme,
|
|
1074
|
-
retryable: false,
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
if (manifest.category !== "data") {
|
|
1078
|
-
return _a.#failure("entry-operation-unsupported", 400, `COPY and MOVE require entry-bearing resources; '${scheme}' is a ${manifest.category} scheme.`, {}, {
|
|
1079
|
-
scheme,
|
|
1080
|
-
category: manifest.category,
|
|
1081
|
-
retryable: false,
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
const fragment = target.kind === "url" ? target.fragment : null;
|
|
1085
|
-
const channel = fragment ?? manifest.defaultChannel;
|
|
1086
|
-
if (channel.length === 0) {
|
|
1087
|
-
return _a.#failure("channel-required", 400, `The '${scheme}' scheme has no default channel.`, {}, {
|
|
1088
|
-
scheme,
|
|
1089
|
-
recovery: "Address a named channel with a URI fragment.",
|
|
1090
|
-
retryable: false,
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
if (fragment !== null
|
|
1094
|
-
&& fragment !== manifest.defaultChannel
|
|
1095
|
-
&& !Object.hasOwn(manifest.channels, fragment)) {
|
|
1096
|
-
const availableChannels = [
|
|
1097
|
-
...new Set([manifest.defaultChannel, ...Object.keys(manifest.channels)]),
|
|
1098
|
-
].filter((candidate) => candidate.length > 0);
|
|
1099
|
-
return _a.#failure("channel-not-found", 400, `Channel #${fragment} is not declared by the '${scheme}' scheme.`, {}, {
|
|
1100
|
-
requestedChannel: fragment,
|
|
1101
|
-
availableChannels,
|
|
1102
|
-
retryable: false,
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
const pathname = entryPathnameOf(target);
|
|
1106
|
-
const canonicalFilePath = scheme === "file"
|
|
1107
|
-
? Namespace.canonicalizeSpelling(pathname, await this.#workspaceRoot(ctx.workspaceId))
|
|
1108
|
-
: pathname;
|
|
1109
|
-
return {
|
|
1110
|
-
target,
|
|
1111
|
-
lineMarker,
|
|
1112
|
-
scheme,
|
|
1113
|
-
pathname,
|
|
1114
|
-
identityPathname: canonicalFilePath ?? pathname,
|
|
1115
|
-
channel,
|
|
1116
|
-
manifest,
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
async #selectSource(selection, ctx) {
|
|
1120
|
-
const handler = this.#schemes.get(selection.scheme);
|
|
1121
|
-
if (handler === undefined) {
|
|
1122
|
-
throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
|
|
1123
|
-
}
|
|
1124
|
-
const addressedScheme = selection.target.kind === "url"
|
|
1125
|
-
? selection.target.scheme
|
|
1126
|
-
: selection.scheme;
|
|
1127
|
-
const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, { publishedChannel: selection.channel });
|
|
1128
|
-
const prepared = await this.#prepareDataRepresentation({
|
|
1129
|
-
target: selection.target,
|
|
1130
|
-
routedScheme: selection.scheme,
|
|
1131
|
-
handler,
|
|
1132
|
-
manifest: selection.manifest,
|
|
1133
|
-
ctx,
|
|
1134
|
-
schemeCtx,
|
|
1135
|
-
publishedChannel: selection.channel,
|
|
1136
|
-
});
|
|
1137
|
-
if (prepared.result !== null)
|
|
1138
|
-
return prepared.result;
|
|
1139
|
-
if (prepared.address === null) {
|
|
1140
|
-
return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`, {}, { target: this.#resourceAddress(selection) });
|
|
1141
|
-
}
|
|
1142
|
-
const storageAddress = prepared.address;
|
|
1143
|
-
const read = await EntryCrud.readEntry(storageAddress.pathname, ctx, storageAddress.scheme, storageAddress.ownerId);
|
|
1144
|
-
if (read.status >= 400)
|
|
1145
|
-
return read;
|
|
1146
|
-
if (read.status !== 200 || read.entry === null) {
|
|
1147
|
-
throw new InvalidOperationResultError(`The '${selection.scheme}' scheme returned status ${read.status} without a COPY/MOVE source entry.`);
|
|
1148
|
-
}
|
|
1149
|
-
const selected = read.entry.channels[selection.channel];
|
|
1150
|
-
if (selected === undefined) {
|
|
1151
|
-
return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(storageAddress.scheme, storageAddress.pathname)}.`, {}, {
|
|
1152
|
-
target: renderAddress(storageAddress.scheme, storageAddress.pathname),
|
|
1153
|
-
requestedChannel: selection.channel,
|
|
1154
|
-
availableChannels: Object.keys(read.entry.channels),
|
|
1155
|
-
retryable: false,
|
|
1156
|
-
});
|
|
1157
|
-
}
|
|
1158
|
-
let content = selected.content;
|
|
1159
|
-
let scopeNormalizations;
|
|
1160
|
-
if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
|
|
1161
|
-
return _a.#failure("binary-source-unsupported", 415, `Channel #${selection.channel} is a binary marker and cannot be copied or moved.`, {}, {
|
|
1162
|
-
channel: selection.channel,
|
|
1163
|
-
mimetype: selected.mimetype,
|
|
1164
|
-
recovery: "Use a source with a readable text projection.",
|
|
1165
|
-
retryable: false,
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
|
-
if (selection.lineMarker !== null) {
|
|
1169
|
-
const sliced = LineMarkerOps.sliceLinesRaw(content, selection.lineMarker);
|
|
1170
|
-
if (sliced.status !== 200)
|
|
1171
|
-
return Results.assert(sliced);
|
|
1172
|
-
content = sliced.text ?? "";
|
|
1173
|
-
scopeNormalizations = sliced.scopeNormalizations;
|
|
1174
|
-
}
|
|
1175
|
-
if (selected.producerResult !== undefined && selected.producerResult.status >= 400) {
|
|
1176
|
-
return Results.assert(selected.producerResult);
|
|
1177
|
-
}
|
|
1178
|
-
return {
|
|
1179
|
-
...selection,
|
|
1180
|
-
storageAddress,
|
|
1181
|
-
content,
|
|
1182
|
-
mimetype: selected.mimetype,
|
|
1183
|
-
...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
static #prependScopeNormalizations(result, scopeNormalizations) {
|
|
1187
|
-
if (scopeNormalizations === undefined)
|
|
1188
|
-
return result;
|
|
1189
|
-
return Results.assert({
|
|
1190
|
-
...result,
|
|
1191
|
-
scopeNormalizations: [
|
|
1192
|
-
...scopeNormalizations,
|
|
1193
|
-
...(result.scopeNormalizations ?? []),
|
|
1194
|
-
],
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
#resourceAddress(selection) {
|
|
1198
|
-
const address = renderTarget({
|
|
1199
|
-
scheme: selection.scheme === "file" ? null : selection.scheme,
|
|
1200
|
-
pathname: selection.scheme === "file"
|
|
1201
|
-
? selection.identityPathname.replace(/^\//, "")
|
|
1202
|
-
: selection.identityPathname,
|
|
1203
|
-
fragment: selection.channel === selection.manifest.defaultChannel
|
|
1204
|
-
? null
|
|
1205
|
-
: selection.channel,
|
|
980
|
+
// {§bare-inference} Provider work is prepared concurrently by Engine; the
|
|
981
|
+
// dispatcher owns only the ordinary operation-result commit and notification.
|
|
982
|
+
async recordBareResult(context, result, modelCallId) {
|
|
983
|
+
assertClassifyingSignal(context.statement);
|
|
984
|
+
Results.assert(result);
|
|
985
|
+
const logEntryId = await this.#writeLog({
|
|
986
|
+
...context,
|
|
987
|
+
result,
|
|
988
|
+
curationPlan: null,
|
|
989
|
+
modelCallId,
|
|
1206
990
|
});
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
return address;
|
|
1210
|
-
}
|
|
1211
|
-
#pendingEffect(selection, action) {
|
|
1212
|
-
return {
|
|
1213
|
-
target: this.#resourceAddress(selection),
|
|
1214
|
-
action,
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
static #appliedEffects(result, pending) {
|
|
1218
|
-
// {§edit-result-copy-move-effects} — only an applied mutation earns
|
|
1219
|
-
// engine-composed effects; native scheme receipts remain internal input.
|
|
1220
|
-
const exact = Results.assert(result);
|
|
1221
|
-
if (exact.status === 304 || exact.status === 202 || exact.status >= 300)
|
|
1222
|
-
return exact;
|
|
1223
|
-
if (exact.effects !== undefined) {
|
|
1224
|
-
throw new InvalidOperationResultError("A COPY/MOVE mutation result supplied effects before engine composition.");
|
|
1225
|
-
}
|
|
1226
|
-
const batch = exact.editReceipt === undefined
|
|
1227
|
-
? undefined
|
|
1228
|
-
: assertEditBatchReceipt(exact.editReceipt);
|
|
1229
|
-
const single = batch === undefined && exact.receipt !== undefined
|
|
1230
|
-
? assertEditReceipt(exact.receipt)
|
|
1231
|
-
: undefined;
|
|
1232
|
-
const batchSize = batch === undefined
|
|
1233
|
-
? undefined
|
|
1234
|
-
: "disposition" in batch
|
|
1235
|
-
? batch.superseded.length
|
|
1236
|
-
: batch.effects.length;
|
|
1237
|
-
if (batchSize !== undefined && batchSize !== pending.length) {
|
|
1238
|
-
throw new InvalidOperationResultError(`COPY/MOVE expected ${pending.length} receipt projections, got ${batchSize}.`);
|
|
1239
|
-
}
|
|
1240
|
-
if (single !== undefined && pending.length !== 1) {
|
|
1241
|
-
throw new InvalidOperationResultError(`COPY/MOVE received one receipt for ${pending.length} resource effects.`);
|
|
1242
|
-
}
|
|
1243
|
-
let effects;
|
|
1244
|
-
if (batch !== undefined && "disposition" in batch) {
|
|
1245
|
-
const replacement = pending[0];
|
|
1246
|
-
if (replacement === undefined) {
|
|
1247
|
-
throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch has no resource effect.");
|
|
1248
|
-
}
|
|
1249
|
-
if (pending.some(({ target, action }) => target !== replacement.target || action !== replacement.action)) {
|
|
1250
|
-
throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch spans incompatible resource effects.");
|
|
1251
|
-
}
|
|
1252
|
-
effects = [{
|
|
1253
|
-
...replacement,
|
|
1254
|
-
receipt: projectEditReceipt(batch, 0),
|
|
1255
|
-
}];
|
|
1256
|
-
}
|
|
1257
|
-
else {
|
|
1258
|
-
effects = pending.map((effect, index) => ({
|
|
1259
|
-
...effect,
|
|
1260
|
-
...(batch !== undefined
|
|
1261
|
-
? { receipt: projectEditReceipt(batch, index) }
|
|
1262
|
-
: single !== undefined
|
|
1263
|
-
? { receipt: single }
|
|
1264
|
-
: {}),
|
|
1265
|
-
}));
|
|
1266
|
-
}
|
|
1267
|
-
assertResourceEffects(effects);
|
|
1268
|
-
const { editReceipt: _editReceipt, receipt: _receipt, ...withoutInternalReceipts } = exact;
|
|
1269
|
-
return {
|
|
1270
|
-
...withoutInternalReceipts,
|
|
1271
|
-
effects,
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
// {§copy-move-observation} {§edit-result-copy-move-effects} — scoped
|
|
1275
|
-
// COPY/MOVE into an unscoped channel is a text materialization even when
|
|
1276
|
-
// CRUD creates the channel wholesale. Carry the receipt through synchronous
|
|
1277
|
-
// writes and proposals; the destination scheme owns reviewed output.
|
|
1278
|
-
static #withEditMaterialization(result, receipt) {
|
|
1279
|
-
const exact = Results.assert(result);
|
|
1280
|
-
if (exact.status !== 200 && exact.status !== 201 && exact.status !== 202)
|
|
1281
|
-
return exact;
|
|
1282
|
-
const materialized = exact.editReceipt === undefined
|
|
1283
|
-
? assertEditBatchReceipt(receipt)
|
|
1284
|
-
: assertEditBatchReceipt(exact.editReceipt);
|
|
1285
|
-
return {
|
|
1286
|
-
...exact,
|
|
1287
|
-
editReceipt: materialized,
|
|
1288
|
-
...(exact.status === 202
|
|
1289
|
-
? {
|
|
1290
|
-
attrs: {
|
|
1291
|
-
...exact.attrs,
|
|
1292
|
-
editReceipt: materialized,
|
|
1293
|
-
},
|
|
1294
|
-
}
|
|
1295
|
-
: {}),
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
#finalizeEffects(result, selection, pending) {
|
|
1299
|
-
const routed = this.#withProposalRoute(result, selection);
|
|
1300
|
-
if (routed.status !== 202)
|
|
1301
|
-
return _a.#appliedEffects(routed, pending);
|
|
1302
|
-
return {
|
|
1303
|
-
...routed,
|
|
1304
|
-
attrs: {
|
|
1305
|
-
...routed.attrs,
|
|
1306
|
-
proposalEffects: pending,
|
|
1307
|
-
},
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
static #effectsOf(result) {
|
|
1311
|
-
return result.effects === undefined
|
|
1312
|
-
? []
|
|
1313
|
-
: assertResourceEffects(result.effects);
|
|
1314
|
-
}
|
|
1315
|
-
static #withCombinedEffects(result, ...additional) {
|
|
1316
|
-
const existing = _a.#effectsOf(result);
|
|
1317
|
-
const effects = [...existing, ...additional.flat()];
|
|
1318
|
-
const { effects: _effects, ...withoutEffects } = result;
|
|
1319
|
-
return effects.length === 0
|
|
1320
|
-
? withoutEffects
|
|
1321
|
-
: { ...withoutEffects, effects: assertResourceEffects(effects) };
|
|
1322
|
-
}
|
|
1323
|
-
static #settleProposalEffects(original, settlement) {
|
|
1324
|
-
const pending = original.attrs
|
|
1325
|
-
?.proposalEffects;
|
|
1326
|
-
if (pending === undefined
|
|
1327
|
-
|| settlement.resolution.decision !== "accept"
|
|
1328
|
-
|| settlement.applied === undefined
|
|
1329
|
-
|| settlement.applied.status >= 300) {
|
|
1330
|
-
return settlement;
|
|
1331
|
-
}
|
|
1332
|
-
const projected = settlement.resolution.result ?? {};
|
|
1333
|
-
const aggregate = settlement.applied.editReceipt;
|
|
1334
|
-
const applied = _a.#appliedEffects({
|
|
1335
|
-
...projected,
|
|
1336
|
-
status: settlement.applied.status,
|
|
1337
|
-
...(aggregate === undefined ? {} : { editReceipt: aggregate }),
|
|
1338
|
-
}, pending);
|
|
1339
|
-
const { status: _status, body: _body, ...result } = applied;
|
|
1340
|
-
const { body: _resolutionBody, ...resolution } = settlement.resolution;
|
|
1341
|
-
return {
|
|
1342
|
-
...settlement,
|
|
1343
|
-
resolution: {
|
|
1344
|
-
...resolution,
|
|
1345
|
-
result,
|
|
1346
|
-
},
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
#recordEditSettlement(statement, settlement) {
|
|
1350
|
-
if (statement.op !== "EDIT")
|
|
1351
|
-
return;
|
|
1352
|
-
const prepared = this.#preparedEdits.get(statement);
|
|
1353
|
-
if (prepared === undefined || !prepared.first) {
|
|
1354
|
-
throw new InvalidOperationResultError("An EDIT proposal settled without its prepared batch owner.");
|
|
1355
|
-
}
|
|
1356
|
-
const { resolution, applied } = settlement;
|
|
1357
|
-
if (resolution.decision !== "accept"
|
|
1358
|
-
|| applied === undefined
|
|
1359
|
-
|| applied.status >= 300) {
|
|
1360
|
-
prepared.batch.aggregate = undefined;
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
if (applied.editReceipt === null) {
|
|
1364
|
-
prepared.batch.aggregate = undefined;
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
if (applied.editReceipt !== undefined) {
|
|
1368
|
-
prepared.batch.aggregate = assertEditBatchReceipt(applied.editReceipt);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
if (resolution.body !== undefined)
|
|
1372
|
-
prepared.batch.aggregate = undefined;
|
|
1373
|
-
}
|
|
1374
|
-
static #settlementEffects(settlement) {
|
|
1375
|
-
const effects = settlement.resolution.result
|
|
1376
|
-
?.effects;
|
|
1377
|
-
return effects === undefined ? [] : assertResourceEffects(effects);
|
|
1378
|
-
}
|
|
1379
|
-
static #withSettlementEffects(settlement, effects) {
|
|
1380
|
-
const projected = (settlement.resolution.result ?? {});
|
|
1381
|
-
const { effects: _effects, ...withoutEffects } = projected;
|
|
1382
|
-
const result = effects.length === 0
|
|
1383
|
-
? withoutEffects
|
|
1384
|
-
: {
|
|
1385
|
-
...withoutEffects,
|
|
1386
|
-
effects: assertResourceEffects(effects),
|
|
1387
|
-
};
|
|
1388
|
-
return {
|
|
1389
|
-
...settlement,
|
|
1390
|
-
resolution: {
|
|
1391
|
-
...settlement.resolution,
|
|
1392
|
-
result,
|
|
1393
|
-
},
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
#sameChannel(left, right) {
|
|
1397
|
-
return left.scheme === right.scheme
|
|
1398
|
-
&& left.identityPathname === right.identityPathname
|
|
1399
|
-
&& left.channel === right.channel;
|
|
1400
|
-
}
|
|
1401
|
-
#withProposalRoute(result, selection) {
|
|
1402
|
-
if (result.status !== 202)
|
|
1403
|
-
return result;
|
|
1404
|
-
return {
|
|
1405
|
-
...result,
|
|
1406
|
-
attrs: {
|
|
1407
|
-
...result.attrs,
|
|
1408
|
-
proposalScheme: selection.scheme,
|
|
1409
|
-
proposalTarget: {
|
|
1410
|
-
scheme: selection.scheme,
|
|
1411
|
-
pathname: selection.identityPathname,
|
|
1412
|
-
},
|
|
1413
|
-
},
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
async #invokeEditBatch(selection, edits, ctx) {
|
|
1417
|
-
const handler = this.#schemes.get(selection.scheme);
|
|
1418
|
-
if (typeof handler?.editBatch !== "function") {
|
|
1419
|
-
return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
|
|
1420
|
-
scheme: selection.scheme,
|
|
1421
|
-
operation: "EDIT",
|
|
1422
|
-
retryable: false,
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
const statements = edits.map(({ marker, body, tags, position }) => ({
|
|
1426
|
-
op: "EDIT",
|
|
1427
|
-
suffix: "",
|
|
1428
|
-
signal: tags,
|
|
1429
|
-
target: selection.target,
|
|
1430
|
-
lineMarker: marker,
|
|
1431
|
-
body,
|
|
1432
|
-
position,
|
|
1433
|
-
}));
|
|
1434
|
-
const addressedScheme = selection.target.kind === "url"
|
|
1435
|
-
? selection.target.scheme
|
|
1436
|
-
: selection.scheme;
|
|
1437
|
-
try {
|
|
1438
|
-
const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions)));
|
|
1439
|
-
return this.#withProposalRoute(result, selection);
|
|
1440
|
-
}
|
|
1441
|
-
catch (err) {
|
|
1442
|
-
if (err instanceof InvalidOperationResultError)
|
|
1443
|
-
throw err;
|
|
1444
|
-
console.error(`Scheme '${selection.scheme}' COPY/MOVE edit threw outside its operation result contract:`, err);
|
|
1445
|
-
return _a.#failure("scheme-handler-threw", 500, `The '${selection.scheme}' scheme did not produce a COPY/MOVE edit result.`, {}, {
|
|
1446
|
-
stage: "scheme-dispatch",
|
|
1447
|
-
scheme: selection.scheme,
|
|
1448
|
-
operation: "EDIT",
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
async #writeDestination(statement, source, destination, ctx) {
|
|
1453
|
-
const existingResult = await this.#readEntry(destination.scheme, destination.pathname, ctx);
|
|
1454
|
-
if (existingResult.status >= 400 && existingResult.status !== 404) {
|
|
1455
|
-
return existingResult;
|
|
1456
|
-
}
|
|
1457
|
-
const existing = existingResult.status === 200
|
|
1458
|
-
? existingResult.entry
|
|
1459
|
-
: null;
|
|
1460
|
-
if (existingResult.status === 200 && existing === null) {
|
|
1461
|
-
throw new InvalidOperationResultError(`The '${destination.scheme}' scheme returned 200 without a destination entry.`);
|
|
1462
|
-
}
|
|
1463
|
-
const destinationChannel = existing?.channels[destination.channel];
|
|
1464
|
-
const expectedMimetype = destinationChannel?.mimetype
|
|
1465
|
-
?? await PathMimetype.resolveEntryMimetype(destination.pathname, destination.manifest.channels[destination.channel] ?? source.mimetype, ctx.mimetypes);
|
|
1466
|
-
if (source.mimetype !== expectedMimetype) {
|
|
1467
|
-
return _a.#failure("mimetype-mismatch", 415, `COPY or MOVE cannot write '${source.mimetype}' into a '${expectedMimetype}' channel.`, {}, {
|
|
1468
|
-
channel: destination.channel,
|
|
1469
|
-
sourceMimetype: source.mimetype,
|
|
1470
|
-
destinationMimetype: expectedMimetype,
|
|
1471
|
-
retryable: false,
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
const tags = Array.isArray(statement.signal) ? statement.signal : [];
|
|
1475
|
-
const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
|
|
1476
|
-
if (destination.lineMarker !== null) {
|
|
1477
|
-
if (destinationChannel === undefined) {
|
|
1478
|
-
return _a.#failure("destination-region-not-found", 404, `A destination region requires an existing #${destination.channel} channel.`, {}, {
|
|
1479
|
-
destination: this.#resourceAddress(destination),
|
|
1480
|
-
retryable: false,
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
if (await MimetypeBinary.isBinaryMimetype(destinationChannel.mimetype, ctx.mimetypes)) {
|
|
1484
|
-
return _a.#failure("binary-region-unsupported", 415, `Channel #${destination.channel} is binary and cannot receive a textual region.`, {}, {
|
|
1485
|
-
channel: destination.channel,
|
|
1486
|
-
mimetype: destinationChannel.mimetype,
|
|
1487
|
-
retryable: false,
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
const edited = await this.#invokeEditBatch(destination, [{
|
|
1491
|
-
marker: destination.lineMarker,
|
|
1492
|
-
body: source.content,
|
|
1493
|
-
tags,
|
|
1494
|
-
position: statement.position,
|
|
1495
|
-
}], ctx);
|
|
1496
|
-
return this.#finalizeEffects(edited, destination, [destinationEffect]);
|
|
1497
|
-
}
|
|
1498
|
-
if (destinationChannel !== undefined
|
|
1499
|
-
&& destinationChannel.content !== source.content) {
|
|
1500
|
-
return _a.#failure("copy-destination-exists", 409, `COPY or MOVE destination ${this.#resourceAddress(destination)} already contains different content.`, {}, {
|
|
1501
|
-
destination: this.#resourceAddress(destination),
|
|
1502
|
-
retryable: false,
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
const priorTags = existing?.tags ?? [];
|
|
1506
|
-
const mergedTags = [...new Set([...priorTags, ...tags])];
|
|
1507
|
-
const addsTags = mergedTags.length !== priorTags.length;
|
|
1508
|
-
if (destinationChannel !== undefined && !addsTags)
|
|
1509
|
-
return { status: 304 };
|
|
1510
|
-
const channels = {
|
|
1511
|
-
...(existing?.channels ?? {}),
|
|
1512
|
-
[destination.channel]: {
|
|
1513
|
-
content: source.content,
|
|
1514
|
-
mimetype: source.mimetype,
|
|
1515
|
-
},
|
|
1516
|
-
};
|
|
1517
|
-
const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels, tags: mergedTags }, ctx);
|
|
1518
|
-
const exactWritten = Results.assert(written);
|
|
1519
|
-
const materialized = source.lineMarker === null
|
|
1520
|
-
|| (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
|
|
1521
|
-
? exactWritten
|
|
1522
|
-
: _a.#withEditMaterialization(exactWritten, editReceipt(destinationChannel?.content ?? "", source.content, [{
|
|
1523
|
-
marker: { marks: [1, -1] },
|
|
1524
|
-
body: source.content,
|
|
1525
|
-
}]));
|
|
1526
|
-
return this.#finalizeEffects(materialized, destination, [destinationEffect]);
|
|
1527
|
-
}
|
|
1528
|
-
async #copyOrchestration({ statement, source, destination, ctx, }) {
|
|
1529
|
-
const resolvedSource = await this.#resolveResourceSelection(source, ctx);
|
|
1530
|
-
if (_a.#isDispatchResult(resolvedSource))
|
|
1531
|
-
return resolvedSource;
|
|
1532
|
-
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1533
|
-
if (_a.#isDispatchResult(resolvedDestination))
|
|
1534
|
-
return resolvedDestination;
|
|
1535
|
-
const selected = await this.#selectSource(resolvedSource, ctx);
|
|
1536
|
-
if (_a.#isDispatchResult(selected))
|
|
1537
|
-
return selected;
|
|
1538
|
-
const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
|
|
1539
|
-
return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
|
|
1540
|
-
}
|
|
1541
|
-
#deferredMoveSource(source, destination) {
|
|
1542
|
-
return {
|
|
1543
|
-
target: source.target,
|
|
1544
|
-
lineMarker: source.lineMarker,
|
|
1545
|
-
scheme: source.scheme,
|
|
1546
|
-
pathname: source.pathname,
|
|
1547
|
-
channel: source.channel,
|
|
1548
|
-
destination: this.#resourceAddress(destination),
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
#moveFailureAfterDestination(result, destination, destinationEffects) {
|
|
1552
|
-
const exact = Results.assert(result);
|
|
1553
|
-
if (exact.status < 400) {
|
|
1554
|
-
throw new InvalidOperationResultError("A successful MOVE source result was classified as a partial failure.");
|
|
1555
|
-
}
|
|
1556
|
-
if (exact.problem === undefined) {
|
|
1557
|
-
throw new InvalidOperationResultError("A failed MOVE source mutation has no Problem Details.");
|
|
1558
|
-
}
|
|
1559
|
-
const failed = Results.assert({
|
|
1560
|
-
...exact,
|
|
1561
|
-
problem: {
|
|
1562
|
-
...exact.problem,
|
|
1563
|
-
operation: "MOVE",
|
|
1564
|
-
destinationWritten: true,
|
|
1565
|
-
destination,
|
|
1566
|
-
},
|
|
1567
|
-
});
|
|
1568
|
-
return _a.#withCombinedEffects(failed, destinationEffects);
|
|
1569
|
-
}
|
|
1570
|
-
async #removeMoveSource(statement, source, ctx) {
|
|
1571
|
-
const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
|
|
1572
|
-
if (source.lineMarker === null) {
|
|
1573
|
-
const deleted = await this.#deleteChannel(source.scheme, source.pathname, source.channel, ctx);
|
|
1574
|
-
return this.#finalizeEffects(Results.assert(deleted), source, [effect]);
|
|
1575
|
-
}
|
|
1576
|
-
const edited = await this.#invokeEditBatch(source, [{
|
|
1577
|
-
marker: source.lineMarker,
|
|
1578
|
-
body: "",
|
|
1579
|
-
tags: null,
|
|
1580
|
-
position: statement.position,
|
|
1581
|
-
}], ctx);
|
|
1582
|
-
return this.#finalizeEffects(edited, source, [effect]);
|
|
1583
|
-
}
|
|
1584
|
-
async #moveWithinChannel(statement, source, destination, ctx) {
|
|
1585
|
-
if (source.lineMarker === null) {
|
|
1586
|
-
if (destination.lineMarker !== null) {
|
|
1587
|
-
return _a.#failure("move-region-overlap", 409, "MOVE cannot insert a whole channel into itself and then remove that channel.", {}, {
|
|
1588
|
-
source: this.#resourceAddress(source),
|
|
1589
|
-
destination: this.#resourceAddress(destination),
|
|
1590
|
-
retryable: false,
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
return this.#writeDestination(statement, source, destination, ctx);
|
|
1594
|
-
}
|
|
1595
|
-
if (destination.lineMarker === null) {
|
|
1596
|
-
return this.#writeDestination(statement, source, destination, ctx);
|
|
1597
|
-
}
|
|
1598
|
-
const moved = await this.#invokeEditBatch(destination, [
|
|
1599
|
-
{
|
|
1600
|
-
marker: destination.lineMarker,
|
|
1601
|
-
body: source.content,
|
|
1602
|
-
tags: Array.isArray(statement.signal) ? statement.signal : [],
|
|
1603
|
-
position: statement.position,
|
|
1604
|
-
},
|
|
1605
|
-
{
|
|
1606
|
-
marker: source.lineMarker,
|
|
1607
|
-
body: "",
|
|
1608
|
-
tags: null,
|
|
1609
|
-
position: statement.position,
|
|
1610
|
-
},
|
|
1611
|
-
], ctx);
|
|
1612
|
-
const effect = this.#pendingEffect(destination, "update");
|
|
1613
|
-
return this.#finalizeEffects(moved, destination, [effect, effect]);
|
|
1614
|
-
}
|
|
1615
|
-
async #moveOrchestration({ statement, source, destination, ctx, }) {
|
|
1616
|
-
const resolvedSource = await this.#resolveResourceSelection(source, ctx);
|
|
1617
|
-
if (_a.#isDispatchResult(resolvedSource))
|
|
1618
|
-
return resolvedSource;
|
|
1619
|
-
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1620
|
-
if (_a.#isDispatchResult(resolvedDestination))
|
|
1621
|
-
return resolvedDestination;
|
|
1622
|
-
const selected = await this.#selectSource(resolvedSource, ctx);
|
|
1623
|
-
if (_a.#isDispatchResult(selected))
|
|
1624
|
-
return selected;
|
|
1625
|
-
if (this.#sameChannel(resolvedSource, resolvedDestination)) {
|
|
1626
|
-
const result = await this.#moveWithinChannel(statement, selected, resolvedDestination, ctx);
|
|
1627
|
-
return resolvedDestination.lineMarker === null
|
|
1628
|
-
? _a.#prependScopeNormalizations(result, selected.scopeNormalizations)
|
|
1629
|
-
: result;
|
|
1630
|
-
}
|
|
1631
|
-
const destinationResult = _a.#prependScopeNormalizations(await this.#writeDestination(statement, selected, resolvedDestination, ctx), selected.scopeNormalizations);
|
|
1632
|
-
if (destinationResult.status >= 400)
|
|
1633
|
-
return destinationResult;
|
|
1634
|
-
const destinationAddress = this.#resourceAddress(resolvedDestination);
|
|
1635
|
-
const destinationEffects = _a.#effectsOf(destinationResult);
|
|
1636
|
-
if (destinationResult.status === 202) {
|
|
1637
|
-
return {
|
|
1638
|
-
...destinationResult,
|
|
1639
|
-
attrs: {
|
|
1640
|
-
...destinationResult.attrs,
|
|
1641
|
-
moveSource: this.#deferredMoveSource(resolvedSource, resolvedDestination),
|
|
1642
|
-
},
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
const sourceResult = await this.#removeMoveSource(statement, resolvedSource, ctx);
|
|
1646
|
-
if (sourceResult.status === 202) {
|
|
1647
|
-
return {
|
|
1648
|
-
...sourceResult,
|
|
1649
|
-
...(destinationResult.scopeNormalizations === undefined
|
|
1650
|
-
? {}
|
|
1651
|
-
: { scopeNormalizations: destinationResult.scopeNormalizations }),
|
|
1652
|
-
attrs: {
|
|
1653
|
-
...sourceResult.attrs,
|
|
1654
|
-
moveDestinationWritten: destinationAddress,
|
|
1655
|
-
moveDestinationEffects: destinationEffects,
|
|
1656
|
-
},
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
if (sourceResult.status >= 400) {
|
|
1660
|
-
return this.#moveFailureAfterDestination(destinationResult.scopeNormalizations === undefined
|
|
1661
|
-
? sourceResult
|
|
1662
|
-
: Results.assert({
|
|
1663
|
-
...sourceResult,
|
|
1664
|
-
scopeNormalizations: destinationResult.scopeNormalizations,
|
|
1665
|
-
}), destinationAddress, destinationEffects);
|
|
1666
|
-
}
|
|
1667
|
-
const base = destinationResult.status === 304
|
|
1668
|
-
? { ...destinationResult, status: 200 }
|
|
1669
|
-
: destinationResult;
|
|
1670
|
-
return _a.#withCombinedEffects(base, _a.#effectsOf(sourceResult));
|
|
1671
|
-
}
|
|
1672
|
-
async #settleMoveProposal({ statement, result, settlement, ctx, ids, }) {
|
|
1673
|
-
if (statement.op !== "MOVE")
|
|
1674
|
-
return settlement;
|
|
1675
|
-
const attrs = result.attrs;
|
|
1676
|
-
const destinationWritten = attrs?.moveDestinationWritten;
|
|
1677
|
-
if (destinationWritten !== undefined) {
|
|
1678
|
-
const destinationEffects = attrs?.moveDestinationEffects === undefined
|
|
1679
|
-
? []
|
|
1680
|
-
: assertResourceEffects(attrs.moveDestinationEffects);
|
|
1681
|
-
if (settlement.resolution.decision !== "accept") {
|
|
1682
|
-
const decision = settlement.resolution.decision;
|
|
1683
|
-
return {
|
|
1684
|
-
resolution: settlement.resolution,
|
|
1685
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("move-source-not-applied", decision === "cancel" ? 499 : 409, `The MOVE destination was written, but source removal was ${decision === "cancel" ? "cancelled" : "rejected"}.`, {}, {
|
|
1686
|
-
retryable: false,
|
|
1687
|
-
}), destinationWritten, destinationEffects),
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
if (settlement.applied === undefined) {
|
|
1691
|
-
return {
|
|
1692
|
-
resolution: settlement.resolution,
|
|
1693
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
|
|
1694
|
-
stage: "proposal-application",
|
|
1695
|
-
retryable: false,
|
|
1696
|
-
}), destinationWritten, destinationEffects),
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1699
|
-
if (settlement.applied.status >= 400) {
|
|
1700
|
-
return {
|
|
1701
|
-
resolution: settlement.resolution,
|
|
1702
|
-
applied: this.#moveFailureAfterDestination(settlement.applied, destinationWritten, destinationEffects),
|
|
1703
|
-
};
|
|
1704
|
-
}
|
|
1705
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1706
|
-
...destinationEffects,
|
|
1707
|
-
..._a.#settlementEffects(settlement),
|
|
1708
|
-
]);
|
|
1709
|
-
}
|
|
1710
|
-
const deferred = attrs?.moveSource;
|
|
1711
|
-
if (deferred === undefined
|
|
1712
|
-
|| settlement.resolution.decision !== "accept"
|
|
1713
|
-
|| (settlement.applied?.status ?? 200) >= 400) {
|
|
1714
|
-
return settlement;
|
|
1715
|
-
}
|
|
1716
|
-
if (settlement.applied === undefined) {
|
|
1717
|
-
return {
|
|
1718
|
-
resolution: settlement.resolution,
|
|
1719
|
-
applied: _a.#failure("proposal-apply-missing", 500, "The destination scheme accepted its MOVE proposal without applying the destination mutation.", {}, {
|
|
1720
|
-
stage: "proposal-application",
|
|
1721
|
-
retryable: false,
|
|
1722
|
-
}),
|
|
1723
|
-
};
|
|
1724
|
-
}
|
|
1725
|
-
const destinationEffects = _a.#settlementEffects(settlement);
|
|
1726
|
-
const resolvedSource = await this.#resolveResourceSelection({
|
|
1727
|
-
target: deferred.target,
|
|
1728
|
-
lineMarker: deferred.lineMarker,
|
|
1729
|
-
}, ctx);
|
|
1730
|
-
if (_a.#isDispatchResult(resolvedSource)) {
|
|
1731
|
-
return {
|
|
1732
|
-
resolution: settlement.resolution,
|
|
1733
|
-
applied: this.#moveFailureAfterDestination(resolvedSource, deferred.destination, destinationEffects),
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
if (resolvedSource.scheme !== deferred.scheme
|
|
1737
|
-
|| resolvedSource.pathname !== deferred.pathname
|
|
1738
|
-
|| resolvedSource.channel !== deferred.channel) {
|
|
1739
|
-
throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
|
|
1740
|
-
}
|
|
1741
|
-
const removed = await this.#removeMoveSource(statement, resolvedSource, ctx);
|
|
1742
|
-
if (removed.status >= 400) {
|
|
1743
|
-
return {
|
|
1744
|
-
resolution: settlement.resolution,
|
|
1745
|
-
applied: this.#moveFailureAfterDestination(removed, deferred.destination, destinationEffects),
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
if (removed.status !== 202) {
|
|
1749
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1750
|
-
...destinationEffects,
|
|
1751
|
-
..._a.#effectsOf(removed),
|
|
1752
|
-
]);
|
|
1753
|
-
}
|
|
1754
|
-
const initialSourceSettlement = await this.#proposals.workerApply(statement, removed, { decision: "accept" }, ids);
|
|
1755
|
-
const sourceSettlement = _a.#settleProposalEffects(removed, initialSourceSettlement);
|
|
1756
|
-
if (sourceSettlement.applied === undefined) {
|
|
1757
|
-
return {
|
|
1758
|
-
resolution: settlement.resolution,
|
|
1759
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
|
|
1760
|
-
stage: "proposal-application",
|
|
1761
|
-
retryable: false,
|
|
1762
|
-
}), deferred.destination, destinationEffects),
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
if (sourceSettlement.applied.status >= 400) {
|
|
1766
|
-
return {
|
|
1767
|
-
resolution: settlement.resolution,
|
|
1768
|
-
applied: this.#moveFailureAfterDestination(sourceSettlement.applied, deferred.destination, destinationEffects),
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1772
|
-
...destinationEffects,
|
|
1773
|
-
..._a.#settlementEffects(sourceSettlement),
|
|
1774
|
-
]);
|
|
991
|
+
context.onDispatch?.(logEntryId);
|
|
992
|
+
return result;
|
|
1775
993
|
}
|
|
1776
994
|
// {§send-premature-terminate} — the unified PENDING SET, judged at the terminal's OWN dispatch
|
|
1777
995
|
// (post-batch: the emission's earlier ops already executed, so a same-turn KILL+[200] repairs in
|
|
1778
996
|
// 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
|
|
997
|
+
// pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN/BARE, results unseen
|
|
1780
998
|
// until next packet). Failed operations are the separate next-packet leg shared by explicit
|
|
1781
999
|
// completion and empty-join completion. Nothing pending may be silently discarded; 499 discards
|
|
1782
1000
|
// BY STATED INTENT and is never gated.
|
|
@@ -1789,30 +1007,32 @@ class Dispatcher {
|
|
|
1789
1007
|
const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
|
|
1790
1008
|
if (liveChild !== undefined)
|
|
1791
1009
|
pending.push("surviving workers");
|
|
1792
|
-
const
|
|
1793
|
-
if (
|
|
1010
|
+
const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
|
|
1011
|
+
if (boundaries.retrievals)
|
|
1794
1012
|
pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
|
|
1795
|
-
if (
|
|
1013
|
+
if (boundaries.streamTerminations) {
|
|
1796
1014
|
pending.push("completed stream results that land in the NEXT packet's Log");
|
|
1797
1015
|
}
|
|
1798
|
-
if (
|
|
1016
|
+
if (boundaries.childTerminations)
|
|
1799
1017
|
pending.push("worker results that arrived during this turn (they land NEXT turn)");
|
|
1800
1018
|
return pending;
|
|
1801
1019
|
}
|
|
1802
|
-
// Results cross an observation boundary only when they have appeared in a
|
|
1803
|
-
//
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1020
|
+
// Results cross an observation boundary only when they have appeared in a packet;
|
|
1021
|
+
// successful FOLD effects likewise become useful through the curated next packet.
|
|
1022
|
+
// Keep every next-packet boundary in one classifier while letting the callers apply
|
|
1023
|
+
// their distinct contracts: retrievals block explicit completion, whereas FOLD only
|
|
1024
|
+
// prevents an empty wait from being inferred as completion.
|
|
1025
|
+
async #nextPacketBoundaries(workerId, turnId) {
|
|
1026
|
+
const [turnBoundaries, streamTermination, childTermination] = await Promise.all([
|
|
1027
|
+
this.#db.engine_turn_packet_boundaries.all({ turn_id: turnId }),
|
|
1809
1028
|
this.#db.engine_worker_has_undelivered_stream_term
|
|
1810
1029
|
.get({ worker_id: workerId }),
|
|
1811
1030
|
this.#db.engine_worker_has_undelivered_child_term
|
|
1812
1031
|
.get({ worker_id: workerId }),
|
|
1813
1032
|
]);
|
|
1814
1033
|
return {
|
|
1815
|
-
retrievals:
|
|
1034
|
+
retrievals: turnBoundaries.some(({ op }) => op !== "FOLD"),
|
|
1035
|
+
folds: turnBoundaries.some(({ op }) => op === "FOLD"),
|
|
1816
1036
|
streamTerminations: streamTermination !== undefined,
|
|
1817
1037
|
childTerminations: childTermination !== undefined,
|
|
1818
1038
|
};
|
|
@@ -1854,14 +1074,14 @@ class Dispatcher {
|
|
|
1854
1074
|
return _a.#failure("send-status-required", 400, "SEND requires a numeric status.", {}, { retryable: false });
|
|
1855
1075
|
}
|
|
1856
1076
|
const raw = statement.body === null ? "" : statement.body.raw;
|
|
1857
|
-
// The park rides SEND
|
|
1077
|
+
// The park rides SEND signal 202 only ({§park-202-only}). A scoped signal 102 is neither
|
|
1858
1078
|
// a wait nor a meaningful continuation, so reject it instead of preserving the
|
|
1859
1079
|
// retired dual spelling.
|
|
1860
1080
|
if (status === 102 && statement.lineMarker !== null) {
|
|
1861
|
-
return _a.#failure("send-scope-invalid", 400, "
|
|
1081
|
+
return _a.#failure("send-scope-invalid", 400, "`## SEND0 [102]` does not accept a scope.", {}, {
|
|
1862
1082
|
requestedStatus: 102,
|
|
1863
1083
|
scope: statement.lineMarker,
|
|
1864
|
-
recovery: "Use
|
|
1084
|
+
recovery: "Use `## SEND0 [202] <scope>` to wait, or remove the scope to continue.",
|
|
1865
1085
|
retryable: false,
|
|
1866
1086
|
});
|
|
1867
1087
|
}
|
|
@@ -1875,7 +1095,7 @@ class Dispatcher {
|
|
|
1875
1095
|
this.#parkDeadlines.set(loopId, -1); // indefinite: the bounded child's terminal is the wake edge
|
|
1876
1096
|
return { status: 102, attrs: { parked: -1, join: true } };
|
|
1877
1097
|
}
|
|
1878
|
-
// {§wait-obligation-matrix} — SEND
|
|
1098
|
+
// {§wait-obligation-matrix} — SEND signal 202 is the obligation-checked join. A live
|
|
1879
1099
|
// obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
|
|
1880
1100
|
// reawakens it ({§worker-lifecycle-child-wake}); a wait on nothing (∅) is already satisfied and
|
|
1881
1101
|
// resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
|
|
@@ -1894,8 +1114,8 @@ class Dispatcher {
|
|
|
1894
1114
|
// all complete-but-unobserved. Their wake edge may already have
|
|
1895
1115
|
// fired, so do not park; continue directly to the packet that
|
|
1896
1116
|
// materializes them.
|
|
1897
|
-
const
|
|
1898
|
-
if (
|
|
1117
|
+
const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
|
|
1118
|
+
if (boundaries.retrievals || boundaries.folds || boundaries.streamTerminations || boundaries.childTerminations) {
|
|
1899
1119
|
return { status: 102 };
|
|
1900
1120
|
}
|
|
1901
1121
|
const failCount = await this.#unobservedFailureCount(turnId);
|
|
@@ -1950,7 +1170,7 @@ class Dispatcher {
|
|
|
1950
1170
|
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
1171
|
pending: [...pending],
|
|
1952
1172
|
stage: "completion",
|
|
1953
|
-
recovery: "Review the results, then use only
|
|
1173
|
+
recovery: "Review the results, then use only `# PLAN0` and `## SEND0 [200]` to conclude.",
|
|
1954
1174
|
retryable: false,
|
|
1955
1175
|
});
|
|
1956
1176
|
}
|
|
@@ -1977,7 +1197,7 @@ class Dispatcher {
|
|
|
1977
1197
|
turn_id: turnId,
|
|
1978
1198
|
});
|
|
1979
1199
|
if (seqs === undefined) {
|
|
1980
|
-
throw new Error(`SEND
|
|
1200
|
+
throw new Error(`SEND signal 499: no coordinate for loop=${loopId} turn=${turnId}`);
|
|
1981
1201
|
}
|
|
1982
1202
|
Results.attachInstance(failure, `log:///${seqs.loop_seq}/${seqs.turn_seq}/${ctx.sequence}/SEND`);
|
|
1983
1203
|
const finished = await this.#lifecycle.finish(loopId, failure);
|
|
@@ -1999,8 +1219,8 @@ class Dispatcher {
|
|
|
1999
1219
|
content: null,
|
|
2000
1220
|
mimetype: null,
|
|
2001
1221
|
results: [],
|
|
2002
|
-
|
|
2003
|
-
|
|
1222
|
+
itemsWeightTotal: 0,
|
|
1223
|
+
returnedItemsWeightTotal: 0,
|
|
2004
1224
|
matchingPathCount: 0,
|
|
2005
1225
|
matchLocationCount: 0,
|
|
2006
1226
|
}
|
|
@@ -2070,6 +1290,7 @@ class Dispatcher {
|
|
|
2070
1290
|
statement,
|
|
2071
1291
|
manifest,
|
|
2072
1292
|
target,
|
|
1293
|
+
identity: target,
|
|
2073
1294
|
representation: resolved.representation,
|
|
2074
1295
|
mimetypes: ctx.mimetypes,
|
|
2075
1296
|
}));
|
|
@@ -2142,8 +1363,8 @@ class Dispatcher {
|
|
|
2142
1363
|
content: null,
|
|
2143
1364
|
mimetype: null,
|
|
2144
1365
|
results: [],
|
|
2145
|
-
|
|
2146
|
-
|
|
1366
|
+
itemsWeightTotal: 0,
|
|
1367
|
+
returnedItemsWeightTotal: 0,
|
|
2147
1368
|
matchingPathCount: 0,
|
|
2148
1369
|
matchLocationCount: 0,
|
|
2149
1370
|
}, { target });
|
|
@@ -2171,11 +1392,14 @@ class Dispatcher {
|
|
|
2171
1392
|
async #runLogCuration(statement, ctx) {
|
|
2172
1393
|
const addressedScheme = schemeNameOf(statement.target);
|
|
2173
1394
|
if (addressedScheme !== null && addressedScheme !== "log") {
|
|
2174
|
-
return
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
1395
|
+
return {
|
|
1396
|
+
result: _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
|
|
1397
|
+
scheme: addressedScheme,
|
|
1398
|
+
operation: statement.op,
|
|
1399
|
+
retryable: false,
|
|
1400
|
+
}),
|
|
1401
|
+
plan: null,
|
|
1402
|
+
};
|
|
2179
1403
|
}
|
|
2180
1404
|
const handler = this.#schemes.get("log");
|
|
2181
1405
|
const manifest = this.#schemes.manifestFor("log");
|
|
@@ -2183,21 +1407,20 @@ class Dispatcher {
|
|
|
2183
1407
|
throw new Error("the core log curation owner is not registered");
|
|
2184
1408
|
}
|
|
2185
1409
|
const schemeCtx = new SchemeCtxImpl(ctx, "log", manifest, this.#liveSubscriptions);
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
: Results.assert(await handler.fold(statement, schemeCtx));
|
|
1410
|
+
const outcome = await handler.curate(statement, schemeCtx);
|
|
1411
|
+
return { result: Results.assert(outcome.result), plan: outcome.plan };
|
|
2189
1412
|
}
|
|
2190
1413
|
// {§proposal}/{§send} — status 202 is a proposal except for broadcast
|
|
2191
|
-
// SEND
|
|
1414
|
+
// SEND signal 202, which parks the loop. The operation disambiguates the status.
|
|
2192
1415
|
static #isProposal(statement, result) {
|
|
2193
|
-
// {§send-300-choices} — SEND
|
|
1416
|
+
// {§send-300-choices} — SEND signal 300 is the proposal exception.
|
|
2194
1417
|
if (result.status !== 202)
|
|
2195
1418
|
return false;
|
|
2196
1419
|
if (statement.op === "SEND" && statement.signal === 300)
|
|
2197
1420
|
return true;
|
|
2198
1421
|
return !(statement.op === "SEND" && statement.target === null);
|
|
2199
1422
|
}
|
|
2200
|
-
async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, }) {
|
|
1423
|
+
async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, curationPlan, modelCallId, }) {
|
|
2201
1424
|
const durableStatement = DurableStatement.project(statement);
|
|
2202
1425
|
const target = this.#extractTarget(durableStatement.target);
|
|
2203
1426
|
await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon}
|
|
@@ -2207,12 +1430,23 @@ class Dispatcher {
|
|
|
2207
1430
|
// A proposal (status 202 from a side-effecting op) is written to the log in
|
|
2208
1431
|
// state='proposed' until the proposal lifecycle resolves it; attrs holds the
|
|
2209
1432
|
// scheme-supplied payload (file diff, exec command, etc.) the client renders
|
|
2210
|
-
// for review and the scheme consumes on accept. A broadcast SEND
|
|
1433
|
+
// for review and the scheme consumes on accept. A broadcast SEND signal 202 is a
|
|
2211
1434
|
// parked-terminal, not a proposal (#isProposal) → state='resolved'.
|
|
2212
1435
|
const isProposed = _a.#isProposal(statement, result);
|
|
2213
1436
|
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
2214
1437
|
? { ...result.attrs }
|
|
2215
1438
|
: {};
|
|
1439
|
+
if (curationPlan !== null) {
|
|
1440
|
+
if (Object.hasOwn(attrsObj, "__plurnk_curation")) {
|
|
1441
|
+
throw new Error("Dispatcher.#writeLog: result attrs collide with private log curation state");
|
|
1442
|
+
}
|
|
1443
|
+
attrsObj.__plurnk_curation = {
|
|
1444
|
+
ids: curationPlan.targetLogEntryIds,
|
|
1445
|
+
expanded: curationPlan.expanded,
|
|
1446
|
+
add: curationPlan.add,
|
|
1447
|
+
remove: curationPlan.remove,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
2216
1450
|
const seqs = statement.op === "EXEC" || result.problem !== undefined
|
|
2217
1451
|
? await this.#db.engine_loop_turn_seqs.get({
|
|
2218
1452
|
loop_id: loopId,
|
|
@@ -2263,6 +1497,7 @@ class Dispatcher {
|
|
|
2263
1497
|
sequence: sequence,
|
|
2264
1498
|
origin,
|
|
2265
1499
|
source: null, // dispatch entries are self-authored; {§env-delta} deltas set this
|
|
1500
|
+
model_call_id: modelCallId,
|
|
2266
1501
|
op: durableStatement.op,
|
|
2267
1502
|
suffix: durableStatement.suffix,
|
|
2268
1503
|
signal: this.#signalToJson(durableStatement.signal),
|
|
@@ -2280,7 +1515,14 @@ class Dispatcher {
|
|
|
2280
1515
|
rx: rxJson,
|
|
2281
1516
|
mimetype_rx: "application/json",
|
|
2282
1517
|
status_rx: result.status,
|
|
2283
|
-
|
|
1518
|
+
weight: LogBody.weight({
|
|
1519
|
+
op: durableStatement.op,
|
|
1520
|
+
attrs,
|
|
1521
|
+
tx: txJson,
|
|
1522
|
+
rx: rxJson,
|
|
1523
|
+
mimetypeTx: "application/json",
|
|
1524
|
+
mimetypeRx: "application/json",
|
|
1525
|
+
}, this.#weighContent),
|
|
2284
1526
|
state: isProposed ? "proposed" : "resolved",
|
|
2285
1527
|
outcome: null,
|
|
2286
1528
|
attrs,
|