@plurnk/plurnk-service 1.0.4 → 1.0.6
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 +55 -31
- package/INSTALL.md +3 -3
- package/README.md +4 -4
- package/SPEC.md +205 -193
- package/dist/Paths.js +1 -1
- package/dist/Paths.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +18 -18
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +17 -17
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +11 -11
- package/dist/core/Dispatcher.d.ts +11 -16
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +122 -125
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +20 -23
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +213 -210
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +108 -130
- package/dist/core/PacketBuilder.d.ts +10 -11
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +204 -128
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +8 -8
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +10 -10
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +1 -0
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +9 -3
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +4 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +18 -9
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/TelemetryChannel.d.ts +5 -5
- package/dist/core/TelemetryChannel.d.ts.map +1 -1
- package/dist/core/TelemetryChannel.js +9 -9
- package/dist/core/TelemetryChannel.js.map +1 -1
- package/dist/core/caps/CapsResolve.js +2 -2
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +2 -2
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +7 -7
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +2 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +4 -4
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/content-hash.d.ts +2 -0
- package/dist/core/content-hash.d.ts.map +1 -0
- package/dist/core/content-hash.js +6 -0
- package/dist/core/content-hash.js.map +1 -0
- package/dist/core/fork.d.ts +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +31 -26
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +41 -32
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +23 -7
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-iso.d.ts +9 -0
- package/dist/core/git-iso.d.ts.map +1 -0
- package/dist/core/git-iso.js +227 -0
- package/dist/core/git-iso.js.map +1 -0
- package/dist/core/git-membership.d.ts +2 -2
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +70 -50
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +1 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +21 -11
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +81 -22
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +2 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +19 -12
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/run-ops.sql +26 -26
- package/dist/core/scheme-types.d.ts +6 -11
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/search-gate.js +0 -0
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/token-ruler.d.ts +2 -0
- package/dist/core/token-ruler.d.ts.map +1 -0
- package/dist/core/token-ruler.js +18 -0
- package/dist/core/token-ruler.js.map +1 -0
- package/dist/core/worker-cap.d.ts +8 -0
- package/dist/core/worker-cap.d.ts.map +1 -0
- package/dist/core/worker-cap.js +20 -0
- package/dist/core/worker-cap.js.map +1 -0
- package/dist/core/{session-settings.d.ts → workspace-settings.d.ts} +5 -5
- package/dist/core/workspace-settings.d.ts.map +1 -0
- package/dist/core/{session-settings.js → workspace-settings.js} +13 -13
- package/dist/core/workspace-settings.js.map +1 -0
- package/dist/digest/Digest.d.ts +3 -3
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +79 -73
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +18 -18
- package/dist/schemes/Exec.d.ts +9 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +121 -68
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +3 -3
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.js +23 -23
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.js +5 -5
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +63 -14
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +49 -8
- package/dist/schemes/Plurnk.js +5 -5
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.js +5 -5
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.js +5 -5
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/{Run.d.ts → Worker.d.ts} +2 -2
- package/dist/schemes/Worker.d.ts.map +1 -0
- package/dist/schemes/{Run.js → Worker.js} +67 -67
- package/dist/schemes/Worker.js.map +1 -0
- package/dist/schemes/_entry-crud.js +11 -11
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +29 -29
- package/dist/schemes/_entry-find.d.ts +2 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +31 -19
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +9 -9
- package/dist/schemes/_entry-graph.d.ts +2 -2
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +18 -18
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +8 -8
- package/dist/schemes/_entry-manifest.d.ts +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +74 -50
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +4 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +25 -25
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +9 -9
- package/dist/schemes/_entry-semantic.d.ts +2 -1
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +23 -4
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +18 -4
- package/dist/schemes/_entry-send.d.ts +1 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +6 -6
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/exec-abort.js +1 -1
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +50 -48
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +307 -297
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/client-input.js +20 -20
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +7 -7
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +27 -27
- package/dist/server/envelope.d.ts +20 -20
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +86 -86
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +39 -39
- package/dist/server/logEntry.d.ts +1 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +1 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/loop-model.d.ts +3 -0
- package/dist/server/loop-model.d.ts.map +1 -0
- package/dist/server/loop-model.js +26 -0
- package/dist/server/loop-model.js.map +1 -0
- package/dist/server/loopDocs.d.ts +1 -1
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +6 -6
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/seam-entry-read.sql +2 -2
- package/dist/server/seam-log-read.sql +3 -3
- package/dist/server/seam-loop.sql +3 -3
- package/dist/server/seam-proposal-list.sql +4 -4
- package/dist/server/yolo.js +2 -2
- package/dist/server/yolo.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +21 -6
- package/dist/service.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +84 -85
- package/package.json +17 -14
- package/dist/core/TokenGauge.d.ts +0 -16
- package/dist/core/TokenGauge.d.ts.map +0 -1
- package/dist/core/TokenGauge.js +0 -58
- package/dist/core/TokenGauge.js.map +0 -1
- package/dist/core/run-cap.d.ts +0 -8
- package/dist/core/run-cap.d.ts.map +0 -1
- package/dist/core/run-cap.js +0 -20
- package/dist/core/run-cap.js.map +0 -1
- package/dist/core/search-prefetch.d.ts +0 -17
- package/dist/core/search-prefetch.d.ts.map +0 -1
- package/dist/core/search-prefetch.js +0 -164
- package/dist/core/search-prefetch.js.map +0 -1
- package/dist/core/session-settings.d.ts.map +0 -1
- package/dist/core/session-settings.js.map +0 -1
- package/dist/schemes/Run.d.ts.map +0 -1
- package/dist/schemes/Run.js.map +0 -1
package/dist/core/Dispatcher.js
CHANGED
|
@@ -2,13 +2,13 @@ var _a;
|
|
|
2
2
|
import { parsePath } from "@plurnk/plurnk-grammar";
|
|
3
3
|
import { foldAuthorityIntoPath, schemeNameOf } from "./plurnk-uri.js";
|
|
4
4
|
import Fork from "./fork.js";
|
|
5
|
-
import
|
|
5
|
+
import WorkerCap from "./worker-cap.js";
|
|
6
6
|
import { decodePathParens } from "./path-decode.js";
|
|
7
7
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
8
8
|
import { LineMarkerOps, MimetypeBinary } from "../content/index.js";
|
|
9
9
|
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
10
10
|
import EntryCrud from "../schemes/_entry-crud.js";
|
|
11
|
-
import
|
|
11
|
+
import WorkspaceSettings from "./workspace-settings.js";
|
|
12
12
|
// SPEC §scheme-surface: writer must be in target scheme's manifest.writableBy.
|
|
13
13
|
// OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
|
|
14
14
|
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL", "FORK", "WORK"]);
|
|
@@ -32,17 +32,17 @@ class Dispatcher {
|
|
|
32
32
|
// Per-loop abort signal, owned by Engine.runLoop — thunked.
|
|
33
33
|
#loopSignal;
|
|
34
34
|
#streamEventNotify;
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
35
|
+
#wakeWorkerNotify;
|
|
36
|
+
#injectWorker;
|
|
37
|
+
#cancelWorker;
|
|
38
38
|
// §send-premature-terminate/[102]<T> — the engine-owned park-deadline registry (loopId → seconds;
|
|
39
39
|
// -1 = indefinite). The dispatcher WRITES at park; the daemon's drain park-exit consumes.
|
|
40
40
|
#parkDeadlines;
|
|
41
41
|
#searchGate;
|
|
42
|
-
// §join-blocking-collect (#354) — loops with a READ(
|
|
42
|
+
// §join-blocking-collect (#354) — loops with a READ(worker://running-child) armed this turn; a bare
|
|
43
43
|
// SEND[102] parks on it (blocking join). Engine-owned, twin of #parkDeadlines.
|
|
44
44
|
#joinTargets;
|
|
45
|
-
constructor({ db, schemes, mimetypes, tokenize, telemetry, proposals, executors, loopSignal, streamEventNotify,
|
|
45
|
+
constructor({ db, schemes, mimetypes, tokenize, telemetry, proposals, executors, loopSignal, streamEventNotify, wakeWorkerNotify, injectWorker, cancelWorker, searchGate, parkDeadlines, joinTargets }) {
|
|
46
46
|
this.#db = db;
|
|
47
47
|
this.#schemes = schemes;
|
|
48
48
|
this.#mimetypes = mimetypes;
|
|
@@ -52,16 +52,16 @@ class Dispatcher {
|
|
|
52
52
|
this.#executors = executors;
|
|
53
53
|
this.#loopSignal = loopSignal;
|
|
54
54
|
this.#streamEventNotify = streamEventNotify;
|
|
55
|
-
this.#
|
|
56
|
-
this.#
|
|
57
|
-
this.#
|
|
55
|
+
this.#wakeWorkerNotify = wakeWorkerNotify;
|
|
56
|
+
this.#injectWorker = injectWorker;
|
|
57
|
+
this.#cancelWorker = cancelWorker;
|
|
58
58
|
this.#parkDeadlines = parkDeadlines ?? new Map();
|
|
59
59
|
this.#searchGate = searchGate;
|
|
60
60
|
this.#joinTargets = joinTargets ?? new Set();
|
|
61
61
|
}
|
|
62
62
|
async dispatch(context) {
|
|
63
|
-
const { statement,
|
|
64
|
-
const schemeCtx = this.#buildSchemeCtx({
|
|
63
|
+
const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, turnParseErrors } = context;
|
|
64
|
+
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
65
65
|
let result;
|
|
66
66
|
let denial = this.#checkWritable(statement, origin);
|
|
67
67
|
if (denial === null)
|
|
@@ -73,7 +73,7 @@ class Dispatcher {
|
|
|
73
73
|
// READ honors FIND: a glob/folder scope or a matcher fans out to one log row per MATCH
|
|
74
74
|
// (its own writeLogs), returning early. A READ never proposes, so it bypasses the
|
|
75
75
|
// single-row path below. A bare entry, body-less, falls through to the direct read. #286
|
|
76
|
-
return await this.#handleReadFanout(statement, schemeCtx, {
|
|
76
|
+
return await this.#handleReadFanout(statement, schemeCtx, { workerId, loopId, turnId, sequence, origin, onDispatch });
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
79
|
// SPEC §scheme-surface + plurnk-schemes#1: action-entry-as-outcome. Scheme-handler
|
|
@@ -83,10 +83,10 @@ class Dispatcher {
|
|
|
83
83
|
// those are system failures.
|
|
84
84
|
try {
|
|
85
85
|
if (statement.op === "SEND" && statement.target === null) {
|
|
86
|
-
result = await this.#handleSendBroadcast(statement, {
|
|
86
|
+
result = await this.#handleSendBroadcast(statement, { workspaceId, workerId, loopId, turnId, turnParseErrors });
|
|
87
87
|
}
|
|
88
88
|
else if (statement.op === "FORK" || statement.op === "WORK") {
|
|
89
|
-
result = await this.#
|
|
89
|
+
result = await this.#handleWorkerControl(statement, schemeCtx);
|
|
90
90
|
}
|
|
91
91
|
else if (statement.op === "COPY") {
|
|
92
92
|
result = await this.#handleCopy(statement, schemeCtx);
|
|
@@ -122,11 +122,11 @@ class Dispatcher {
|
|
|
122
122
|
// recorded (#382 — a curation act with no forensic trace is how run43's task-frame fold
|
|
123
123
|
// stayed invisible), but engine_render_log suppresses them from the packet, so the receipt
|
|
124
124
|
// rents zero model-facing space — the original clutter concern, resolved by hide-not-drop.
|
|
125
|
-
// §join-blocking-collect (#354) — Run.read on a still-running child returns an
|
|
125
|
+
// §join-blocking-collect (#354) — Run.read on a still-running child returns an awaitWorker signal;
|
|
126
126
|
// arm the join so THIS turn's bare SEND[102] parks (the blocking collect) instead of spinning.
|
|
127
|
-
if (typeof result.
|
|
127
|
+
if (typeof result.awaitWorker === "string")
|
|
128
128
|
this.#joinTargets.add(loopId);
|
|
129
|
-
const logEntryId = await this.#writeLog({ statement, result,
|
|
129
|
+
const logEntryId = await this.#writeLog({ statement, result, workerId, loopId, turnId, sequence, origin });
|
|
130
130
|
// §search-gate — register successful searches AFTER #writeLog stamps the runtime
|
|
131
131
|
// entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
|
|
132
132
|
if (statement.op === "EXEC" && result.status < 400) {
|
|
@@ -149,7 +149,7 @@ class Dispatcher {
|
|
|
149
149
|
// no human gate, no loop/proposal notification. Accept + apply
|
|
150
150
|
// in-process; the model sees the outcome directly, never a review.
|
|
151
151
|
if (result.attrs?.inline === true) {
|
|
152
|
-
const effective = await this.#proposals.
|
|
152
|
+
const effective = await this.#proposals.workerApply(statement, result, { decision: "accept" }, { workspaceId, workerId, loopId, turnId });
|
|
153
153
|
return this.#proposals.applyResolution(logEntryId, effective);
|
|
154
154
|
}
|
|
155
155
|
// Register the resolution waiter SYNCHRONOUSLY before any await
|
|
@@ -165,9 +165,9 @@ class Dispatcher {
|
|
|
165
165
|
const flags = await this.#loadLoopFlags(loopId); // the loop/proposal notification carries flags (yolo) — §dual-yolo-proposal-carries-flags
|
|
166
166
|
// #note10 — if the target diverged on disk this turn, the model's EDIT is based
|
|
167
167
|
// on a stale read; flag it so a YOLO auto-accept rejects instead of clobbering.
|
|
168
|
-
const diverged = await this.#db.engine_target_diverged_this_turn.get({
|
|
168
|
+
const diverged = await this.#db.engine_target_diverged_this_turn.get({ worker_id: workerId, turn_id: turnId, scheme: target.scheme, pathname: target.pathname });
|
|
169
169
|
const event = {
|
|
170
|
-
logEntryId,
|
|
170
|
+
logEntryId, workspaceId, workerId, loopId, turnId,
|
|
171
171
|
op: statement.op,
|
|
172
172
|
target: { scheme: target.scheme, pathname: target.pathname },
|
|
173
173
|
body: typeof result.body === "string" ? result.body : "",
|
|
@@ -182,7 +182,7 @@ class Dispatcher {
|
|
|
182
182
|
// 4xx/5xx or throws, the resolution is downgraded to a reject
|
|
183
183
|
// with the failure outcome — engine treats it like a client
|
|
184
184
|
// rejection.
|
|
185
|
-
const effective = await this.#proposals.
|
|
185
|
+
const effective = await this.#proposals.workerApply(statement, result, resolution, { workspaceId, workerId, loopId, turnId });
|
|
186
186
|
// MOVE into a proposed dest: the deferred source-delete fires ONLY now,
|
|
187
187
|
// after the dest write landed (accept). On reject the source survives.
|
|
188
188
|
if (effective.decision === "accept") {
|
|
@@ -209,30 +209,29 @@ class Dispatcher {
|
|
|
209
209
|
// dispatch's resolve path minus #writeLog. Runs on the client loop, so the
|
|
210
210
|
// human's inspection is never constrained by a model loop's flags. {§op-look}
|
|
211
211
|
async look(context) {
|
|
212
|
-
const { statement,
|
|
212
|
+
const { statement, workspaceId, workerId, loopId, origin = "client" } = context;
|
|
213
213
|
if (statement.op !== "READ")
|
|
214
214
|
throw new Error(`look resolves READ only; got ${statement.op}`);
|
|
215
215
|
// turnId is a write-time FK only — a look writes no row, so 0 (no turn) is inert.
|
|
216
|
-
const schemeCtx = this.#buildSchemeCtx({
|
|
216
|
+
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId: 0, origin });
|
|
217
217
|
const denial = await this.#checkFlagsGate(statement, loopId);
|
|
218
218
|
if (denial !== null)
|
|
219
219
|
return denial;
|
|
220
220
|
return this.#run(schemeNameOf(statement.target), statement, schemeCtx);
|
|
221
221
|
}
|
|
222
222
|
#buildSchemeCtx(ids) {
|
|
223
|
-
const {
|
|
223
|
+
const { workspaceId, workerId, loopId, turnId, origin } = ids;
|
|
224
224
|
return {
|
|
225
225
|
db: this.#db,
|
|
226
|
-
|
|
226
|
+
workspaceId, workerId, loopId, turnId,
|
|
227
227
|
writer: origin,
|
|
228
228
|
signal: this.#loopSignal(loopId),
|
|
229
229
|
streamEventNotify: this.#streamEventNotify,
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
wakeWorkerNotify: this.#wakeWorkerNotify,
|
|
231
|
+
injectWorker: this.#injectWorker,
|
|
232
232
|
mimetypes: this.#mimetypes,
|
|
233
233
|
tokenize: this.#tokenize,
|
|
234
|
-
|
|
235
|
-
pushTelemetry: (event) => this.#telemetry.push(sessionId, loopId, event),
|
|
234
|
+
pushTelemetry: (event) => this.#telemetry.push(workspaceId, loopId, event),
|
|
236
235
|
executors: this.#executors(),
|
|
237
236
|
};
|
|
238
237
|
}
|
|
@@ -269,11 +268,11 @@ class Dispatcher {
|
|
|
269
268
|
if (statement.op === "EXEC") {
|
|
270
269
|
return this.#denyIfDisallowed("exec", origin);
|
|
271
270
|
}
|
|
272
|
-
// Run control (FORK/WORK →
|
|
271
|
+
// Run control (FORK/WORK → worker://<name>, spawn or fork) is gated by worker://'s writableBy — its
|
|
273
272
|
// body is a seed prompt, not a dst path, so the entry-COPY dst-parse below doesn't apply.
|
|
274
273
|
// §machine-processes
|
|
275
|
-
if (this.#
|
|
276
|
-
return this.#denyIfDisallowed("
|
|
274
|
+
if (this.#isWorkerControl(statement))
|
|
275
|
+
return this.#denyIfDisallowed("worker", origin);
|
|
277
276
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
278
277
|
const dst = statement.op === "COPY" ? (statement.body === null ? null : parsePath(statement.body)) : statement.body;
|
|
279
278
|
const dstScheme = schemeNameOf(dst);
|
|
@@ -303,7 +302,7 @@ class Dispatcher {
|
|
|
303
302
|
const command = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
304
303
|
const verdict = this.#searchGate?.check(loopId, turnId, runtime, command) ?? { verdict: "pass" };
|
|
305
304
|
if (verdict.verdict === "capped") {
|
|
306
|
-
return { status: 429, error: `Per-turn search limit reached (${verdict.cap})
|
|
305
|
+
return { status: 429, error: `Per-turn search limit reached (${verdict.cap}).` };
|
|
307
306
|
}
|
|
308
307
|
if (verdict.verdict === "duplicate") {
|
|
309
308
|
const prior = await EntryCrud.readEntry(verdict.priorPathname, ctx, runtime);
|
|
@@ -381,7 +380,7 @@ class Dispatcher {
|
|
|
381
380
|
return null;
|
|
382
381
|
return { status: 403, error: `'${scheme}' is unavailable: ${restriction}. Do NOT retry it — it is unavailable, not failing; answer or advise the user directly (an act-mode loop is required to use it).` };
|
|
383
382
|
};
|
|
384
|
-
if (this.#
|
|
383
|
+
if (this.#isWorkerControl(statement))
|
|
385
384
|
return check(statement.target); // body is a spawn/fork task, not a dst path
|
|
386
385
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
387
386
|
return check(statement.target) ?? check(statement.op === "COPY" ? (statement.body === null ? null : parsePath(statement.body)) : statement.body);
|
|
@@ -389,59 +388,59 @@ class Dispatcher {
|
|
|
389
388
|
return check(statement.target);
|
|
390
389
|
}
|
|
391
390
|
// Run control is FORK/WORK (grammar 0.74.55), not COPY — its body
|
|
392
|
-
// is the new
|
|
391
|
+
// is the new worker's seed prompt, not a destination path. The COPY gates and #handleCopy
|
|
393
392
|
// branch on this so they never parse the prompt as a dst path.
|
|
394
|
-
#
|
|
395
|
-
return statement.op === "FORK" || statement.op === "WORK"; // run control targets
|
|
393
|
+
#isWorkerControl(statement) {
|
|
394
|
+
return statement.op === "FORK" || statement.op === "WORK"; // run control targets worker://<name> (grammar 0.74.55)
|
|
396
395
|
}
|
|
397
|
-
// FORK/WORK(
|
|
398
|
-
// •
|
|
396
|
+
// FORK/WORK(worker://<name>):task — run control (grammar 0.74.55):
|
|
397
|
+
// • worker://self → FORK: deep-copy the current worker's log into a new sister (Fork), then
|
|
399
398
|
// continue it with the prompt (§machine-processes-fork-copies-the-log).
|
|
400
|
-
// •
|
|
399
|
+
// • worker://<name> → SPAWN: a fresh sister (empty log) named <name>, started on the prompt.
|
|
401
400
|
// A LIVE sister already holding <name> is a 409 conflict; a free or terminated name is
|
|
402
|
-
// reclaimed (§
|
|
403
|
-
// Both ride the daemon inject and obey the active-runs cap (508, §
|
|
401
|
+
// reclaimed (§worker-scheme-spawn). The self form is fork; only a name spawns.
|
|
402
|
+
// Both ride the daemon inject and obey the active-runs cap (508, §worker-scheme-cap).
|
|
404
403
|
// FORK/WORK — run control (grammar 0.74.55). Both name a NEW run in the target authority
|
|
405
|
-
// (
|
|
406
|
-
// the current
|
|
404
|
+
// (worker://<name>) and carry its seed task in the body. WORK spawns a fresh worker; FORK branches
|
|
405
|
+
// the current worker's log into a named sister. Replaces the COPY(worker://) overload — one verb, one
|
|
407
406
|
// intent, so the model never conflates the target slot with the body (grammar#52).
|
|
408
|
-
async #
|
|
407
|
+
async #handleWorkerControl(statement, ctx) {
|
|
409
408
|
const target = statement.target;
|
|
410
409
|
if (target === null)
|
|
411
|
-
return { status: 400, error: `${statement.op} requires a
|
|
412
|
-
const name = target.kind === "url" ? (target.hostname ?? "") : ""; // §
|
|
410
|
+
return { status: 400, error: `${statement.op} requires a worker target (${statement.op}(worker://<name>))` };
|
|
411
|
+
const name = target.kind === "url" ? (target.hostname ?? "") : ""; // §worker-scheme — run is the AUTHORITY (worker://<name>), not the path
|
|
413
412
|
if (name === "")
|
|
414
|
-
return { status: 400, error: `${statement.op} requires a
|
|
413
|
+
return { status: 400, error: `${statement.op} requires a worker name (worker://<name>)` };
|
|
415
414
|
if (name === "self")
|
|
416
|
-
return { status: 400, error: `'self' is the current run — ${statement.op} names a NEW run (
|
|
417
|
-
if (ctx.
|
|
418
|
-
throw new Error("run control:
|
|
419
|
-
const denied = await
|
|
415
|
+
return { status: 400, error: `'self' is the current run — ${statement.op} names a NEW run (worker://<name>)` };
|
|
416
|
+
if (ctx.injectWorker === undefined)
|
|
417
|
+
throw new Error("run control: injectWorker capability absent");
|
|
418
|
+
const denied = await WorkerCap.deny(this.#db, ctx.workspaceId);
|
|
420
419
|
if (denied !== null)
|
|
421
420
|
return denied;
|
|
422
421
|
const prompt = typeof statement.body === "string" ? statement.body : "";
|
|
423
|
-
// §
|
|
422
|
+
// §worker-delegation-inherits-flags — authority flows down the delegation edge: the child's live
|
|
424
423
|
// loop runs with ITS DELEGATOR'S flags. A flagless (non-YOLO) child's every side-effecting op
|
|
425
424
|
// proposes into a resolver-less void — 300s auto-cancel per attempt was the fan-out wedge.
|
|
426
425
|
const flags = await this.#loadLoopFlags(ctx.loopId);
|
|
427
|
-
// A name is frozen per
|
|
426
|
+
// A name is frozen per worker but reclaimable across time (§machine-processes-worker-origin): a LIVE
|
|
428
427
|
// sister holding it is a 409 (legible, never a raw UNIQUE 500); a free/terminated name reclaims.
|
|
429
|
-
const live = await this.#db.
|
|
428
|
+
const live = await this.#db.worker_live_by_name.get({ workspace_id: ctx.workspaceId, name });
|
|
430
429
|
if (live !== undefined)
|
|
431
|
-
return { status: 409, error: `
|
|
430
|
+
return { status: 409, error: `worker '${name}' is already running` };
|
|
432
431
|
if (statement.op === "FORK") {
|
|
433
|
-
// Branch the current
|
|
434
|
-
const
|
|
435
|
-
await ctx.
|
|
432
|
+
// Branch the current worker's log into a named sister.
|
|
433
|
+
const branchWorkerId = await Fork.fork(this.#db, ctx.workerId, name);
|
|
434
|
+
await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: branchWorkerId, prompt, flags });
|
|
436
435
|
return { status: 200, body: name };
|
|
437
436
|
}
|
|
438
437
|
// WORK — a fresh worker sister named <name>.
|
|
439
|
-
const row = await this.#db.
|
|
440
|
-
|
|
438
|
+
const row = await this.#db.fork_insert_worker.get({
|
|
439
|
+
workspace_id: ctx.workspaceId, name, parent_worker_id: ctx.workerId, origin: ctx.writer,
|
|
441
440
|
});
|
|
442
441
|
if (row === undefined)
|
|
443
442
|
throw new Error("run spawn: run insert returned no row");
|
|
444
|
-
await ctx.
|
|
443
|
+
await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags });
|
|
445
444
|
return { status: 200, body: name };
|
|
446
445
|
}
|
|
447
446
|
async #handleCopy(statement, ctx) {
|
|
@@ -519,37 +518,37 @@ class Dispatcher {
|
|
|
519
518
|
if (killable !== undefined && typeof killable.kill === "function") {
|
|
520
519
|
return await killable.kill(pathnameFromPath(path), statement.signal, ctx);
|
|
521
520
|
}
|
|
522
|
-
if (schemeName === "
|
|
523
|
-
// Entry-path present → KILL a
|
|
521
|
+
if (schemeName === "worker") {
|
|
522
|
+
// Entry-path present → KILL a worker-scope scratch ENTRY (delete it), self-only —
|
|
524
523
|
// NOT run cancellation. The authority (hostname) names the owner, the pathname the
|
|
525
|
-
// entry; only the path-ABSENT form (
|
|
524
|
+
// entry; only the path-ABSENT form (worker://<name>) terminates the run-as-actor. §worker-scheme
|
|
526
525
|
const entryPath = path.kind === "url" ? (path.pathname ?? "") : "";
|
|
527
526
|
if (entryPath !== "" && entryPath !== "/") {
|
|
528
|
-
const
|
|
529
|
-
return await
|
|
527
|
+
const workerHandler = this.#schemes.get("worker");
|
|
528
|
+
return await workerHandler.deleteEntry(statement, ctx);
|
|
530
529
|
}
|
|
531
|
-
// terminate — abort any
|
|
532
|
-
// `
|
|
530
|
+
// terminate — abort any worker by address; whoever holds it may end it.
|
|
531
|
+
// `worker://self` = self. cancelWorker (→ Daemon.cancelDrain) aborts the worker's signal
|
|
533
532
|
// (its loop closes 499); an idle run is a no-op-200, a missing run 404.
|
|
534
|
-
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §
|
|
533
|
+
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §worker-scheme — run is the AUTHORITY
|
|
535
534
|
if (name === "")
|
|
536
|
-
return { status: 400, error: "
|
|
537
|
-
let
|
|
535
|
+
return { status: 400, error: "worker:// kill requires a worker name or 'self' (worker://<name>)" };
|
|
536
|
+
let workerId = ctx.workerId;
|
|
538
537
|
if (name !== "self") {
|
|
539
|
-
const row = await this.#db.
|
|
538
|
+
const row = await this.#db.worker_resolve_by_name.get({ workspace_id: ctx.workspaceId, name });
|
|
540
539
|
if (row === undefined)
|
|
541
|
-
return { status: 404, error: `
|
|
542
|
-
|
|
540
|
+
return { status: 404, error: `worker://${name} not found in this workspace` };
|
|
541
|
+
workerId = row.id;
|
|
543
542
|
}
|
|
544
|
-
if (this.#
|
|
545
|
-
throw new Error("run kill:
|
|
546
|
-
// §op-synchronous — KILL is DECISIVE: flip the
|
|
543
|
+
if (this.#cancelWorker === undefined)
|
|
544
|
+
throw new Error("run kill: cancelWorker capability absent");
|
|
545
|
+
// §op-synchronous — KILL is DECISIVE: flip the worker's live loops to 499 NOW so the
|
|
547
546
|
// same-turn premature-terminate gate sees it dead (KILL … SEND[200] concludes in ONE
|
|
548
547
|
// turn — the model never reasons about async reap timing). The physical scope reap
|
|
549
|
-
// (drain abort + stream teardown) then rides
|
|
550
|
-
// because
|
|
551
|
-
await this.#db.
|
|
552
|
-
this.#
|
|
548
|
+
// (drain abort + stream teardown) then rides cancelWorker; a killed loop can't heal back
|
|
549
|
+
// because cancelWorker aborts its drain's signal.
|
|
550
|
+
await this.#db.engine_terminate_worker_live_loops.run({ worker_id: workerId, message: "killed via worker:// KILL" });
|
|
551
|
+
this.#cancelWorker(workerId);
|
|
553
552
|
return { status: 200 };
|
|
554
553
|
}
|
|
555
554
|
const handler = this.#schemes.get(schemeName);
|
|
@@ -590,7 +589,7 @@ class Dispatcher {
|
|
|
590
589
|
return { ...statement, target: target, lineMarker, body: null };
|
|
591
590
|
}
|
|
592
591
|
async #handleReadFanout(statement, ctx, ids) {
|
|
593
|
-
const {
|
|
592
|
+
const { workerId, loopId, turnId, sequence, origin, onDispatch } = ids;
|
|
594
593
|
const schemeName = schemeNameOf(statement.target);
|
|
595
594
|
const found = await this.#run(schemeName, { ...statement, op: "FIND" }, ctx);
|
|
596
595
|
const matches = found.matches ?? [];
|
|
@@ -598,7 +597,7 @@ class Dispatcher {
|
|
|
598
597
|
// status, exactly like a non-fanned READ. The model sees the empty/failed result, not silence.
|
|
599
598
|
if (found.status !== 200 || matches.length === 0) {
|
|
600
599
|
const result = { status: found.status === 200 ? 204 : found.status };
|
|
601
|
-
const id = await this.#writeLog({ statement, result,
|
|
600
|
+
const id = await this.#writeLog({ statement, result, workerId, loopId, turnId, sequence, origin });
|
|
602
601
|
onDispatch?.(id);
|
|
603
602
|
return { ...result, rowsWritten: 1 };
|
|
604
603
|
}
|
|
@@ -612,7 +611,7 @@ class Dispatcher {
|
|
|
612
611
|
// had FINDed then READ. On a degenerate single-line document the N delivery rows are
|
|
613
612
|
// identical whole-file lines; the summary row is what tells the model its query hit N
|
|
614
613
|
// times and WHERE (run30: two hits indistinguishable from failure; 17 retries, 508).
|
|
615
|
-
const findRowId = await this.#writeLog({ statement: { ...statement, op: "FIND" }, result: found,
|
|
614
|
+
const findRowId = await this.#writeLog({ statement: { ...statement, op: "FIND" }, result: found, workerId, loopId, turnId, sequence, origin });
|
|
616
615
|
onDispatch?.(findRowId);
|
|
617
616
|
const wholeByPath = new Map();
|
|
618
617
|
const fannedStatuses = [];
|
|
@@ -624,7 +623,7 @@ class Dispatcher {
|
|
|
624
623
|
wholeByPath.set(m.pathname, whole);
|
|
625
624
|
}
|
|
626
625
|
const result = _a.#sliceMatch(whole, m.span);
|
|
627
|
-
const id = await this.#writeLog({ statement: _a.#retargetRead(statement, m.pathname, m.span), result,
|
|
626
|
+
const id = await this.#writeLog({ statement: _a.#retargetRead(statement, m.pathname, m.span), result, workerId, loopId, turnId, sequence: sequence + written, origin });
|
|
628
627
|
onDispatch?.(id);
|
|
629
628
|
fannedStatuses.push(result.status);
|
|
630
629
|
written++;
|
|
@@ -646,9 +645,9 @@ class Dispatcher {
|
|
|
646
645
|
// the model can finally SEE its own prior output (and reason through its own syntax errors).
|
|
647
646
|
// Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
|
|
648
647
|
// (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
|
|
649
|
-
async writeModelEntry({ verbatim,
|
|
648
|
+
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model" }) {
|
|
650
649
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
651
|
-
|
|
650
|
+
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
|
|
652
651
|
origin, source: null, op: "model", suffix: "", signal: null,
|
|
653
652
|
scheme: null, username: null, password: null, hostname: null, port: null,
|
|
654
653
|
pathname: null, params: null, fragment: null, lineMarker: null,
|
|
@@ -755,41 +754,41 @@ class Dispatcher {
|
|
|
755
754
|
// pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
|
|
756
755
|
// until next packet). One rule, one steer, one repair family: nothing pending may be silently
|
|
757
756
|
// discarded; 499 discards BY STATED INTENT and is never gated.
|
|
758
|
-
async #pendingSet(
|
|
757
|
+
async #pendingSet(workerId, turnId) {
|
|
759
758
|
const pending = [];
|
|
760
|
-
const openSubs = await this.#db.
|
|
759
|
+
const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
|
|
761
760
|
const execHandler = this.#schemes.get("exec");
|
|
762
|
-
if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(
|
|
763
|
-
pending.push("
|
|
764
|
-
const liveChild = await this.#db.
|
|
761
|
+
if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(workerId) === true)
|
|
762
|
+
pending.push("surviving streams");
|
|
763
|
+
const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
|
|
765
764
|
if (liveChild !== undefined)
|
|
766
|
-
pending.push("
|
|
765
|
+
pending.push("surviving workers");
|
|
767
766
|
const retrievals = await this.#db.engine_turn_retrievals.all({ turn_id: turnId });
|
|
768
767
|
if (retrievals.length > 0)
|
|
769
|
-
pending.push("
|
|
768
|
+
pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
|
|
770
769
|
// §send-undelivered-child-term — a worker that concluded DURING this turn's generation is no
|
|
771
770
|
// longer "live" but its deliverable hasn't reached any packet yet; concluding discards it.
|
|
772
|
-
const undelivered = await this.#db.
|
|
771
|
+
const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
|
|
773
772
|
if (undelivered !== undefined)
|
|
774
773
|
pending.push("worker results that arrived during this turn (they land NEXT turn)");
|
|
775
774
|
return pending;
|
|
776
775
|
}
|
|
777
776
|
// J — a live obligation to WAIT on: a spawned child or an open stream (NOT retrievals, which land
|
|
778
777
|
// next turn regardless). The wait-side twin of #pendingSet's stream+child legs (§wait-obligation-matrix).
|
|
779
|
-
async #hasLiveWork(
|
|
780
|
-
const openSubs = await this.#db.
|
|
778
|
+
async #hasLiveWork(workerId) {
|
|
779
|
+
const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
|
|
781
780
|
if (openSubs.length > 0)
|
|
782
781
|
return true;
|
|
783
782
|
const execHandler = this.#schemes.get("exec");
|
|
784
|
-
if (execHandler?.hasActiveSpawns?.(
|
|
783
|
+
if (execHandler?.hasActiveSpawns?.(workerId) === true)
|
|
785
784
|
return true;
|
|
786
|
-
const liveChild = await this.#db.
|
|
785
|
+
const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
|
|
787
786
|
return liveChild !== undefined;
|
|
788
787
|
}
|
|
789
788
|
async #handleSendBroadcast(statement, ctx) {
|
|
790
789
|
if (statement.op !== "SEND")
|
|
791
790
|
throw new Error("unreachable");
|
|
792
|
-
const {
|
|
791
|
+
const { workerId, loopId, turnId } = ctx;
|
|
793
792
|
const status = statement.signal;
|
|
794
793
|
if (status === null)
|
|
795
794
|
return { status: 400 };
|
|
@@ -800,7 +799,7 @@ class Dispatcher {
|
|
|
800
799
|
// indefinitely (the butler/worker pattern — owner-ruled ungated; the instrumentation renders
|
|
801
800
|
// it legibly). Internally the parked state remains loops.status=202: the model-facing SIGNAL
|
|
802
801
|
// retired, the engine's park state did not.
|
|
803
|
-
// §join-blocking-collect (#354) — a READ(
|
|
802
|
+
// §join-blocking-collect (#354) — a READ(worker://running-child) this turn armed a join. A BARE
|
|
804
803
|
// continue becomes an indefinite PARK: the blocking collect, on the same park machinery <-1>
|
|
805
804
|
// uses. The armed READ said "I want this result"; parking IS the collect (the model never had
|
|
806
805
|
// to know the park syntax — the engine holds the join). Any SEND clears the per-turn arm; a
|
|
@@ -814,13 +813,13 @@ class Dispatcher {
|
|
|
814
813
|
// §wait-obligation-matrix — the WAIT: SEND[202], and the legacy SEND[102]<T> the terminal
|
|
815
814
|
// redesign spelled the same park with, are one obligation-checked wait (waitpid). A live
|
|
816
815
|
// obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
|
|
817
|
-
// reawakens it (§
|
|
816
|
+
// reawakens it (§worker-lifecycle-child-wake); a wait on nothing (∅) is already satisfied and
|
|
818
817
|
// resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
|
|
819
818
|
// retrieval (R) just lands next turn, so the wait continues.
|
|
820
819
|
if (status === 202 || (status === 102 && statement.lineMarker !== null)) {
|
|
821
820
|
const marks = statement.lineMarker?.marks[0];
|
|
822
821
|
const seconds = typeof marks === "number" ? marks : -1; // bare 202 / absent T = indefinite, bounded by the join
|
|
823
|
-
if (await this.#hasLiveWork(
|
|
822
|
+
if (await this.#hasLiveWork(workerId)) {
|
|
824
823
|
await this.#db.engine_loop_set_status.run({ status: 202, loop_id: loopId, message: raw.length > 0 ? raw : "waiting on live work" });
|
|
825
824
|
this.#parkDeadlines.set(loopId, seconds);
|
|
826
825
|
return { status: 202, attrs: { waiting: seconds } };
|
|
@@ -833,26 +832,24 @@ class Dispatcher {
|
|
|
833
832
|
// NEXT build. The wait is NOT on nothing — the results are on the doorstep. Parking
|
|
834
833
|
// would hang (the wake edges already fired into an unparked run); R semantics: continue,
|
|
835
834
|
// the deltas land next turn, the model weighs them and concludes.
|
|
836
|
-
const undelivered = await this.#db.
|
|
835
|
+
const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
|
|
837
836
|
if (undelivered !== undefined)
|
|
838
837
|
return { status: 102 };
|
|
839
|
-
// ∅ — a wait on
|
|
840
|
-
// #
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
await this.#db.engine_loop_mark_terminated_by.run({ terminated_by: "collapse", loop_id: loopId });
|
|
845
|
-
return { status: 200 };
|
|
838
|
+
// ∅ — a wait on nothing is a contradiction, the mirror of premature-terminate (owner
|
|
839
|
+
// ruling, #502 run113): concluding it silently laundered the one signal that something
|
|
840
|
+
// was structurally off (the model believed work was pending; nothing was). 409 gives
|
|
841
|
+
// the turn back with the fact — the recovery rail this exact confusion needs.
|
|
842
|
+
return { status: 409, error: "Attempted [202] wait with nothing pending: no surviving streams, no worker runs, no unretrieved results." };
|
|
846
843
|
}
|
|
847
844
|
// §send-300-choices — ask the operator and park (the answer returns via loop.inject).
|
|
848
845
|
// The three-state cascade (owner design): PLURNK_QUESTIONS unset = ALLOWED (not enabled);
|
|
849
846
|
// =0 = DENIED servicewide (a ceiling the client cannot override); ENABLED requires the
|
|
850
|
-
// client to affirmatively pass it per
|
|
851
|
-
// with a human enables its own
|
|
847
|
+
// client to affirmatively pass it per workspace (settings.questions — the interactive client
|
|
848
|
+
// with a human enables its own workspaces; headless/bench never asks). Enabled workspaces ALSO
|
|
852
849
|
// get the questions.md teaching injected (docEntries) — capability and teaching gate as one.
|
|
853
850
|
// Disabled → refused with a self-decide steer, never a park into the void.
|
|
854
851
|
if (status === 300) {
|
|
855
|
-
if (!(await
|
|
852
|
+
if (!(await WorkspaceSettings.questionsEnabled(this.#db, ctx.workspaceId))) {
|
|
856
853
|
return { status: 409, error: "Operator asks ([300]) are not enabled in this environment — no one is watching to answer. Decide yourself and proceed: SEND[102] to continue, or [200] to conclude." };
|
|
857
854
|
}
|
|
858
855
|
// Owner ruling (#346): the question rides the SAME proposal system as file edits and
|
|
@@ -880,12 +877,12 @@ class Dispatcher {
|
|
|
880
877
|
if (failCount > 0) {
|
|
881
878
|
return { status: 409, error: `Termination attempted despite ${failCount} failed operation(s) this turn. The errors land in your log next turn — weigh them, then conclude (or SEND[499] to abandon).` };
|
|
882
879
|
}
|
|
883
|
-
const pending = await this.#pendingSet(
|
|
880
|
+
const pending = await this.#pendingSet(workerId, turnId);
|
|
884
881
|
if (pending.length > 0) {
|
|
885
882
|
// Kind-specific steer (owner wording, xpath/topo forensics): retrievals-only is
|
|
886
883
|
// gemma's read-and-conclude idiom — no lever to pull, the results simply arrive;
|
|
887
884
|
// KILL/park advice only muddies it. Streams/children keep the remedy steer.
|
|
888
|
-
const retrievalsOnly = pending.every((k) => k.startsWith("
|
|
885
|
+
const retrievalsOnly = pending.every((k) => k.startsWith("this turn's retrieval results"));
|
|
889
886
|
if (retrievalsOnly) {
|
|
890
887
|
// attrs.retrievalOnly — the strike decoupler (owner ruling): atomic-turn-
|
|
891
888
|
// pretrained models pair fetch-and-answer by habit; the refusal teaches,
|
|
@@ -898,7 +895,7 @@ class Dispatcher {
|
|
|
898
895
|
// that re-armed this gate four times while she held the correct answer).
|
|
899
896
|
return { status: 409, error: "Last turn both performed retrieval operations and attempted to terminate. Retrieval operations force an additional turn to receive results for review and reaction. To conclude, only use PLAN and SEND[200] operations.", attrs: { retrievalOnly: true } };
|
|
900
897
|
}
|
|
901
|
-
return { status: 409, error: `Attempted [200] termination
|
|
898
|
+
return { status: 409, error: `Attempted [200] termination in a turn that performs retrieval or has surviving work: ${pending.join("; ")}. KILL what you no longer need; SEND[102] (or [102]<seconds>) to receive the rest; then conclude.` };
|
|
902
899
|
}
|
|
903
900
|
await this.#db.engine_loop_set_status.run({ status: 200, loop_id: loopId, message: raw === "" ? null : raw });
|
|
904
901
|
return { status: 200 };
|
|
@@ -945,7 +942,7 @@ class Dispatcher {
|
|
|
945
942
|
return true;
|
|
946
943
|
return !(statement.op === "SEND" && statement.target === null);
|
|
947
944
|
}
|
|
948
|
-
async #writeLog({ statement, result,
|
|
945
|
+
async #writeLog({ statement, result, workerId, loopId, turnId, sequence, origin, }) {
|
|
949
946
|
const target = this.#extractTarget(statement.target);
|
|
950
947
|
const lineMarkerJson = "lineMarker" in statement && statement.lineMarker !== null
|
|
951
948
|
? JSON.stringify(statement.lineMarker)
|
|
@@ -978,7 +975,7 @@ class Dispatcher {
|
|
|
978
975
|
attrsObj.stream = `${runtime}://${coordPathname}`;
|
|
979
976
|
// Mutate the in-memory result.attrs too: the dispatch path
|
|
980
977
|
// hands originalResult.attrs to handler.applyResolution after
|
|
981
|
-
// proposal accept (see ProposalLifecycle.
|
|
978
|
+
// proposal accept (see ProposalLifecycle.workerApply). Both views —
|
|
982
979
|
// the stored row AND the in-memory proposal — need the same
|
|
983
980
|
// pathname so applyResolution writes the entry at the same URI.
|
|
984
981
|
if (result.attrs !== undefined && result.attrs !== null) {
|
|
@@ -989,7 +986,7 @@ class Dispatcher {
|
|
|
989
986
|
const txJson = JSON.stringify(statement);
|
|
990
987
|
const rxJson = JSON.stringify(result);
|
|
991
988
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
992
|
-
|
|
989
|
+
worker_id: workerId,
|
|
993
990
|
loop_id: loopId,
|
|
994
991
|
turn_id: turnId,
|
|
995
992
|
sequence: sequence,
|
|
@@ -1037,10 +1034,10 @@ class Dispatcher {
|
|
|
1037
1034
|
// Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
|
|
1038
1035
|
// it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
|
|
1039
1036
|
// the entry (/prompt/<run>/<loop>/<N>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
|
|
1040
|
-
// namespace: keep it in hostname.
|
|
1041
|
-
// run selector (§
|
|
1037
|
+
// namespace: keep it in hostname. worker:// is the one registered EXCEPTION — its authority IS the
|
|
1038
|
+
// run selector (§worker-scheme), and worker://self must stay distinct from worker://name, so Run.ts
|
|
1042
1039
|
// folds the owner into the storage path itself, never here.
|
|
1043
|
-
const foldNs = scheme !== null && scheme !== "
|
|
1040
|
+
const foldNs = scheme !== null && scheme !== "worker" && this.#schemes.has(scheme);
|
|
1044
1041
|
return {
|
|
1045
1042
|
scheme, username: path.username, password: path.password,
|
|
1046
1043
|
hostname: foldNs ? null : path.hostname, port: path.port,
|