@plurnk/plurnk-service 1.0.5 → 1.0.7

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.
Files changed (201) hide show
  1. package/.env.defaults +66 -48
  2. package/INSTALL.md +3 -3
  3. package/README.md +4 -4
  4. package/SPEC.md +212 -196
  5. package/dist/Paths.js +1 -1
  6. package/dist/Paths.js.map +1 -1
  7. package/dist/core/ChannelWrite.d.ts +18 -18
  8. package/dist/core/ChannelWrite.d.ts.map +1 -1
  9. package/dist/core/ChannelWrite.js +17 -17
  10. package/dist/core/ChannelWrite.js.map +1 -1
  11. package/dist/core/ChannelWrite.sql +11 -11
  12. package/dist/core/Dispatcher.d.ts +15 -11
  13. package/dist/core/Dispatcher.d.ts.map +1 -1
  14. package/dist/core/Dispatcher.js +124 -125
  15. package/dist/core/Dispatcher.js.map +1 -1
  16. package/dist/core/Engine.d.ts +20 -23
  17. package/dist/core/Engine.d.ts.map +1 -1
  18. package/dist/core/Engine.js +210 -185
  19. package/dist/core/Engine.js.map +1 -1
  20. package/dist/core/Engine.sql +106 -109
  21. package/dist/core/PacketBuilder.d.ts +9 -9
  22. package/dist/core/PacketBuilder.d.ts.map +1 -1
  23. package/dist/core/PacketBuilder.js +199 -151
  24. package/dist/core/PacketBuilder.js.map +1 -1
  25. package/dist/core/ProposalLifecycle.d.ts +8 -8
  26. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  27. package/dist/core/ProposalLifecycle.js +10 -10
  28. package/dist/core/ProposalLifecycle.js.map +1 -1
  29. package/dist/core/ProviderInstantiate.d.ts +1 -0
  30. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  31. package/dist/core/ProviderInstantiate.js +9 -3
  32. package/dist/core/ProviderInstantiate.js.map +1 -1
  33. package/dist/core/SchemeRegistry.d.ts +4 -1
  34. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  35. package/dist/core/SchemeRegistry.js +18 -9
  36. package/dist/core/SchemeRegistry.js.map +1 -1
  37. package/dist/core/TelemetryChannel.d.ts +5 -5
  38. package/dist/core/TelemetryChannel.d.ts.map +1 -1
  39. package/dist/core/TelemetryChannel.js +9 -9
  40. package/dist/core/TelemetryChannel.js.map +1 -1
  41. package/dist/core/caps/CapsResolve.js +2 -2
  42. package/dist/core/caps/CapsResolve.js.map +1 -1
  43. package/dist/core/caps/DbNotifyCaps.js +2 -2
  44. package/dist/core/caps/DbNotifyCaps.js.map +1 -1
  45. package/dist/core/caps/DbSubscriptionCaps.js +7 -7
  46. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  47. package/dist/core/caps/SchemeCtxImpl.d.ts +2 -2
  48. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  49. package/dist/core/caps/SchemeCtxImpl.js +4 -4
  50. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  51. package/dist/core/fork.d.ts +1 -1
  52. package/dist/core/fork.d.ts.map +1 -1
  53. package/dist/core/fork.js +31 -26
  54. package/dist/core/fork.js.map +1 -1
  55. package/dist/core/fork.sql +41 -32
  56. package/dist/core/git-env.d.ts.map +1 -1
  57. package/dist/core/git-env.js +23 -7
  58. package/dist/core/git-env.js.map +1 -1
  59. package/dist/core/git-iso.d.ts +9 -0
  60. package/dist/core/git-iso.d.ts.map +1 -0
  61. package/dist/core/git-iso.js +227 -0
  62. package/dist/core/git-iso.js.map +1 -0
  63. package/dist/core/git-membership.d.ts +2 -2
  64. package/dist/core/git-membership.d.ts.map +1 -1
  65. package/dist/core/git-membership.js +70 -50
  66. package/dist/core/git-membership.js.map +1 -1
  67. package/dist/core/git-state.d.ts +1 -1
  68. package/dist/core/git-state.d.ts.map +1 -1
  69. package/dist/core/git-state.js +21 -11
  70. package/dist/core/git-state.js.map +1 -1
  71. package/dist/core/packet-wire.d.ts.map +1 -1
  72. package/dist/core/packet-wire.js +81 -22
  73. package/dist/core/packet-wire.js.map +1 -1
  74. package/dist/core/plurnk-uri.d.ts +2 -2
  75. package/dist/core/plurnk-uri.d.ts.map +1 -1
  76. package/dist/core/plurnk-uri.js +19 -12
  77. package/dist/core/plurnk-uri.js.map +1 -1
  78. package/dist/core/run-ops.sql +26 -26
  79. package/dist/core/scheme-types.d.ts +6 -6
  80. package/dist/core/scheme-types.d.ts.map +1 -1
  81. package/dist/core/search-gate.js +0 -0
  82. package/dist/core/search-gate.js.map +1 -1
  83. package/dist/core/token-ruler.js +2 -2
  84. package/dist/core/token-ruler.js.map +1 -1
  85. package/dist/core/worker-cap.d.ts +8 -0
  86. package/dist/core/worker-cap.d.ts.map +1 -0
  87. package/dist/core/worker-cap.js +20 -0
  88. package/dist/core/worker-cap.js.map +1 -0
  89. package/dist/core/{session-settings.d.ts → workspace-settings.d.ts} +5 -5
  90. package/dist/core/workspace-settings.d.ts.map +1 -0
  91. package/dist/core/{session-settings.js → workspace-settings.js} +13 -13
  92. package/dist/core/workspace-settings.js.map +1 -0
  93. package/dist/digest/Digest.d.ts +3 -3
  94. package/dist/digest/Digest.d.ts.map +1 -1
  95. package/dist/digest/Digest.js +83 -73
  96. package/dist/digest/Digest.js.map +1 -1
  97. package/dist/digest/digest.sql +18 -18
  98. package/dist/schemes/Exec.d.ts +9 -2
  99. package/dist/schemes/Exec.d.ts.map +1 -1
  100. package/dist/schemes/Exec.js +124 -68
  101. package/dist/schemes/Exec.js.map +1 -1
  102. package/dist/schemes/ExecOutputScheme.js +3 -3
  103. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  104. package/dist/schemes/File.js +23 -23
  105. package/dist/schemes/File.js.map +1 -1
  106. package/dist/schemes/Known.js +5 -5
  107. package/dist/schemes/Known.js.map +1 -1
  108. package/dist/schemes/Log.d.ts.map +1 -1
  109. package/dist/schemes/Log.js +63 -14
  110. package/dist/schemes/Log.js.map +1 -1
  111. package/dist/schemes/Log.sql +49 -8
  112. package/dist/schemes/Plurnk.js +5 -5
  113. package/dist/schemes/Plurnk.js.map +1 -1
  114. package/dist/schemes/Skill.js +5 -5
  115. package/dist/schemes/Skill.js.map +1 -1
  116. package/dist/schemes/Unknown.js +5 -5
  117. package/dist/schemes/Unknown.js.map +1 -1
  118. package/dist/schemes/{Run.d.ts → Worker.d.ts} +2 -2
  119. package/dist/schemes/Worker.d.ts.map +1 -0
  120. package/dist/schemes/{Run.js → Worker.js} +67 -67
  121. package/dist/schemes/Worker.js.map +1 -0
  122. package/dist/schemes/_entry-crud.js +8 -8
  123. package/dist/schemes/_entry-crud.js.map +1 -1
  124. package/dist/schemes/_entry-crud.sql +29 -29
  125. package/dist/schemes/_entry-find.d.ts +2 -1
  126. package/dist/schemes/_entry-find.d.ts.map +1 -1
  127. package/dist/schemes/_entry-find.js +31 -19
  128. package/dist/schemes/_entry-find.js.map +1 -1
  129. package/dist/schemes/_entry-find.sql +9 -9
  130. package/dist/schemes/_entry-graph.d.ts +2 -2
  131. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  132. package/dist/schemes/_entry-graph.js +18 -18
  133. package/dist/schemes/_entry-graph.js.map +1 -1
  134. package/dist/schemes/_entry-graph.sql +8 -8
  135. package/dist/schemes/_entry-manifest.d.ts +1 -1
  136. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  137. package/dist/schemes/_entry-manifest.js +72 -28
  138. package/dist/schemes/_entry-manifest.js.map +1 -1
  139. package/dist/schemes/_entry-ops.d.ts +4 -4
  140. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  141. package/dist/schemes/_entry-ops.js +25 -25
  142. package/dist/schemes/_entry-ops.js.map +1 -1
  143. package/dist/schemes/_entry-ops.sql +9 -9
  144. package/dist/schemes/_entry-semantic.d.ts +2 -1
  145. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  146. package/dist/schemes/_entry-semantic.js +23 -4
  147. package/dist/schemes/_entry-semantic.js.map +1 -1
  148. package/dist/schemes/_entry-semantic.sql +18 -4
  149. package/dist/schemes/_entry-send.d.ts +1 -1
  150. package/dist/schemes/_entry-send.d.ts.map +1 -1
  151. package/dist/schemes/_entry-send.js +6 -6
  152. package/dist/schemes/_entry-send.js.map +1 -1
  153. package/dist/schemes/exec-abort.js +1 -1
  154. package/dist/schemes/exec-abort.js.map +1 -1
  155. package/dist/server/Daemon.d.ts +48 -48
  156. package/dist/server/Daemon.d.ts.map +1 -1
  157. package/dist/server/Daemon.js +295 -292
  158. package/dist/server/Daemon.js.map +1 -1
  159. package/dist/server/client-input.js +20 -20
  160. package/dist/server/client-input.js.map +1 -1
  161. package/dist/server/dispatch-as-plurnk.d.ts +1 -1
  162. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  163. package/dist/server/dispatch-as-plurnk.js +7 -7
  164. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  165. package/dist/server/drain.sql +27 -27
  166. package/dist/server/envelope.d.ts +20 -20
  167. package/dist/server/envelope.d.ts.map +1 -1
  168. package/dist/server/envelope.js +86 -86
  169. package/dist/server/envelope.js.map +1 -1
  170. package/dist/server/envelope.sql +39 -39
  171. package/dist/server/logEntry.d.ts +1 -1
  172. package/dist/server/logEntry.d.ts.map +1 -1
  173. package/dist/server/logEntry.js +1 -1
  174. package/dist/server/logEntry.js.map +1 -1
  175. package/dist/server/loopDocs.d.ts +1 -1
  176. package/dist/server/loopDocs.d.ts.map +1 -1
  177. package/dist/server/loopDocs.js +6 -6
  178. package/dist/server/loopDocs.js.map +1 -1
  179. package/dist/server/seam-entry-read.sql +2 -2
  180. package/dist/server/seam-log-read.sql +3 -3
  181. package/dist/server/seam-loop.sql +3 -3
  182. package/dist/server/seam-proposal-list.sql +4 -4
  183. package/dist/server/yolo.js +2 -2
  184. package/dist/server/yolo.js.map +1 -1
  185. package/dist/service.d.ts.map +1 -1
  186. package/dist/service.js +22 -7
  187. package/dist/service.js.map +1 -1
  188. package/migrations/0000-00-00.01_schema.sql +82 -70
  189. package/package.json +18 -15
  190. package/dist/core/run-cap.d.ts +0 -8
  191. package/dist/core/run-cap.d.ts.map +0 -1
  192. package/dist/core/run-cap.js +0 -20
  193. package/dist/core/run-cap.js.map +0 -1
  194. package/dist/core/search-prefetch.d.ts +0 -17
  195. package/dist/core/search-prefetch.d.ts.map +0 -1
  196. package/dist/core/search-prefetch.js +0 -164
  197. package/dist/core/search-prefetch.js.map +0 -1
  198. package/dist/core/session-settings.d.ts.map +0 -1
  199. package/dist/core/session-settings.js.map +0 -1
  200. package/dist/schemes/Run.d.ts.map +0 -1
  201. package/dist/schemes/Run.js.map +0 -1
@@ -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 RunCap from "./run-cap.js";
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 SessionSettings from "./session-settings.js";
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
- #wakeRunNotify;
36
- #injectRun;
37
- #cancelRun;
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(run://running-child) armed this turn; a bare
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, wakeRunNotify, injectRun, cancelRun, searchGate, parkDeadlines, joinTargets }) {
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.#wakeRunNotify = wakeRunNotify;
56
- this.#injectRun = injectRun;
57
- this.#cancelRun = cancelRun;
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, sessionId, runId, loopId, turnId, sequence, origin, onDispatch, turnParseErrors } = context;
64
- const schemeCtx = this.#buildSchemeCtx({ sessionId, runId, loopId, turnId, origin });
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, { runId, loopId, turnId, sequence, origin, onDispatch });
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, { sessionId, runId, loopId, turnId, turnParseErrors });
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.#handleRunControl(statement, schemeCtx);
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 awaitRun signal;
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.awaitRun === "string")
127
+ if (typeof result.awaitWorker === "string")
128
128
  this.#joinTargets.add(loopId);
129
- const logEntryId = await this.#writeLog({ statement, result, runId, loopId, turnId, sequence, origin });
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.runApply(statement, result, { decision: "accept" }, { sessionId, runId, loopId, turnId });
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({ run_id: runId, turn_id: turnId, scheme: target.scheme, pathname: target.pathname });
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, sessionId, runId, loopId, turnId,
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.runApply(statement, result, resolution, { sessionId, runId, loopId, turnId });
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, sessionId, runId, loopId, origin = "client" } = context;
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({ sessionId, runId, loopId, turnId: 0, origin });
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 { sessionId, runId, loopId, turnId, origin } = ids;
223
+ const { workspaceId, workerId, loopId, turnId, origin } = ids;
224
224
  return {
225
225
  db: this.#db,
226
- sessionId, runId, loopId, turnId,
226
+ workspaceId, workerId, loopId, turnId,
227
227
  writer: origin,
228
228
  signal: this.#loopSignal(loopId),
229
229
  streamEventNotify: this.#streamEventNotify,
230
- wakeRunNotify: this.#wakeRunNotify,
231
- injectRun: this.#injectRun,
230
+ wakeWorkerNotify: this.#wakeWorkerNotify,
231
+ injectWorker: this.#injectWorker,
232
232
  mimetypes: this.#mimetypes,
233
233
  tokenize: this.#tokenize,
234
- pushTelemetry: (event) => this.#telemetry.push(sessionId, loopId, event),
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 → run://<name>, spawn or fork) is gated by run://'s writableBy — its
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.#isRunControl(statement))
275
- return this.#denyIfDisallowed("run", origin);
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}) — act on the results you already hold; further searches continue next turn.` };
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.#isRunControl(statement))
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 run's seed prompt, not a destination path. The COPY gates and #handleCopy
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
- #isRunControl(statement) {
394
- return statement.op === "FORK" || statement.op === "WORK"; // run control targets run://<name> (grammar 0.74.55)
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(run://<name>):task — run control (grammar 0.74.55):
397
- // • run://self → FORK: deep-copy the current run's log into a new sister (Fork), then
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
- // • run://<name> → SPAWN: a fresh sister (empty log) named <name>, started on the prompt.
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 (§run-scheme-spawn). The self form is fork; only a name spawns.
402
- // Both ride the daemon inject and obey the active-runs cap (508, §run-scheme-cap).
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
- // (run://<name>) and carry its seed task in the body. WORK spawns a fresh worker; FORK branches
405
- // the current run's log into a named sister. Replaces the COPY(run://) overload — one verb, one
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 #handleRunControl(statement, ctx) {
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 run target (${statement.op}(run://<name>))` };
411
- const name = target.kind === "url" ? (target.hostname ?? "") : ""; // §run-scheme — run is the AUTHORITY (run://<name>), not the path
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 run name (run://<name>)` };
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 (run://<name>)` };
416
- if (ctx.injectRun === undefined)
417
- throw new Error("run control: injectRun capability absent");
418
- const denied = await RunCap.deny(this.#db, ctx.sessionId);
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
- // §run-delegation-inherits-flags — authority flows down the delegation edge: the child's live
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 run but reclaimable across time (§machine-processes-run-origin): a LIVE
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.run_live_by_name.get({ session_id: ctx.sessionId, name });
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: `run '${name}' is already running` };
430
+ return { status: 409, error: `worker '${name}' is already running` };
431
431
  if (statement.op === "FORK") {
432
- // Branch the current run's log into a named sister.
433
- const branchRunId = await Fork.fork(this.#db, ctx.runId, name);
434
- await ctx.injectRun({ sessionId: ctx.sessionId, runId: branchRunId, prompt, flags });
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.fork_insert_run.get({
439
- session_id: ctx.sessionId, name, parent_run_id: ctx.runId, origin: ctx.writer,
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.injectRun({ sessionId: ctx.sessionId, runId: row.id, prompt, flags });
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 === "run") {
522
- // Entry-path present → KILL a run-scope scratch ENTRY (delete it), self-only —
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 (run://<name>) terminates the run-as-actor. §run-scheme
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 runHandler = this.#schemes.get("run");
528
- return await runHandler.deleteEntry(statement, ctx);
527
+ const workerHandler = this.#schemes.get("worker");
528
+ return await workerHandler.deleteEntry(statement, ctx);
529
529
  }
530
- // terminate — abort any run by address; whoever holds it may end it.
531
- // `run://self` = self. cancelRun (→ Daemon.cancelDrain) aborts the run's signal
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 ?? "") : ""; // §run-scheme — run is the AUTHORITY
533
+ const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §worker-scheme — run is the AUTHORITY
534
534
  if (name === "")
535
- return { status: 400, error: "run:// kill requires a run name or 'self' (run://<name>)" };
536
- let runId = ctx.runId;
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.run_resolve_by_name.get({ session_id: ctx.sessionId, name });
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: `run://${name} not found in this session` };
541
- runId = row.id;
540
+ return { status: 404, error: `worker://${name} not found in this workspace` };
541
+ workerId = row.id;
542
542
  }
543
- if (this.#cancelRun === undefined)
544
- throw new Error("run kill: cancelRun capability absent");
545
- // §op-synchronous — KILL is DECISIVE: flip the run's live loops to 499 NOW so 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 cancelRun; a killed loop can't heal back
549
- // because cancelRun aborts its drain's signal.
550
- await this.#db.engine_terminate_run_live_loops.run({ run_id: runId, message: "killed via run:// KILL" });
551
- this.#cancelRun(runId);
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 { runId, loopId, turnId, sequence, origin, onDispatch } = ids;
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, runId, loopId, turnId, sequence, origin });
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, runId, loopId, turnId, sequence, origin });
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, runId, loopId, turnId, sequence: sequence + written, origin });
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,16 +645,17 @@ 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, runId, loopId, turnId, sequence, folded, origin = "model" }) {
648
+ async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model", reasoningEncrypted }) {
649
649
  const row = await this.#db.engine_insert_log_entry.get({
650
- run_id: runId, loop_id: loopId, turn_id: turnId, sequence,
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,
654
654
  tx: "", mimetype_tx: "text/vnd.plurnk",
655
655
  rx: JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" }),
656
656
  mimetype_rx: "application/json",
657
- status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null, attrs: "{}",
657
+ status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
658
+ attrs: reasoningEncrypted !== undefined && reasoningEncrypted.length > 0 ? JSON.stringify({ reasoningEncrypted }) : "{}",
658
659
  });
659
660
  if (row === undefined)
660
661
  throw new Error("Dispatcher.writeModelEntry: insert returned no row");
@@ -754,41 +755,41 @@ class Dispatcher {
754
755
  // pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
755
756
  // until next packet). One rule, one steer, one repair family: nothing pending may be silently
756
757
  // discarded; 499 discards BY STATED INTENT and is never gated.
757
- async #pendingSet(runId, turnId) {
758
+ async #pendingSet(workerId, turnId) {
758
759
  const pending = [];
759
- const openSubs = await this.#db.find_open_subscriptions_for_run.all({ run_id: runId });
760
+ const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
760
761
  const execHandler = this.#schemes.get("exec");
761
- if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(runId) === true)
762
- pending.push("open streams");
763
- const liveChild = await this.#db.engine_run_has_live_child.get({ run_id: runId });
762
+ if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(workerId) === true)
763
+ pending.push("surviving streams");
764
+ const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
764
765
  if (liveChild !== undefined)
765
- pending.push("live worker runs");
766
+ pending.push("surviving workers");
766
767
  const retrievals = await this.#db.engine_turn_retrievals.all({ turn_id: turnId });
767
768
  if (retrievals.length > 0)
768
- pending.push("results of this turn's READ/FIND/OPEN (they arrive NEXT turn)");
769
+ pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
769
770
  // §send-undelivered-child-term — a worker that concluded DURING this turn's generation is no
770
771
  // longer "live" but its deliverable hasn't reached any packet yet; concluding discards it.
771
- const undelivered = await this.#db.engine_run_has_undelivered_child_term.get({ run_id: runId, turn_id: turnId });
772
+ const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
772
773
  if (undelivered !== undefined)
773
774
  pending.push("worker results that arrived during this turn (they land NEXT turn)");
774
775
  return pending;
775
776
  }
776
777
  // J — a live obligation to WAIT on: a spawned child or an open stream (NOT retrievals, which land
777
778
  // next turn regardless). The wait-side twin of #pendingSet's stream+child legs (§wait-obligation-matrix).
778
- async #hasLiveWork(runId) {
779
- const openSubs = await this.#db.find_open_subscriptions_for_run.all({ run_id: runId });
779
+ async #hasLiveWork(workerId) {
780
+ const openSubs = await this.#db.find_open_subscriptions_for_worker.all({ worker_id: workerId });
780
781
  if (openSubs.length > 0)
781
782
  return true;
782
783
  const execHandler = this.#schemes.get("exec");
783
- if (execHandler?.hasActiveSpawns?.(runId) === true)
784
+ if (execHandler?.hasActiveSpawns?.(workerId) === true)
784
785
  return true;
785
- const liveChild = await this.#db.engine_run_has_live_child.get({ run_id: runId });
786
+ const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
786
787
  return liveChild !== undefined;
787
788
  }
788
789
  async #handleSendBroadcast(statement, ctx) {
789
790
  if (statement.op !== "SEND")
790
791
  throw new Error("unreachable");
791
- const { runId, loopId, turnId } = ctx;
792
+ const { workerId, loopId, turnId } = ctx;
792
793
  const status = statement.signal;
793
794
  if (status === null)
794
795
  return { status: 400 };
@@ -799,7 +800,7 @@ class Dispatcher {
799
800
  // indefinitely (the butler/worker pattern — owner-ruled ungated; the instrumentation renders
800
801
  // it legibly). Internally the parked state remains loops.status=202: the model-facing SIGNAL
801
802
  // retired, the engine's park state did not.
802
- // §join-blocking-collect (#354) — a READ(run://running-child) this turn armed a join. A BARE
803
+ // §join-blocking-collect (#354) — a READ(worker://running-child) this turn armed a join. A BARE
803
804
  // continue becomes an indefinite PARK: the blocking collect, on the same park machinery <-1>
804
805
  // uses. The armed READ said "I want this result"; parking IS the collect (the model never had
805
806
  // to know the park syntax — the engine holds the join). Any SEND clears the per-turn arm; a
@@ -813,13 +814,13 @@ class Dispatcher {
813
814
  // §wait-obligation-matrix — the WAIT: SEND[202], and the legacy SEND[102]<T> the terminal
814
815
  // redesign spelled the same park with, are one obligation-checked wait (waitpid). A live
815
816
  // obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
816
- // reawakens it (§run-lifecycle-child-wake); a wait on nothing (∅) is already satisfied and
817
+ // reawakens it (§worker-lifecycle-child-wake); a wait on nothing (∅) is already satisfied and
817
818
  // resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
818
819
  // retrieval (R) just lands next turn, so the wait continues.
819
820
  if (status === 202 || (status === 102 && statement.lineMarker !== null)) {
820
821
  const marks = statement.lineMarker?.marks[0];
821
822
  const seconds = typeof marks === "number" ? marks : -1; // bare 202 / absent T = indefinite, bounded by the join
822
- if (await this.#hasLiveWork(runId)) {
823
+ if (await this.#hasLiveWork(workerId)) {
823
824
  await this.#db.engine_loop_set_status.run({ status: 202, loop_id: loopId, message: raw.length > 0 ? raw : "waiting on live work" });
824
825
  this.#parkDeadlines.set(loopId, seconds);
825
826
  return { status: 202, attrs: { waiting: seconds } };
@@ -832,26 +833,24 @@ class Dispatcher {
832
833
  // NEXT build. The wait is NOT on nothing — the results are on the doorstep. Parking
833
834
  // would hang (the wake edges already fired into an unparked run); R semantics: continue,
834
835
  // the deltas land next turn, the model weighs them and concludes.
835
- const undelivered = await this.#db.engine_run_has_undelivered_child_term.get({ run_id: runId, turn_id: turnId });
836
+ const undelivered = await this.#db.engine_worker_has_undelivered_child_term.get({ worker_id: workerId, turn_id: turnId });
836
837
  if (undelivered !== undefined)
837
838
  return { status: 102 };
838
- // ∅ — a wait on zero obligations is already satisfied; conclude like 200 (incl. <-1>+∅).
839
- // #379 (owner ruling): the ENGINE concluded this loop, not the model — mark it as state
840
- // so the COLLECT/delta render the act instead of presenting the park text as a result.
841
- // The model's words (terminal_message) are never rewritten.
842
- await this.#db.engine_loop_set_status.run({ status: 200, loop_id: loopId, message: raw === "" ? null : raw });
843
- await this.#db.engine_loop_mark_terminated_by.run({ terminated_by: "collapse", loop_id: loopId });
844
- return { status: 200 };
839
+ // ∅ — a wait on nothing is a contradiction, the mirror of premature-terminate (owner
840
+ // ruling, #502 run113): concluding it silently laundered the one signal that something
841
+ // was structurally off (the model believed work was pending; nothing was). 409 gives
842
+ // the turn back with the fact — the recovery rail this exact confusion needs.
843
+ return { status: 409, error: "Attempted [202] wait with nothing pending: no surviving streams, no worker runs, no unretrieved results." };
845
844
  }
846
845
  // §send-300-choices — ask the operator and park (the answer returns via loop.inject).
847
846
  // The three-state cascade (owner design): PLURNK_QUESTIONS unset = ALLOWED (not enabled);
848
847
  // =0 = DENIED servicewide (a ceiling the client cannot override); ENABLED requires the
849
- // client to affirmatively pass it per session (settings.questions — the interactive client
850
- // with a human enables its own sessions; headless/bench never asks). Enabled sessions ALSO
848
+ // client to affirmatively pass it per workspace (settings.questions — the interactive client
849
+ // with a human enables its own workspaces; headless/bench never asks). Enabled workspaces ALSO
851
850
  // get the questions.md teaching injected (docEntries) — capability and teaching gate as one.
852
851
  // Disabled → refused with a self-decide steer, never a park into the void.
853
852
  if (status === 300) {
854
- if (!(await SessionSettings.questionsEnabled(this.#db, ctx.sessionId))) {
853
+ if (!(await WorkspaceSettings.questionsEnabled(this.#db, ctx.workspaceId))) {
855
854
  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
855
  }
857
856
  // Owner ruling (#346): the question rides the SAME proposal system as file edits and
@@ -879,12 +878,12 @@ class Dispatcher {
879
878
  if (failCount > 0) {
880
879
  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
880
  }
882
- const pending = await this.#pendingSet(runId, turnId);
881
+ const pending = await this.#pendingSet(workerId, turnId);
883
882
  if (pending.length > 0) {
884
883
  // Kind-specific steer (owner wording, xpath/topo forensics): retrievals-only is
885
884
  // gemma's read-and-conclude idiom — no lever to pull, the results simply arrive;
886
885
  // KILL/park advice only muddies it. Streams/children keep the remedy steer.
887
- const retrievalsOnly = pending.every((k) => k.startsWith("results of this turn's"));
886
+ const retrievalsOnly = pending.every((k) => k.startsWith("this turn's retrieval results"));
888
887
  if (retrievalsOnly) {
889
888
  // attrs.retrievalOnly — the strike decoupler (owner ruling): atomic-turn-
890
889
  // pretrained models pair fetch-and-answer by habit; the refusal teaches,
@@ -897,7 +896,7 @@ class Dispatcher {
897
896
  // that re-armed this gate four times while she held the correct answer).
898
897
  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
898
  }
900
- return { status: 409, error: `Attempted [200] termination with pending work: ${pending.join("; ")}. KILL what you no longer need; SEND[102] (or [102]<seconds>) to receive the rest; then conclude.` };
899
+ 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
900
  }
902
901
  await this.#db.engine_loop_set_status.run({ status: 200, loop_id: loopId, message: raw === "" ? null : raw });
903
902
  return { status: 200 };
@@ -944,7 +943,7 @@ class Dispatcher {
944
943
  return true;
945
944
  return !(statement.op === "SEND" && statement.target === null);
946
945
  }
947
- async #writeLog({ statement, result, runId, loopId, turnId, sequence, origin, }) {
946
+ async #writeLog({ statement, result, workerId, loopId, turnId, sequence, origin, }) {
948
947
  const target = this.#extractTarget(statement.target);
949
948
  const lineMarkerJson = "lineMarker" in statement && statement.lineMarker !== null
950
949
  ? JSON.stringify(statement.lineMarker)
@@ -977,7 +976,7 @@ class Dispatcher {
977
976
  attrsObj.stream = `${runtime}://${coordPathname}`;
978
977
  // Mutate the in-memory result.attrs too: the dispatch path
979
978
  // hands originalResult.attrs to handler.applyResolution after
980
- // proposal accept (see ProposalLifecycle.runApply). Both views —
979
+ // proposal accept (see ProposalLifecycle.workerApply). Both views —
981
980
  // the stored row AND the in-memory proposal — need the same
982
981
  // pathname so applyResolution writes the entry at the same URI.
983
982
  if (result.attrs !== undefined && result.attrs !== null) {
@@ -988,7 +987,7 @@ class Dispatcher {
988
987
  const txJson = JSON.stringify(statement);
989
988
  const rxJson = JSON.stringify(result);
990
989
  const row = await this.#db.engine_insert_log_entry.get({
991
- run_id: runId,
990
+ worker_id: workerId,
992
991
  loop_id: loopId,
993
992
  turn_id: turnId,
994
993
  sequence: sequence,
@@ -1036,10 +1035,10 @@ class Dispatcher {
1036
1035
  // Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
1037
1036
  // it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
1038
1037
  // the entry (/prompt/<run>/<loop>/<N>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
1039
- // namespace: keep it in hostname. run:// is the one registered EXCEPTION — its authority IS the
1040
- // run selector (§run-scheme), and run://self must stay distinct from run://name, so Run.ts
1038
+ // namespace: keep it in hostname. worker:// is the one registered EXCEPTION — its authority IS the
1039
+ // run selector (§worker-scheme), and worker://self must stay distinct from worker://name, so Run.ts
1041
1040
  // folds the owner into the storage path itself, never here.
1042
- const foldNs = scheme !== null && scheme !== "run" && this.#schemes.has(scheme);
1041
+ const foldNs = scheme !== null && scheme !== "worker" && this.#schemes.has(scheme);
1043
1042
  return {
1044
1043
  scheme, username: path.username, password: path.password,
1045
1044
  hostname: foldNs ? null : path.hostname, port: path.port,