@plurnk/plurnk-service 1.0.5 → 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 +204 -192
- 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 -11
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +122 -124
- 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 +207 -183
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +106 -109
- package/dist/core/PacketBuilder.d.ts +9 -9
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +183 -115
- 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/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 -6
- 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.js +2 -2
- package/dist/core/token-ruler.js.map +1 -1
- 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 +8 -8
- 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 +72 -28
- 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 +48 -48
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +285 -291
- 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/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 +82 -70
- package/package.json +17 -14
- 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,29 +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
|
-
pushTelemetry: (event) => this.#telemetry.push(
|
|
234
|
+
pushTelemetry: (event) => this.#telemetry.push(workspaceId, loopId, event),
|
|
235
235
|
executors: this.#executors(),
|
|
236
236
|
};
|
|
237
237
|
}
|
|
@@ -268,11 +268,11 @@ class Dispatcher {
|
|
|
268
268
|
if (statement.op === "EXEC") {
|
|
269
269
|
return this.#denyIfDisallowed("exec", origin);
|
|
270
270
|
}
|
|
271
|
-
// Run control (FORK/WORK →
|
|
271
|
+
// Run control (FORK/WORK → worker://<name>, spawn or fork) is gated by worker://'s writableBy — its
|
|
272
272
|
// body is a seed prompt, not a dst path, so the entry-COPY dst-parse below doesn't apply.
|
|
273
273
|
// §machine-processes
|
|
274
|
-
if (this.#
|
|
275
|
-
return this.#denyIfDisallowed("
|
|
274
|
+
if (this.#isWorkerControl(statement))
|
|
275
|
+
return this.#denyIfDisallowed("worker", origin);
|
|
276
276
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
277
277
|
const dst = statement.op === "COPY" ? (statement.body === null ? null : parsePath(statement.body)) : statement.body;
|
|
278
278
|
const dstScheme = schemeNameOf(dst);
|
|
@@ -302,7 +302,7 @@ class Dispatcher {
|
|
|
302
302
|
const command = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
|
|
303
303
|
const verdict = this.#searchGate?.check(loopId, turnId, runtime, command) ?? { verdict: "pass" };
|
|
304
304
|
if (verdict.verdict === "capped") {
|
|
305
|
-
return { status: 429, error: `Per-turn search limit reached (${verdict.cap})
|
|
305
|
+
return { status: 429, error: `Per-turn search limit reached (${verdict.cap}).` };
|
|
306
306
|
}
|
|
307
307
|
if (verdict.verdict === "duplicate") {
|
|
308
308
|
const prior = await EntryCrud.readEntry(verdict.priorPathname, ctx, runtime);
|
|
@@ -380,7 +380,7 @@ class Dispatcher {
|
|
|
380
380
|
return null;
|
|
381
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).` };
|
|
382
382
|
};
|
|
383
|
-
if (this.#
|
|
383
|
+
if (this.#isWorkerControl(statement))
|
|
384
384
|
return check(statement.target); // body is a spawn/fork task, not a dst path
|
|
385
385
|
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
386
386
|
return check(statement.target) ?? check(statement.op === "COPY" ? (statement.body === null ? null : parsePath(statement.body)) : statement.body);
|
|
@@ -388,59 +388,59 @@ class Dispatcher {
|
|
|
388
388
|
return check(statement.target);
|
|
389
389
|
}
|
|
390
390
|
// Run control is FORK/WORK (grammar 0.74.55), not COPY — its body
|
|
391
|
-
// is the new
|
|
391
|
+
// is the new worker's seed prompt, not a destination path. The COPY gates and #handleCopy
|
|
392
392
|
// branch on this so they never parse the prompt as a dst path.
|
|
393
|
-
#
|
|
394
|
-
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)
|
|
395
395
|
}
|
|
396
|
-
// FORK/WORK(
|
|
397
|
-
// •
|
|
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
|
|
398
398
|
// continue it with the prompt (§machine-processes-fork-copies-the-log).
|
|
399
|
-
// •
|
|
399
|
+
// • worker://<name> → SPAWN: a fresh sister (empty log) named <name>, started on the prompt.
|
|
400
400
|
// A LIVE sister already holding <name> is a 409 conflict; a free or terminated name is
|
|
401
|
-
// reclaimed (§
|
|
402
|
-
// 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).
|
|
403
403
|
// FORK/WORK — run control (grammar 0.74.55). Both name a NEW run in the target authority
|
|
404
|
-
// (
|
|
405
|
-
// 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
|
|
406
406
|
// intent, so the model never conflates the target slot with the body (grammar#52).
|
|
407
|
-
async #
|
|
407
|
+
async #handleWorkerControl(statement, ctx) {
|
|
408
408
|
const target = statement.target;
|
|
409
409
|
if (target === null)
|
|
410
|
-
return { status: 400, error: `${statement.op} requires a
|
|
411
|
-
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
|
|
412
412
|
if (name === "")
|
|
413
|
-
return { status: 400, error: `${statement.op} requires a
|
|
413
|
+
return { status: 400, error: `${statement.op} requires a worker name (worker://<name>)` };
|
|
414
414
|
if (name === "self")
|
|
415
|
-
return { status: 400, error: `'self' is the current run — ${statement.op} names a NEW run (
|
|
416
|
-
if (ctx.
|
|
417
|
-
throw new Error("run control:
|
|
418
|
-
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);
|
|
419
419
|
if (denied !== null)
|
|
420
420
|
return denied;
|
|
421
421
|
const prompt = typeof statement.body === "string" ? statement.body : "";
|
|
422
|
-
// §
|
|
422
|
+
// §worker-delegation-inherits-flags — authority flows down the delegation edge: the child's live
|
|
423
423
|
// loop runs with ITS DELEGATOR'S flags. A flagless (non-YOLO) child's every side-effecting op
|
|
424
424
|
// proposes into a resolver-less void — 300s auto-cancel per attempt was the fan-out wedge.
|
|
425
425
|
const flags = await this.#loadLoopFlags(ctx.loopId);
|
|
426
|
-
// A name is frozen per
|
|
426
|
+
// A name is frozen per worker but reclaimable across time (§machine-processes-worker-origin): a LIVE
|
|
427
427
|
// sister holding it is a 409 (legible, never a raw UNIQUE 500); a free/terminated name reclaims.
|
|
428
|
-
const live = await this.#db.
|
|
428
|
+
const live = await this.#db.worker_live_by_name.get({ workspace_id: ctx.workspaceId, name });
|
|
429
429
|
if (live !== undefined)
|
|
430
|
-
return { status: 409, error: `
|
|
430
|
+
return { status: 409, error: `worker '${name}' is already running` };
|
|
431
431
|
if (statement.op === "FORK") {
|
|
432
|
-
// Branch the current
|
|
433
|
-
const
|
|
434
|
-
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 });
|
|
435
435
|
return { status: 200, body: name };
|
|
436
436
|
}
|
|
437
437
|
// WORK — a fresh worker sister named <name>.
|
|
438
|
-
const row = await this.#db.
|
|
439
|
-
|
|
438
|
+
const row = await this.#db.fork_insert_worker.get({
|
|
439
|
+
workspace_id: ctx.workspaceId, name, parent_worker_id: ctx.workerId, origin: ctx.writer,
|
|
440
440
|
});
|
|
441
441
|
if (row === undefined)
|
|
442
442
|
throw new Error("run spawn: run insert returned no row");
|
|
443
|
-
await ctx.
|
|
443
|
+
await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags });
|
|
444
444
|
return { status: 200, body: name };
|
|
445
445
|
}
|
|
446
446
|
async #handleCopy(statement, ctx) {
|
|
@@ -518,37 +518,37 @@ class Dispatcher {
|
|
|
518
518
|
if (killable !== undefined && typeof killable.kill === "function") {
|
|
519
519
|
return await killable.kill(pathnameFromPath(path), statement.signal, ctx);
|
|
520
520
|
}
|
|
521
|
-
if (schemeName === "
|
|
522
|
-
// Entry-path present → KILL a
|
|
521
|
+
if (schemeName === "worker") {
|
|
522
|
+
// Entry-path present → KILL a worker-scope scratch ENTRY (delete it), self-only —
|
|
523
523
|
// NOT run cancellation. The authority (hostname) names the owner, the pathname the
|
|
524
|
-
// entry; only the path-ABSENT form (
|
|
524
|
+
// entry; only the path-ABSENT form (worker://<name>) terminates the run-as-actor. §worker-scheme
|
|
525
525
|
const entryPath = path.kind === "url" ? (path.pathname ?? "") : "";
|
|
526
526
|
if (entryPath !== "" && entryPath !== "/") {
|
|
527
|
-
const
|
|
528
|
-
return await
|
|
527
|
+
const workerHandler = this.#schemes.get("worker");
|
|
528
|
+
return await workerHandler.deleteEntry(statement, ctx);
|
|
529
529
|
}
|
|
530
|
-
// terminate — abort any
|
|
531
|
-
// `
|
|
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
|
|
532
532
|
// (its loop closes 499); an idle run is a no-op-200, a missing run 404.
|
|
533
|
-
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §
|
|
533
|
+
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §worker-scheme — run is the AUTHORITY
|
|
534
534
|
if (name === "")
|
|
535
|
-
return { status: 400, error: "
|
|
536
|
-
let
|
|
535
|
+
return { status: 400, error: "worker:// kill requires a worker name or 'self' (worker://<name>)" };
|
|
536
|
+
let workerId = ctx.workerId;
|
|
537
537
|
if (name !== "self") {
|
|
538
|
-
const row = await this.#db.
|
|
538
|
+
const row = await this.#db.worker_resolve_by_name.get({ workspace_id: ctx.workspaceId, name });
|
|
539
539
|
if (row === undefined)
|
|
540
|
-
return { status: 404, error: `
|
|
541
|
-
|
|
540
|
+
return { status: 404, error: `worker://${name} not found in this workspace` };
|
|
541
|
+
workerId = row.id;
|
|
542
542
|
}
|
|
543
|
-
if (this.#
|
|
544
|
-
throw new Error("run kill:
|
|
545
|
-
// §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
|
|
546
546
|
// same-turn premature-terminate gate sees it dead (KILL … SEND[200] concludes in ONE
|
|
547
547
|
// turn — the model never reasons about async reap timing). The physical scope reap
|
|
548
|
-
// (drain abort + stream teardown) then rides
|
|
549
|
-
// because
|
|
550
|
-
await this.#db.
|
|
551
|
-
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);
|
|
552
552
|
return { status: 200 };
|
|
553
553
|
}
|
|
554
554
|
const handler = this.#schemes.get(schemeName);
|
|
@@ -589,7 +589,7 @@ class Dispatcher {
|
|
|
589
589
|
return { ...statement, target: target, lineMarker, body: null };
|
|
590
590
|
}
|
|
591
591
|
async #handleReadFanout(statement, ctx, ids) {
|
|
592
|
-
const {
|
|
592
|
+
const { workerId, loopId, turnId, sequence, origin, onDispatch } = ids;
|
|
593
593
|
const schemeName = schemeNameOf(statement.target);
|
|
594
594
|
const found = await this.#run(schemeName, { ...statement, op: "FIND" }, ctx);
|
|
595
595
|
const matches = found.matches ?? [];
|
|
@@ -597,7 +597,7 @@ class Dispatcher {
|
|
|
597
597
|
// status, exactly like a non-fanned READ. The model sees the empty/failed result, not silence.
|
|
598
598
|
if (found.status !== 200 || matches.length === 0) {
|
|
599
599
|
const result = { status: found.status === 200 ? 204 : found.status };
|
|
600
|
-
const id = await this.#writeLog({ statement, result,
|
|
600
|
+
const id = await this.#writeLog({ statement, result, workerId, loopId, turnId, sequence, origin });
|
|
601
601
|
onDispatch?.(id);
|
|
602
602
|
return { ...result, rowsWritten: 1 };
|
|
603
603
|
}
|
|
@@ -611,7 +611,7 @@ class Dispatcher {
|
|
|
611
611
|
// had FINDed then READ. On a degenerate single-line document the N delivery rows are
|
|
612
612
|
// identical whole-file lines; the summary row is what tells the model its query hit N
|
|
613
613
|
// times and WHERE (run30: two hits indistinguishable from failure; 17 retries, 508).
|
|
614
|
-
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 });
|
|
615
615
|
onDispatch?.(findRowId);
|
|
616
616
|
const wholeByPath = new Map();
|
|
617
617
|
const fannedStatuses = [];
|
|
@@ -623,7 +623,7 @@ class Dispatcher {
|
|
|
623
623
|
wholeByPath.set(m.pathname, whole);
|
|
624
624
|
}
|
|
625
625
|
const result = _a.#sliceMatch(whole, m.span);
|
|
626
|
-
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 });
|
|
627
627
|
onDispatch?.(id);
|
|
628
628
|
fannedStatuses.push(result.status);
|
|
629
629
|
written++;
|
|
@@ -645,9 +645,9 @@ class Dispatcher {
|
|
|
645
645
|
// the model can finally SEE its own prior output (and reason through its own syntax errors).
|
|
646
646
|
// Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
|
|
647
647
|
// (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
|
|
648
|
-
async writeModelEntry({ verbatim,
|
|
648
|
+
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model" }) {
|
|
649
649
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
650
|
-
|
|
650
|
+
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
|
|
651
651
|
origin, source: null, op: "model", suffix: "", signal: null,
|
|
652
652
|
scheme: null, username: null, password: null, hostname: null, port: null,
|
|
653
653
|
pathname: null, params: null, fragment: null, lineMarker: null,
|
|
@@ -754,41 +754,41 @@ class Dispatcher {
|
|
|
754
754
|
// pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
|
|
755
755
|
// until next packet). One rule, one steer, one repair family: nothing pending may be silently
|
|
756
756
|
// discarded; 499 discards BY STATED INTENT and is never gated.
|
|
757
|
-
async #pendingSet(
|
|
757
|
+
async #pendingSet(workerId, turnId) {
|
|
758
758
|
const pending = [];
|
|
759
|
-
const openSubs = await this.#db.
|
|
759
|
+
const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
|
|
760
760
|
const execHandler = this.#schemes.get("exec");
|
|
761
|
-
if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(
|
|
762
|
-
pending.push("
|
|
763
|
-
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 });
|
|
764
764
|
if (liveChild !== undefined)
|
|
765
|
-
pending.push("
|
|
765
|
+
pending.push("surviving workers");
|
|
766
766
|
const retrievals = await this.#db.engine_turn_retrievals.all({ turn_id: turnId });
|
|
767
767
|
if (retrievals.length > 0)
|
|
768
|
-
pending.push("
|
|
768
|
+
pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
|
|
769
769
|
// §send-undelivered-child-term — a worker that concluded DURING this turn's generation is no
|
|
770
770
|
// longer "live" but its deliverable hasn't reached any packet yet; concluding discards it.
|
|
771
|
-
const undelivered = await this.#db.
|
|
771
|
+
const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
|
|
772
772
|
if (undelivered !== undefined)
|
|
773
773
|
pending.push("worker results that arrived during this turn (they land NEXT turn)");
|
|
774
774
|
return pending;
|
|
775
775
|
}
|
|
776
776
|
// J — a live obligation to WAIT on: a spawned child or an open stream (NOT retrievals, which land
|
|
777
777
|
// next turn regardless). The wait-side twin of #pendingSet's stream+child legs (§wait-obligation-matrix).
|
|
778
|
-
async #hasLiveWork(
|
|
779
|
-
const openSubs = await this.#db.
|
|
778
|
+
async #hasLiveWork(workerId) {
|
|
779
|
+
const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
|
|
780
780
|
if (openSubs.length > 0)
|
|
781
781
|
return true;
|
|
782
782
|
const execHandler = this.#schemes.get("exec");
|
|
783
|
-
if (execHandler?.hasActiveSpawns?.(
|
|
783
|
+
if (execHandler?.hasActiveSpawns?.(workerId) === true)
|
|
784
784
|
return true;
|
|
785
|
-
const liveChild = await this.#db.
|
|
785
|
+
const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
|
|
786
786
|
return liveChild !== undefined;
|
|
787
787
|
}
|
|
788
788
|
async #handleSendBroadcast(statement, ctx) {
|
|
789
789
|
if (statement.op !== "SEND")
|
|
790
790
|
throw new Error("unreachable");
|
|
791
|
-
const {
|
|
791
|
+
const { workerId, loopId, turnId } = ctx;
|
|
792
792
|
const status = statement.signal;
|
|
793
793
|
if (status === null)
|
|
794
794
|
return { status: 400 };
|
|
@@ -799,7 +799,7 @@ class Dispatcher {
|
|
|
799
799
|
// indefinitely (the butler/worker pattern — owner-ruled ungated; the instrumentation renders
|
|
800
800
|
// it legibly). Internally the parked state remains loops.status=202: the model-facing SIGNAL
|
|
801
801
|
// retired, the engine's park state did not.
|
|
802
|
-
// §join-blocking-collect (#354) — a READ(
|
|
802
|
+
// §join-blocking-collect (#354) — a READ(worker://running-child) this turn armed a join. A BARE
|
|
803
803
|
// continue becomes an indefinite PARK: the blocking collect, on the same park machinery <-1>
|
|
804
804
|
// uses. The armed READ said "I want this result"; parking IS the collect (the model never had
|
|
805
805
|
// to know the park syntax — the engine holds the join). Any SEND clears the per-turn arm; a
|
|
@@ -813,13 +813,13 @@ class Dispatcher {
|
|
|
813
813
|
// §wait-obligation-matrix — the WAIT: SEND[202], and the legacy SEND[102]<T> the terminal
|
|
814
814
|
// redesign spelled the same park with, are one obligation-checked wait (waitpid). A live
|
|
815
815
|
// obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
|
|
816
|
-
// reawakens it (§
|
|
816
|
+
// reawakens it (§worker-lifecycle-child-wake); a wait on nothing (∅) is already satisfied and
|
|
817
817
|
// resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
|
|
818
818
|
// retrieval (R) just lands next turn, so the wait continues.
|
|
819
819
|
if (status === 202 || (status === 102 && statement.lineMarker !== null)) {
|
|
820
820
|
const marks = statement.lineMarker?.marks[0];
|
|
821
821
|
const seconds = typeof marks === "number" ? marks : -1; // bare 202 / absent T = indefinite, bounded by the join
|
|
822
|
-
if (await this.#hasLiveWork(
|
|
822
|
+
if (await this.#hasLiveWork(workerId)) {
|
|
823
823
|
await this.#db.engine_loop_set_status.run({ status: 202, loop_id: loopId, message: raw.length > 0 ? raw : "waiting on live work" });
|
|
824
824
|
this.#parkDeadlines.set(loopId, seconds);
|
|
825
825
|
return { status: 202, attrs: { waiting: seconds } };
|
|
@@ -832,26 +832,24 @@ class Dispatcher {
|
|
|
832
832
|
// NEXT build. The wait is NOT on nothing — the results are on the doorstep. Parking
|
|
833
833
|
// would hang (the wake edges already fired into an unparked run); R semantics: continue,
|
|
834
834
|
// the deltas land next turn, the model weighs them and concludes.
|
|
835
|
-
const undelivered = await this.#db.
|
|
835
|
+
const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
|
|
836
836
|
if (undelivered !== undefined)
|
|
837
837
|
return { status: 102 };
|
|
838
|
-
// ∅ — a wait on
|
|
839
|
-
// #
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
|
|
843
|
-
await this.#db.engine_loop_mark_terminated_by.run({ terminated_by: "collapse", loop_id: loopId });
|
|
844
|
-
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." };
|
|
845
843
|
}
|
|
846
844
|
// §send-300-choices — ask the operator and park (the answer returns via loop.inject).
|
|
847
845
|
// The three-state cascade (owner design): PLURNK_QUESTIONS unset = ALLOWED (not enabled);
|
|
848
846
|
// =0 = DENIED servicewide (a ceiling the client cannot override); ENABLED requires the
|
|
849
|
-
// client to affirmatively pass it per
|
|
850
|
-
// 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
|
|
851
849
|
// get the questions.md teaching injected (docEntries) — capability and teaching gate as one.
|
|
852
850
|
// Disabled → refused with a self-decide steer, never a park into the void.
|
|
853
851
|
if (status === 300) {
|
|
854
|
-
if (!(await
|
|
852
|
+
if (!(await WorkspaceSettings.questionsEnabled(this.#db, ctx.workspaceId))) {
|
|
855
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." };
|
|
856
854
|
}
|
|
857
855
|
// Owner ruling (#346): the question rides the SAME proposal system as file edits and
|
|
@@ -879,12 +877,12 @@ class Dispatcher {
|
|
|
879
877
|
if (failCount > 0) {
|
|
880
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).` };
|
|
881
879
|
}
|
|
882
|
-
const pending = await this.#pendingSet(
|
|
880
|
+
const pending = await this.#pendingSet(workerId, turnId);
|
|
883
881
|
if (pending.length > 0) {
|
|
884
882
|
// Kind-specific steer (owner wording, xpath/topo forensics): retrievals-only is
|
|
885
883
|
// gemma's read-and-conclude idiom — no lever to pull, the results simply arrive;
|
|
886
884
|
// KILL/park advice only muddies it. Streams/children keep the remedy steer.
|
|
887
|
-
const retrievalsOnly = pending.every((k) => k.startsWith("
|
|
885
|
+
const retrievalsOnly = pending.every((k) => k.startsWith("this turn's retrieval results"));
|
|
888
886
|
if (retrievalsOnly) {
|
|
889
887
|
// attrs.retrievalOnly — the strike decoupler (owner ruling): atomic-turn-
|
|
890
888
|
// pretrained models pair fetch-and-answer by habit; the refusal teaches,
|
|
@@ -897,7 +895,7 @@ class Dispatcher {
|
|
|
897
895
|
// that re-armed this gate four times while she held the correct answer).
|
|
898
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 } };
|
|
899
897
|
}
|
|
900
|
-
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.` };
|
|
901
899
|
}
|
|
902
900
|
await this.#db.engine_loop_set_status.run({ status: 200, loop_id: loopId, message: raw === "" ? null : raw });
|
|
903
901
|
return { status: 200 };
|
|
@@ -944,7 +942,7 @@ class Dispatcher {
|
|
|
944
942
|
return true;
|
|
945
943
|
return !(statement.op === "SEND" && statement.target === null);
|
|
946
944
|
}
|
|
947
|
-
async #writeLog({ statement, result,
|
|
945
|
+
async #writeLog({ statement, result, workerId, loopId, turnId, sequence, origin, }) {
|
|
948
946
|
const target = this.#extractTarget(statement.target);
|
|
949
947
|
const lineMarkerJson = "lineMarker" in statement && statement.lineMarker !== null
|
|
950
948
|
? JSON.stringify(statement.lineMarker)
|
|
@@ -977,7 +975,7 @@ class Dispatcher {
|
|
|
977
975
|
attrsObj.stream = `${runtime}://${coordPathname}`;
|
|
978
976
|
// Mutate the in-memory result.attrs too: the dispatch path
|
|
979
977
|
// hands originalResult.attrs to handler.applyResolution after
|
|
980
|
-
// proposal accept (see ProposalLifecycle.
|
|
978
|
+
// proposal accept (see ProposalLifecycle.workerApply). Both views —
|
|
981
979
|
// the stored row AND the in-memory proposal — need the same
|
|
982
980
|
// pathname so applyResolution writes the entry at the same URI.
|
|
983
981
|
if (result.attrs !== undefined && result.attrs !== null) {
|
|
@@ -988,7 +986,7 @@ class Dispatcher {
|
|
|
988
986
|
const txJson = JSON.stringify(statement);
|
|
989
987
|
const rxJson = JSON.stringify(result);
|
|
990
988
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
991
|
-
|
|
989
|
+
worker_id: workerId,
|
|
992
990
|
loop_id: loopId,
|
|
993
991
|
turn_id: turnId,
|
|
994
992
|
sequence: sequence,
|
|
@@ -1036,10 +1034,10 @@ class Dispatcher {
|
|
|
1036
1034
|
// Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
|
|
1037
1035
|
// it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
|
|
1038
1036
|
// the entry (/prompt/<run>/<loop>/<N>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
|
|
1039
|
-
// namespace: keep it in hostname.
|
|
1040
|
-
// 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
|
|
1041
1039
|
// folds the owner into the storage path itself, never here.
|
|
1042
|
-
const foldNs = scheme !== null && scheme !== "
|
|
1040
|
+
const foldNs = scheme !== null && scheme !== "worker" && this.#schemes.has(scheme);
|
|
1043
1041
|
return {
|
|
1044
1042
|
scheme, username: path.username, password: path.password,
|
|
1045
1043
|
hostname: foldNs ? null : path.hostname, port: path.port,
|