@plurnk/plurnk-service 1.21.1 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/.env.defaults +16 -3
  2. package/INSTALL.md +2 -2
  3. package/README.md +1 -1
  4. package/SPEC.md +519 -213
  5. package/dist/build-info.json +1 -1
  6. package/dist/content/body-preview.js +1 -1
  7. package/dist/content/body-preview.js.map +1 -1
  8. package/dist/content/edit-receipt.d.ts.map +1 -1
  9. package/dist/content/edit-receipt.js +7 -13
  10. package/dist/content/edit-receipt.js.map +1 -1
  11. package/dist/content/index.d.ts +2 -7
  12. package/dist/content/index.d.ts.map +1 -1
  13. package/dist/content/index.js +2 -5
  14. package/dist/content/index.js.map +1 -1
  15. package/dist/content/line-anchors.d.ts.map +1 -1
  16. package/dist/content/line-anchors.js +7 -9
  17. package/dist/content/line-anchors.js.map +1 -1
  18. package/dist/content/line-marker.d.ts +1 -0
  19. package/dist/content/line-marker.d.ts.map +1 -1
  20. package/dist/content/line-marker.js +2 -0
  21. package/dist/content/line-marker.js.map +1 -1
  22. package/dist/content/matcher.d.ts +1 -1
  23. package/dist/content/matcher.d.ts.map +1 -1
  24. package/dist/content/matcher.js +8 -10
  25. package/dist/content/matcher.js.map +1 -1
  26. package/dist/content/pattern-edits.d.ts +6 -0
  27. package/dist/content/pattern-edits.d.ts.map +1 -1
  28. package/dist/content/pattern-edits.js +14 -0
  29. package/dist/content/pattern-edits.js.map +1 -1
  30. package/dist/content/read-projector.d.ts.map +1 -1
  31. package/dist/content/read-projector.js +2 -1
  32. package/dist/content/read-projector.js.map +1 -1
  33. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  34. package/dist/core/AdmittedTurnExecutor.js +7 -2
  35. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  36. package/dist/core/BudgetReadout.js +1 -1
  37. package/dist/core/BudgetReadout.js.map +1 -1
  38. package/dist/core/DataStatementRunner.js +1 -1
  39. package/dist/core/DataStatementRunner.js.map +1 -1
  40. package/dist/core/Dispatcher.d.ts +3 -8
  41. package/dist/core/Dispatcher.d.ts.map +1 -1
  42. package/dist/core/Dispatcher.js +9 -16
  43. package/dist/core/Dispatcher.js.map +1 -1
  44. package/dist/core/Dispatcher.sql +1 -1
  45. package/dist/core/EditMutations.js +6 -6
  46. package/dist/core/EditMutations.js.map +1 -1
  47. package/dist/core/EditSequence.d.ts.map +1 -1
  48. package/dist/core/EditSequence.js +2 -1
  49. package/dist/core/EditSequence.js.map +1 -1
  50. package/dist/core/Engine.d.ts +9 -17
  51. package/dist/core/Engine.d.ts.map +1 -1
  52. package/dist/core/Engine.js +27 -19
  53. package/dist/core/Engine.js.map +1 -1
  54. package/dist/core/Engine.sql +22 -0
  55. package/dist/core/ErrorDetail.d.ts +3 -4
  56. package/dist/core/ErrorDetail.d.ts.map +1 -1
  57. package/dist/core/ErrorDetail.js +3 -18
  58. package/dist/core/ErrorDetail.js.map +1 -1
  59. package/dist/core/ExecutorRegistry.d.ts +1 -0
  60. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  61. package/dist/core/ExecutorRegistry.js +5 -1
  62. package/dist/core/ExecutorRegistry.js.map +1 -1
  63. package/dist/core/FabricatedLog.d.ts +8 -0
  64. package/dist/core/FabricatedLog.d.ts.map +1 -0
  65. package/dist/core/FabricatedLog.js +16 -0
  66. package/dist/core/FabricatedLog.js.map +1 -0
  67. package/dist/core/HostPaths.d.ts +1 -0
  68. package/dist/core/HostPaths.d.ts.map +1 -1
  69. package/dist/core/HostPaths.js +39 -13
  70. package/dist/core/HostPaths.js.map +1 -1
  71. package/dist/core/KnownToxins.d.ts +0 -1
  72. package/dist/core/KnownToxins.d.ts.map +1 -1
  73. package/dist/core/KnownToxins.js +3 -12
  74. package/dist/core/KnownToxins.js.map +1 -1
  75. package/dist/core/LogBody.d.ts.map +1 -1
  76. package/dist/core/LogBody.js +12 -10
  77. package/dist/core/LogBody.js.map +1 -1
  78. package/dist/core/LogVisibility.d.ts +3 -1
  79. package/dist/core/LogVisibility.d.ts.map +1 -1
  80. package/dist/core/LogVisibility.js +30 -16
  81. package/dist/core/LogVisibility.js.map +1 -1
  82. package/dist/core/LoopDriver.d.ts.map +1 -1
  83. package/dist/core/LoopDriver.js +3 -4
  84. package/dist/core/LoopDriver.js.map +1 -1
  85. package/dist/core/LoopOutcome.d.ts +0 -1
  86. package/dist/core/LoopOutcome.d.ts.map +1 -1
  87. package/dist/core/LoopOutcome.js +1 -1
  88. package/dist/core/LoopOutcome.js.map +1 -1
  89. package/dist/core/LoopPolicies.js +1 -1
  90. package/dist/core/LoopPolicies.js.map +1 -1
  91. package/dist/core/OutsideEvent.d.ts +10 -0
  92. package/dist/core/OutsideEvent.d.ts.map +1 -0
  93. package/dist/core/OutsideEvent.js +5 -0
  94. package/dist/core/OutsideEvent.js.map +1 -0
  95. package/dist/core/PacketBuilder.d.ts.map +1 -1
  96. package/dist/core/PacketBuilder.js +7 -3
  97. package/dist/core/PacketBuilder.js.map +1 -1
  98. package/dist/core/PatternSelection.d.ts +1 -1
  99. package/dist/core/PatternSelection.d.ts.map +1 -1
  100. package/dist/core/PatternSelection.js +8 -6
  101. package/dist/core/PatternSelection.js.map +1 -1
  102. package/dist/core/PreviousEmission.d.ts +14 -0
  103. package/dist/core/PreviousEmission.d.ts.map +1 -0
  104. package/dist/core/PreviousEmission.js +24 -0
  105. package/dist/core/PreviousEmission.js.map +1 -0
  106. package/dist/core/ProposalLifecycle.d.ts +9 -13
  107. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  108. package/dist/core/ProposalLifecycle.js +7 -11
  109. package/dist/core/ProposalLifecycle.js.map +1 -1
  110. package/dist/core/ProviderInstantiate.d.ts +4 -4
  111. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  112. package/dist/core/ProviderInstantiate.js +24 -25
  113. package/dist/core/ProviderInstantiate.js.map +1 -1
  114. package/dist/core/ProviderRecovery.d.ts +1 -1
  115. package/dist/core/ProviderRecovery.d.ts.map +1 -1
  116. package/dist/core/ProviderRecovery.js +5 -8
  117. package/dist/core/ProviderRecovery.js.map +1 -1
  118. package/dist/core/ResourceSelector.js +1 -1
  119. package/dist/core/ResourceSelector.js.map +1 -1
  120. package/dist/core/SchemeRegistry.d.ts +2 -1
  121. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  122. package/dist/core/SchemeRegistry.js +10 -2
  123. package/dist/core/SchemeRegistry.js.map +1 -1
  124. package/dist/core/StoredPacket.d.ts +1 -1
  125. package/dist/core/StoredPacket.d.ts.map +1 -1
  126. package/dist/core/StoredPacket.js +6 -15
  127. package/dist/core/StoredPacket.js.map +1 -1
  128. package/dist/core/StrikeRail.d.ts +2 -0
  129. package/dist/core/StrikeRail.d.ts.map +1 -1
  130. package/dist/core/StrikeRail.js +7 -1
  131. package/dist/core/StrikeRail.js.map +1 -1
  132. package/dist/core/Turn.d.ts +1 -1
  133. package/dist/core/Turn.d.ts.map +1 -1
  134. package/dist/core/Turn.js.map +1 -1
  135. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  136. package/dist/core/TurnDispositionHandler.js +1 -4
  137. package/dist/core/TurnDispositionHandler.js.map +1 -1
  138. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  139. package/dist/core/TurnMaterialization.js +5 -7
  140. package/dist/core/TurnMaterialization.js.map +1 -1
  141. package/dist/core/TurnRunner.d.ts +6 -8
  142. package/dist/core/TurnRunner.d.ts.map +1 -1
  143. package/dist/core/TurnRunner.js +126 -60
  144. package/dist/core/TurnRunner.js.map +1 -1
  145. package/dist/core/TurnSources.sql +15 -0
  146. package/dist/core/WorkerControlHandler.d.ts.map +1 -1
  147. package/dist/core/WorkerControlHandler.js +6 -1
  148. package/dist/core/WorkerControlHandler.js.map +1 -1
  149. package/dist/core/WorkerName.sql +2 -2
  150. package/dist/core/attachments.d.ts +0 -3
  151. package/dist/core/attachments.d.ts.map +1 -1
  152. package/dist/core/attachments.js +3 -3
  153. package/dist/core/attachments.js.map +1 -1
  154. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  155. package/dist/core/caps/DbSubscriptionCaps.js +1 -2
  156. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  157. package/dist/core/file-materialization.d.ts.map +1 -1
  158. package/dist/core/file-materialization.js +4 -3
  159. package/dist/core/file-materialization.js.map +1 -1
  160. package/dist/core/fork.sql +2 -2
  161. package/dist/core/git-env.d.ts.map +1 -1
  162. package/dist/core/git-env.js +2 -7
  163. package/dist/core/git-env.js.map +1 -1
  164. package/dist/core/git-state.js +1 -1
  165. package/dist/core/git-state.js.map +1 -1
  166. package/dist/core/notifications.d.ts +17 -0
  167. package/dist/core/notifications.d.ts.map +1 -0
  168. package/dist/core/notifications.js +2 -0
  169. package/dist/core/notifications.js.map +1 -0
  170. package/dist/core/packet-inject.d.ts.map +1 -1
  171. package/dist/core/packet-inject.js +2 -3
  172. package/dist/core/packet-inject.js.map +1 -1
  173. package/dist/core/packet-wire.d.ts +4 -4
  174. package/dist/core/packet-wire.d.ts.map +1 -1
  175. package/dist/core/packet-wire.js +94 -16
  176. package/dist/core/packet-wire.js.map +1 -1
  177. package/dist/core/plurnk-uri.d.ts +0 -1
  178. package/dist/core/plurnk-uri.d.ts.map +1 -1
  179. package/dist/core/plurnk-uri.js +1 -1
  180. package/dist/core/plurnk-uri.js.map +1 -1
  181. package/dist/core/teaching.js +1 -1
  182. package/dist/core/teaching.js.map +1 -1
  183. package/dist/core/worker-ops.sql +1 -1
  184. package/dist/digest/Digest.d.ts.map +1 -1
  185. package/dist/digest/Digest.js +25 -14
  186. package/dist/digest/Digest.js.map +1 -1
  187. package/dist/digest/DigestRender.d.ts +3 -1
  188. package/dist/digest/DigestRender.d.ts.map +1 -1
  189. package/dist/digest/DigestRender.js +235 -29
  190. package/dist/digest/DigestRender.js.map +1 -1
  191. package/dist/digest/DigestRequiem.d.ts.map +1 -1
  192. package/dist/digest/DigestRequiem.js +10 -14
  193. package/dist/digest/DigestRequiem.js.map +1 -1
  194. package/dist/digest/digest-paths.d.ts.map +1 -1
  195. package/dist/digest/digest-paths.js +5 -14
  196. package/dist/digest/digest-paths.js.map +1 -1
  197. package/dist/digest/digest-rows.d.ts +29 -1
  198. package/dist/digest/digest-rows.d.ts.map +1 -1
  199. package/dist/digest/digest-rows.js +1 -1
  200. package/dist/digest/digest-rows.js.map +1 -1
  201. package/dist/digest/digest.sql +12 -1
  202. package/dist/launch/Launch.d.ts +49 -0
  203. package/dist/launch/Launch.d.ts.map +1 -0
  204. package/dist/launch/Launch.js +179 -0
  205. package/dist/launch/Launch.js.map +1 -0
  206. package/dist/observe/spans.d.ts +1 -1
  207. package/dist/observe/spans.d.ts.map +1 -1
  208. package/dist/observe/spans.js +5 -64
  209. package/dist/observe/spans.js.map +1 -1
  210. package/dist/schemes/EffectPolicy.js +2 -2
  211. package/dist/schemes/EffectPolicy.js.map +1 -1
  212. package/dist/schemes/Exec.d.ts +2 -2
  213. package/dist/schemes/Exec.d.ts.map +1 -1
  214. package/dist/schemes/Exec.js +46 -22
  215. package/dist/schemes/Exec.js.map +1 -1
  216. package/dist/schemes/ExecScratch.d.ts +10 -0
  217. package/dist/schemes/ExecScratch.d.ts.map +1 -0
  218. package/dist/schemes/ExecScratch.js +43 -0
  219. package/dist/schemes/ExecScratch.js.map +1 -0
  220. package/dist/schemes/ExecutionInput.d.ts.map +1 -1
  221. package/dist/schemes/ExecutionInput.js +2 -5
  222. package/dist/schemes/ExecutionInput.js.map +1 -1
  223. package/dist/schemes/File.d.ts.map +1 -1
  224. package/dist/schemes/File.js +44 -5
  225. package/dist/schemes/File.js.map +1 -1
  226. package/dist/schemes/Log.d.ts.map +1 -1
  227. package/dist/schemes/Log.js +5 -4
  228. package/dist/schemes/Log.js.map +1 -1
  229. package/dist/schemes/TurnSource.js +2 -2
  230. package/dist/schemes/TurnSource.js.map +1 -1
  231. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  232. package/dist/schemes/_entry-crud.js +2 -3
  233. package/dist/schemes/_entry-crud.js.map +1 -1
  234. package/dist/schemes/_entry-find.d.ts.map +1 -1
  235. package/dist/schemes/_entry-find.js +2 -1
  236. package/dist/schemes/_entry-find.js.map +1 -1
  237. package/dist/schemes/_entry-fts.d.ts +1 -0
  238. package/dist/schemes/_entry-fts.d.ts.map +1 -1
  239. package/dist/schemes/_entry-fts.js +34 -2
  240. package/dist/schemes/_entry-fts.js.map +1 -1
  241. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  242. package/dist/schemes/_entry-graph.js +2 -6
  243. package/dist/schemes/_entry-graph.js.map +1 -1
  244. package/dist/schemes/_entry-manifest.js +1 -1
  245. package/dist/schemes/_entry-manifest.js.map +1 -1
  246. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  247. package/dist/schemes/_entry-ops.js +3 -2
  248. package/dist/schemes/_entry-ops.js.map +1 -1
  249. package/dist/schemes/_path-scope.d.ts.map +1 -1
  250. package/dist/schemes/_path-scope.js +2 -3
  251. package/dist/schemes/_path-scope.js.map +1 -1
  252. package/dist/schemes/_search-index.d.ts.map +1 -1
  253. package/dist/schemes/_search-index.js +2 -8
  254. package/dist/schemes/_search-index.js.map +1 -1
  255. package/dist/schemes/exec-abort.js +1 -1
  256. package/dist/schemes/exec-abort.js.map +1 -1
  257. package/dist/schemes/exec-lifetime.d.ts.map +1 -1
  258. package/dist/schemes/exec-lifetime.js +2 -1
  259. package/dist/schemes/exec-lifetime.js.map +1 -1
  260. package/dist/server/ClientReads.d.ts +1 -1
  261. package/dist/server/ClientReads.d.ts.map +1 -1
  262. package/dist/server/ClientReads.js +1 -1
  263. package/dist/server/ClientReads.js.map +1 -1
  264. package/dist/server/Daemon.d.ts +35 -20
  265. package/dist/server/Daemon.d.ts.map +1 -1
  266. package/dist/server/Daemon.js +101 -61
  267. package/dist/server/Daemon.js.map +1 -1
  268. package/dist/server/DaemonModule.d.ts +2 -63
  269. package/dist/server/DaemonModule.d.ts.map +1 -1
  270. package/dist/server/DrainSupervisor.d.ts +3 -3
  271. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  272. package/dist/server/DrainSupervisor.js +13 -4
  273. package/dist/server/DrainSupervisor.js.map +1 -1
  274. package/dist/server/EnvFunctionality.d.ts +2 -2
  275. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  276. package/dist/server/EnvFunctionality.js +1 -1
  277. package/dist/server/EnvFunctionality.js.map +1 -1
  278. package/dist/server/Functionality.d.ts +2 -1
  279. package/dist/server/Functionality.d.ts.map +1 -1
  280. package/dist/server/Functionality.js.map +1 -1
  281. package/dist/server/MembersFunctionality.d.ts +2 -10
  282. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  283. package/dist/server/MembersFunctionality.js +1 -1
  284. package/dist/server/MembersFunctionality.js.map +1 -1
  285. package/dist/server/SkillsFunctionality.d.ts +2 -1
  286. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  287. package/dist/server/SkillsFunctionality.js +1 -1
  288. package/dist/server/SkillsFunctionality.js.map +1 -1
  289. package/dist/server/WorkerModelResolver.d.ts +8 -8
  290. package/dist/server/WorkerModelResolver.d.ts.map +1 -1
  291. package/dist/server/WorkerModelResolver.js +46 -45
  292. package/dist/server/WorkerModelResolver.js.map +1 -1
  293. package/dist/server/WorkspaceResidency.d.ts +2 -1
  294. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  295. package/dist/server/WorkspaceResidency.js.map +1 -1
  296. package/dist/server/client-input.d.ts +2 -1
  297. package/dist/server/client-input.d.ts.map +1 -1
  298. package/dist/server/client-input.js +7 -0
  299. package/dist/server/client-input.js.map +1 -1
  300. package/dist/server/drain.sql +6 -6
  301. package/dist/server/envelope.d.ts +2 -9
  302. package/dist/server/envelope.d.ts.map +1 -1
  303. package/dist/server/envelope.js +2 -2
  304. package/dist/server/envelope.js.map +1 -1
  305. package/dist/server/envelope.sql +12 -9
  306. package/dist/server/logEntry.d.ts +1 -31
  307. package/dist/server/logEntry.d.ts.map +1 -1
  308. package/dist/server/logEntry.js.map +1 -1
  309. package/dist/server/loopDocs.js +1 -1
  310. package/dist/server/loopDocs.js.map +1 -1
  311. package/dist/server/model-catalog.js +3 -3
  312. package/dist/server/model-catalog.js.map +1 -1
  313. package/dist/server/model-route.d.ts +2 -2
  314. package/dist/server/model-route.d.ts.map +1 -1
  315. package/dist/server/model-route.js +5 -5
  316. package/dist/server/model-route.js.map +1 -1
  317. package/dist/server/module-discovery.d.ts.map +1 -1
  318. package/dist/server/module-discovery.js +4 -22
  319. package/dist/server/module-discovery.js.map +1 -1
  320. package/dist/service.d.ts.map +1 -1
  321. package/dist/service.js +45 -7
  322. package/dist/service.js.map +1 -1
  323. package/dist/share/Share.d.ts +15 -0
  324. package/dist/share/Share.d.ts.map +1 -0
  325. package/dist/share/Share.js +106 -0
  326. package/dist/share/Share.js.map +1 -0
  327. package/dist/share/share.sql +6 -0
  328. package/docs/env.md +18 -0
  329. package/migrations/001_workspaces.sql +2 -2
  330. package/migrations/002_workers.sql +4 -6
  331. package/migrations/003_loops.sql +2 -2
  332. package/migrations/004_inference.sql +2 -2
  333. package/migrations/005_entries.sql +2 -2
  334. package/migrations/006_log.sql +2 -2
  335. package/migrations/007_subscriptions.sql +2 -2
  336. package/migrations/008_interactions.sql +2 -2
  337. package/migrations/009_effort.sql +7 -0
  338. package/migrations/010_outside_text.sql +41 -0
  339. package/migrations/011_settled.sql +133 -0
  340. package/package.json +46 -37
  341. package/dist/core/Knob.d.ts +0 -9
  342. package/dist/core/Knob.d.ts.map +0 -1
  343. package/dist/core/Knob.js +0 -50
  344. package/dist/core/Knob.js.map +0 -1
package/SPEC.md CHANGED
@@ -92,7 +92,7 @@ Independent axes on entries and channels. Confusion across them is a recurring s
92
92
  | Term | Meaning |
93
93
  |---|---|
94
94
  | **writer** | The identity authoring a write. One of `model \| client \| _plurnk \| plugin`. Carried on `ctx.writer` for schemes; engine enforces `manifest.writableBy`. |
95
- | **origin** | Synonym for writer in log_entries (`log_entries.origin`). Historical naming; treat as equivalent. |
95
+ | **origin** | Synonym for writer in log_entries (`log_entries.origin`). Synonym for writer. |
96
96
  | **writable_by** | The set of writers a scheme accepts. Subset of `{model, client, _plurnk, plugin}`. Engine rejects writes outside the set with 403; the rejection is logged as the action-entry ({§subscriptions} action-entry-as-outcome). |
97
97
 
98
98
  ### Execution terms
@@ -224,6 +224,32 @@ before provider or capability initialization can perform external work. Every
224
224
  later startup failure closes resources in reverse ownership order while
225
225
  preserving the originating failure: daemon, observability, database, listener.
226
226
 
227
+ §startup-readiness-line **Readiness is one stdout line.** After the client interface is mounted
228
+ the service prints exactly one line, `plurnk-service agui=<url> db=<json string> route=<json string>`:
229
+ the URL brackets an IPv6 host, and the database path and the route (the active model route or
230
+ `no model`) are JSON strings, so a path or route containing spaces is exact and a consumer parses
231
+ the URL as a URL and the strings as JSON; nothing else the service prints on stdout before it has
232
+ that prefix. Before the line the listener answers `503 service-starting`; after it,
233
+ `discover` is the identity check a launcher uses to tell this daemon from any other listener. A bind
234
+ failure is an exit with the originating address error and means *occupied*, not *foreign* — another
235
+ plurnk-service may be starting there, and only `discover` says which.
236
+
237
+ §daemon-launch **The service ships its own launcher; launchers own policy.** `@plurnk/plurnk-service/launch`
238
+ spawns a daemon argv with the caller's environment, an optional {§state-root}, host and port, and
239
+ resolves on the readiness line with the published address, database path, route and a `stop()` that
240
+ is SIGTERM, a stated grace, then SIGKILL, resolving when the process has ended. It holds no timing of
241
+ its own: the caller states the readiness timeout and the stop grace. A start that fails — spawn error,
242
+ exit before readiness, or timeout — is stopped and awaited before the failure is thrown with its kind
243
+ and both output streams; the helper never creates, keeps or removes state. A shared daemon survives
244
+ the launcher that started it (scheduled deliveries, other clients and inbound A2A depend on it); a
245
+ private daemon is its launcher's child and ends with it under managed shutdown. The launcher
246
+ spawns either: `lifetime: "private"` (the default) pipes both streams to the launcher; `lifetime:
247
+ "shared"` puts the daemon in its own process group with both streams appended to the caller's
248
+ `logFile`, reads readiness from that file, and releases the process once ready, so the launcher may
249
+ exit while the daemon runs on and no output accumulates in a launcher that has left; until
250
+ readiness the launcher owns it either way, and `stop()` ends it while the launcher lives. Shell and container
251
+ launchers consume the same contract by reading the line themselves.
252
+
227
253
  ## §actor-boundary Workers and workspace boundaries
228
254
 
229
255
  ```mermaid
@@ -352,7 +378,7 @@ direct-entry-plus-directory count; `-1` enables the ordinary markerless page;
352
378
  unset / `0` disables previews. `log://` is absent because the current worker's
353
379
  log already renders in present mode.
354
380
 
355
- §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning and program READs in {§reasoning-initial-read} execute under {§op-execution-order}. The full `<1,-1>` READ of its own `ops://<worker>/<loop>/<turn>` source supplies the worked program example; no actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
381
+ §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning READ in {§reasoning-initial-read} execute under {§op-execution-order}. Its program supplies the worked example as the first request's assistant message ({§packet-wire-envelope}); no program READ, actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
356
382
 
357
383
  Incoming messages publish once as inbound SEND rows in the first model turn
358
384
  ({§message-arrival}); initialization neither READs nor archives them. The turn
@@ -496,7 +522,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
496
522
  | `READ` | existing literal name | Collect the named worker's deliverable. |
497
523
  | `KILL` | existing literal name | Terminate the named worker or caller. |
498
524
 
499
- - §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
525
+ - §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409 whose receipt names the working forms — `SEND (worker://<name>)` with the task as the body to give the live worker more work, or a name no worker holds for another; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
500
526
  - §worker-spawn-prompt-resource **The spawn slot is overloaded by scheme.** A `worker://` path is
501
527
  the child's address and keeps the address rules ({§worker-control-addressing}). A path of any
502
528
  other scheme is the child's prompt resource: it is read whole (`<1,-1>`) under the caller's read capabilities, composed with
@@ -506,7 +532,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
506
532
  resource with no body is `422 spawn-prompt-empty`. Naming the child and giving a resource in one
507
533
  statement is not expressible; the body can READ the resource instead. Taught in the deep
508
534
  reference only.
509
- - §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and reasoning policy; the sender and daemon default do not re-select it. The caller addresses itself by its literal name; a literal name with no worker in the workspace is 404.
535
+ - §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and effort; the sender and daemon default do not re-select it. The caller addresses itself by its literal name; a literal name with no worker in the workspace is 404.
510
536
  - §worker-scheme-fork **Fork** — ```` ```FORK (worker://<name>)? ```` with a task body branches the
511
537
  current worker into a **named** child: its log is deep-copied
512
538
  ({§machine-processes-fork-copies-the-log}), which continues with `task`; the
@@ -541,8 +567,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
541
567
  missing name is 404. The model therefore reads the worker itself for its
542
568
  outcome or a wait rather than guessing a scratch path to "check on" it.
543
569
  - §worker-loop-result `ops://<name>/<sequence>` selects one worker-local positive safe-integer
544
- loop sequence ({§loop-answer}; the retired `loop://` scheme is gone, and one address now serves
545
- both what a loop said and how it ended). It is a read-only resource, not an actor control
570
+ loop sequence ({§loop-answer}; one address serves both what a loop said and how it ended). It is a read-only resource, not an actor control
546
571
  address: READ, FIND and COPY use ordinary projections; EDIT, MOVE-source, and KILL cannot change
547
572
  it. No query, userinfo, or port is accepted. A coordinate that is not a positive safe integer is
548
573
  400; a missing loop 404; a loop that has not answered and has not concluded 425. A concluded
@@ -575,19 +600,22 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
575
600
  `"parent": null` at a root, so a worker never infers its rank from silence.
576
601
  - §packet-current-turn **The packet says who and which turn, below the log.** The
577
602
  `## Worker` block is the first section after the log, carrying
578
- `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T}`: the actor,
579
- whose child it is, and the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
580
- and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows; a model never infers the
581
- present from the last row's coordinate, which may or may not be its own turn. The block
603
+ `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`:
604
+ the actor, whose child it is, the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
605
+ and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows, and the address of the program
606
+ the envelope's assistant message carries ({§packet-wire-envelope}), so the rows sharing that coordinate
607
+ are its receipts; a model never infers the present from the last row's coordinate, which may or may
608
+ not be its own turn. The block
582
609
  changes every turn, so nothing of it precedes the log, and the packet carries no date, time
583
- or zone anywhere (operator, 2026-09-13: no date or time injection, and nothing volatile above
584
- the log, which is the cached prefix). The coordinate only; the source addresses stay
585
- documented, not taught.
610
+ or zone anywhere. The coordinates and the last program's address only; the other source
611
+ addresses stay documented, not taught.
586
612
 
587
613
  Worker control rides the daemon's inject seam (active→fold, idle→enqueue+drain), so the handler creates/branches the worker and hands off; the daemon owns provider + system prompt. FORK/WORK carry the seed task in the body and are their own ops, dispatched to worker control — never the entry-copy path.
588
614
 
589
615
  ## §membership File membership and project roots
590
616
 
617
+ A file is a member of a workspace when Git tracks it or a members definition includes it and none excludes it; only members can be READ, found by FIND, or changed by EDIT, and every member path resolves under the project root.
618
+
591
619
  The project-file path has two explicit reconciliation gates. Internal entries do
592
620
  not participate in this disk loop.
593
621
 
@@ -642,8 +670,7 @@ and never re-fetch a match.
642
670
  files by an exact creation record with recorded provenance, and never by `git add`.
643
671
  Changing this clause, the register, or the composition is an operator ruling recorded
644
672
  on the issue that lands it — never an implementation convenience, never a side effect
645
- of making a file visible to solve the problem at hand. The 2026-07-12 – 2026-08-27
646
- "untracked-but-not-ignored" ambient admission is retired.
673
+ of making a file visible to solve the problem at hand.
647
674
  - §membership-model-universe **The exception register — files in the model's universe.**
648
675
  Admitted by exact creation records (`source: "create"`, origin `constraint`), never
649
676
  staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
@@ -656,7 +683,7 @@ and never re-fetch a match.
656
683
  exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
657
684
  is not projected. (4) A definition the model proposes through the `members`
658
685
  family ({§members-functionality}), admitted only under the operator's ceiling
659
- `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (shipped `namespace`), projected with source
686
+ `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}), projected with source
660
687
  `model`, and never admitted past the repository's ignore rules or an exclusion.
661
688
  Nothing else.
662
689
  - §membership-git-membership The workspace owns the Git repository containing
@@ -713,7 +740,7 @@ identity, and terminal disposition without exposing raw bytes or a base64 lane.
713
740
  workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
714
741
  byte ceiling over one disk source before Core reads it into the canonical file
715
742
  snapshot. Its valid range is `1..104857600`, bounded by the channel storage
716
- contract, and it ships at that 100 MiB maximum. An oversized path remains a real member
743
+ contract. An oversized path remains a real member
717
744
  with an empty body channel carrying a durable 413 producer result; no diagnostic
718
745
  sentinel impersonates file content. READ therefore names the path, observed bytes,
719
746
  ceiling, and recovery through the ordinary result contract, while EDIT returns the
@@ -769,7 +796,7 @@ The version travels *with the proposal*, never re-read from the entry at accept:
769
796
 
770
797
  The CAS is the **hard backstop**, at the moment of writing, on every accept path. It composes with the model-facing {§line-anchors}: an anchor rejects a target whose relevant neighborhood changed before dispatch, while the CAS refuses to write against a snapshot disk left after proposal. An unanchored edit deliberately claims no pre-dispatch stale-view guarantee.
771
798
 
772
- §membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1` (default) includes the repository containing `project_root`; `=0` disables automatic Git membership, leaving member definitions as the only membership source. `ALLOWED` gates `AUTO`.
799
+ §membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1` includes the repository containing `project_root`; `=0` disables automatic Git membership, leaving member definitions as the only membership source. `ALLOWED` gates `AUTO`.
773
800
 
774
801
  **Rationale.** Workspace is the right scope unit and the containing Git repository is its ordinary development boundary. Membership curation is tiered: Git bounds it by tracking, the client supersedes by overlay, and the model curates its render by READ/KILL. Supporting several independent repositories as one world would require Plurnk-owned topology, synchronization, and model teaching that Git already solves cleanly by treating them as separate workspaces.
775
802
 
@@ -859,8 +886,8 @@ waited 7.5 h, #703) is a number rather than a gap.
859
886
  §digest-storage **The digest states the file's health.** Beside the database path it
860
887
  reports the file size, the free pages it holds, its `auto_vacuum` mode, and the six
861
888
  largest tables and indexes by allocated bytes (`dbstat`), so growth is a number in
862
- every digest (#764). The digest reads loops as stored, so a database made before a
863
- lifecycle column was added still digests.
889
+ every digest (#764). The digest reads loops as stored, so it tolerates databases
890
+ missing later lifecycle columns.
864
891
 
865
892
  §loop-execution-allowance **One task has one execution allowance.** The first
866
893
  execution snapshots `PLURNK_SERVICE_LOOP_TIMEOUT` on the loop. Active segments
@@ -1073,6 +1100,15 @@ single operation captures the equivalent boundary before dispatch. This limits
1073
1100
  only log-row selection: operation phasing and same-turn resource effects retain
1074
1101
  their ordinary contracts.
1075
1102
 
1103
+ ### §engine-notifications One bundle of daemon callbacks
1104
+
1105
+ The daemon's observation callbacks — stream, reasoning, outside-text and packet
1106
+ events, worker wake, inject and cancel, operation settlement, notices — are one
1107
+ declared bundle (`EngineNotifications`). The Engine receives them flat, carries
1108
+ them as one value, and every consumer reads the callbacks it uses from that
1109
+ value; adding one is a declaration and a use, never an edit to the constructors
1110
+ between. Scheme contexts still expose the specific callbacks a handler may call.
1111
+
1076
1112
  ### §engine-rails Engine rails
1077
1113
 
1078
1114
  After each admitted turn, one inline verdict decides whether the loop continues.
@@ -1081,7 +1117,7 @@ These are the complete strike sources:
1081
1117
 
1082
1118
  | Strike source | Exact trigger | Model-visible occurrence |
1083
1119
  |---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
1084
- | Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501`. | The originating failure row. |
1120
+ | Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501`, in a turn where no operation succeeded ({§strike-progress-immunity}). | The originating failure row. |
1085
1121
  | Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
1086
1122
  | Empty turn | An admitted turn with no authored response operation ({§empty-turn}). | The turn's `422` error row, and its reasoning read back ({§reasoning-empty-turn-read}). |
1087
1123
 
@@ -1114,8 +1150,8 @@ effects. Ordinary contract strikes and operator budgets remain independent.
1114
1150
  call ({§bare-inference}) takes the same recovery as the loop's own inference: each re-issue
1115
1151
  is its own model call on the ledger, and a spent window leaves the operation's result as the
1116
1152
  provider's exact failure. When a model
1117
- call fails with a network failure, rate limit, deadline, or interrupted resource after
1118
- the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
1153
+ call fails with a kind the provider marks `retryable` ({§provider-retryable-truth}: network
1154
+ failure, rate limit, deadline, interrupted resource, dropped output) after the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
1119
1155
  notices the client (`engine:provider` / `provider_unavailable`), waits with
1120
1156
  exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling up to
1121
1157
  `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX`), and re-issues the same call against the exact frozen model
@@ -1134,18 +1170,28 @@ instead, because parking stops the execution clock and no wake would ever arrive
1134
1170
  ({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
1135
1171
  authorization, quota, an invalid response) settles a loop on a provider failure.
1136
1172
 
1137
- **Contract Strikes** (operator mandate, 2026-09-01): *Every turn with one or
1138
- more contract violations earns a strike. A turn without any contract violations
1139
- clears the strikes. Three (not four) strikes and you're out, by default.*
1140
- The streak counts consecutive violating turns; `MAX_STRIKES` (default 3) is the
1141
- threshold, crossed ON the third strike; the crossing turn terminates at **508
1173
+ **Contract Strikes**: every turn with one or more contract violations earns a
1174
+ strike; a turn without any contract violations clears the strikes.
1175
+
1176
+ §strike-progress-immunity **A turn in which at least one operation succeeded is
1177
+ immune to the hard-result source** (operator ruling, 2026-09-26, #853): its hard
1178
+ failures keep their exact rows, but the turn counts as progress — it earns no strike
1179
+ and clears the streak. A successful operation is any admitted operation that acts on the
1180
+ task — executions included — whose result status is `< 400`. Operations that steer the
1181
+ loop never qualify: NOTE (it cannot fail; reasoning NOTEs are filed as NOTEs, and outside
1182
+ text is no operation at all, {§outside-text}), WAIT, a parameterless KILL and a targetless SEND; a turn
1183
+ of failures beside a WAIT still strikes. The cycle source is not exempted: a repeating
1184
+ turn's operations succeed by construction, and the backstop exists to catch exactly
1185
+ that ({§engine-cycle-evidence}).
1186
+ The streak counts consecutive violating turns; `PLURNK_SERVICE_MAX_STRIKES` is the
1187
+ threshold, crossed ON the strike that reaches it; the crossing turn terminates at **508
1142
1188
  Loop Detected** when cycle-detected, otherwise **500**.
1143
1189
 
1144
1190
  The contracts, and the violation of each that strikes:
1145
1191
 
1146
1192
  | Contract | Violation that strikes |
1147
1193
  |---|---|
1148
- | operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
1194
+ | operation contract | a hard operation failure (status ≥ 400) in an admitted turn where no operation succeeded ({§strike-progress-immunity}) — soft statuses below excluded |
1149
1195
  | review contract | none: an eligible final response joins live obligations ({§completion-joins-live-work}) or continues to observe results ({§completion-defers-to-results}) |
1150
1196
  | progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
1151
1197
  | frame contract | emission attempts exhausted with no admissible turn |
@@ -1203,10 +1249,10 @@ Author-facing contract: [`@plurnk/plurnk-providers`](../plurnk-providers/SPEC.md
1203
1249
  Three current entry points:
1204
1250
 
1205
1251
  - §provider-surface-generate `provider.generate(args)` — once per logical model call. An emission attempt supplies the complete packet messages, worker/turn coordinates, generation envelope, optional local grammar, first-party metadata, and `callKind: "emission"`. A BARE inference supplies only one user message containing its resolved prompt plus non-prompt call identity and accounting metadata, including `callKind: "bare"` ({§bare-inference} {§provider-call-kind}). Both receive a durable physical-request observer; provider-owned retry and failover may issue several ordered requests beneath either call. A successful `ProviderResponse` reaches its call-specific consumer; a `ProviderError.attempt` remains failed response evidence under {§provider-interrupted-attempt}. Core persists normalized response evidence separately from physical accounting.
1206
- - §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — provider-owned intersection of request-shaped token evidence and every known physical input limit. It admits, rejects only a proven exact overflow, or defers ambiguity to upstream ({§tokenomics-context-envelope-admission}). `generate` performs this assessment for its exact request and preserves the evidence on success and capacity failure.
1207
- - §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with `exact`, `upper_bound`, `estimate`, or `unavailable` provenance. Core never substitutes this physical fact for its curation ruler.
1252
+ - §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — the provider's verdict under {§provider-capacity-admission}; core acts on it under {§tokenomics-context-envelope-admission}. `generate` performs this assessment for its exact request and preserves the evidence on success and capacity failure.
1253
+ - §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with the provenance of {§provider-prompt-measurement}. Core never substitutes this physical fact for its curation ruler.
1208
1254
 
1209
- §provider-surface-identity Provider capacity and identity are immutable for one instance. `contextWindow`, `maxInputTokens`, and `maxOutputTokens` carry known model limits; `outputBudget` is the total generation envelope, optional `reasoningBudget` is its strict subset, and `inputCapacity` is the stable intersection of known input constraints ({§tokenomics}). Unknown facts remain `null`. `model` identifies persisted turn/provider evidence. Local GBNF admission also consumes `constrainsOutput` ({§grammar-configuration-admission}).
1255
+ §provider-surface-identity Provider capacity and identity are immutable for one instance ({§provider-interface}); core invents no stand-in for a `null` fact. `inputCapacity` feeds {§tokenomics}; a call's response grant may expand under {§provider-flexed-allowance}; `model` identifies persisted turn/provider evidence; local GBNF admission also consumes `constrainsOutput` ({§grammar-configuration-admission}).
1210
1256
 
1211
1257
  §inference-ledger **Logical inference is provider-neutral and physical requests have one ledger.** Every `inference_calls` identity belongs to a workspace and a model/inference turn, records its ordered kind and request model, and has a forward-only lifecycle. Its `model_calls` specialization owns normalized failure and capacity evidence; the response body is `model_call_responses`, present or retired under {§retention-policy}; one observation view records evidence, body and close together, and a settled call refuses a second observation. Only an `emission` has `turn_attempts` admission evidence; `bare` calls retain independent results. Every physical request is an ordered `provider_requests` child opened before I/O and settled once. Calls contribute to turn, loop, worker, and workspace accounting; only an emission supplies the latest context gauge.
1212
1258
 
@@ -1214,7 +1260,7 @@ Three current entry points:
1214
1260
 
1215
1261
  ### Engine → provider guarantees
1216
1262
 
1217
- - `messages` is a complete prompt (the section list, pre-assembled into the system + user messages). Provider does not reorder.
1263
+ - `messages` is a complete prompt (the section list, pre-assembled into the wire envelope, {§packet-wire-envelope}). Provider does not reorder.
1218
1264
  - §provider-guarantees-signal-wired `signal` is wired to the worker's AbortController.
1219
1265
  - §provider-guarantees-serial-attempts Emission attempts for one engine turn are serial. They reuse the exact messages, coordinates, generation limits, and strike state; two attempts for that turn never overlap.
1220
1266
  - BARE calls admitted by one turn launch as one parallel batch; each call retains independent observer and failure state, and the engine awaits the complete batch before committing results in authored order ({§bare-inference}).
@@ -1230,9 +1276,13 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
1230
1276
  | Parsed response | Admission |
1231
1277
  |---|---|
1232
1278
  | Bounded program, including malformed operations | Admit valid operations and record parser failures; with no authored operation, apply {§empty-turn}. |
1233
- | Outside response text | Keep it as the model's NOTE under {§response-text-note}; never deliver it or infer completion. |
1279
+ | Outside response text | Store it as the turn's `outside` source under {§outside-text}; never a row, never delivered, never completion. |
1234
1280
  | Lost boundary after a closed operation | Admit the closed operations and record the boundary diagnostic under {§unparsed-tail-boundary}. |
1235
1281
  | Lost boundary before any closed operation | Reject the attempt; neither outside text nor a reasoning NOTE substitutes for a closed response operation. |
1282
+ | Outside text carrying a log-entry heading | Reject the attempt ({§fabricated-log-entry}). |
1283
+ | A response the provider stopped at a repeated line | Reject the attempt with the provider's sentence as its diagnostic ({§repetition-stop}); no provider recovery, notice, or problem row. |
1284
+
1285
+ §fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. Write the operation, then wait for its receipt. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
1236
1286
 
1237
1287
  Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
1238
1288
  discloses truncation and precludes completion; it is not independently a rejection.
@@ -1242,7 +1292,7 @@ optional, with no omission warning or invented operation ({§turn-shape}).
1242
1292
 
1243
1293
  §safe-uri-target-groups After source and authored-command admission, Core tolerates one target group on READ or KILL only when splitting its raw target at top-level comma or whitespace separators produces at least two members and every member independently parses as an explicit `scheme://` URI. Request-metadata blocks are opaque to this split. Each member becomes one ordinary statement with an independent dispatch outcome and log row, in authored member order at that operation's position under {§op-execution-order}. Otherwise the target remains exactly singular, including local filenames containing spaces or commas. The stored `turnOps` and authored command count remain unexpanded, and no other operation admits target groups.
1244
1294
 
1245
- Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `inference_calls` row with its `model_calls` specialization and emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the logical call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone extends `turns.packet` with response evidence; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `packetNNN.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
1295
+ Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `inference_calls` row with its `model_calls` specialization and emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the logical call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone extends `turns.packet` with response evidence; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `<stem>.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
1246
1296
 
1247
1297
  When the loop continues after exhaustion under {§invalid-emission-attempts}, the next ordinary turn's packet projects the latest rejected response visibly from a durably body-suppressed emission-attempt item under {§rejected-emission-entry} and carries one transient `invalid_emission` Notice: `Response rejected before dispatch; no operations were performed.` followed by `Parser: <the latest attempt's first diagnostic>` with its `content-offset` position — the model sees why, at which line, against its own projected text. The Notice states only observed admission facts; it does not classify the response as unrecoverable, infer why generation ended, or prescribe intent beyond the parser-owned diagnostic. Attempt count and rail state never become model-facing. The recovery turn has its own honestly stored packet and its configured private same-packet attempts. The packet-local projection never changes the row's curation state, so no later packet repeats that malformed body unless the model explicitly READs its exact address. Admission clears the recovery projection; another exhaustion replaces it with the latest rejected response if the loop continues.
1248
1298
 
@@ -1335,7 +1385,7 @@ whose text is read once per daemon and handed to the provider verbatim
1335
1385
  name with no path separator is refused with an error that says so, and an
1336
1386
  unreadable file fails the constrained generation loudly; neither ever silently
1337
1387
  becomes unconstrained. Nothing generates, validates, or grades a grammar, and
1338
- no reasoning policy is implied by one. The turn records transport as evidence:
1388
+ no effort is implied by one. The turn records transport as evidence:
1339
1389
  `railsAttached: "client"` when the provider reports it sent the grammar, or
1340
1390
  `"withheld"` when it reports it did not ({§provider-grammar-evidence}); there
1341
1391
  is no verdict key and no notice about conformance, because the parser's
@@ -1344,7 +1394,7 @@ adds no grammar state at all.
1344
1394
 
1345
1395
  ```dotenv
1346
1396
  PLURNK_MODEL_gemma=openai/macher.gguf
1347
- PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
1397
+ PLURNK_MODEL_flash=openrouter/deepseek/deepseek-v4.1-flash
1348
1398
  PLURNK_MODEL=gemma
1349
1399
  ```
1350
1400
 
@@ -1419,6 +1469,8 @@ historical execution. No address grants ownership or access restrictions.
1419
1469
 
1420
1470
  §fs-visibility-grantors **File visibility has two represented grantors; Plurnk never invents a private third one.** A file member is admitted by the active Git substrate or by an ordinary `include` row of the overlay. An `include` is either a projected `members` definition ({§members-projection}) or the exact, inspectable record of an accepted creation ({§fs-create-record}); both resolve to `constraint` membership. AGENTS.md remains auto-pulled as POLICY ({§policy-sections}), deliberately not a file member. A physically existing path that neither Git nor an `include` admits does not exist for the model and cannot be overwritten.
1421
1471
 
1472
+ ### File scheme: creation and misses
1473
+
1422
1474
  §fs-write-surface **The write surface — one admission and incorporation path.** Existing writes remain membership-gated. An absent path additionally crosses the effective creation scope and the complete constraint/Git policy before a proposal is issued. EDIT, COPY destinations, and MOVE destinations use this same path regardless of whether the producer is a model, client, plugin, or `_plurnk`. A COPY or MOVE destination scope on an absent channel resolves against its empty pre-mutation value under {§empty-mutation-scope}; a valid scope creates the channel with the selected source as its complete value. A coordinate outside that empty value is 416. Binary scopes remain numeric byte positions or ranges under {§binary-parity}.
1423
1475
 
1424
1476
  | Case | Required admission | Accepted result |
@@ -1474,9 +1526,9 @@ Every fact names the canonical key, never the host root or an echo of the
1474
1526
  model's spelling. These classes let a caller distinguish a wrong address, an
1475
1527
  invalid range, read-only authority, and occupied hidden state without guessing.
1476
1528
 
1477
- §membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.`, with a recovery naming both doors: EDIT creates a member at the path, and ```` ```members (add) ```` with a `{"glob": "<path>"}` body admits a file that already exists. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo. Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.` Occupancy may surface there; content never does ({§membership}).
1529
+ §membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.` Recovery treats path correction with FIND, creation with EDIT, and admission with `members (add)` as alternatives; it must not presume a missing READ requires creation or admission. Admission takes a `{"glob": "<path>"}` body. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo. Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.`, with admission-only recovery. Occupancy may surface there; content never does ({§membership}).
1478
1530
 
1479
- §fs-world-state **The world-state harness — coverage that closes the class.** Op-outcome tests check what an op returned; the harness checks the resulting world. `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, channel orphan-freedom, the closed admission set (every file row's origin is Git or constraint), and sig-coherence. Generated-pick incorporation and lifecycle require filesystem/Git evidence and are covered by the composed creation matrix rather than a false pure-database proxy. The harness runs as a lifecycle-test epilogue and at every soak turn boundary, where the delta half applies: an idle turn grows the entries table by ZERO. A violation names its law and its row.
1531
+ §file-directory-target **A directory is named as a directory.** Inside the root, a READ (or other exact-path read), KILL or EDIT whose target is a directory on disk — with or without a trailing slash — is refused `path-is-directory`, never as a missing or non-member file, since admitting it is not what the model needs: READ and KILL answer 404, EDIT 403. The detail is `'<key>' is a directory, not a file; <OP> reads/removes/writes one file.` and the recovery names the listing that reaches its files, `` List its files with `FIND (<key>/)`, then READ one by its path. `` (KILL: `then KILL each by its path`; EDIT: `` Name a file inside it, as `EDIT (<key>/<file>)`; list its files with `FIND (<key>/)`. ``). Beyond the root the disk stays dark and {§membership-read-refusal} holds unchanged.
1480
1532
 
1481
1533
  ### §scheme-manifest Manifest
1482
1534
 
@@ -1484,10 +1536,9 @@ invalid range, read-only authority, and occupied hidden state without guessing.
1484
1536
 
1485
1537
  ### §crud CRUD primitives
1486
1538
 
1487
- Entry-bearing schemes expose direct storage through their manifest-bound
1488
- `ctx.entries` capability (`read`, `write`, and `delete`). The engine uses that
1489
- same public capability for COPY/MOVE/KILL orchestration when a scheme does not
1490
- own a more specific operation. A stored-entry publication atomically upserts
1539
+ Core implements the `ctx.entries` capability of {§scheme-ctx-entries} and drives
1540
+ it for COPY/MOVE/KILL orchestration when a scheme does not own a more specific
1541
+ operation. A stored-entry publication atomically upserts
1491
1542
  one workspace identity, metadata, and its complete channel set. Concurrent
1492
1543
  publications expose one complete result, never a mix of channels; a failed
1493
1544
  publication leaves the prior entry unchanged. Omitted attributes preserve the
@@ -1535,7 +1586,7 @@ Registration precedes loop affinity:
1535
1586
  - §anchor-offset **An anchor offset is tolerated, never taught (#749).** A line mark may carry an offset from its anchor (`@abcde+1`, `@abcde-2`), and a bare `+N` after an anchor counts from that anchor (`<@abcde,+1>`). The anchor resolves as usual and the offset is added; a result before line 1 is an invalid mark, and past the end is the ordinary range refusal. Continuity and current-anchor preconditions check the anchor's own line. No teaching text, scope table or receipt mentions offsets; `plurnk.md` keeps its two anchor forms. A bare `+N` with no anchor before it is refused as before.
1536
1587
  - §edit-batch **One compound operation may require atomic splices.** The scheme's `editBatch` primitive validates all supplied numeric edits against one snapshot and commits one revision or none. Core supplies one statement for an authored EDIT; same-resource MOVE can supply multiple splices as one operation. This primitive does not group separate authored operations. Its replacement, insertion, conflict, and receipt rules remain owned by the shared Slicer.
1537
1588
  - §edit-batch-receipt **A refusal describes its own unapplied work.** An anchor collision lists every distinct unresolved anchor in that EDIT, including both range endpoints, in `unresolvedAnchors` (`anchor`, `kind: missing | ambiguous`, and matching `lines` when ambiguous). Missing is not proof of earlier validity or subsequent change. It carries `editCount: 1`, `applied: 0`, and recovery directing a READ for current coordinates; it makes no claim about other operations. A refused compound splice batch lists all conflicting pairs in `conflicts`, non-conflicting regions in `cleanRegions`, its first pair in `conflictingRegions`, and its own `editCount` and `applied: 0`.
1538
- - §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published `@xxxxx L:` prefixes is stripped when those prefixes verify against current anchors or this worker's preserved READ receipts (`rendered-prefix-stripped`); otherwise it remains literal content (`rendered-prefix-unverified`). Within a single atomic splice batch, the Slicer can deduplicate identical regions/bodies, concatenate same-boundary insertions, assign a shared endpoint to the sole body reproducing that line, or relocate an inner change when its original content occurs exactly once in the outer body. An already-applied inner body can be dropped. Unevidenced overlap remains a collision. These batch resolutions never reinterpret separate authored EDITs. Applied normalizations carry their exact merge facts and a notice; receipts describe only the applied effects.
1589
+ - §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published `L<@xxxxx>` prefixes (the number right-aligned) is stripped when those prefixes verify against current anchors or this worker's preserved READ receipts (`rendered-prefix-stripped`); otherwise it remains literal content (`rendered-prefix-unverified`). Within a single atomic splice batch, the Slicer can deduplicate identical regions/bodies, concatenate same-boundary insertions, assign a shared endpoint to the sole body reproducing that line, or relocate an inner change when its original content occurs exactly once in the outer body. An already-applied inner body can be dropped. Unevidenced overlap remains a collision. These batch resolutions never reinterpret separate authored EDITs. Applied normalizations carry their exact merge facts and a notice; receipts describe only the applied effects.
1539
1590
 
1540
1591
  ### Cross-scheme orchestration
1541
1592
 
@@ -1703,12 +1754,11 @@ empty setting excludes nothing, and the first match is the observable reason.
1703
1754
 
1704
1755
  §search-size-bound Every search subject, whatever its scheme, is also bounded
1705
1756
  by size when the operator sets one: a body longer than
1706
- `PLURNK_SERVICE_SEARCH_MAX_BYTES` (default empty = unbounded) is `excluded` with the reason `larger than N bytes`, before
1757
+ `PLURNK_SERVICE_SEARCH_MAX_BYTES` (empty = unbounded) is `excluded` with the reason `larger than N bytes`, before
1707
1758
  its body is read. It is neither parsed for symbols nor full-text indexed; READ,
1708
1759
  FIND by path, and membership are unaffected. The reason joins the derivation
1709
1760
  identity, so changing the bound re-derives the affected bodies and retention
1710
- collects what they leave. Origin (#729): the dogfood workspaces indexed 29
1711
- tokenizer vocabularies (up to 31 MB each) as full text.
1761
+ collects what they leave (#729).
1712
1762
 
1713
1763
  A match produces the `excluded` derivation disposition and suppresses graph
1714
1764
  and FTS while leaving the stored channel and direct READ unchanged. The
@@ -1896,9 +1946,11 @@ anchors from the complete canonical selected channel before applying the
1896
1946
  authored text slice; its durable result retains the canonical derivation
1897
1947
  identity and anchors aligned with returned lines. Packet rendering right-aligns
1898
1948
  `L` to the decimal width of the complete canonical selected channel's final
1899
- addressable line and emits `@xxxxx L:<content>` with one or more ASCII spaces
1900
- before `L`; a source line therefore retains the same prefix across projections
1901
- of one revision.
1949
+ addressable line and emits `L<@xxxxx><content>` with `L` right-aligned to that
1950
+ width, the scope literal as the delimiter, the content beginning after `>`. The anchor
1951
+ stands against its own text and never opens the row after the previous line's text —
1952
+ the placement that reads correctly at long context on every model measured (#893); a
1953
+ source line therefore retains the same prefix across projections of one revision.
1902
1954
  An explicit default-channel fragment and its fragmentless spelling share that
1903
1955
  identity; a selected non-default channel retains its canonical `#channel`.
1904
1956
 
@@ -1914,8 +1966,7 @@ range. COPY/MOVE mutation owners retain
1914
1966
  the resolved endpoint neighborhoods as compare-and-swap preconditions. There is
1915
1967
  no revision sidecar or fuzzy relocation. A range authenticates both endpoint
1916
1968
  neighborhoods, so every line of a range up to `2C + 2` lines is covered; a
1917
- longer range retains an unauthenticated interior gap. The shipped `C = 2`
1918
- covers ranges through six lines.
1969
+ longer range retains an unauthenticated interior gap.
1919
1970
 
1920
1971
  ### §edit EDIT
1921
1972
 
@@ -2012,8 +2063,15 @@ READ is the one fan-out core performs ({§read-fan-out}).
2012
2063
  result carries `matched`, the count of selected lines inside the scope. Zero
2013
2064
  matches is an empty read (204, `matched: 0`), never a failure. A full-text
2014
2065
  (`~`) or graph (`&`) pattern selects resources, not lines: 400
2015
- `pattern-dialect-unsupported`; a matcher its mimetype cannot run answers the
2066
+ `pattern-dialect-unsupported` ({§pattern-dialect-find-only}); a matcher its mimetype cannot run answers the
2016
2067
  matcher's own 415/400 ({§matcher-dispatch}).
2068
+ - §pattern-dialect-find-only **A `~` or `&` matcher outside FIND is refused as FIND's alone.** Every
2069
+ 400 `pattern-dialect-unsupported` — READ, EDIT, KILL, COPY, MOVE, SEND — names the model's
2070
+ matcher and says only FIND takes it, and its recovery gives both working forms with the
2071
+ model's own target: the FIND carrying that matcher, and the same operation with a text
2072
+ pattern built from the symbol or words it named (regex metacharacters escaped, words joined
2073
+ by `|`): `` Locate it with `FIND (django/urls/resolvers.py) &RoutePattern`, or select lines
2074
+ with a text pattern: `READ (django/urls/resolvers.py) /RoutePattern/`. ``
2017
2075
  - §read-fan-out **A READ over a glob reads every matching path.** `READ (pets_*.md)`
2018
2076
  and `READ (pets_*.md) /dogs/i` keep their glob ({§read-find-normalization} in the
2019
2077
  contracts SPEC) and dispatch fans them out: the ordinary FIND over the same
@@ -2033,9 +2091,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
2033
2091
  FIND failure is that failure on the authored glob. The FIND's resource page bounds
2034
2092
  the fan-out: when more paths matched than were read, one `read_fanout_bounded`
2035
2093
  notice names both counts. A full-text (`~`) or graph (`&`) matcher selects
2036
- resources, not lines, so that READ dispatches as the FIND survey. Operator,
2037
- 2026-09-13: "give it what it asked for" — a model that asked to read the pantry
2038
- looped five turns on the catalog it was handed instead.
2094
+ resources, not lines, so that READ dispatches as the FIND survey.
2039
2095
  - §read-bytes A binary channel, and the `#bytes` view of
2040
2096
  any resource whose scheme supplies bytes, reads as the source bytes one hexadecimal
2041
2097
  octet per line: coordinate = line = byte, so `<a,b>` selects bytes, the markerless
@@ -2136,8 +2192,8 @@ turn admitted under {§empty-turn}, one runtime turn of the same loop
2136
2192
  reasoning source; its receipt renders in the next packet like any other log row.
2137
2193
  `PLURNK_REASONING_EMPTY_TURN_LINES` (alias-scoped, default in `.env.defaults`) selects the scope on the same
2138
2194
  scale as `PLURNK_REASONING_VIEW_LINES`. No read follows a turn without reasoning, and none follows
2139
- a turn whose emission or reasoning carries a foreign tool-call grammar ({§response-text-note});
2140
- the strike and its error row are unchanged.
2195
+ a turn whose emission or reasoning carries a foreign tool-call grammar or leaked template token
2196
+ (`KnownToxins` names them); the strike and its error row are unchanged.
2141
2197
 
2142
2198
  ### §log-kill-scope KILL on the log: whole items and scoped bodies
2143
2199
 
@@ -2145,6 +2201,8 @@ AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMar
2145
2201
 
2146
2202
  KILL deletes context from the **log** (`log:///`, {§packet}). Without a scope it retires the selected rows from the active projection ({§log-history-projection}). With a one-line or inclusive two-line scope it removes only that body's intersecting body-relative physical lines from the readable projection, and the row stays active. An anchor may be one published on that body or one returned by READing its `log:///` coordinate ({§line-anchors}); an anchor absent from the current body selects no line, as with an out-of-bounds numeric line. Scoped KILL is one-way: intervals accumulate, the durable body is untouched, and subsequent access follows {§log-readable-projection}. A scoped KILL on a bodyless row is a friendly 200 no-op with `matched` reported. A KILL that addresses no row is 404 on an exact coordinate and 204 on a sweep ({§log-curation-folder-idiom}). Selection composes target/glob with an optional heading pattern ({§log-curation-set-selection}). Parameterless KILL instead requests completion ({§kill-conclusion}).
2147
2203
 
2204
+ §log-scope-recovery A log-body scope follows the file slicer's range rule ({§range-starts-at-one} in the schemes SPEC): a range starting at 0 — `<0,-1>` included — is refused 416 `range-not-satisfiable` on every body, empty ones too, and never clamped; its detail is the slicer's own sentence, `Range <0,-1> starts at 0, which is not a line; lines are numbered from 1.`, and its recovery names the forms a log body takes — `Write <1,-1> to trim every line of the body; KILL (log:///1/9/2/READ) with no scope retires the whole row.`, or `To trim lines 1 through M, write <1,M>; …` — never the insert and append positions a body cannot take. Every other scope that names no line is 400 `curation-scope-invalid` and likewise names the model's mistake in its coordinates and the forms that work on that row: `<0>` offers `<1>`; an end below 1 offers `<L,-1>`; a backward `<5,3>` offers `<3,5>`; anything else offers `<L>`, `<L,M>` and the unscoped row KILL.
2205
+
2148
2206
  A READ carrying active native media is atomic: any KILL scope is ignored and the entire observation is retired, including its native context contribution ({§packet-attachment-parts}). For a model turn, native activity is the attachment selection in its actual input packet; without a model packet, a native observation is atomic by default. Text-only observations in the same selection retain ordinary scoped behavior. Neither form deletes source data or forensic evidence.
2149
2207
 
2150
2208
  §log-readable-projection Log content has two independent projections:
@@ -2253,7 +2311,10 @@ Authored `metadata` retains its opaque ordered block strings under {§scheme-met
2253
2311
  This is retained evidence, not a separate visibility or delivery lifecycle. Source mutation/deletion
2254
2312
  cannot change a retained observation. Explicit READ of its still-active log source can acquire the same media again.
2255
2313
  Every compatible-model packet includes one file part per retained, admitted READ observation, after the
2256
- packet text, in observation order. Model-response settlement never consumes an observation. KILL follows
2314
+ packet text, in observation order, each preceded by a text part that names the observation's log
2315
+ coordinate, source path and projection facts and states that the bytes are that READ's own, retained
2316
+ until its row is KILLed: an uncaptioned native part on the user turn reads as a fresh arrival (#899).
2317
+ Model-response settlement never consumes an observation. KILL follows
2257
2318
  {§log-kill-scope}; forks inherit the snapshot and ordinary projection state independently. Output withholding
2258
2319
  suppresses the complete native part under {§context-output-admission}. Unsupported routes receive only the
2259
2320
  text projection and no native charge; switching back to a compatible route exposes still-retained media.
@@ -2316,10 +2377,10 @@ single line past the end, a reversed range, empty content, a command's log row
2316
2377
 
2317
2378
  | Surface | Contract |
2318
2379
  |---|---|
2319
- | Identity | `ops://<worker>/<loop>/<turn>`, `reasoning://<worker>/<loop>/<turn>`, and `note://<worker>/<loop>/<turn>/<item>` name a worker in the current workspace and its durable coordinates. A note's item is its dispatched NOTE ordinal. The worker authority is required and case-sensitive; userinfo, ports, and queries are invalid. Source identity never depends on the reading worker. `log:///` remains local; READ, FIND and KILL reject log authorities, userinfo, ports and queries with 400, never substitute the caller's log. |
2380
+ | Identity | `ops://<worker>/<loop>/<turn>`, `reasoning://<worker>/<loop>/<turn>`, and `note://<worker>/<loop>/<turn>/<item>` name a worker in the current workspace and its durable coordinates. A note's item is its dispatched NOTE ordinal. The `outside` source ({§outside-text}) has no address: no `outside://` scheme exists, and it is reached only through `outside/event`, FORK and the digest. The worker authority is required and case-sensitive; userinfo, ports, and queries are invalid. Source identity never depends on the reading worker. `log:///` remains local; READ, FIND and KILL reject log authorities, userinfo, ports and queries with 400, never substitute the caller's log. |
2320
2381
  | Source | `ops` is exact admitted `text/vnd.plurnk`; `reasoning` is `text/plain` containing the selected original provider reasoning or a non-model producer's authored rationale. Producer identity comes from the owning turn; a harness rationale is not provider evidence. The turn decides existence and the source decides content: a turn that exists but has no source of that kind reads as the ordinary empty resource (204, empty body), never a fabricated one; a worker or turn that does not exist is 404. |
2321
2382
  | Notes | Each dispatched NOTE stores its exact literal body as an immutable `text/plain` source and returns its worker-qualified address. There may be multiple notes in a turn, from reasoning, content, or another producer. A missing note is 404, not an empty invented note. Sharing its URI uses ordinary SEND; the receiver deliberately READs it. NOTE itself sends no ambient update. |
2322
- | Retention | One ops source and one reasoning source per turn; one note source per NOTE ordinal. An optional inference-call link records provenance. Source removal follows deletion of its owning turn, never log curation. |
2383
+ | Retention | One ops source, one reasoning source and one outside source per turn; one note source per NOTE ordinal. An optional inference-call link records provenance. Source removal follows deletion of its owning turn, never log curation. |
2323
2384
  | Operations | Ordinary scoped READ, FIND, content search and COPY from any named worker's source within the workspace. FIND accepts authority and path patterns, retaining complete worker-qualified identities in results and folder selectors. READ returns data and never executes it. Sources are read-only for every actor and have no edit hashes. |
2324
2385
  | Index | Source text uses the existing derivation, FTS and graph machinery; only its derivation attachment is replaceable. |
2325
2386
  | FORK | Sources copy with the inherited turns at identical loop/turn/item coordinates under the fork's own authority. Bytes and embedded source references are preserved verbatim; an explicit reference still names its original worker. Branch receipt curation is independent; neither branch can rewrite source evidence. |
@@ -2405,7 +2466,7 @@ per-operation projection on `rx`; the aggregate remains inside dispatch.
2405
2466
  | `effect.source`, `result` | `effect` as `<source> -> <result>` | Resolved scopes mapping the source snapshot into the landed body; the admitted marker stays in durable `requested` and `tx`. |
2406
2467
  | `effect.removed`, `inserted` | `change` | Removed and inserted counts in the receipt unit. |
2407
2468
  | `effect.removedText` | `removed` | {§edit-receipt-removed-text}: a pure deletion's removed text, its first `PLURNK_SERVICE_EDIT_RECEIPT_REMOVED_LINES` lines; absent when the edit inserted anything. |
2408
- | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. |
2469
+ | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. A pattern batch's `last` context follows the first's when it differs, with no blank line between: every line of a row body carries its coordinate. |
2409
2470
  | `disposition`, `requested` | `disposition`, `requested` | A reviewer-replaced batch preserves the authored marker while stating that its attributed effect was superseded. |
2410
2471
  | `replacement` | `replacement`, `change`, canonical proposal-owner body | The one whole-resource effect actually applied by the reviewer replacement; never duplicated across authored rows. |
2411
2472
 
@@ -2550,9 +2611,10 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2550
2611
 
2551
2612
  - §log-uniform-query **Log speaks the universal query contract** — ```` ```FIND (log://…) ```` works like every scheme's FIND. Candidates are worker rows scoped by the coordinate hierarchy ({§log-coordinate-hierarchy}) and projected exactly as READ shows them. Content dialects use `Matcher.matchCandidates`; `~` full-text and `&graph` use the same persistent derivation artifacts and candidate rankers as entries. Broad results are one-channel catalog groups whose `[0].path` is `log:///loop/turn/seq/OP`; exact matcher results are flat locations ({§find-result-projection}). Log remains the core event ledger rather than duplicating rows into `entries`; its core-private storage adapter supplies one complete channel representation to the same READ projector. That adapter is not a plugin seam and grants no protocol scheme an alternate READ path.
2552
2613
  - §find-source-agnostic **The content matcher is source-agnostic** — `Matcher.matchCandidates(body, candidates, mimetypes)` applies a content matcher (regex/jsonpath/xpath/glob) to candidates from ANY source, keyed by the caller's own identity (a pathname for entries, a `loop/turn/seq` coordinate for log). The matcher never cares what table the content came from, so FIND works uniformly across schemes by construction: `EntryFind` and `Log.find` run the one shared primitive rather than re-implementing it per scheme. Log stays its own event stream, but its rows are candidates the shared matcher covers like any entry's content.
2614
+ - §find-line-anchors **A FIND regex anchors each line**, as READ, EDIT and KILL do ({§read-pattern}, {§edit-pattern}): `^` and `$` are a line's ends in every FIND content match — over entries, log rows, turn sources and a binary channel's bytes — so ```` ```FIND (django/urls/resolvers.py) /^from|^import/ ```` locates the same import lines a READ with that pattern shows, never a false 204.
2553
2615
  - §find-candidate-containment **One candidate's crash is that candidate's problem** — arbitrary member content can crash a mimetype handler mid-match (an unbalanced template partial crashed Readability and killed a 1,916-file FIND as a blank 500, #449). `Matcher.matchCandidates` contains a per-candidate handler throw: the candidate drops out exactly like unsupported content, the cause goes to daemon stderr, and only a FIND whose every candidate crashed reports a 415 whose Problem names the first crashing member and handler. The operation's other candidates always answer.
2554
2616
 
2555
- - §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does — in every spelling: a `**` glued to a name (`**.go`, `src/**.ts`) is matched as `**/*.go` / `src/**/*.ts`, never demoted to a one-level `*` the way a native matcher reads it (run67, 2026-08-29: a whole-repository search silently confined to the root). Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
2617
+ - §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does — in every spelling: a `**` glued to a name (`**.go`, `src/**.ts`) is matched as `**/*.go` / `src/**/*.ts`, never demoted to a one-level `*` the way a native matcher reads it. Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
2556
2618
 
2557
2619
  Resource-authority globs select authorities independently of the path scope.
2558
2620
  Matching resources retain their full addresses through pattern matching,
@@ -2567,6 +2629,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2567
2629
  matches the selected channel's content or derivation; path globs select
2568
2630
  resources through `(target)` ({§path-glob}).
2569
2631
  - §find-fulltext-selection Every matcher operates only over the candidate set selected by `(target)`; indexed matchers do not bypass that selection. `~query` passes the native FTS5 expression to SQLite and ranks matching candidates by ascending BM25, with resource identity breaking ties. Native BM25 uses the shared index's term statistics; candidate visibility, owner, channel and target filters determine which resources can be returned. The ordinary FIND pager selects resources for broad targets or match locations for exact targets: markerless search uses {§markerless-first-page}, `<N>` selects position N and `<N,M>` selects an inclusive range. Fractions are invalid result coordinates, not similarity thresholds. Results expose addressable matched text regions; neither cosine scores nor percentage similarity is invented. Native query-syntax failures return 400 with SQLite's diagnostic; database and implementation failures propagate.
2632
+ - §fts-word-phrase **A word with inner punctuation is the phrase of its tokens.** FTS5 barewords hold only letters, digits, `_` and non-ASCII, so before the query reaches SQLite each word outside a quoted string or `NEAR(…)` group that is not a bareword (with optional leading `^` and trailing `*`) is quoted as a phrase: `~inherited-members` searches `"inherited-members"` — the adjacent tokens `inherited members` — instead of failing as `no such column: members`, and `c++`, `x.y`, `a/b` likewise. FTS5's own syntax passes untouched: `AND`/`OR`/`NOT`/`NEAR`, `+`, quoted phrases, parentheses, and column filters (a word containing `:`, `{` or `}`, or opening with `-`). A column-filter failure keeps SQLite's diagnostic and its recovery says what the filter is and gives the bare-word and `NOT` forms: `` `members:` and `-members` are FTS5 column filters, and the index has one column; to search for a word write it bare, as `~members`, and to exclude one write `NOT` between terms, as `~a NOT members`. ``
2570
2633
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
2571
2634
  - §find-result-projection **The authored target shape determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsWeightTotal, returnedItemsWeightTotal }`:
2572
2635
 
@@ -2654,7 +2717,7 @@ same durable liveness.
2654
2717
  | Fresh operation/parser failure, without an authored WAIT | Continue before any automatic parking. |
2655
2718
  | Neither an authored WAIT nor an eligible completion request ({§kill-conclusion}) | Continue, regardless of earlier replies or live work. |
2656
2719
  | Live work and either WAIT or an eligible completion request | Park the same loop; message arrival, child or stream settlement, or stream cadence wakes it. No final-answer body is delivered while joining. |
2657
- | WAIT without live work | Continue; never invent a future wake. The first such WAIT is an honest yield and its row says only `Nothing is in flight. Continuing.`; a second in the same loop is the model waiting on a wake nothing can send, so its own row instead names what WAIT is for and what to reach for — `WAIT doesn't wait unless there's a child worker or stream to wait on. Use schedule for specific timing decisions.` The correction rides the operation's own result, which is the surface the model is certain to read (operator, 2026-09-22). |
2720
+ | WAIT without live work | Continue; never invent a future wake. The first such WAIT is an honest yield and its row says only `Nothing is in flight. Continuing.`; a second in the same loop is the model waiting on a wake nothing can send, so its own row instead names what WAIT is for and what to reach for — `WAIT doesn't wait unless there's a child worker or stream to wait on. Use schedule for specific timing decisions.` The correction rides the operation's own result, which is the surface the model is certain to read. |
2658
2721
  | Unanswered messages | Continue. |
2659
2722
  | Unobserved operation results, failures, child results or stream conclusions | Continue; the next packet presents them. |
2660
2723
  | Eligible completion request with no outstanding messages, live work or unobserved results | Conclude successfully. |
@@ -2709,8 +2772,8 @@ accounting and model-visible failure evidence remain separately owned by
2709
2772
  contains exactly one KILL without a target, scope, matcher or metadata, no hard
2710
2773
  parse error or lost boundary, and was not cut at the provider's output allowance.
2711
2774
  The operation limit must admit the entire program.
2712
- SEND, NOTE (outside text included, {§response-text-note}) and log-targeted KILL may
2713
- accompany it; every other operation requires continuation. This tolerance is unadvertised:
2775
+ SEND, NOTE and log-targeted KILL may accompany it, as may outside text ({§outside-text});
2776
+ every other operation requires continuation. This tolerance is unadvertised:
2714
2777
  model teaching requests KILL alone. Reasoning-side NOTEs remain ordinary notes.
2715
2778
  An aside is allowed. After the program settles, {§wait-obligation-matrix} admits the
2716
2779
  completion or returns a non-striking continuation/parking receipt explaining the
@@ -2722,23 +2785,23 @@ accounting and model-visible failure evidence remain separately owned by
2722
2785
  completion. New arrivals still guard the terminal transition atomically
2723
2786
  ({§completion-defers-to-messages}); an arrival concurrent with an accepted reply
2724
2787
  remains unanswered and keeps the loop running. No implicit successful exit exists.
2725
- - §response-text-note **Text outside the operations is the model's NOTE, never delivered.** Each
2726
- span {§response-text} supplies becomes an ordinary NOTE in source order, unmarked, so the
2727
- model's own log files its self-narration where it belongs. It is not an authored operation:
2728
- {§empty-turn} still strikes a turn that holds only text, and the exact emission is retained.
2729
- Delivered as a SEND, the text read as an answer and confirmed that speaking outside operations
2730
- works; reported as a count of invalid characters, it sent a model to repair its prose into
2731
- live operations (`demo-show-dont-run-qdN9u2` executed the KILL it meant to show). A NOTE
2732
- neither delivers nor concludes (operator, 2026-09-22). Storing interstitial text is a
2733
- privilege, not a right (operator, 2026-09-23): a span is retained only on a turn that
2734
- executed at least one operation, and only when it is narration. An empty turn retains no
2735
- NOTE, and on any turn a span that carries a known foreign tool-call grammar, a leaked
2736
- template token, or an operation attempt outside its fence retains none either. The exact
2737
- emission stays at `ops://`, and the packet never echoes the grammar that broke a turn for
2738
- the next turn to imitate. The registers are mechanism (`KnownToxins`). This is the far end of the teaching
2739
- scale: text outside every operation breaks the first rule of `plurnk.md` — *"YOU MUST ONLY
2740
- use valid Plurnk OPs"* — and takes the largest reinterpretation, while a
2741
- departure as small as a missing closer is read as meant ({§closer-fallback}).
2788
+ - §outside-text **Text outside the operations is the turn's `outside` source: stored, weighed, never a row.**
2789
+ The spans {§response-text} supplies are stored verbatim as one immutable `outside` turn source
2790
+ per admitted emission, in source order joined by a blank line ({§turn-source-resources}); no
2791
+ operation is minted for them, a repetitive or length-cut response stays one source, and nothing
2792
+ filters what is stored. The model hears only the weight: the next packet's Notices section
2793
+ carries `outside_text: N tokens emitted outside OPs. Discarded.`, N by {§tokenomics-agnostic-ruler};
2794
+ the text itself never enters a packet, is never delivered and never concludes.
2795
+ {§empty-turn} still strikes a turn that holds only text, with unchanged reasoning recovery
2796
+ ({§reasoning-empty-turn-read}), reply accounting and completion rules. A log-entry heading in
2797
+ outside text still rejects the attempt ({§fabricated-log-entry}); an unfenced operation line is
2798
+ not response text ({§unfenced-operation}) and so never reaches the source; `KnownToxins` guards
2799
+ only the read-back ({§reasoning-empty-turn-read}). Clients receive the text once through
2800
+ `outside/event` ({§notifications-outside-event}, {§agui-outside-text}); FORK snapshots the
2801
+ source with the turn's others; the digest names its weight. It has no address: there is no
2802
+ `outside://` scheme, and exact emissions remain at `ops://`. This is evidence, not an alternate
2803
+ authoring format: the `plurnk.md` requirement to use only valid Plurnk OPs remains, and
2804
+ {§response-text} alone owns which bytes are operations, quotations or outside text.
2742
2805
  - §loop-answer **A loop's address is what it said.** READ `ops://<worker>/<loop>` resolves to
2743
2806
  the latest reply the loop gave to the message that started it: the body of a SEND
2744
2807
  or accepted final KILL that answered that message. A running loop without one is 425; a loop that
@@ -2748,10 +2811,9 @@ accounting and model-visible failure evidence remain separately owned by
2748
2811
  remains that turn's emission. A concluded child's `loop_termination` row to its parent
2749
2812
  READs this same loop resource. Witness: `test/intg/loop-answer.test.ts`.
2750
2813
  - §empty-turn **No authored response operation is a recoverable turn, never completion.**
2751
- Count parsed response operations before outside-text and reasoning NOTEs join them;
2752
- neither enters the count. When none exist and no boundary was lost, retain the turn and its raw
2753
- sources and count one progress-contract strike, whether or not the turn carried text
2754
- ({§response-text-note}). The strike sends no notice of its own: the turn records one `_plurnk`
2814
+ Count parsed response operations before reasoning NOTEs join them; neither they nor outside
2815
+ text ({§outside-text}) enter the count. When none exist and no boundary was lost, retain the turn
2816
+ and its raw sources and count one progress-contract strike, whether or not the turn carried text. The strike sends no notice of its own: the turn records one `_plurnk`
2755
2817
  error row, `422` `The turn performed no operation.`, which rides the next packet's errors like
2756
2818
  any failure ({§operation-result-uniform-error-channel}), and its reasoning is read back to the
2757
2819
  model under {§reasoning-empty-turn-read}; the threshold terminal still says why ({§engine-rails}).
@@ -2777,8 +2839,8 @@ accounting and model-visible failure evidence remain separately owned by
2777
2839
  empty one. Attachment is owned by the one terminal seam.
2778
2840
  - §metadata-ignored **Options a scheme does not take are dropped, not refused.** A READ, FIND,
2779
2841
  EDIT or KILL carrying `[metadata]` for a scheme whose manifest takes none runs without it,
2780
- and the packet carries one `metadata_ignored` notice naming the scheme (operator,
2781
- 2026-09-12: a gentle warning, never a refusal). The `pattern` option never reaches this
2842
+ and the packet carries one `metadata_ignored` notice naming the scheme (a warning,
2843
+ never a refusal). The `pattern` option never reaches this
2782
2844
  path; it is lifted into the matcher at parse time ({§matcher-option}). SEND recipients,
2783
2845
  executions, WORK and FORK own their input and receive it whole ({§send-resource-attachments},
2784
2846
  {§env-option}); a key they do not take is their own 400.
@@ -2852,15 +2914,15 @@ anything spawns — a file is the script; a directory is refused `400 target-not
2852
2914
  pointing at `[{"cwd": "…"}]`; an absent path is refused `400 target-not-found`, giving the
2853
2915
  applicable accepted form without inferring what the model meant. When the target is a
2854
2916
  registered tool of another executor, recovery gives that tool's exact bracketed
2855
- invocation; otherwise it points at an existing script or a bare shell-command body. A non-file resource
2917
+ invocation; when it names another available executor (`sh (python3)` over a Python body, #895),
2918
+ recovery names that executor's fence — `` `python3` is its own executor; use that name on the
2919
+ opening fence and put the program in the body. ``; otherwise it points at an existing script or a bare shell-command body. A non-file resource
2856
2920
  target that cannot be read keeps the owning READ's failure identity (#163) and states
2857
2921
  the slot contract in its recovery — the resource is the program and the body its stdin;
2858
2922
  a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
2859
2923
  names the working directory only when it is not the project root, and then in the
2860
2924
  model's own project-relative form ({§fs-namespace}: the root is the model's `/`, so it
2861
- is never rendered, and no receipt or Problem carries a host-absolute path — the
2862
- batch of 2026-08-29 showed the absolute `cwd` copied back into the target slot as
2863
- `(cwd: /host/path)`). The `(path)` is a program — a script for an interpreter, a tool name for a tool
2925
+ is never rendered, and no receipt or Problem carries a host-absolute path). The `(path)` is a program — a script for an interpreter, a tool name for a tool
2864
2926
  family — and neither a command nor a working directory is ever a target. The default
2865
2927
  shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
2866
2928
 
@@ -2873,6 +2935,10 @@ streams are eight hex digits) is the writer's name for the run: with a body, the
2873
2935
  body runs as if targetless; without one, the source read refuses as before. A
2874
2936
  real stream id is always the program source.
2875
2937
 
2938
+ §exec-target-documentation **Generated reference is never a program.** A resource target under
2939
+ `worker:///_plurnk/` — the executor and scheme documentation the harness generates — is refused
2940
+ at admission, 400 `target-is-documentation`, before any source is realized or run: `` `worker:///_plurnk/plurnk/sh.md` is reference documentation the harness generated, not a program; sh cannot run it. `` With a body the recovery is `Drop the target and keep the command: the opening fence line is sh alone, with the command lines beneath it.`; without one it points to READ for the documentation and to the program's own path or a targetless heading to run something. Admitting it realized the markdown as a host temporary file that a sandboxed runtime could not open (`cannot open /tmp/plurnk-exec-….md`), and ran markdown where it could.
2941
+
2876
2942
  §exec-tool-fall-through **A tool run as a shell command is named at the failure
2877
2943
  site.** A bare shell command whose program is the name of a tool published by
2878
2944
  another enabled runtime (`brave_web_search {…}` under the default shell) exits
@@ -2915,14 +2981,29 @@ its filename, extension, sibling imports, and source-relative assets remain
2915
2981
  intact. This neither bypasses admission nor changes the executor's working
2916
2982
  directory. A disappeared native source fails; it never runs a stale projection.
2917
2983
  Other resources and derived channels supply standalone source, not a filesystem:
2918
- Core creates one temporary file, preserving the source extension, with an exclusive,
2919
- process- and database-coordinate-independent identity. No sibling tree is copied
2920
- and no relative-resource filesystem is emulated. The temporary file lives through
2984
+ Core creates one file under the scratch directory ({§exec-scratch-directory}), preserving the
2985
+ source extension, with an exclusive, process- and database-coordinate-independent identity. No
2986
+ sibling tree is copied and no relative-resource filesystem is emulated. The file lives through
2921
2987
  the executor run and core removes it after the subscription's terminal result
2922
2988
  has settled. A removal failure is reported to daemon diagnostics with its
2923
2989
  complete cause; it cannot rewrite the execution result, stream state, or
2924
2990
  completion wake.
2925
2991
 
2992
+ §exec-scratch-directory **A realized source is readable by its executor for the execution's
2993
+ lifetime.** The standalone file is written under the one scratch directory
2994
+ `PLURNK_SERVICE_EXEC_SCRATCH` names: empty, it is `$XDG_RUNTIME_DIR/plurnk` when
2995
+ `XDG_RUNTIME_DIR` is set and the platform temporary directory otherwise; an explicit value is
2996
+ an absolute directory (`~` expands), and a relative one fails at boot by name. The directory
2997
+ is created on first use with mode `0700`; each file is created exclusively with mode `0600`
2998
+ under a unique name. An executor that runs in another filesystem namespace — a container that
2999
+ mounts only the repository — is given a directory both sides can see by pointing the knob at
3000
+ it. Admission ensures the directory: one the daemon cannot create or write refuses the
3001
+ execution, 400 `scratch-unavailable`, whose detail names the directory, the knob and the
3002
+ failure code, and whose recovery has the operator point the knob at a writable absolute
3003
+ directory the executor can also read and, meanwhile, has the writer target a program file the
3004
+ executor can reach or run the command beneath a targetless heading. An execution never fails
3005
+ mid-run for this reason.
3006
+
2926
3007
  Loop-flag authority follows the selected runtime's declaration:
2927
3008
 
2928
3009
  | Target realization | Schemes that must be active |
@@ -2967,7 +3048,8 @@ Per-tool programs such as `go`, `cargo`, `make`, and `npm` do not earn executor
2967
3048
 
2968
3049
  §exec-lifetime **How long a spawn may live is the fence's metadata, one field.**
2969
3050
  `[{"lifetime": …}]` takes a duration (`30s`, `30m`, `2h`), or one of three words;
2970
- absent is `loop`. An execution takes no scope: a numeric coordinate on an
3051
+ absent is `loop`. The key is one of the service's reserved metadata keys, withheld
3052
+ from every owner by the framework ({§service-metadata-keys}). An execution takes no scope: a numeric coordinate on an
2971
3053
  executor target is refused `scope-unsupported` (400), naming the field.
2972
3054
 
2973
3055
  | `lifetime` | The spawn |
@@ -3050,7 +3132,7 @@ two states and no others:
3050
3132
 
3051
3133
  | state | what the model receives |
3052
3134
  |---|---|
3053
- | active | nothing in the Log. The `## Delegation` stream pointer names the stream with each channel's size and its growth since the last packet ({§child-orientation}); the model READs any range it wants, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing (operator, 2026-09-13). |
3135
+ | active | nothing in the Log. The `## Delegation` stream pointer names the stream with each channel's size and its growth since the last packet ({§child-orientation}); the model READs any range it wants, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing. |
3054
3136
  | terminal | ONE `origin=_plurnk` READ at the execution's channel address, born visible, that is exactly a markerless READ of the channel — its bounded first page ({§read-selection-projection}, the whole channel when it fits, the channel's own mimetype), the `range` or `region`, terminal status and Problem, `terminal: true`, and any producer-supplied integer `exitCode`. The packet writes the read resource as its operand, exactly as an explicit READ does ({§log-address-metadata}). |
3055
3137
 
3056
3138
  §stream-observation-result **One liveness fact.** The durable READ result owns
@@ -3060,11 +3142,13 @@ observations alike, independently of mimetype: `active` gives false, `closed` or
3060
3142
  projection preserves that Boolean, any included
3061
3143
  integer `exitCode`, and a producer's `page` receipt ({§executor-page-receipt}), even for an empty body. An automatic observation's atomic
3062
3144
  publication transition consumes the same result flag; private log attributes
3063
- retain only the publication offset, not a second liveness value.
3145
+ retain only the publication offset, not a second liveness value. A closed channel
3146
+ publishes only once subscription settlement has installed its terminal result;
3147
+ until then the stream is still in flight and publishes on the turn after it settles.
3064
3148
 
3065
3149
  §exec-concurrency **Bounded admission per workspace (#389).** At most
3066
- `PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (shipped `12`;
3067
- `-1` unbounded); the scope is the workspace, so neither delegation nor later turns
3150
+ `PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (`-1`
3151
+ unbounded); the scope is the workspace, so neither delegation nor later turns
3068
3152
  bypass it and no other workspace can starve it. Every admitted execution still creates its
3069
3153
  entry, channels, and open subscription before its receipt returns, so queued work is
3070
3154
  cancellable, restart-reconcilable, completion-gated, and observed through the ordinary
@@ -3098,9 +3182,7 @@ channel that holds content, and an empty sibling channel is a fact on that row
3098
3182
  (`channels: {"#stderr": 0}`), never a row of its own; only a stream that printed
3099
3183
  nothing on any channel lands one bodyless conclusion row, on its default
3100
3184
  channel, whose terminal fact, causal execution link, and available exit code make
3101
- completion explicit without invented narration (operator, 2026-09-13: the
3102
- per-channel empty row was "a useless packet bomb" — 131 of 298 conclusion rows
3103
- in the candidate4 run). A skipped channel's publication is still marked
3185
+ completion explicit without invented narration. A skipped channel's publication is still marked
3104
3186
  terminal, so the stream's termination is delivered and never left pending. KILL may curate
3105
3187
  that log row without rewinding the cursor or publishing the terminal result
3106
3188
  again; the exact terminal result and channel content remain READable at the
@@ -3181,8 +3263,8 @@ body prefixes.
3181
3263
  key WORK or FORK does not take is refused the same way. The durable row redacts the block
3182
3264
  wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
3183
3265
  provenance as the modifier's.
3184
- - §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env, shipped listing the search family), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits an executor fence, optionally followed by WAIT; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
3185
- - §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address.
3266
+ - §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits an executor fence, optionally followed by WAIT; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
3267
+ - §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address. Web acquisition and materialization are the `https` handler's {§web-materialization-contract}, reached through the scheme registry; core names no leaf package.
3186
3268
 
3187
3269
  | Input / effect | Consumer behavior |
3188
3270
  | --- | --- |
@@ -3204,7 +3286,7 @@ body prefixes.
3204
3286
 
3205
3287
  - **Client disposition** ({§methods-proposal-resolve}) — a client interface delivers accept, reject, or cancel; AG-UI uses standard resume entries ({§agui-proposal-resolve}).
3206
3288
  - **Loop disposition** ({§proposal-disposition}) — core applies the exact automatic accept/reject before observational subscribers run; automatic policy is not an event listener or client fallback.
3207
- - §proposal-timeout-cancels **Timeout is OPT-IN; the shipped default is a world that WAITS** - `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` empty (shipped) means a pending proposal - a file edit awaiting review - waits indefinitely for its human: absence is not an answer, so the service does not synthesize a cancellation. A finite positive millisecond value establishes the bound; then elapsing synthesizes `cancel` (outcome `timeout`), server-side, needing no client. Every other explicit value fails at the proposal lifecycle owner and terminalizes an already-written proposal rather than silently choosing an indefinite wait. Indefinite is with respect to the clock alone: the wait ends with its loop. A loop whose signal aborts — its own timeout, `loop.cancel`, worker `KILL` — settles every proposal it is holding through {§proposal-cancel-aborts}, carrying the abort's reason as the outcome, because a cancelled loop is not a loop awaiting a decision.
3289
+ - §proposal-timeout-cancels **Timeout is OPT-IN; an empty deadline WAITS** - an empty `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` means a pending proposal - a file edit awaiting review - waits indefinitely for its human: absence is not an answer, so the service does not synthesize a cancellation. A finite positive millisecond value establishes the bound; then elapsing synthesizes `cancel` (outcome `timeout`), server-side, needing no client. Every other explicit value fails at the proposal lifecycle owner and terminalizes an already-written proposal rather than silently choosing an indefinite wait. Indefinite is with respect to the clock alone: the wait ends with its loop. A loop whose signal aborts — its own timeout, `loop.cancel`, worker `KILL` — settles every proposal it is holding through {§proposal-cancel-aborts}, carrying the abort's reason as the outcome, because a cancelled loop is not a loop awaiting a decision.
3208
3290
 
3209
3291
  **The decision drives a one-way state transition** on `log_entries.state` (resolution is idempotent — `WHERE state='proposed'`, so a second resolution 404s):
3210
3292
 
@@ -3214,7 +3296,9 @@ body prefixes.
3214
3296
  | §proposal-reject-fails reject | `failed` | 400 | `rejected` | none — the action did not occur. |
3215
3297
  | §proposal-cancel-aborts cancel | `cancelled` | 499 | `loop_aborted` | none — the loop is abandoning. |
3216
3298
 
3217
- §proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400). A settlement the **harness itself** decided — nobody attending, no answer before a deadline, a loop or daemon ending while the proposal waited — also states that condition and an exit in its detail and `recovery`, because a reviewer's outcome is a reviewer's word but these have no author present to explain them; the one-word token stays as the forensic `outcome` either way.
3299
+ §proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400).
3300
+
3301
+ §proposal-harness-settlement **A settlement the harness itself decided names its condition and its recovery.** Nobody attending, no answer before a deadline, a loop or daemon ending while the proposal waited: each states that condition and an exit in its detail and `recovery`, because a reviewer's outcome is a reviewer's word but these have no author present to explain them; the one-word token stays as the forensic `outcome` either way ({§proposal-outcome-terse-error}).
3218
3302
 
3219
3303
  §proposal-proposed-hidden **A proposed row is invisible until it resolves.** A `state='proposed'` / 202 row is withheld from both packet materialization and `log/entry`; it surfaces exactly once after resolution, carrying its terminal status — models and clients see outcomes, never pending proposals.
3220
3304
 
@@ -3425,19 +3509,19 @@ SQLite (`node:sqlite`) with WAL mode and STRICT tables. Hand-written DDL; CI-ali
3425
3509
 
3426
3510
  No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, explicit `NOT NULL`, indexed query paths, deliberate FK `ON DELETE`/`ON UPDATE`, `WITHOUT ROWID` where access pattern warrants, generated columns, FTS5.
3427
3511
 
3428
- | Concern | Current pre-migration rule |
3512
+ | Concern | Rule |
3429
3513
  |---|---|
3430
- | §db-schema-baseline Baseline | `migrations/` holds the baseline as domain chapters — `001_workspaces`, `002_workers`, `003_loops`, `004_inference`, `005_entries`, `006_log`, `007_subscriptions`, `008_interactions` — each one `MIGRATE` block whose version is the file's numeric prefix. Together they declaratively create the complete current *shape* from an empty database: tables, indexes, views, the constraint triggers that are a table's invariants (guards that only `RAISE`), and a view's `INSTEAD OF` write path. A chapter holds no `INIT` block and no trigger that writes a row. Version numbers order the chapters on a fresh database (sqlrite applies them ascending, each in its own transaction); they are not history. |
3431
- | Shape change | Edit the chapter in place. `PRAGMA user_version` equal to the last chapter's number means only that the baseline was applied; it is not schema-evolution history. A process trigger's change needs no recreation: its `INIT` block re-declares it on the next open ({§db-process-triggers}). |
3432
- | Existing database | A table, index, or view change: delete and recreate it. Development data has no upgrade-compatibility guarantee during this phase. |
3433
- | Prohibited | Incremental migration blocks (a version that alters what an earlier chapter created), compatibility transforms, historical backfills, and upgrade-path tests. The operator must explicitly end the **No Migrations Yet** phase before any are introduced; when it ends, evolution begins at the version after the last chapter. |
3514
+ | §db-schema-baseline Baseline | Versions 1–8 of `migrations/` are the released baseline, as domain chapters — `001_workspaces`, `002_workers`, `003_loops`, `004_inference`, `005_entries`, `006_log`, `007_subscriptions`, `008_interactions` — each one `MIGRATE` block whose version is the file's numeric prefix. They create the shape 1.21.1 shipped: tables, indexes, views, the constraint triggers that are a table's invariants (guards that only `RAISE`), and a view's `INSTEAD OF` write path. No migration holds an `INIT` block or a trigger that writes a row. |
3515
+ | §db-migrations Evolution | A released version is frozen: only its comments may change. Every shape change is one new file at the next version (`009_effort` renames the #877 columns), applied by sqlrite above the database's `PRAGMA user_version`, ascending, each in its own transaction with its version bump. A fresh database takes the same path as an existing one. Each migration carries upgrade coverage: `test/intg/schema-baseline.test.ts` pins the released shape's fingerprint and migrates a released database, asserting its rows survive. A process trigger's change needs no migration: its `INIT` block re-declares it on the next open ({§db-process-triggers}). A table anything references cannot be rebuilt in a migration: foreign keys stay enforced inside the migration transaction, so the drop cascades through its children (`011_settled.sql`); such a table evolves by adding columns or redeclaring its guard triggers. Only an unreferenced table is rebuilt (`010_outside_text.sql`). |
3516
+ | §validation-topology Where an invariant is enforced | The SQL core owns each invariant: a chapter's CHECK constraints and guard triggers are its one statement, and a rule two tables share is the same expression over each column (`entry_channel_producer_result_contract` and `subscriptions_result_contract_update` hold the settled-result rule as one text, `011_settled`). Contracts (JSON Schema) are enforced at the gates: a scheme's result entering core (`Results` in plurnk-schemes) and the wire leaving to clients (plurnk-agui's `Validator` calls). Everything between trusts core and the gates and carries no defensive re-validation: a result read back from a row is parsed, never re-asserted. Witness: `test/intg/validation-topology.test.ts` applies one corpus of settled results to the gate, to chapter 5 and to chapter 7 and asserts the three agree on every row. |
3517
+ | Open failure | A missing table or column after migration means the file's shape disagrees with its version: a database from a newer release, or one from an unreleased development build. The daemon refuses to open it and names both remedies. |
3434
3518
  | §db-process-triggers Processes beside their owners | A trigger that writes rows — a cascade, a capture, an ambient event, a publication cursor, a landed curation — is a process, not shape. It is declared as an `-- INIT: <trigger name>` block in the `.sql` file beside the statements that fire it (`ambient.sql` for the ambient feed, `LoopLifecycle.sql`, `Turn.sql`, `Engine.sql` for model calls, `_entry-crud.sql`, `Log.sql`, `ChannelWrite.sql`), as `DROP TRIGGER IF EXISTS` then `CREATE TRIGGER`, so the definition is current on every open of a database whose shape is current. `MIGRATE` always precedes `INIT` and `INIT` runs on the writer only, so a process may reference any table regardless of file order and never runs on the read pool. `test/intg/schema-composition.test.ts` fails on a baseline trigger that writes, an `INIT` trigger that only guards, a block not named after its trigger or not dropping first, and a live trigger set that differs from the declared set after a first and a second open. |
3435
3519
  | §db-fk-indexes Foreign-key check paths | Every foreign-key column a delete, cascade, or parent replacement can check carries an index (partial where the column is nullable), and no registry statement's plan scans a growing table: `test/intg/schema-query-plans.test.ts` runs `EXPLAIN QUERY PLAN` over every `-- PREP` statement against the baseline and fails on a `SCAN` of a growing table, except statements that read a whole table by design (digest, startup recovery, whole-workspace listings, scheduled-loop claims). An index claim is a plan, never a grep of index names. |
3436
3520
  | §db-index-owners Every index has an owner | An explicit index earns its place one of three ways: a registry statement's plan uses it, its leading column is a foreign key whose check it serves, or it enforces uniqueness. The same test fails on any other index, naming it: an index nobody reads is a write on every insert. Duplicates of a `UNIQUE` constraint's own index and sort-only indexes no plan selects were removed on this rule; a column no statement reads (`symbol_refs.col`, `ambient_events.created_at`) is not stored. |
3437
3521
  | §db-maintenance-optimize Statistics at shutdown | The daemon's last database step before the caller closes SQLite is `PRAGMA optimize` on the writer (`maintenance_optimize`), so `sqlite_stat1` reflects tables the connection planned against, bounded by SQLite's own analysis limit; a failure there is a reported shutdown error, never silent. Retention runs before it under the operator's policy ({§retention-policy}) and ends with a WAL truncation ({§db-space-reclamation}); no periodic `ANALYZE` runs. |
3438
- | §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental`, the default, or `none`) names the mode the daemon keeps its file in. At start, before any drain, a database in another mode is converted (set the mode, one `VACUUM`, which rewrites the file and needs free disk about its size) and the journal says so with page counts before and after. Under `incremental`, every retention pass ends by stepping `PRAGMA incremental_vacuum` to completion once free pages reach `PLURNK_SERVICE_RECLAIM_MIN_FREE_BYTES` (0, the default, = every pass), and reports `reclaimedPages`; below the floor, free pages stay for SQLite to reuse. Under `none` the file never shrinks and freed pages are reused. No operator step is involved beyond the knobs. The WAL stays bounded by SQLite's automatic checkpoint (#764). |
3439
- | §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS` (1). Witnesses: `test/intg/retention.test.ts`, `test/intg/entries.test.ts`, `test/intg/fulltext-index.test.ts`. |
3440
- | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon construction (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (thirty days; -1 = no age limit) retire a completed turn's packet composition (`turn_sections`, {§packet-items}) once it is beyond the newest N packet-bearing turns of its loop or older than the age; the turn, its bag, its log rows and its accounting stay, and an open turn is never retired. `PLURNK_SERVICE_COLLECT_PACKET_ITEMS` (1) collects items no composition references. `PLURNK_SERVICE_COLLECT_CONTENTS` (1) collects stored bodies nothing holds ({§content-store}), after the collectors that release them. `PLURNK_SERVICE_COLLECT_DERIVATIONS` (1) collects derivations no channel, turn source, or log row cites — superseded editions — with their symbols (cascade) and their full-text shadow (`derivations_delete_fts`, a process trigger beside the FTS statements, on every delete path). `PLURNK_SERVICE_RETAIN_RESPONSE_TURNS` (-1) and `PLURNK_SERVICE_RETAIN_RESPONSE_MS` (thirty days) retire a settled call's response body (`model_call_responses`) once it is beyond the newest N body-bearing calls of its loop or its turn is older than the age; the call's identity, failure, capacity, admission and accounting stay, and the digest renders such a call request-only. Under the shipped defaults the durable record is kept forever, while packets and response bodies — transient evidence — are collected after thirty days, so a daemon left running for months stops growing (#788). A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
3522
+ | §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental` or `none`) names the mode the daemon keeps its file in. At start, before any drain, a database in another mode is converted (set the mode, one `VACUUM`, which rewrites the file and needs free disk about its size) and the journal says so with page counts before and after. Under `incremental`, every retention pass ends by stepping `PRAGMA incremental_vacuum` to completion once free pages reach `PLURNK_SERVICE_RECLAIM_MIN_FREE_BYTES` (0 = every pass), and reports `reclaimedPages`; below the floor, free pages stay for SQLite to reuse. Under `none` the file never shrinks and freed pages are reused. No operator step is involved beyond the knobs. The WAL stays bounded by SQLite's automatic checkpoint (#764). |
3523
+ | §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS`. Witnesses: `test/intg/retention.test.ts`, `test/intg/entries.test.ts`, `test/intg/fulltext-index.test.ts`. |
3524
+ | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon construction (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (-1 = no age limit) retire a completed turn's packet composition (`turn_sections`, {§packet-items}) once it is beyond the newest N packet-bearing turns of its loop or older than the age; the turn, its bag, its log rows and its accounting stay, and an open turn is never retired. `PLURNK_SERVICE_COLLECT_PACKET_ITEMS` collects items no composition references. `PLURNK_SERVICE_COLLECT_CONTENTS` collects stored bodies nothing holds ({§content-store}), after the collectors that release them. `PLURNK_SERVICE_COLLECT_DERIVATIONS` collects derivations no channel, turn source, or log row cites — superseded editions — with their symbols (cascade) and their full-text shadow (`derivations_delete_fts`, a process trigger beside the FTS statements, on every delete path). `PLURNK_SERVICE_RETAIN_RESPONSE_TURNS` and `PLURNK_SERVICE_RETAIN_RESPONSE_MS` retire a settled call's response body (`model_call_responses`) once it is beyond the newest N body-bearing calls of its loop or its turn is older than the age; the call's identity, failure, capacity, admission and accounting stay, and the digest renders such a call request-only. The durable record is kept; packets and response bodies — transient evidence — age out, so a daemon left running for months stops growing (#788). A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
3441
3525
 
3442
3526
  - DDL = storage truth; JSON Schemas = wire truth. They are allowed to differ where ergonomics demand.
3443
3527
  - §entry-identity-no-null **Identity components are never NULL.** `(workspace_id, scheme, authority, pathname)` is a unique key. `workspace_id` references the workspace directly with cascading deletion. Namespace schemes use empty authority; resource schemes retain their canonical authority. File members use nonempty `scheme="file"` and render as bare paths. Registration refuses `storedScheme: null`.
@@ -3589,6 +3673,14 @@ and is ignored rather than resolved against the working directory.
3589
3673
  | Reproducible cache | `$XDG_CACHE_HOME` (default `~/.cache`) | Reserved; no directory is created without an owned artifact. |
3590
3674
  | Shared global Agent Skills | User home | `.agents/skills/<name>/SKILL.md` |
3591
3675
 
3676
+ §state-root **A private daemon has one root.** `PLURNK_SERVICE_STATE_ROOT` (absolute; a leading
3677
+ `~/` expands; a relative value fails hard by name) replaces the data, state, cache and runtime homes
3678
+ with `<root>/data`, `<root>/state`, `<root>/cache` and `<root>/runtime`, the database with them
3679
+ (`PLURNK_SERVICE_DB_PATH` still names the database exactly). Configuration stays where the cascade
3680
+ reads it and the shared Agent Skills root stays under the user's home: a state root separates what
3681
+ the daemon *writes*, not what the operator supplies, and is no execution sandbox. What a launcher
3682
+ keeps or removes under a root after the daemon stops is that launcher's retention decision.
3683
+
3592
3684
  The service creates only a directory required by the current command. A newly
3593
3685
  created configuration or data directory uses mode `0700`; a newly seeded
3594
3686
  secret-bearing `.env` uses `0600`. Existing user-owned permissions are not
@@ -3648,14 +3740,15 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
3648
3740
  | Var | Purpose |
3649
3741
  |---|---|
3650
3742
  | `PLURNK_SERVICE_DB_PATH` | SQLite file path; an explicit non-empty value overrides the derived default. |
3743
+ | `PLURNK_SERVICE_SHARE_FOLDER` | Parent of the shares written when no folder is named ({§share-folder}); empty is `$XDG_STATE_HOME/plurnk/shares`. |
3651
3744
  | §operator-config-shared-keys `PLURNK_HOST`, `PLURNK_PORT` | The listener's bind address and TCP port — THE client surface, the AG-UI+ listener the plurnk-agui module binds at boot; production is single-listener. **A key the daemon and its clients both read has a shared owner**: `@plurnk/plurnk-contracts` declares these two and the optional `PLURNK_AGUI_URL` on its own panel, the one package every side depends on. The daemon folds it like any installed member's, a client folds it beneath its own, and so neither holds the other's default. The service's `--host` and `--port` flags are generated from that panel. |
3652
3745
  | §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
3653
3746
  | §operator-config-file-create-scope `PLURNK_SERVICE_FILE_CREATE_SCOPE` | Hard file-creation ceiling: `none < root < namespace`. `none` denies new filesystem files, `root` admits only paths inside `project_root`, and `namespace` also admits canonical outside-root paths. Existing-member writes are unaffected. |
3654
3747
  | `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` | Byte ceiling in `1..104857600` for one workspace-file snapshot ({§membership-materialization-limit}). |
3655
- | `PLURNK_SERVICE_MAX_TURNS` | Operator inference-turn **ceiling** — `-1` = no cap; a positive value clamps `runLoop({maxTurns})`. The effective value is persisted on the durable loop and counts completed model/inference turns cumulatively across every `202` park/resume; `_plurnk`, client, and plugin turns remain chronology but consume none of this allowance. |
3656
- | `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap (default) — every generated op dispatches. A positive value caps dispatched actions: overflow ops drop with one durable `max-commands-exceeded` error row on the next packet. The final disposition always dispatch. Tightened per workspace via `settings.maxCommands` (min wins). |
3748
+ | `PLURNK_SERVICE_MAX_TURNS` | Operator model-call **ceiling** — `-1` = no cap; a positive value clamps `runLoop({maxTurns})`. The durable worker-tree budget includes descendant calls, BARE, and park/resume under {§turn-cap-counts-the-tree}; non-model chronology consumes none. |
3749
+ | `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap — every generated op dispatches. A positive value caps dispatched actions: overflow ops drop with one durable `max-commands-exceeded` error row on the next packet. The final disposition always dispatch. Tightened per workspace via `settings.maxCommands` (min wins). |
3657
3750
  | §operator-config-loop-timeout `PLURNK_SERVICE_LOOP_TIMEOUT` | Positive ms of cumulative active execution per loop ({§loop-execution-allowance}); excludes parked/queued time. Snapshotted on first execution, retained across wakes. Exhaustion aborts in-flight work and terminates `504 loop_timeout`, including a stuck provider call. |
3658
- | `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms a turn keeps re-issuing its provider call after a recoverable provider failure before the loop parks ({§provider-recovery}); `0` parks at once. |
3751
+ | `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms of recovery after the first recoverable provider failure; `0` disables reissue. Expiry parks attended loops, concludes unattended loops, or returns BARE's failure under {§provider-recovery}. |
3659
3752
  | `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF` | First recovery delay (ms); doubles per failure up to `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX` ({§provider-recovery}). |
3660
3753
  | `PLURNK_SERVICE_MAX_STRIKES` | Consecutive turn-contract strike threshold ({§engine-rails}). |
3661
3754
  | `PLURNK_SERVICE_EMISSION_ATTEMPTS` | Completed provider responses allowed beneath one engine turn before frame admission is exhausted. Bounded interior operation errors are admitted without spending this budget. Exhaustion contributes one frame-contract strike under {§invalid-emission-attempts}. |
@@ -3671,6 +3764,7 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
3671
3764
  | `PLURNK_SERVICE_FILES_ITEMS` | Turn-0 catalog preview. Folder-capable schemes render a one-level `*` map with `dir/**` rollups; kernel docs remain recursive and explicitly complete. `-1` = markerless first pages; positive `N` explicitly caps only file-map rows; `0` / unset = off ({§actor-boundary-catalog-preview}). |
3672
3765
  | `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` | Ceiling for a model's `members` definitions in the lattice `none < root < namespace`; `none` refuses every model definition ({§members-model-scope}). |
3673
3766
  | `PLURNK_SERVICE_EXEC_CONCURRENCY` | Executions admitted at once per workspace; the rest queue FIFO with `202 queued` receipts; `-1` unbounded ({§exec-concurrency}). |
3767
+ | `PLURNK_SERVICE_EXEC_SCRATCH` | Directory a standalone execution source is written to for its run; empty derives `$XDG_RUNTIME_DIR/plurnk`, else the platform temporary directory ({§exec-scratch-directory}). |
3674
3768
  | `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` | Finite positive milliseconds before cancellation with outcome `timeout`; empty waits, and every other explicit value fails ({§proposal-timeout-cancels}). |
3675
3769
  | §operator-config-worker-warm `PLURNK_SERVICE_WORKSPACE_WARM_MS` | Milliseconds a lease-free workspace Functionality snapshot remains warm; `0` cools without grace and `-1` disables time-based cooling ({§module-workspace-residency}). |
3676
3770
  | `PLURNK_SERVICE_WORKSPACE_WARM_MAX` | Maximum lease-free workspace Functionality snapshots retained process-wide; `0` retains none and `-1` disables the idle-LRU bound ({§module-workspace-residency}). |
@@ -3679,55 +3773,22 @@ Every core knob listed is enforced at its owning read site; `.env.defaults` is t
3679
3773
 
3680
3774
  **Two override semantics — ceiling vs default.** Which kind a var is determines what "override" means across the cascade:
3681
3775
 
3682
- - **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1` ships it off; a positive value caps the per-call request). The sandbox/cost guarantee: the operator caps it; no client widens it.
3776
+ - **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1` = no cap; a positive value caps the per-call request). The sandbox/cost guarantee: the operator caps it; no client widens it.
3683
3777
  - **Default** (explicit-wins) — a fallback the most-specific setter replaces freely: `PLURNK_MODEL` (a `runLoop({selector})` request overrides it) and the config-time vars (`HOST` / `PORT` / `DB_PATH`).
3684
3778
 
3685
- §operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
3686
- test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
3687
- the policy renders in exactly one packet section. Every other tier runs the
3688
- test cascade, so shipped-default regressions are otherwise invisible by
3689
- construction.
3690
-
3691
3779
  §operator-config-flag-parity The companion **flag-parity** check binds code and
3692
3780
  template both ways: every `PLURNK_SERVICE_*` the service reads has a
3693
3781
  `.env.defaults` line — a floor, a `--flag`, and a legend entry — and every
3694
3782
  declared `PLURNK_SERVICE_*` is read. A half-landed rename therefore fails a test
3695
3783
  instead of a user's boot, and a dead knob cannot ship.
3696
3784
 
3697
- §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, ambient MCP selections and schedules disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. Configuration with a narrower or variable owner stays outside it:
3698
-
3699
- | Owner | Configuration |
3700
- |---|---|
3701
- | `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
3702
- | Live/demo scripts | The repository policy path and runner topology. |
3703
- | Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
3704
- | Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
3705
- | `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
3706
-
3707
- The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
3708
- `AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
3709
- bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
3710
- none implicitly inherit the daily-driving policy. This does not disable project
3711
- `AGENTS.md` guidance or modify the operator's file.
3712
-
3713
- The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
3785
+ Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
3714
3786
 
3715
- §operator-config-zero-pin-gate **Zero-pin is a counterfactual real-model gate,
3716
- not another configuration source.** The live/demo `:zeropin` scripts load the
3717
- ordinary environment cascade, then the test floor removes operator model tuning
3718
- before assembled package defaults fill unset values:
3787
+ External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
3719
3788
 
3720
- | Configuration family | Zero-pin treatment |
3721
- |-----------------------------------------------------------|--------------------|
3722
- | Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
3723
- | Alias-specific output and reasoning budgets | Remove |
3724
- | Model selection, routes, and credentials | Retain |
3725
- | Bare shipped generation-envelope defaults | Retain |
3726
- | Unrelated environment | Retain |
3789
+ §operator-config-cli-flags **Admin CLI flags derive only from the service package's `.env.defaults`.** Every `PLURNK_*` declared there becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). A comment immediately above the declaration becomes its `-h` description. Installed plugin defaults join the environment floor and catalog but do not implicitly expand the service executable's flag surface.
3727
3790
 
3728
- The floor reports every removed key. A gate that succeeds only with those pins
3729
- is red because provider capacity did not derive for
3730
- a fresh-user configuration.
3791
+ ### Loop limits
3731
3792
 
3732
3793
  §turn-cap-counts-the-tree **The turn ceiling is the worker tree's budget of model
3733
3794
  calls.** The owner is the current loop of the topmost ancestor-or-self worker that has
@@ -3740,7 +3801,9 @@ terminal ({§loop-terminals}) when the ceiling is met; a BARE beyond the budget
3740
3801
  429 `max-turns` before any provider call, so one turn cannot spend past it with a batch. A
3741
3802
  child loop inherits the value and binds the same count.
3742
3803
 
3743
- §operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS` ships **off** (`-1` = no cap; the loop ends via SEND, budget, strikes, or cycle detection) and, when an operator sets a positive value, the per-call request is `min()`-capped against it.
3804
+ §operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS` at `-1` is no cap; when an operator sets a positive value, the per-call request is `min()`-capped against it. Other termination rules remain independent ({§loop-terminals}).
3805
+
3806
+ ### Workspace settings
3744
3807
 
3745
3808
  §operator-config-workspace-settings **Client open-context (per workspace).**
3746
3809
  `workspace.create({ settings })` accepts only the following fields, normalizes
@@ -3775,18 +3838,55 @@ leak into another.
3775
3838
  floor — the tightest — admitting a plan and disposition with zero actions.
3776
3839
  - §operator-config-workspace-git `settings.git` (`false`) **denies** git for the workspace (`PLURNK_SERVICE_GIT_ALLOWED` AND workspace) — the client opts its workspace out of git membership and working-tree status; it can never re-enable git past the operator's service-wide lockout.
3777
3840
  - §operator-config-workspace-file-create-scope `settings.fileCreateScope` narrows `PLURNK_SERVICE_FILE_CREATE_SCOPE` by the ordered lattice `none < root < namespace`; a workspace may disable creation or confine a namespace-enabled service to its root, but never widen the operator's ceiling. Unknown service values fail configuration validation and unknown workspace values fail `workspace.create`.
3778
- - §operator-config-workspace-members-model-scope `settings.membersModelScope` narrows `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` by the same lattice; a workspace may refuse the model's definitions entirely under a permissive service ({§members-model-scope}).
3841
+ - `settings.membersModelScope` narrows `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}).
3779
3842
  - §operator-config-workspace-capabilities `settings.capabilities` is one
3780
3843
  workspace-stable `CapabilityPolicy` layer in {§capability-admission}. It may
3781
3844
  narrow any registered operation, scheme, runtime, tool, access class, or
3782
3845
  trait through the canonical `only`/`deny` selectors; it cannot register a
3783
3846
  capability or restore one removed by the service layer.
3784
3847
 
3785
- Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
3848
+ ### Gate profiles
3786
3849
 
3787
- External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
3850
+ §operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
3851
+ test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
3852
+ the policy renders in exactly one packet section. Every other tier runs the
3853
+ test cascade, so shipped-default regressions are otherwise invisible by
3854
+ construction.
3788
3855
 
3789
- §operator-config-cli-flags **Admin CLI flags derive only from the service package's `.env.defaults`.** Every `PLURNK_*` declared there becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). A comment immediately above the declaration becomes its `-h` description. Installed plugin defaults join the environment floor and catalog but do not implicitly expand the service executable's flag surface.
3856
+ §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, ambient MCP selections and schedules disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. Configuration with a narrower or variable owner stays outside it:
3857
+
3858
+ | Owner | Configuration |
3859
+ |---|---|
3860
+ | `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
3861
+ | Live/demo scripts | The repository policy path and runner topology. |
3862
+ | Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
3863
+ | Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
3864
+ | `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
3865
+
3866
+ The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
3867
+ `AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
3868
+ bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
3869
+ none implicitly inherit the daily-driving policy. This does not disable project
3870
+ `AGENTS.md` guidance or modify the operator's file.
3871
+
3872
+ The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
3873
+
3874
+ §operator-config-zero-pin-gate **Zero-pin is a counterfactual real-model gate,
3875
+ not another configuration source.** The live/demo `:zeropin` scripts load the
3876
+ ordinary environment cascade, then the test floor removes operator model tuning
3877
+ before assembled package defaults fill unset values:
3878
+
3879
+ | Configuration family | Zero-pin treatment |
3880
+ |-----------------------------------------------------------|--------------------|
3881
+ | Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
3882
+ | Alias-specific output and reasoning budgets | Remove |
3883
+ | Model selection, routes, and credentials | Retain |
3884
+ | Bare shipped generation-envelope defaults | Retain |
3885
+ | Unrelated environment | Retain |
3886
+
3887
+ The floor reports every removed key. A gate that succeeds only with those pins
3888
+ is red because provider capacity did not derive for
3889
+ a fresh-user configuration.
3790
3890
 
3791
3891
  ---
3792
3892
 
@@ -3946,8 +4046,8 @@ definition for the submitting client or worker.
3946
4046
  capability-aware operations, scoped module actions, and retained provider work
3947
4047
  lease the workspace's Functionality. Boot, workspace or worker creation,
3948
4048
  attachment, listing, naming, idle clients, and parked state alone do not.
3949
- After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` (default
3950
- `900000`) and `PLURNK_SERVICE_WORKSPACE_WARM_MAX` (default `2`) bound idle
4049
+ After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` and
4050
+ `PLURNK_SERVICE_WORKSPACE_WARM_MAX` bound idle
3951
4051
  residency. `0` disables the respective grace or allowance; `-1` disables that
3952
4052
  bound. Concurrent demand coalesces; cooling never closes a leased connection.
3953
4053
 
@@ -3963,8 +4063,8 @@ documents and its state. Only a family that holds processes prepares
3963
4063
  `runtimes` (MCP servers today); the field is absent for every other family, so
3964
4064
  warming and cooling bound workspaces, never families, and the two-stage rollback
3965
4065
  guards the manager registration of every family alongside the one family's
3966
- processes. There is no per-family residency policy (operator, 2026-09-14:
3967
- residency is MCP-specific and is not a family policy system).
4066
+ processes. There is no per-family residency policy: residency is
4067
+ MCP-specific.
3968
4068
 
3969
4069
  The version-1 baseline table `workspace_module_state` stores one JSON value
3970
4070
  per `(workspace_id, namespace_owner)`. It is configuration, not an executable
@@ -4003,7 +4103,12 @@ actions from model operations where the family contract requires it
4003
4103
  outcomes, and a snapshot with `commit`/`abort`. Successful publication commits;
4004
4104
  failure aborts; cooling tears down. Protocol continuations remain ordinary
4005
4105
  module actions. Optional `forget` releases an installed or provisioned
4006
- definition before removal; failure rejects removal ({§skills-remove}).
4106
+ definition before removal; failure rejects removal ({§skills-remove}). The
4107
+ seam's shapes — the identity a verb acts under, its options, definition
4108
+ sources, outcomes, preparation, the prepared result and the family handle —
4109
+ are declared once in `plurnk-contracts` and imported by core and every
4110
+ module; core adds only its own face of the seam, the runtime registration a
4111
+ resident family prepares and the scheme facet it may expose.
4007
4112
 
4008
4113
  An adapter may expose a `scheme` facet beneath its family's runtime namespace
4009
4114
  ({§runtime-resource-binding}). A facet claims a path subtree and is the scheme's
@@ -4179,11 +4284,12 @@ Core's behavior behind them.
4179
4284
  | §methods-conversation-worker Workspace lifecycle | `createConversationWorker({ workspaceId, name? })` | Creates a distinct model-origin root worker with empty history: a fresh conversation over the same world, not a fork or the stable default. |
4180
4285
  | Workspace lifecycle | `forkWorker({ workspaceId, workerId, name? })` | Creates a child worker that branches the source worker's history while sharing workspace state. |
4181
4286
  | §methods-workspace-rename Workspace metadata | `renameWorkspace(workspaceId, name)` | Changes only the world's unique mutable name; workers, log, and membership remain intact. |
4182
- | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?)` | Returns nonempty loop-seed prompts from the workspace's model-origin root conversations, newest-first. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`; spawned and forked child prompts are excluded. |
4287
+ | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?, workerId?)` | Returns nonempty loop-seed prompts a client addressed to the workspace's model workers, newest-first; `workerId` narrows to one worker. Authorship is the seed message's address ({§message-arrival}): a worker-issued seed (WORK, FORK, SEND to a worker) has none and is never history, whichever worker it seeded; a client prompt at a forked conversation worker is. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`. |
4183
4288
  | Workspace metadata | `listWorkspaces()`, `workspaceDerivationStatus(...)` | Reads current workspace identity and derivation progress. |
4184
4289
  | §methods-worker-read Worker topology | `readWorker({ workspaceId, identity })` | Ownership-bounds an exact id-or-name lookup and returns one durable Worker projection or `null` under {§application-worker-observation}. Supplying both identities or neither is invalid. |
4185
4290
  | §methods-worker-list Worker topology | `listWorkers(workspaceId, query?)` | Returns the workspace's durable Worker projections under {§application-worker-observation}. The origin filter is exact; an explicitly present `parentWorkerId` filters roots (`null`) or one immediate parent (id), while omission returns every lineage position. Each projection carries `kind` (`conversation`, `fork` for a child with a fork boundary, `work` for any other child) and `lifecycle`, the representative work loop's status through {§loop-lifecycle-vocabulary} (`idle` with no work loop), so a directory row shows the same lifecycle glyph the bound worker's own status gauge shows; clients infer neither (#523). |
4186
4291
  | §methods-worker-loops Loop lifecycle | `listWorkerLoops({ workspaceId, workerId })` | Ownership-checks the Worker and returns its Loops in sequence order under {§application-loop-observation}, including the validated exact terminal result when one exists. It performs no scheduling or event replay. |
4292
+ | §methods-worker-descendants Descendant spend | `descendantAccounting({ workspaceId, workerId, loopId })` | Ownership-checks the Worker and returns the {§provider-accounting} projection of every settled request on a loop that a descendant of the Worker (`parent_worker_id`, to any depth) ran after `loopId` — this delegation's spend, the same tree the turn cap counts ({§turn-cap-counts-the-tree}). The Worker's own loop is never included: its accounting stays {§notifications-loop-terminated}'s. `loopId: null` is the empty projection, explicit zero. Derived from the request ledger on every read ({§tokenomics-provider-usage}); no rollup, no second store. |
4187
4293
  | Extension actions | `listModuleActions()`, `invokeModuleAction(name, params, context)` | Lists setup-registered `{ name, scope, inputSchema, outputSchema }` descriptors in sorted order. Invocation requires a context matching the registered scope; missing names, forged scope, and missing workspace identity fail before the owner runs. Handler values remain opaque to core. |
4188
4294
 
4189
4295
  §methods-loop-run-fold-consistency **A folded prompt cannot silently reconfigure
@@ -4299,8 +4405,8 @@ registration; there is no separate per-tool availability system.
4299
4405
 
4300
4406
  §model-catalog **Model discovery is a bounded local projection, not provider
4301
4407
  activity.** Core composes the release-pinned Models.dev snapshot with
4302
- provider-owned `{§model-catalog-readiness}` and {§provider-reasoning-policy}.
4303
- Each entry includes the exact route's admitted `reasoningPolicies`; worker-level
4408
+ provider-owned `{§model-catalog-readiness}` and {§provider-effort}.
4409
+ Each entry includes the exact route's admitted `efforts`; worker-level
4304
4410
  model/spawn intersections and alias tuning are not catalog facts. The default query includes only
4305
4411
  providers configured enough to attempt; `availability: "all"` includes every
4306
4412
  catalog model with structured missing-configuration causes. Provider and text
@@ -4322,8 +4428,8 @@ cascade. A WORK/FORK child copies the spawning loop's effective spawn model
4322
4428
  no live link and begins with no override, so a later parent change affects
4323
4429
  only that worker's future loops and descendants. Client operation actors and
4324
4430
  Plurnk-owned bookkeeping workers run no model loops and own no model
4325
- selection; the model, spawn-override, and reasoning controls refuse them with
4326
- `409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or reasoning-policy change while
4431
+ selection; the model, spawn-override, and effort controls refuse them with
4432
+ `409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or effort change while
4327
4433
  the worker holds any queued, running, or parked loop is a precise
4328
4434
  `409 worker-loop-active` ({§worker-lifecycle-live}), independent of a process-local
4329
4435
  drain. The policy write checks liveness atomically, including selections carried
@@ -4333,11 +4439,11 @@ First-time initialization of an unset worker model remains legal and never
4333
4439
  rewrites an existing loop's generation snapshot.
4334
4440
 
4335
4441
  A client-created branch copies the source worker's durable model, spawn
4336
- override, and reasoning policy by value alongside its history. It retains no
4442
+ override, and effort by value alongside its history. It retains no
4337
4443
  live policy link to the source worker.
4338
4444
 
4339
- §worker-reasoning-policy **Reasoning is a durable worker policy.** Each selected
4340
- worker model has exactly one member of the shared `{§reasoning-policy-wire}`;
4445
+ §worker-effort **Reasoning is a durable worker policy.** Each selected
4446
+ worker model has exactly one member of the shared `{§effort-wire}`;
4341
4447
  a modelless worker has none. A declared alias's scoped environment value—or the
4342
4448
  global provider value for an exact route—seeds the policy only when the worker
4343
4449
  first receives its model. Model identity and reasoning
@@ -4346,17 +4452,17 @@ token ceilings remain separate concerns. An explicit policy change validates
4346
4452
  the exact policy against both the worker model and its optional spawn model and
4347
4453
  is refused while the worker owns a live or parked loop. Effort is identity-grade:
4348
4454
  every client-visible model route carries the worker's durable policy as
4349
- `reasoningPolicy`, omitted only when the cataloged model has no reasoning
4455
+ `effort`, omitted only when the cataloged model has no reasoning
4350
4456
  dimension. Client inspection
4351
4457
  returns the supported-policy intersection of those two routes. Inspection or
4352
4458
  mutation materializes the daemon-default model and policy onto an uninitialized
4353
4459
  model worker before answering; a deliberately modelless daemon remains unset.
4354
4460
 
4355
- §worker-reasoning-source **A default never masquerades as a choice.** The worker row records
4356
- `reasoning_source` beside `reasoning_policy`: `default` when the value was seeded from the alias
4357
- or provider configuration, `explicit` only after `worker.reasoning.set`. `worker.reasoning.get`
4358
- returns `source`, and a projected `ModelRoute` carries `reasoningSource` exactly when it carries
4359
- `reasoningPolicy`, so a client can render `deepdumb[low]` differently from a seeded `low` without
4461
+ §worker-effort-source **A default never masquerades as a choice.** The worker row records
4462
+ `effort_source` beside `effort`: `default` when the value was seeded from the alias
4463
+ or provider configuration, `explicit` only after `worker.effort.set`. `worker.effort.get`
4464
+ returns `source`, and a projected `ModelRoute` carries `effortSource` exactly when it carries
4465
+ `effort`, so a client can render `deepdumb[low]` differently from a seeded `low` without
4360
4466
  inferring anything. Selecting a new model keeps an explicit policy (validated against the new
4361
4467
  model) and re-derives a default one from the new alias, so a seeded value never outlives the alias
4362
4468
  that supplied it; the source itself is not part of the mid-loop generation-change check, because
@@ -4377,7 +4483,7 @@ addressed worker before the loop snapshots it; an omitted selector is not a
4377
4483
  selection and continues the worker's durable model
4378
4484
  ({§worker-model-selection}). The fully resolved provider identity and reasoning
4379
4485
  policy are persisted on the loop and remain immutable through turns, parks,
4380
- wakes, and restart ({§worker-reasoning-policy}).
4486
+ wakes, and restart ({§worker-effort}).
4381
4487
  Injecting into an existing loop with a conflicting explicit selection fails
4382
4488
  before work is accepted. Provider instances are cached; no resume path
4383
4489
  substitutes a boot default for missing or malformed durable selection.
@@ -4436,6 +4542,7 @@ adding a loop to it. LOOK text anchors resolve through the same
4436
4542
  | §notifications-stream-event-on-channel-change `stream/event` | `{ entryId, workerId, target, channel, state, contentLength, mimetype?, loop_seq?, turn_seq?, sequence? }` | Channel content grows or channel state transitions. `workerId` is the initiating actor used for conversation routing, never entry ownership or access control. `target` is the canonical resource URI. Optional numeric coordinates identify the causal log item, independently of that URI. Core-managed channel writes include the current stored `mimetype`, which may change per call ({§channel-mimetype}); the generic plugin notification capability does not require it. It carries metadata, not content; consumers read bytes by canonical workspace address. |
4437
4543
  | §notifications-stream-concluded `stream/concluded` | `{ entryId, workerId, target, subscriptionId, scheme, result, summary, wakeAction, loop_seq?, turn_seq?, sequence? }` | A subscription closes. `workerId` identifies the initiating actor; `target` is the canonical resource URI. Optional numeric fields identify the causal log item, never parsed from `target`. Exact result truth is preserved. `wakeAction` reports `wake-pending` before settlement, `no-op-active-loop` when work is already executing, `no-loop`, or `skipped-aborted`/`skipped-cancelled` for an aborted worker scope. A pending wake predicts neither execution nor recipient count; subsequent ordinary loop events report actual progress and completion. |
4438
4544
  | §notifications-notice-event `notice/event` | `{ workerId, loopId, notice: Notice }` | A transient observation or progress notice occurs. `workerId` owns loop activity; only workspace derivation progress uses `null` with `loopId=0`. It cannot alter durable history, scheduling, recovery, or model-visible failure truth. |
4545
+ | §notifications-outside-event `outside/event` | `{ workerId, loopId, turnId, coordinate, text, tokens }` | An admitted emission carried text outside every operation ({§outside-text}): once per admitted emission, the exact stored text, its packet weight, and the turn's `<worker>-<loop>-<turn>` coordinate. It is transient presentation evidence; the turn's `outside` source remains the durable authority. |
4439
4546
  | §notifications-reasoning-event `reasoning/event` | `{ workerId, loopId, turnId, modelCallId, requestSequence, phase, delta? }` | A main emission call exposes readable reasoning. Each physical request that emits reasoning owns a distinct positive `requestSequence` and balanced start/content/end stream; opening a retry closes the preceding stream before any retry delta. Only content carries a nonempty exact delta. It is transient presentation evidence, never a log row, Notice, packet field, or BARE/child channel. The settled provider response remains the durable authority. |
4440
4547
 
4441
4548
  §notifications-stream-event-failure-isolation The plugin-facing
@@ -4487,6 +4594,26 @@ flowchart LR
4487
4594
  measure --> rail[Engine budget admission and dispatch]
4488
4595
  ```
4489
4596
 
4597
+ ### §packet-wire-envelope The wire envelope
4598
+
4599
+ The packet reaches the provider under the roles the model was tuned on, its bytes unchanged:
4600
+
4601
+ | Message | Role | Content |
4602
+ |:--|:--|:--|
4603
+ | 1 | `system` | the system slot, as rendered |
4604
+ | 2 … | `user` | the log's records, one message per completed turn in record order; the first opens with `## Log` |
4605
+ | next | `assistant` | the canonical rendering ({§statement-rendering}) of every statement the parser admitted from the worker's most recent program that admitted any ({§turn-source-resources}, kind `ops`), in order and alone: free text and unadmitted forms are absent, a recovered native call ({§native-tool-calls}) appears as the operation it was read as, and an operation whose receipt failed stays, since it produced its row; turn zero's survey ({§worker-initialization-entry}) is the first, so every model request carries one |
4606
+ | last | `user` | the current turn's records, then the remaining user sections in {§packet-cache-monotone} order; native parts ride here ({§packet-attachment-parts}) |
4607
+
4608
+ Only role boundaries are added. Curation governs every record as before, so a KILLed
4609
+ row is absent from its turn's message; the one program is bounded and the model's own last operations,
4610
+ a demonstration of the grammar beside what the log made of it. Whatever sits under the assistant marker
4611
+ is what the model writes next, for better and for worse: shown its own slip, a model repeats it, so the
4612
+ slot carries the grammar's reading and never the bytes as typed. On the first request it is turn zero's
4613
+ survey, the worked example in the model's own place. The prefix through the last completed
4614
+ turn stays reusable across requests; the assistant message and the closing user message are the
4615
+ changing tail. The digest's packet artifacts record the packet; the envelope is its projection (#903).
4616
+
4490
4617
  ### §packet-cache-monotone Default order and cache locality
4491
4618
 
4492
4619
  Conditional absence never reorders the surviving default sections.
@@ -4497,7 +4624,7 @@ Conditional absence never reorders the surviving default sections.
4497
4624
  | 2 | system | `system-policy` | Operator policy; empty content is omitted on the wire. |
4498
4625
  | 3 | system | `inject` | Present only when operator notes are configured. |
4499
4626
  | 4 | user | `log` | Append-mostly model-visible history; the first user section, so the cached prefix ends inside it. |
4500
- | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T}`, the actor and the coordinate this packet's response becomes ({§packet-current-turn}). |
4627
+ | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`, the actor, the coordinate this packet's response becomes and the address of its previous program ({§packet-current-turn}). |
4501
4628
  | 6 | user | `delegation` | `Delegation`: per-turn `{workers, streams}` pointers; always present, each list `[]` when empty ({§packet-empty-sections}). |
4502
4629
  | 7 | user | `errors` | Per-turn failure pointers; empty content is omitted. |
4503
4630
  | 8 | user | `notices` | Per-turn observations; empty content is omitted. |
@@ -4538,14 +4665,14 @@ time of measurement.
4538
4665
  | Fact | Owner and unit | Time | Contract |
4539
4666
  |:-----|:---------------|:-----|:---------|
4540
4667
  | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, and rendered packet slots | Write/build | Stable, model-independent pressure and curation savings; never a tokenizer claim. |
4541
- | §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured total output envelope, in provider tokens | Before every logical request | `min(maxInputTokens, contextWindow - outputBudget)` over the known terms. The provider alone measures the complete request and admits, defers, or rejects it. |
4542
- | Provider generation envelope | Provider total output budget and optional reasoning subset, in provider tokens | Before every logical request | One total output budget includes hidden reasoning. A reasoning budget is a strict subset, never an additive reserve. |
4668
+ | §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured output reservation, in provider tokens | Before every logical request | `min(maxInputTokens, contextWindow - outputBudget)` over the known terms. The provider alone measures the complete request and admits, defers, or rejects it. |
4669
+ | Provider generation envelope | Provider response grant and optional reasoning subset, in provider tokens | Before every logical request | The reservation includes hidden reasoning; its strict reasoning subset is never additive. The response grant follows {§provider-flexed-allowance}. |
4543
4670
  | Provider usage and cost | Provider-reported input/output/cache/reasoning tokens and monetary evidence | After every physical request | Durable physical-request forensics under {§provider-usage}; never curation state or a preflight estimate. |
4544
4671
 
4545
- - §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
4672
+ - §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write as the persisted mirror of {§logical-line-count} (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
4546
4673
  - §tokenomics-render-weight-budget **Packet curation budget.** `logTokensTotal` measures the *complete assembled packet* after section transforms and readout substitution; it is not a sum of log-row `logTokens` fields. Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
4547
4674
  - §tokenomics-calibrated-readout **Convert capacity, never content costs.** Before packet assembly, Core obtains the answering model's last five settled emission responses pairing a measured packet weight with a provider-reported prompt count. The conversion factor is `sum(reported) / sum(weight)`; fewer than three samples use 1. `logTokensMax = floor(inputCapacity / factor)` converts provider capacity into curation units. Zero means no whole curation unit fits; unknown input capacity remains `null`. The built packet captures this allowance once for its readout, pressure inventory, overflow admission, and persisted client gauge. Later responses cannot change that packet's allowance. Samples are model-keyed, not worker-local; a model with no samples starts at 1. Calibration never changes stored weights, rendered receipt costs, or the immutable request history ({§tokenomics-agnostic-ruler}).
4548
- - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits and the configured total output envelope. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. `PLURNK_SERVICE_PROMPT_BUDGET`, `PLURNK_SERVICE_SAFETY`, and the additive reasoning/completion reserve knobs are retired; local and custom deployments tune context window, total output budget, optional reasoning subset, and prompt-projection percentage at their owning layers.
4675
+ - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits, the configured output reservation, and each call's response grant. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. `PLURNK_SERVICE_PROMPT_BUDGET`, `PLURNK_SERVICE_SAFETY`, and the additive reasoning/completion reserve knobs are retired; local and custom deployments tune context window, total output budget, optional reasoning subset, and prompt-projection percentage at their owning layers.
4549
4676
  - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
4550
4677
  `PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
4551
4678
  `(0, 100)`. It allocates that share of the cold-start curation allowance
@@ -4706,8 +4833,7 @@ Stream progress remains owned by {§exec-stream}.
4706
4833
 
4707
4834
  ## §packet Packet shape
4708
4835
 
4709
- §packet-markdown **The packet's Markdown projection, owned here since the packet
4710
- projection package retired (#626).** Core renders the transformed section list
4836
+ §packet-markdown **The packet's Markdown projection (#626).** Core renders the transformed section list
4711
4837
  into one system string and one user string. Within each slot, list order is
4712
4838
  preserved. A nonempty section with a header renders as an H2 immediately followed
4713
4839
  by its JSON object/array content; non-JSON content has one blank line after the
@@ -4769,8 +4895,7 @@ directly with `json_extract`. A packet transformed by a plugin, or any non-log s
4769
4895
  item. Items no composition references are transient data: `retention_collect_packet_items`
4770
4896
  collects them under the retention policy ({§retention-policy}), which is how a deleted worker's or
4771
4897
  workspace's packets release their space while shared items survive. A fork copies the composition
4772
- and shares the items ({§worker-fork-trigger}). A database written before this shape has no
4773
- `turn_packets` view and is recreated, never read, under {§db-schema-baseline}.
4898
+ and shares the items ({§worker-fork-trigger}).
4774
4899
 
4775
4900
  | Field | Presence | Contract |
4776
4901
  | ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -4793,23 +4918,30 @@ turn receives a note instead of a fabricated response.
4793
4918
  §digest-turn-artifact-identity **Digest packet artifacts project durable turns.**
4794
4919
  After selectors are applied, digest retains every turn with exact program source, a
4795
4920
  valid stored provider request, or malformed stored packet evidence; orders those
4796
- turns by durable chronology; and names them contiguously from `packet000`. The
4921
+ turns by durable chronology; and names each by its log coordinate ({§share-packet-names}). The
4797
4922
  producer does not affect projection.
4798
4923
 
4924
+ §share-packet-names **Packet artifacts carry the coordinate the log uses.** A turn's files are named
4925
+ `<worker>-<loop>-<turn>`, the worker's name and the loop and turn sequences that `log:///<loop>/<turn>/…`
4926
+ addresses: the model's first turn in its first loop is `<worker>-1-2`, because the initialization
4927
+ survey is turn 1 and writes no packet. A digest spanning several workspaces nests each workspace's
4928
+ files in a folder named for it. `digest.json` records each turn's stem as `artifact`, so no
4929
+ consumer reconstructs a name. A name that cannot be a file name, or two turns sharing one, fails.
4930
+
4799
4931
  | Artifact | Present when | Authority |
4800
4932
  |----------|--------------|-----------|
4801
- | `packetNNN.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
4802
- | `packetNNN.system.md`, `packetNNN.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
4933
+ | `<stem>.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
4934
+ | `<stem>.system.md`, `<stem>.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
4803
4935
  | `digest.json` turn `attachments` | Every turn | Stored native attachment descriptors; `[]` means a request without attachments, `null` means no valid stored request. Selection is not proof of provider acceptance. |
4804
- | `packetNNN.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
4805
- | `packetNNN.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
4806
- | `packetNNN.packet.raw.txt` | The stored packet fails typed validation | Exact stored packet text |
4807
- | `packetNNN.packet.invalid.json` | The stored packet fails typed validation | Turn identity and complete validation error chain |
4936
+ | `<stem>.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
4937
+ | `<stem>.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
4938
+ | `<stem>.packet.raw.txt` | The stored packet fails typed validation | Exact stored packet text |
4939
+ | `<stem>.packet.invalid.json` | The stored packet fails typed validation | Turn identity and complete validation error chain |
4808
4940
 
4809
4941
  A source-backed turn without provider participation therefore produces only
4810
4942
  `assistant.md`; a request-only turn produces no fabricated assistant. A
4811
4943
  source-less programmatic turn with no provider request has no forensic payload
4812
- to project and reserves no ordinal.
4944
+ to project and writes no files.
4813
4945
 
4814
4946
  The external tokenless draft and transformation boundary is owned by
4815
4947
  {§scheme-packet-transform}. Core alone extends each validated draft with its
@@ -4905,11 +5037,9 @@ producers notify the same settlement path after durable execution; reply wake-up
4905
5037
  shows in Open Messages and in an arrival row's `resource`. A client's own identity for the same
4906
5038
  message — an AG-UI message UUID, an A2A address — stays the durable `path` that correlation,
4907
5039
  delivery and reply accounting use, and remains addressable in its own scheme; the short form is an
4908
- additional alias. Answering either reaches the same message. Origin (operator, 2026-09-18): the
4909
- packet showed a 77-character `agui://anonymous/threads/…/messages/<uuid>` twice per open message,
4910
- while the docs taught the short form.
5040
+ additional alias. Answering either reaches the same message.
4911
5041
 
4912
- §message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`: left bare, it read as the model's own SEND, and a model that had finished the work could no longer find the request it was answering (operator, 2026-09-22). The Open Messages pointer carries the same attribution ({§message-arrival}).
5042
+ §message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`; a bare arrival would read as the model's own SEND and hide the request it answers. The Open Messages pointer carries the same attribution ({§message-arrival}).
4913
5043
 
4914
5044
  §message-projection **Message storage is unbounded by model context; automatic materialization is not.** Core persists every accepted message completely before packet assembly. The selected provider's derived `inputCapacity` and the alias-resolved percentage from `PLURNK_SERVICE_PROMPT_PROJECTION` derive one aggregate curation-weight allowance for the visible bodies of arrivals other than a peer worker's — every `source` that is not a `worker://` address, the loop's own assignment included. Complete bodies render when their aggregate weight fits. Otherwise all such visible rows share the allowance: full bodies consume only their required share, unused shares are redistributed, and partial bodies render the largest leading complete-line region that fits their share or an exact character-bound prefix when the first physical line alone is larger. The sum of their rendered body weights never exceeds the allowance. Every partial body carries `preview` under {§packet-extent-metadata}. The row remains complete and READable by coordinate; its `log:///` body additionally obeys deliberate curation under {§log-readable-projection}. A peer worker's message takes the ordinary bounds. When provider input capacity is unknown the percentage is underivable, so arrival rows retain the ordinary bounded projection rather than inventing capacity. This policy never rejects, summarizes, or discards a message because it exceeds a context window.
4915
5045
 
@@ -4992,24 +5122,39 @@ retain distinct contracts and lifetimes.
4992
5122
 
4993
5123
  §notice-event-notify **Client surface.** Engine Notices broadcast live via the `notice/event` notification — `{ workerId, loopId, notice: { source, kind, level, message?, position?, …kind-specific } }` per the grammar's `Notice` schema — the moment they land. A loop Notice names its owning Worker; workspace derivation progress alone carries `workerId=null, loopId=0`. AG-UI projects the same observation as the custom `plurnk.notice` event. Failures do not broadcast on this surface: they are log rows, and the client reads them through `log.read` / the `log/entry` notification, the durable log.
4994
5124
 
5125
+ §loop-status-notice **The drain beats the loop's lifecycle on the notice channel.** When the drain claims a loop to run it broadcasts `notice/event` `{ workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status: 102 } }`, and when it leaves a loop parked ({§loop-wake-identity}) the same with `status: 202`; a wake that the drain claims again is another `102`. The beat is transient: broadcast to the workspace like any notice ({§notice-event-notify}), never a log row, never in a packet. Terminals stay `loop/terminated`'s; a client that reads the beat has the running/parked edges a parked delegation otherwise never publishes.
5126
+
5127
+ §share **A share is the database's record, ready to send.** `plurnk-service share [<file.db>] [<folder>]`, and `npm run share` from a checkout, take a consistent copy of the database (`VACUUM INTO`; a live database is never read in place), and write its digest into `<folder>`, an ordinary folder the user archives or attaches however they like. Without a database the service's own is shared. Nothing is overwritten: a folder that exists and is not empty is refused, and a caller reusing a place removes it first. The share is the user's bug report and our dogfood, benchmark and forensics artifact alike.
5128
+
5129
+ §share-snapshot **A database is copied by SQLite, never by the filesystem.** `Share.snapshot(dbPath, copy)`, exported as `@plurnk/plurnk-service/share` with `Share.write`, is the one consistent copy: a byte copy of a WAL-mode database drops every committed page still in its `-wal` file. A harness that keeps the database beside its digest takes it through `snapshot`; an existing `copy` is refused.
5130
+
5131
+ §share-scope **A share is unredacted.** `--workspace=<id>` limits a share to one workspace; without it the whole database is shared. Nothing is filtered, redacted or scanned: a share holds what the models saw and wrote in scope, including prompts, file contents read, command output and reasoning, and the command says so. `--requiem` adds the forensic interview ({§digest-requiem}), which calls a model; `plurnk-service requiem <file.db> <folder>` adds it later to a digest already written.
5132
+
5133
+ §share-folder **Shares land in one place.** With no folder named, a share is a stamped child, `share-<UTC stamp>`, of `PLURNK_SERVICE_SHARE_FOLDER` (a leading `~/` expands, as for every explicit Plurnk path) or of `$XDG_STATE_HOME/plurnk/shares`.
5134
+
4995
5135
  §digest-programmatic-surface **The digest is an importable forensic surface.**
4996
5136
 
4997
5137
  | Surface | Contract |
4998
5138
  | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
4999
5139
  | Import `@plurnk/plurnk-service/digest` | Ships `Digest` and its package-owned SqlRite statements; importing performs no I/O or process action. The CLI wrapper alone invokes it. |
5000
5140
  | `run({ dbPath })` | Reads the required database and writes a complete digest to `./test/digest` relative to the caller's working directory. |
5001
- | `digestDir` | Selects a nonempty output directory. Both `run` and `requiem` refuse an output containing the input pathname or its resolved database before database/provider I/O or output writes; normalized and real paths participate in that check. `run` removes and recreates output so stale artifacts cannot survive; concurrent callers use distinct directories. |
5141
+ | `digestDir` | Selects a nonempty output path. `run` refuses a folder that exists and is not empty, and `requiem` refuses an existing `requiem.json` or `requiem.md`, before database or provider I/O; neither deletes ({§share}). Concurrent callers use distinct folders. |
5002
5142
  | Reader lifetime | `run` reads heavy evidence on demand while rendering, then closes its reader on success or failure. `requiem` closes its reader before awaiting witness inference. |
5143
+ | §candidate-pinned-runtime Candidate runtime | At launch, after its optional build, the candidate copies every workspace's package projection (`files`) into `<state>/runtime`, links third-party dependencies, and resolves `@plurnk/*` to those copies. Its daemon and its digest export both run from that copy, never the shared checkout, so neither source edits nor a concurrent candidate's or developer's rebuild after launch changes the code a run finishes on. The copy is removed once the digest is written. |
5003
5144
  | Export completion | Packet and response bodies are read and serialized one record at a time, without discarding evidence. `digest.json` is promoted from a partial file only after every artifact is written; its absence identifies an incomplete export. |
5004
5145
  | `workerId` | Narrows workers and every dependent loop, turn, turn-attached logical inference, specialization, physical request, and log row to that one worker. |
5005
5146
  | `workspaceId` | Narrows workers plus every logical inference and dependent evidence owned by one workspace, when both selectors are present they intersect. |
5006
5147
 
5007
5148
  §digest-cost-kind **Cost basis named.** A rendered Cost line carries the basis of its dollar figure: `(charged)` only when every settled request's cost is provider-charged; `(estimated — catalog rates)` when any settled request's cost is an estimate, because a mixed sum is no more trustworthy than its weakest term. A dollar figure without its basis reads as billed truth, and an estimate must never impersonate a charge.
5008
5149
 
5009
- §output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number was a fact without a use — across 9,124 recorded model turns none was cut at the allowance, while 45 emissions or reasonings spent tokens interpreting it (#826, operator, 2026-09-24). Overflow tolerance (#482) is likewise never advertised; a cut's notice names the true per-call grant from the response's own capacity record. When a provider finish is `length`, the engine emits an `output_truncated` notice (source `engine:capacity`) naming the allowance — the fact alone, never advice on what to do about it — on every path — railed or not — and the rails verdict never blames the model's grammar for a cut the engine's own ceiling made. The same precedence governs a cut so deep no operation parses: the rejection notice names the truncation as the cause, not the parser's symptom, overriding {§invalid-emission-attempts}'s parser diagnostic for `length` finishes.
5150
+ §output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number is a fact without a use (#826). Overflow tolerance (#482) is likewise never advertised; a cut's notice names the true per-call grant from the response's own capacity record. When a provider finish is `length`, the engine emits an `output_truncated` notice (source `engine:capacity`) naming the allowance — the fact alone, never advice on what to do about it — on every path — railed or not — and the rails verdict never blames the model's grammar for a cut the engine's own ceiling made. The same precedence governs a cut so deep no operation parses: the rejection notice names the truncation as the cause, not the parser's symptom, overriding {§invalid-emission-attempts}'s parser diagnostic for `length` finishes.
5010
5151
 
5011
5152
  §digest-wire-line **Wire health aggregated.** Each worker summary renders a `Wire:` line — total physical provider requests, error-outcome count, and the error percentage when nonzero. Provider-level failures are absorbed by retries below the packet stream, so without this aggregate a rate-limit storm is invisible in every summary while the model's experience stays clean.
5012
5153
 
5154
+ §digest-cache-ledger **Cacheable versus cached, per request.** For every physical provider request the digest computes its cacheable prefix: the longest common prefix, in characters, between the prompt its turn stored (the packet's rendered `system` text followed by its `user` text — the bytes `.system.md` and `.user.md` carry) and the prompt of the previous provider request in the same loop, taken as that prefix's share of the whole prompt in the packet's own token estimate ({§tokenomics-agnostic-ruler}) and applied to the provider's reported input count, so `cacheableTokens` sits in the same units as the cache read beside it; a loop's first request has 0, and a request whose turn stores no valid packet or whose provider reported no input count has none. Beside it sit the provider's reported cache read as `cachedTokens` (`provider_requests.usage_input_cache_read`) and `inputTokens` (`usage_input`). `digest.json` carries the three on every provider-request row; an inference turn line carries `cache=<cached>/<cacheable>` summed over the turn's requests; each workspace heading is followed by `Cache: <cached> of <cacheable> cacheable tokens reported (<pct>%) over <n> requests`. A provider that reported no cache field at all (null, not 0) renders `?` on the turn line and is counted apart on the workspace line (`· <k> unreported (cached=?)`), outside both sums and the percentage; a request without a stored packet or without a reported input count is likewise counted apart. The prefix measures what the daemon kept identical between consecutive requests; it is no claim about the provider's tokenization or cache-block alignment, so a provider that under-caches an identical prefix reads as such, apart from a prefix the daemon itself broke.
5155
+
5156
+ §digest-edit-census **Every model EDIT by the form it authored, how it landed, and whether it came back.** For each worker the digest reads every model-authored EDIT row and classifies the form from the row's stored marker and the durable statement's pattern: `hash` (one anchor), `line` (one line number), `range` (two marks), `insert` (the zero-width `<L,1,L,1>` form, {§zero-width-column-one-insert}), `column` (any other four-mark region), `prepend` / `append` (`<0>` / `<-1>`), `offset` (a tolerated anchor offset, {§anchor-offset}), `pattern` (a selection matcher), `whole` (no marker: a creation when it lands 201). It counts the EDITs, those refused (status ≥ 400), and the *revisits*: an EDIT of a path the same worker had edited within its previous two model turns — the shape of a repair without the claim of one. Each worker summary renders `EDITs: <n> · <form>=<count>… · refused=<k> · revisits=<r>` (`(no edits)` for none); `digest.json` carries the census as `edit_census` on every worker and stamps every EDIT log entry with its `edit_form` and `edit_revisit`. A form is a fact about what was written, never about intent; the bench sheet reads the counts as friction and leaves the judgement to the reader.
5157
+
5013
5158
  §digest-forensic-fidelity **Forensic fidelity and cardinality.** The digest's machine-readable JSON preserves every log event with its initial and current projection, causal `source`, and structured `attrs`; every exact log-KILL target effect; the exact Problem on every failed row; each loop's exact terminal result, settlement time, scheduled due time, recurring interval, and recurrence lineage; and every ordered physical provider request. Programs still produce chronological `assistant.md` artifacts after every READ receipt is KILLed; source is independent of log curation. Each stored packet validates independently: one malformed historical packet remains exact raw evidence with its complete validation error chain and never prevents healthy turns from being projected. Accounting on broader rows is the shared exact derivation from that ledger, never a second stored fact. A worker's Cost line names how many settled requests carry no usage at all (errored or aborted exchanges) — their server-side spend is unrecorded rather than silently priced as zero. The reasoning chronology distinguishes readable reasoning content from provider-reported reasoning usage: when tokens were reported but no readable content was returned, it states both facts instead of implying that no reasoning occurred. The human Markdown waterfall shows a present causal source and may preview only the Problem detail because it remains a triage projection, not the machine record. Targets reconstruct the model-visible address, including hostname, port, serialized query, and fragment; an authority-bearing URL must never degrade from `https://host/path` to `https:///path`, and durable resource coordinates render back to their authority form. Its human Markdown waterfall groups identical per-turn op outcomes and typed `entry_materialized` narrations, reporting the exact count and sequence span (`xN (seq A-B)`). Grouping keys include source and the complete target, so distinct causes, authorities, or channels never collapse. Thus amplification is conspicuous without making the diagnostic artifact itself pathological; valid packet files remain byte-identical records of what the model saw.
5014
5159
 
5015
5160
  Unrecognized actionless log rows are retained and labelled as such, not
@@ -5147,8 +5292,7 @@ or an unknown enabled alias fails the daemon at boot.
5147
5292
  namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). `none`
5148
5293
  refuses every model definition — inclusion or exclusion — as `403
5149
5294
  members/functionality/model-scope`, naming `git add` and the operator's `/members add` as
5150
- the paths that remain; `root` admits patterns inside the root; `namespace`, the shipped
5151
- default, admits `../` too.
5295
+ the paths that remain; `root` admits patterns inside the root; `namespace` admits `../` too.
5152
5296
  `auto` loops self-approve proposals, so the ceiling — not the proposal — is the guard
5153
5297
  ({§membership-baseline}). The coordinator hands `admit` the caller (`action` | `operation`)
5154
5298
  so the family bounds the model without a second grammar.
@@ -5188,7 +5332,7 @@ source it rides the definition. The workspace's durable state owns enablement
5188
5332
  model-facing trace.
5189
5333
 
5190
5334
  *Discovery is inert.* `discover {query}` searches the ecosystem registry
5191
- (`PLURNK_SERVICE_SKILLS_REGISTRY_URL`, default `https://skills.sh`; empty disables it
5335
+ (`PLURNK_SERVICE_SKILLS_REGISTRY_URL`; empty disables it
5192
5336
  with 501 `registry-not-configured`) and returns one candidate per hit with
5193
5337
  `registry` provenance and the exact `owner/repo` source. `discover {source}`
5194
5338
  lists the skills one standard package reference contains with `source`
@@ -5204,8 +5348,8 @@ names, rather than the coordinator's generic default.
5204
5348
  *Preparation.* For each enabled alias the adapter selects the host-provided
5205
5349
  tree for `service` scope or locates the directory at the filesystem scope;
5206
5350
  a workspace definition whose directory is absent is installed
5207
- through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, default `npx --yes skills`:
5208
- `add <source> --agent universal --skill <name> --yes [--global]`, run with
5351
+ through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, invoked as
5352
+ `<cli> add <source> --agent universal --skill <name> --yes [--global]`, run with
5209
5353
  `HOME` set to the service's user home so the installer's `~` is the global
5210
5354
  root) and the installed `SKILL.md` — never the installer's output — is the
5211
5355
  evidence.
@@ -5297,11 +5441,11 @@ section because they are language extensions rather than executable tools.
5297
5441
 
5298
5442
  ### §inject system.inject — the operator injection
5299
5443
 
5300
- §packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard (a deliberate setting with a broken path is a misconfig, surfaced not hidden). `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section.
5444
+ §packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard as {§policy-sections} rules. `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section.
5301
5445
 
5302
5446
  ### §policy system.policy — the client's policy injection
5303
5447
 
5304
- §policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (default `$XDG_CONFIG_HOME/plurnk/AGENTS.md`, {§host-path-layout}), with no engine-generated heading. The policy document owns its Markdown structure. Policy is the client's authoritative rules promoted into the privileged zone — NOT a log entry; the model cannot READ or KILL it. A default-absent path is silent (the section is omitted); an explicit override (env set) that fails to read fails the turn hard — a deliberate setting with a broken path is a misconfig, surfaced not hidden. Read per-turn so edits take effect live. The PROJECT `AGENTS.md` is local guidance, not policy: it rides turn 0 as the foisted `worker:///_plurnk/AGENTS.md` entry ({§turn0-agents-stunt}); references and skills use native discovery ({§skills-functionality}).
5448
+ §policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (unset resolves to the policy member in {§host-path-layout}), with no engine-generated heading. The policy document owns its Markdown structure. Policy is the client's authoritative rules promoted into the privileged zone — NOT a log entry; the model cannot READ or KILL it. A default-absent path is silent (the section is omitted); an explicit override (env set) that fails to read fails the turn hard — a deliberate setting with a broken path is a misconfig, surfaced not hidden. Read per-turn so edits take effect live. The PROJECT `AGENTS.md` is local guidance, not policy: it rides turn 0 as the foisted `worker:///_plurnk/AGENTS.md` entry ({§turn0-agents-stunt}); references and skills use native discovery ({§skills-functionality}).
5305
5449
 
5306
5450
  On first run, and only when `$XDG_CONFIG_HOME/plurnk` itself is absent, the service seeds
5307
5451
  `AGENTS.md` from `@plurnk/plurnk-meta/POLICY.md` ({§teaching-corpus}).
@@ -5461,6 +5605,17 @@ READ/EDIT/COPY/MOVE scopes use the same physical text:
5461
5605
  One/two-coordinate line shorthand is newline-aware so deleting a line does not
5462
5606
  leave an empty line. A terminal position after a final newline is an exact
5463
5607
  insertion anchor, not an additional whole line. `<1,-1>` selects all content.
5608
+
5609
+ §zero-width-column-one-insert **A zero-width region at column 1 inserts whole lines.** The
5610
+ schemes region algebra ({§slicer-text-algebra}) inserts every body verbatim; the missing
5611
+ newline is a fence artifact, so core repairs it where a fenced EDIT body becomes inserted
5612
+ content, through the one schemes helper `wholeLineBody`, at the mutation and again in the
5613
+ receipt and anchor-continuity recomputation so every site sees one body. At `<L,1,L,1>`,
5614
+ an anchored `<@hash,1,@hash,1>`, or `L` = final line + 1 when the content ends with a
5615
+ newline, a non-empty body that does not end in a newline is inserted with the content's
5616
+ line separator appended, so `X` at `<2,1,2,1>` into `a\nb` yields `a\nX\nb`. An empty
5617
+ body inserts nothing. A zero-width region at any other column stays a byte-exact insert with
5618
+ nothing appended. COPY and MOVE transfer source bytes, not a fenced body, and are untouched.
5464
5619
  The runtime also tolerates an authored three-coordinate
5465
5620
  `<startLine,startColumn,endLine>` scope, immediately lowers it to the complete
5466
5621
  four-coordinate region ending after the final code point of `endLine`, and
@@ -5572,7 +5727,7 @@ Carried from the contract walk; durable.
5572
5727
 
5573
5728
  A KILL with a text-coordinate scope aimed at an entry-bearing scheme deletes exactly that span: core prepares and dispatches it as an EDIT with an empty body over the same marker, so anchors resolve, proposals gate it, and the merge facts and receipt are the EDIT path's — while the log row records the model's KILL. Its packet metadata and canonical log body use {§edit-result-receipt-projection}. ```` ```EDIT (path) <scope> ```` with an empty body remains the same act spelled the other way; the teaching names KILL.
5574
5729
 
5575
- §kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported`). The log stays the exception: a pattern on `log:///` selects rows ({§log-curation-set-selection}), and a stream scheme's KILL is process control, so a pattern there is 400 `kill-pattern-unsupported`.
5730
+ §kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported`, {§pattern-dialect-find-only}). The log stays the exception: a pattern on `log:///` selects rows ({§log-curation-set-selection}), and a stream scheme's KILL is process control, so a pattern there is 400 `kill-pattern-unsupported`.
5576
5731
 
5577
5732
  ---
5578
5733
 
@@ -5617,3 +5772,154 @@ marker file or a sweep; an unstamped invocation is not a special case with its o
5617
5772
  simply an unstamped run with its own directory. A stamped run that passes is reclaimed when it
5618
5773
  exits; a failed suite's evidence is never touched and stays exactly where the run reported it. A cross-package test may reuse Core's migration fixture only by passing a path inside the
5619
5774
  caller's own run directory.
5775
+
5776
+ §fs-world-state **The world-state harness — coverage that closes the class.** Op-outcome tests check what an op returned; the harness checks the resulting world. `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, channel orphan-freedom, the closed admission set (every file row's origin is Git or constraint), and sig-coherence. Generated-pick incorporation and lifecycle require filesystem/Git evidence and are covered by the composed creation matrix rather than a false pure-database proxy. The harness runs as a lifecycle-test epilogue and at every soak turn boundary, where the delta half applies: an idle turn grows the entries table by ZERO. A violation names its law and its row.
5777
+
5778
+ ## Problem codes and pinned wording
5779
+
5780
+ Every Problem code core mints is named here under its family ({§problem-error-carrier} carries it); the root lint (`scripts/problem-codes.mjs`) refuses a code no owning SPEC names.
5781
+
5782
+ §problems-dispatch **Dispatch Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5783
+
5784
+ | code | status | contract |
5785
+ |---|---:|---|
5786
+ | `target-required` | 400 | *OP* requires a target path. Recovery: Write the target in parentheses on the opening fence line: `OP (path)`. |
5787
+ | `scheme-not-found` | 501 | Scheme '*name*' is not registered. |
5788
+ | `scheme-metadata-unsupported` | 400 | *OP* on '*scheme*' does not accept the [metadata] modifier. |
5789
+ | `operation-not-implemented` | 501 | Scheme '*name*' does not implement *OP* (or exec). |
5790
+ | `entry-read-not-implemented` | 501 | The '*scheme*' scheme does not provide entry reads. |
5791
+ | `entry-write-not-implemented` | 501 | The '*scheme*' scheme does not provide entry writes. |
5792
+ | `entry-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide entry deletion. |
5793
+ | `channel-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide channel deletion. |
5794
+ | `scheme-handler-threw` | 500 | The '*scheme*' scheme did not produce a result for *OP*. |
5795
+ | `exec-source-not-data` | 501 | Scheme '*name*' is not a data source for an execution. |
5796
+ | `writer-forbidden` | 403 | Writer '*origin*' cannot modify scheme '*name*'. |
5797
+ | `capability-denied` | 403 | Capability '*route*' is denied by *scope* policy. |
5798
+ | `spawn-prompt-empty` | 422 | *OP* has no prompt text: the resource is empty and there is no body. |
5799
+ | `message-not-found` | 404 | No accepted message exists at *address*. |
5800
+ | `edit-collision` | 409 | EDIT collided with the current resource state ({§edit-collision}). Recovery: *n* of *m* edits applied. READ the target for current coordinates. |
5801
+ | `edit-target-required` | 400 | A line-anchored EDIT requires a target resource. Recovery: Provide the target that rendered the line anchor. |
5802
+ | `kill-target-required` | 400 | KILL requires a target path. |
5803
+ | `kill-target-scheme-required` | 400 | KILL target requires a scheme. |
5804
+ | `worker-not-found` | 404 | Worker '*name*' does not exist in this workspace. |
5805
+ | `entry-operation-unsupported` | 400 | KILL requires an entry-bearing target; '*scheme*' does not provide one. |
5806
+ | `resource-scheme-required` | 400 | Resource selection requires an address. |
5807
+ | `channel-required` | 400 | The '*scheme*' scheme has no default channel. Recovery: Address a named channel with a URI fragment. |
5808
+ | `binary-source-unsupported` | 415 | Channel #*name* is binary and its scheme keeps no bytes to transfer. |
5809
+ | `metadata-unsupported` | 400 | *OP* takes only the env option; '*key*' is not one. |
5810
+ | `worker-name-conflict` | 409 | Worker '*name*' already exists in this workspace. Recovery: To give '*name*' more work, write `SEND (worker://_name_)` with the task as the body; to start another worker, choose a name no worker holds. |
5811
+ | `no-operation` | 422 | The turn performed no operation ({§empty-turn}). |
5812
+ | `send-target-not-a-recipient` | 400 | The addressed scheme is not a SEND recipient. Recovery: A targetless SEND answers the open messages; a directed SEND requires a recipient that implements SEND. |
5813
+
5814
+ §problems-content **Content and transfer Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5815
+
5816
+ | code | status | contract |
5817
+ |---|---:|---|
5818
+ | `handler-crashed` | 415 | The *mimetype* content handler failed on *key*: *cause*. |
5819
+ | `line-anchor-unsupported` | 400 | The byte view of *target* publishes no anchors. Recovery: Use byte coordinates: `<first,last>`. |
5820
+ | `line-anchor-invalid` | 400 | A line anchor in the marker is malformed or names no current line ({§line-anchors}). |
5821
+ | `channel-not-found` | 404 | The addressed channel does not exist at *target*. Recovery: Use one of the available channels: #*a*, #*b*. |
5822
+ | `binary-read-unsupported` | 415 | The representation at *target* is binary and cannot be rendered. |
5823
+ | `pattern-unapplicable` | 422 | The pattern could not be applied to *target*. |
5824
+ | `move-region-overlap` | 409 | MOVE cannot insert a whole channel into itself and then remove that channel. |
5825
+ | `mimetype-mismatch` | 415 | COPY or MOVE cannot write '*source-mimetype*' into a '*destination-mimetype*' channel. |
5826
+ | `binary-region-unsupported` | 415 | Channel #*name* is binary and cannot receive a textual region. |
5827
+ | `copy-destination-exists` | 409 | COPY or MOVE destination *address* already contains different content. |
5828
+ | `proposal-apply-missing` | 500 | The source scheme accepted its MOVE proposal without applying the source mutation. |
5829
+ | `line-anchor-collision` | 409 | READ coordinates collided with current content at *target*. |
5830
+
5831
+ §problems-file **File scheme Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5832
+
5833
+ | code | status | contract |
5834
+ |---|---:|---|
5835
+ | `edit-empty` | 400 | EDIT requires at least one statement. Recovery: Provide an EDIT statement. |
5836
+ | `edit-batch-mismatch` | 400 | The EDIT batch spans multiple resources. Recovery: Submit a separate EDIT batch for each resource. |
5837
+ | `line-marker-required` | 400 | EDIT of an existing file requires a line marker ({§edit-marker-required-on-existing}). Recovery: Name the lines to replace with `<@hash>` or `<@start,@end>` from a READ of the file; `<L,1,L,1>` inserts before line L, and `<1,-1>` replaces the whole file. |
5838
+ | `creation-batch-conflict` | 409 | Multiple EDIT operations attempted to create the same file. Recovery: Create the file with one EDIT before applying additional edits. |
5839
+ | `member-read-only` | 403 | The mounted member '*path*' is read-only. |
5840
+ | `project-root-required` | 400 | The workspace has no project root, so it cannot write files. |
5841
+ | `path-names-no-file` | 403 | The spelling '*path*' does not name a file: it is empty, or it names a directory. |
5842
+ | `path-occupied-by-nonmember` | 403 | A non-member file already occupies '*path*'. Recovery: Choose an unoccupied member path. |
5843
+ | `path-outside-workspace` | 403 | A symlink on '*path*' resolves outside the namespace. |
5844
+ | `binary-write-unsupported` | 415 | A text EDIT cannot author binary '*mimetype*'; COPY or MOVE the bytes instead. |
5845
+ | `file-create-excluded` | 403 | A members exclusion (`!_glob_`) covers '*path*'. Recovery: Remove or disable the excluding members definition, or choose another path. |
5846
+ | `file-create-gitignored` | 403 | Active Git policy ignores '*path*', and no members definition includes it. Recovery: Choose a Git-admitted path or add a members definition that includes it. |
5847
+ | `file-materialization-limit` | 413 | The file exceeds the materialization byte limit and is not read into the workspace. |
5848
+ | `entry-not-found` | 404 | No member of this workspace is at '*path*'. Recovery: Check the path with FIND. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. |
5849
+
5850
+ §problems-exec **Execution Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5851
+
5852
+ | code | status | contract |
5853
+ |---|---:|---|
5854
+ | `invalid-input-target` | 400 | SEND input addresses an execution, without a channel or scope. |
5855
+ | `input-unavailable` | 409 | This execution's input receiver is no longer enabled. |
5856
+ | `stream-not-found` | 404 | No execution exists at the requested address. |
5857
+ | `input-closed` | 410 | Execution input is closed. |
5858
+
5859
+ §problems-entries **Entry scheme Problems (log, worker, entries).** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5860
+
5861
+ | code | status | contract |
5862
+ |---|---:|---|
5863
+ | `read-target-required` | 400 | READ requires a log coordinate. Recovery: Provide one exact log coordinate. |
5864
+ | `coordinate-malformed` | 400 | The log coordinate '*path*' is malformed. Recovery: Use one exact loop/turn/sequence coordinate. |
5865
+ | `worker-target-required` | 400 | EDIT requires a worker:// target. Recovery: Provide the worker target. |
5866
+ | `binary-edit-unsupported` | 415 | The #*channel* channel is binary and cannot be edited. |
5867
+ | `message-not-implemented` | 501 | SEND does not deliver messages to *scheme* entries. Recovery: To reply, SEND to an Open Message address or omit the target. `SEND (worker://<name>)` sends a new message. |
5868
+ | `worker-entity-not-editable` | 400 | A worker entity is not an editable entry. Recovery: EDIT requires an entry path, such as worker:///example.md. |
5869
+ | `message-empty` | 400 | SEND has no message text or attachments. |
5870
+ | `scope-unsupported` | 400 | A worker SEND takes no scope. |
5871
+
5872
+ §problems-functionality **Server and Functionality Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5873
+
5874
+ | code | status | contract |
5875
+ |---|---:|---|
5876
+ | `service-starting` | 503 | The PLURNK service owns this listener but has not admitted its client interface yet. |
5877
+ | `configuration-unsupported` | 400 | Environment discovery reads this installation's declared configuration; client configuration contributes nothing. |
5878
+ | `name-reserved` | 400 | '*alias*' is plurnk's own: PLURNK_* configuration and provider credential names never reach a subprocess. |
5879
+ | `value-invalid` | 400 | '*alias*' needs a string value. |
5880
+ | `env-invalid` | 400 | `env` must be an object of string values; '*name*' is not a name a shell can export. |
5881
+ | `query-required` | 400 | discover takes a path or a glob. Recovery: Supply `{ "query": "<path or glob>" }`. |
5882
+ | `headless` | 409 | The workspace has no project root, so there are no file members. Recovery: Open the workspace on a project root. |
5883
+ | `definition-invalid` | 400 | A members definition is { glob }: a gitignore-style pattern, `!glob` to exclude; a skill definition names an installable skill. |
5884
+ | `model-scope` | 403 | The model may not change membership here: the members scope is none. Recovery: `git add` the file so git tracks it, or ask the operator to add it (/members add) or raise PLURNK_SERVICE_MEMBERS_MODEL_SCOPE. |
5885
+ | `registry-unreachable` | 502 | Skills registry *url* could not be reached. |
5886
+ | `registry-rejected` | 502 | Skills registry *url* answered *status*. |
5887
+ | `registry-invalid` | 502 | Skills registry *url* returned no skills array. |
5888
+ | `discover-failed` | 502 | Agent Skills source '*source*' could not be listed: *cause*. |
5889
+ | `alias-mismatch` | 400 | Alias '*alias*' must equal the skill name '*name*'. |
5890
+ | `scope-not-installable` | 400 | Service-provided skills can be enabled or disabled; adding a skill requires project or global scope. Recovery: Add it with scope "global" or open a workspace rooted in a project. |
5891
+ | `source-required` | 400 | Adding '*alias*' requires the standard installer source that provides it. |
5892
+ | `uninstall-failed` | 502 | Agent Skill '*name*' could not be removed from its *scope* root: *cause*. |
5893
+ | `workspace-not-found` | 404 | Workspace *id* does not exist. |
5894
+ | `state-not-json` | 400 | Worker module state is not JSON-serializable. |
5895
+ | `workspace-busy` | 409 | Workspace *id* is running an operation or another capability change. Recovery: Settle the current operation and retry the capability change. |
5896
+ | `not-configured` | 503 | No provider is configured for this worker. |
5897
+ | `model-worker-required` | 404 | No model worker exists for prompt injection (or to fork). |
5898
+ | `name-conflict` | 409 | The worker name is taken. Recovery: Choose another worker name. |
5899
+ | `offset-channel-required` | 400 | Recovery: Select the channel to read from the offset. |
5900
+ | `target-invalid` | 400 | Recovery: Use a scheme://path target. |
5901
+ | `proposal-not-pending` | 409 | Recovery: Refresh pending proposals before resolving one. |
5902
+ | `loop-policy-invalid` | 400 | An unattended loop cannot hold a proposal for review: nobody is present to answer. Recovery: State proposals accept or reject, or attend the loop. |
5903
+ | `scope-cancelled` | 499 | The worker scope was cancelled: *reason*. |
5904
+ | `range-not-satisfiable` | 416 | `Range <0,-1>` starts at 0, which is not a line; lines are numbered from 1. Recovery: Write `<1,-1>` to trim every line of the body; `KILL (log:///…/READ)` with no scope retires the item. |
5905
+ | `registry-not-configured` | 501 | Skills registry search is disabled; PLURNK_SERVICE_SKILLS_REGISTRY_URL is empty. |
5906
+ | `install-failed` | 502 | Agent Skill '*name*' could not be installed from '*source*': *cause* (or the installer reported it but its SKILL.md does not exist). |
5907
+ | `skill-missing` | 404 | Agent Skill '*alias*' is not installed under its *scope* root, or is not provided by this service. |
5908
+ | `skill-invalid` | 422 | Agent Skill '*alias*' is not a valid standard skill: *cause*. |
5909
+
5910
+ §pinned-wording-core **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.
5911
+
5912
+ | sentence | arises when |
5913
+ |---|---|
5914
+ | Nothing is in flight. Continuing. | a WAIT (or a premature terminal) with no live work ({§wait-obligation-matrix}) |
5915
+ | Completion deferred. Conclude with KILL alone. | a concluding KILL that carries other operations ({§kill-conclusion}) |
5916
+ | Context Token Budget Overflow: logTokensTotal exceeds logTokensMax; retained context cannot be admitted. | output admission over the retained-context ceiling |
5917
+ | This run is unattended: nobody is present to answer. | a capability that needs a present operator in an unattended loop ({§loop-attendance}) |
5918
+ | Worker name '*name*' must match `[A-Za-z0-9][A-Za-z0-9_-]{0,62}`. Recovery: Use 1–63 ASCII letters, digits, '_' or '-', starting with a letter or digit. | an invalid worker name |
5919
+ | Provide the client identifier. / Provide an absolute project path. / Use a positive integer limit. / prompt is not a non-empty string. | client input validation on the daemon's methods |
5920
+ | The stream was cancelled by KILL. | a stream terminal after KILL |
5921
+ | '*program*' exited with code *n*. | an execution's non-zero exit |
5922
+ | '*path*' is a directory, not a file; READ reads one file. Recovery: List its files with `FIND (_path_/)`, then READ one by its path. | READ of a directory |
5923
+ | The execution at ops://*worker*/*loop* has not concluded. | a bare READ of a running worker's result (425) |
5924
+ | The child provider failed. | a child's provider failure read back by its parent |
5925
+ | '*path*' exists on disk but is not a member of this workspace. Recovery: Admit it with `members (add)` and a `{"glob": "<path>"}` body. | a non-member on disk at the addressed path |