@plurnk/plurnk-service 1.21.1 → 1.22.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 (235) hide show
  1. package/.env.defaults +12 -3
  2. package/INSTALL.md +2 -2
  3. package/README.md +1 -1
  4. package/SPEC.md +264 -194
  5. package/dist/build-info.json +1 -1
  6. package/dist/content/edit-receipt.js +4 -3
  7. package/dist/content/edit-receipt.js.map +1 -1
  8. package/dist/content/index.d.ts +2 -7
  9. package/dist/content/index.d.ts.map +1 -1
  10. package/dist/content/index.js +2 -5
  11. package/dist/content/index.js.map +1 -1
  12. package/dist/content/line-marker.d.ts +1 -0
  13. package/dist/content/line-marker.d.ts.map +1 -1
  14. package/dist/content/line-marker.js +2 -0
  15. package/dist/content/line-marker.js.map +1 -1
  16. package/dist/content/matcher.d.ts +1 -1
  17. package/dist/content/matcher.d.ts.map +1 -1
  18. package/dist/content/matcher.js +7 -9
  19. package/dist/content/matcher.js.map +1 -1
  20. package/dist/content/pattern-edits.d.ts +6 -0
  21. package/dist/content/pattern-edits.d.ts.map +1 -1
  22. package/dist/content/pattern-edits.js +14 -0
  23. package/dist/content/pattern-edits.js.map +1 -1
  24. package/dist/content/read-projector.d.ts.map +1 -1
  25. package/dist/content/read-projector.js +2 -1
  26. package/dist/content/read-projector.js.map +1 -1
  27. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  28. package/dist/core/AdmittedTurnExecutor.js +5 -2
  29. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  30. package/dist/core/DataStatementRunner.js +1 -1
  31. package/dist/core/DataStatementRunner.js.map +1 -1
  32. package/dist/core/Dispatcher.d.ts.map +1 -1
  33. package/dist/core/Dispatcher.js +1 -0
  34. package/dist/core/Dispatcher.js.map +1 -1
  35. package/dist/core/Dispatcher.sql +1 -1
  36. package/dist/core/EditMutations.js +4 -4
  37. package/dist/core/EditMutations.js.map +1 -1
  38. package/dist/core/EditSequence.d.ts.map +1 -1
  39. package/dist/core/EditSequence.js +2 -1
  40. package/dist/core/EditSequence.js.map +1 -1
  41. package/dist/core/Engine.d.ts +4 -1
  42. package/dist/core/Engine.d.ts.map +1 -1
  43. package/dist/core/Engine.js +7 -2
  44. package/dist/core/Engine.js.map +1 -1
  45. package/dist/core/ExecutorRegistry.d.ts +1 -0
  46. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  47. package/dist/core/ExecutorRegistry.js +4 -0
  48. package/dist/core/ExecutorRegistry.js.map +1 -1
  49. package/dist/core/FabricatedLog.d.ts +8 -0
  50. package/dist/core/FabricatedLog.d.ts.map +1 -0
  51. package/dist/core/FabricatedLog.js +16 -0
  52. package/dist/core/FabricatedLog.js.map +1 -0
  53. package/dist/core/KnownToxins.d.ts +0 -1
  54. package/dist/core/KnownToxins.d.ts.map +1 -1
  55. package/dist/core/KnownToxins.js +3 -12
  56. package/dist/core/KnownToxins.js.map +1 -1
  57. package/dist/core/LogBody.d.ts.map +1 -1
  58. package/dist/core/LogBody.js +3 -2
  59. package/dist/core/LogBody.js.map +1 -1
  60. package/dist/core/LogVisibility.d.ts +3 -1
  61. package/dist/core/LogVisibility.d.ts.map +1 -1
  62. package/dist/core/LogVisibility.js +30 -16
  63. package/dist/core/LogVisibility.js.map +1 -1
  64. package/dist/core/LoopDriver.d.ts.map +1 -1
  65. package/dist/core/LoopDriver.js +2 -3
  66. package/dist/core/LoopDriver.js.map +1 -1
  67. package/dist/core/LoopOutcome.d.ts +0 -1
  68. package/dist/core/LoopOutcome.d.ts.map +1 -1
  69. package/dist/core/LoopOutcome.js +1 -1
  70. package/dist/core/LoopOutcome.js.map +1 -1
  71. package/dist/core/OutsideEvent.d.ts +10 -0
  72. package/dist/core/OutsideEvent.d.ts.map +1 -0
  73. package/dist/core/OutsideEvent.js +5 -0
  74. package/dist/core/OutsideEvent.js.map +1 -0
  75. package/dist/core/PacketBuilder.js +2 -2
  76. package/dist/core/PacketBuilder.js.map +1 -1
  77. package/dist/core/PatternSelection.d.ts +1 -1
  78. package/dist/core/PatternSelection.d.ts.map +1 -1
  79. package/dist/core/PatternSelection.js +8 -6
  80. package/dist/core/PatternSelection.js.map +1 -1
  81. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  82. package/dist/core/ProposalLifecycle.js +2 -4
  83. package/dist/core/ProposalLifecycle.js.map +1 -1
  84. package/dist/core/ProviderInstantiate.d.ts +4 -4
  85. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  86. package/dist/core/ProviderInstantiate.js +24 -25
  87. package/dist/core/ProviderInstantiate.js.map +1 -1
  88. package/dist/core/ProviderRecovery.d.ts +1 -1
  89. package/dist/core/ProviderRecovery.d.ts.map +1 -1
  90. package/dist/core/ProviderRecovery.js +3 -1
  91. package/dist/core/ProviderRecovery.js.map +1 -1
  92. package/dist/core/ResourceSelector.js +1 -1
  93. package/dist/core/ResourceSelector.js.map +1 -1
  94. package/dist/core/StoredPacket.d.ts +1 -1
  95. package/dist/core/StoredPacket.d.ts.map +1 -1
  96. package/dist/core/StoredPacket.js +6 -15
  97. package/dist/core/StoredPacket.js.map +1 -1
  98. package/dist/core/StrikeRail.d.ts +2 -0
  99. package/dist/core/StrikeRail.d.ts.map +1 -1
  100. package/dist/core/StrikeRail.js +7 -1
  101. package/dist/core/StrikeRail.js.map +1 -1
  102. package/dist/core/Turn.d.ts +1 -1
  103. package/dist/core/Turn.d.ts.map +1 -1
  104. package/dist/core/Turn.js.map +1 -1
  105. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  106. package/dist/core/TurnDispositionHandler.js +1 -4
  107. package/dist/core/TurnDispositionHandler.js.map +1 -1
  108. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  109. package/dist/core/TurnMaterialization.js +5 -7
  110. package/dist/core/TurnMaterialization.js.map +1 -1
  111. package/dist/core/TurnRunner.d.ts +5 -1
  112. package/dist/core/TurnRunner.d.ts.map +1 -1
  113. package/dist/core/TurnRunner.js +108 -23
  114. package/dist/core/TurnRunner.js.map +1 -1
  115. package/dist/core/WorkerControlHandler.d.ts.map +1 -1
  116. package/dist/core/WorkerControlHandler.js +6 -1
  117. package/dist/core/WorkerControlHandler.js.map +1 -1
  118. package/dist/core/WorkerName.sql +2 -2
  119. package/dist/core/attachments.d.ts +0 -3
  120. package/dist/core/attachments.d.ts.map +1 -1
  121. package/dist/core/attachments.js +3 -3
  122. package/dist/core/attachments.js.map +1 -1
  123. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  124. package/dist/core/caps/DbSubscriptionCaps.js +1 -2
  125. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  126. package/dist/core/fork.sql +2 -2
  127. package/dist/core/packet-wire.d.ts +1 -2
  128. package/dist/core/packet-wire.d.ts.map +1 -1
  129. package/dist/core/packet-wire.js +2 -3
  130. package/dist/core/packet-wire.js.map +1 -1
  131. package/dist/core/plurnk-uri.d.ts +0 -1
  132. package/dist/core/plurnk-uri.d.ts.map +1 -1
  133. package/dist/core/plurnk-uri.js +1 -1
  134. package/dist/core/plurnk-uri.js.map +1 -1
  135. package/dist/core/worker-ops.sql +1 -1
  136. package/dist/digest/Digest.d.ts.map +1 -1
  137. package/dist/digest/Digest.js +15 -12
  138. package/dist/digest/Digest.js.map +1 -1
  139. package/dist/digest/DigestRender.d.ts.map +1 -1
  140. package/dist/digest/DigestRender.js +50 -29
  141. package/dist/digest/DigestRender.js.map +1 -1
  142. package/dist/digest/DigestRequiem.d.ts.map +1 -1
  143. package/dist/digest/DigestRequiem.js +8 -4
  144. package/dist/digest/DigestRequiem.js.map +1 -1
  145. package/dist/digest/digest-paths.d.ts.map +1 -1
  146. package/dist/digest/digest-paths.js +5 -14
  147. package/dist/digest/digest-paths.js.map +1 -1
  148. package/dist/digest/digest-rows.d.ts +3 -1
  149. package/dist/digest/digest-rows.d.ts.map +1 -1
  150. package/dist/digest/digest.sql +2 -1
  151. package/dist/schemes/EffectPolicy.js +1 -1
  152. package/dist/schemes/EffectPolicy.js.map +1 -1
  153. package/dist/schemes/Exec.d.ts.map +1 -1
  154. package/dist/schemes/Exec.js +26 -6
  155. package/dist/schemes/Exec.js.map +1 -1
  156. package/dist/schemes/ExecScratch.d.ts +10 -0
  157. package/dist/schemes/ExecScratch.d.ts.map +1 -0
  158. package/dist/schemes/ExecScratch.js +43 -0
  159. package/dist/schemes/ExecScratch.js.map +1 -0
  160. package/dist/schemes/File.d.ts.map +1 -1
  161. package/dist/schemes/File.js +39 -5
  162. package/dist/schemes/File.js.map +1 -1
  163. package/dist/schemes/Log.d.ts.map +1 -1
  164. package/dist/schemes/Log.js +5 -4
  165. package/dist/schemes/Log.js.map +1 -1
  166. package/dist/schemes/TurnSource.js +2 -2
  167. package/dist/schemes/TurnSource.js.map +1 -1
  168. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  169. package/dist/schemes/_entry-crud.js +2 -3
  170. package/dist/schemes/_entry-crud.js.map +1 -1
  171. package/dist/schemes/_entry-find.d.ts.map +1 -1
  172. package/dist/schemes/_entry-find.js +2 -1
  173. package/dist/schemes/_entry-find.js.map +1 -1
  174. package/dist/schemes/_entry-fts.d.ts +1 -0
  175. package/dist/schemes/_entry-fts.d.ts.map +1 -1
  176. package/dist/schemes/_entry-fts.js +34 -2
  177. package/dist/schemes/_entry-fts.js.map +1 -1
  178. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  179. package/dist/schemes/_entry-ops.js +3 -2
  180. package/dist/schemes/_entry-ops.js.map +1 -1
  181. package/dist/schemes/_path-scope.d.ts.map +1 -1
  182. package/dist/schemes/_path-scope.js +2 -3
  183. package/dist/schemes/_path-scope.js.map +1 -1
  184. package/dist/server/Daemon.d.ts +25 -13
  185. package/dist/server/Daemon.d.ts.map +1 -1
  186. package/dist/server/Daemon.js +89 -58
  187. package/dist/server/Daemon.js.map +1 -1
  188. package/dist/server/DrainSupervisor.d.ts +3 -3
  189. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  190. package/dist/server/DrainSupervisor.js +3 -3
  191. package/dist/server/DrainSupervisor.js.map +1 -1
  192. package/dist/server/EnvFunctionality.d.ts +0 -1
  193. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  194. package/dist/server/EnvFunctionality.js +1 -1
  195. package/dist/server/EnvFunctionality.js.map +1 -1
  196. package/dist/server/MembersFunctionality.d.ts +0 -9
  197. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  198. package/dist/server/WorkerModelResolver.d.ts +8 -8
  199. package/dist/server/WorkerModelResolver.d.ts.map +1 -1
  200. package/dist/server/WorkerModelResolver.js +46 -45
  201. package/dist/server/WorkerModelResolver.js.map +1 -1
  202. package/dist/server/client-input.d.ts +1 -0
  203. package/dist/server/client-input.d.ts.map +1 -1
  204. package/dist/server/client-input.js +7 -0
  205. package/dist/server/client-input.js.map +1 -1
  206. package/dist/server/drain.sql +6 -6
  207. package/dist/server/envelope.sql +6 -6
  208. package/dist/server/loopDocs.js +1 -1
  209. package/dist/server/loopDocs.js.map +1 -1
  210. package/dist/server/model-catalog.js +2 -2
  211. package/dist/server/model-catalog.js.map +1 -1
  212. package/dist/server/model-route.d.ts +2 -2
  213. package/dist/server/model-route.d.ts.map +1 -1
  214. package/dist/server/model-route.js +5 -5
  215. package/dist/server/model-route.js.map +1 -1
  216. package/dist/service.d.ts.map +1 -1
  217. package/dist/service.js +41 -6
  218. package/dist/service.js.map +1 -1
  219. package/dist/share/Share.d.ts +15 -0
  220. package/dist/share/Share.d.ts.map +1 -0
  221. package/dist/share/Share.js +106 -0
  222. package/dist/share/Share.js.map +1 -0
  223. package/dist/share/share.sql +6 -0
  224. package/migrations/001_workspaces.sql +2 -2
  225. package/migrations/002_workers.sql +4 -6
  226. package/migrations/003_loops.sql +2 -2
  227. package/migrations/004_inference.sql +2 -2
  228. package/migrations/005_entries.sql +2 -2
  229. package/migrations/006_log.sql +2 -2
  230. package/migrations/007_subscriptions.sql +2 -2
  231. package/migrations/008_interactions.sql +2 -2
  232. package/migrations/009_effort.sql +7 -0
  233. package/migrations/010_outside_text.sql +41 -0
  234. package/migrations/011_settled.sql +133 -0
  235. package/package.json +42 -37
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
@@ -496,7 +496,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
496
496
  | `READ` | existing literal name | Collect the named worker's deliverable. |
497
497
  | `KILL` | existing literal name | Terminate the named worker or caller. |
498
498
 
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.
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 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
500
  - §worker-spawn-prompt-resource **The spawn slot is overloaded by scheme.** A `worker://` path is
501
501
  the child's address and keeps the address rules ({§worker-control-addressing}). A path of any
502
502
  other scheme is the child's prompt resource: it is read whole (`<1,-1>`) under the caller's read capabilities, composed with
@@ -506,7 +506,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
506
506
  resource with no body is `422 spawn-prompt-empty`. Naming the child and giving a resource in one
507
507
  statement is not expressible; the body can READ the resource instead. Taught in the deep
508
508
  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.
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 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
510
  - §worker-scheme-fork **Fork** — ```` ```FORK (worker://<name>)? ```` with a task body branches the
511
511
  current worker into a **named** child: its log is deep-copied
512
512
  ({§machine-processes-fork-copies-the-log}), which continues with `task`; the
@@ -541,8 +541,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
541
541
  missing name is 404. The model therefore reads the worker itself for its
542
542
  outcome or a wait rather than guessing a scratch path to "check on" it.
543
543
  - §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
544
+ 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
545
  address: READ, FIND and COPY use ordinary projections; EDIT, MOVE-source, and KILL cannot change
547
546
  it. No query, userinfo, or port is accepted. A coordinate that is not a positive safe integer is
548
547
  400; a missing loop 404; a loop that has not answered and has not concluded 425. A concluded
@@ -580,8 +579,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
580
579
  and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows; a model never infers the
581
580
  present from the last row's coordinate, which may or may not be its own turn. The block
582
581
  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
582
+ or zone anywhere. The coordinate only; the source addresses stay
585
583
  documented, not taught.
586
584
 
587
585
  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.
@@ -642,8 +640,7 @@ and never re-fetch a match.
642
640
  files by an exact creation record with recorded provenance, and never by `git add`.
643
641
  Changing this clause, the register, or the composition is an operator ruling recorded
644
642
  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.
643
+ of making a file visible to solve the problem at hand.
647
644
  - §membership-model-universe **The exception register — files in the model's universe.**
648
645
  Admitted by exact creation records (`source: "create"`, origin `constraint`), never
649
646
  staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
@@ -656,7 +653,7 @@ and never re-fetch a match.
656
653
  exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
657
654
  is not projected. (4) A definition the model proposes through the `members`
658
655
  family ({§members-functionality}), admitted only under the operator's ceiling
659
- `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (shipped `namespace`), projected with source
656
+ `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}), projected with source
660
657
  `model`, and never admitted past the repository's ignore rules or an exclusion.
661
658
  Nothing else.
662
659
  - §membership-git-membership The workspace owns the Git repository containing
@@ -713,7 +710,7 @@ identity, and terminal disposition without exposing raw bytes or a base64 lane.
713
710
  workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
714
711
  byte ceiling over one disk source before Core reads it into the canonical file
715
712
  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
713
+ contract. An oversized path remains a real member
717
714
  with an empty body channel carrying a durable 413 producer result; no diagnostic
718
715
  sentinel impersonates file content. READ therefore names the path, observed bytes,
719
716
  ceiling, and recovery through the ordinary result contract, while EDIT returns the
@@ -769,7 +766,7 @@ The version travels *with the proposal*, never re-read from the entry at accept:
769
766
 
770
767
  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
768
 
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`.
769
+ §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
770
 
774
771
  **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
772
 
@@ -859,8 +856,8 @@ waited 7.5 h, #703) is a number rather than a gap.
859
856
  §digest-storage **The digest states the file's health.** Beside the database path it
860
857
  reports the file size, the free pages it holds, its `auto_vacuum` mode, and the six
861
858
  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.
859
+ every digest (#764). The digest reads loops as stored, so it tolerates databases
860
+ missing later lifecycle columns.
864
861
 
865
862
  §loop-execution-allowance **One task has one execution allowance.** The first
866
863
  execution snapshots `PLURNK_SERVICE_LOOP_TIMEOUT` on the loop. Active segments
@@ -1081,7 +1078,7 @@ These are the complete strike sources:
1081
1078
 
1082
1079
  | Strike source | Exact trigger | Model-visible occurrence |
1083
1080
  |---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
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. |
1081
+ | 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
1082
  | Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
1086
1083
  | 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
1084
 
@@ -1114,8 +1111,8 @@ effects. Ordinary contract strikes and operator budgets remain independent.
1114
1111
  call ({§bare-inference}) takes the same recovery as the loop's own inference: each re-issue
1115
1112
  is its own model call on the ledger, and a spent window leaves the operation's result as the
1116
1113
  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,
1114
+ call fails with a kind the provider marks `retryable` ({§provider-retryable-truth}: network
1115
+ 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
1116
  notices the client (`engine:provider` / `provider_unavailable`), waits with
1120
1117
  exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling up to
1121
1118
  `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX`), and re-issues the same call against the exact frozen model
@@ -1134,18 +1131,28 @@ instead, because parking stops the execution clock and no wake would ever arrive
1134
1131
  ({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
1135
1132
  authorization, quota, an invalid response) settles a loop on a provider failure.
1136
1133
 
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
1134
+ **Contract Strikes**: every turn with one or more contract violations earns a
1135
+ strike; a turn without any contract violations clears the strikes.
1136
+
1137
+ §strike-progress-immunity **A turn in which at least one operation succeeded is
1138
+ immune to the hard-result source** (operator ruling, 2026-09-26, #853): its hard
1139
+ failures keep their exact rows, but the turn counts as progress — it earns no strike
1140
+ and clears the streak. A successful operation is any admitted operation that acts on the
1141
+ task — executions included — whose result status is `< 400`. Operations that steer the
1142
+ loop never qualify: NOTE (it cannot fail; reasoning NOTEs are filed as NOTEs, and outside
1143
+ text is no operation at all, {§outside-text}), WAIT, a parameterless KILL and a targetless SEND; a turn
1144
+ of failures beside a WAIT still strikes. The cycle source is not exempted: a repeating
1145
+ turn's operations succeed by construction, and the backstop exists to catch exactly
1146
+ that ({§engine-cycle-evidence}).
1147
+ The streak counts consecutive violating turns; `PLURNK_SERVICE_MAX_STRIKES` is the
1148
+ threshold, crossed ON the strike that reaches it; the crossing turn terminates at **508
1142
1149
  Loop Detected** when cycle-detected, otherwise **500**.
1143
1150
 
1144
1151
  The contracts, and the violation of each that strikes:
1145
1152
 
1146
1153
  | Contract | Violation that strikes |
1147
1154
  |---|---|
1148
- | operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
1155
+ | operation contract | a hard operation failure (status ≥ 400) in an admitted turn where no operation succeeded ({§strike-progress-immunity}) — soft statuses below excluded |
1149
1156
  | review contract | none: an eligible final response joins live obligations ({§completion-joins-live-work}) or continues to observe results ({§completion-defers-to-results}) |
1150
1157
  | progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
1151
1158
  | frame contract | emission attempts exhausted with no admissible turn |
@@ -1203,10 +1210,10 @@ Author-facing contract: [`@plurnk/plurnk-providers`](../plurnk-providers/SPEC.md
1203
1210
  Three current entry points:
1204
1211
 
1205
1212
  - §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.
1213
+ - §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.
1214
+ - §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
1215
 
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}).
1216
+ §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
1217
 
1211
1218
  §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
1219
 
@@ -1230,9 +1237,13 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
1230
1237
  | Parsed response | Admission |
1231
1238
  |---|---|
1232
1239
  | 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. |
1240
+ | Outside response text | Store it as the turn's `outside` source under {§outside-text}; never a row, never delivered, never completion. |
1234
1241
  | Lost boundary after a closed operation | Admit the closed operations and record the boundary diagnostic under {§unparsed-tail-boundary}. |
1235
1242
  | Lost boundary before any closed operation | Reject the attempt; neither outside text nor a reasoning NOTE substitutes for a closed response operation. |
1243
+ | Outside text carrying a log-entry heading | Reject the attempt ({§fabricated-log-entry}). |
1244
+ | 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. |
1245
+
1246
+ §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
1247
 
1237
1248
  Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
1238
1249
  discloses truncation and precludes completion; it is not independently a rejection.
@@ -1242,7 +1253,7 @@ optional, with no omission warning or invented operation ({§turn-shape}).
1242
1253
 
1243
1254
  §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
1255
 
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.
1256
+ 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
1257
 
1247
1258
  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
1259
 
@@ -1335,7 +1346,7 @@ whose text is read once per daemon and handed to the provider verbatim
1335
1346
  name with no path separator is refused with an error that says so, and an
1336
1347
  unreadable file fails the constrained generation loudly; neither ever silently
1337
1348
  becomes unconstrained. Nothing generates, validates, or grades a grammar, and
1338
- no reasoning policy is implied by one. The turn records transport as evidence:
1349
+ no effort is implied by one. The turn records transport as evidence:
1339
1350
  `railsAttached: "client"` when the provider reports it sent the grammar, or
1340
1351
  `"withheld"` when it reports it did not ({§provider-grammar-evidence}); there
1341
1352
  is no verdict key and no notice about conformance, because the parser's
@@ -1344,7 +1355,7 @@ adds no grammar state at all.
1344
1355
 
1345
1356
  ```dotenv
1346
1357
  PLURNK_MODEL_gemma=openai/macher.gguf
1347
- PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
1358
+ PLURNK_MODEL_flash=openrouter/deepseek/deepseek-v4.1-flash
1348
1359
  PLURNK_MODEL=gemma
1349
1360
  ```
1350
1361
 
@@ -1419,6 +1430,8 @@ historical execution. No address grants ownership or access restrictions.
1419
1430
 
1420
1431
  §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
1432
 
1433
+ ### File scheme: creation and misses
1434
+
1422
1435
  §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
1436
 
1424
1437
  | Case | Required admission | Accepted result |
@@ -1474,9 +1487,9 @@ Every fact names the canonical key, never the host root or an echo of the
1474
1487
  model's spelling. These classes let a caller distinguish a wrong address, an
1475
1488
  invalid range, read-only authority, and occupied hidden state without guessing.
1476
1489
 
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}).
1490
+ §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
1491
 
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.
1492
+ §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
1493
 
1481
1494
  ### §scheme-manifest Manifest
1482
1495
 
@@ -1484,10 +1497,9 @@ invalid range, read-only authority, and occupied hidden state without guessing.
1484
1497
 
1485
1498
  ### §crud CRUD primitives
1486
1499
 
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
1500
+ Core implements the `ctx.entries` capability of {§scheme-ctx-entries} and drives
1501
+ it for COPY/MOVE/KILL orchestration when a scheme does not own a more specific
1502
+ operation. A stored-entry publication atomically upserts
1491
1503
  one workspace identity, metadata, and its complete channel set. Concurrent
1492
1504
  publications expose one complete result, never a mix of channels; a failed
1493
1505
  publication leaves the prior entry unchanged. Omitted attributes preserve the
@@ -1703,12 +1715,11 @@ empty setting excludes nothing, and the first match is the observable reason.
1703
1715
 
1704
1716
  §search-size-bound Every search subject, whatever its scheme, is also bounded
1705
1717
  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
1718
+ `PLURNK_SERVICE_SEARCH_MAX_BYTES` (empty = unbounded) is `excluded` with the reason `larger than N bytes`, before
1707
1719
  its body is read. It is neither parsed for symbols nor full-text indexed; READ,
1708
1720
  FIND by path, and membership are unaffected. The reason joins the derivation
1709
1721
  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.
1722
+ collects what they leave (#729).
1712
1723
 
1713
1724
  A match produces the `excluded` derivation disposition and suppresses graph
1714
1725
  and FTS while leaving the stored channel and direct READ unchanged. The
@@ -1914,8 +1925,7 @@ range. COPY/MOVE mutation owners retain
1914
1925
  the resolved endpoint neighborhoods as compare-and-swap preconditions. There is
1915
1926
  no revision sidecar or fuzzy relocation. A range authenticates both endpoint
1916
1927
  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.
1928
+ longer range retains an unauthenticated interior gap.
1919
1929
 
1920
1930
  ### §edit EDIT
1921
1931
 
@@ -2012,8 +2022,15 @@ READ is the one fan-out core performs ({§read-fan-out}).
2012
2022
  result carries `matched`, the count of selected lines inside the scope. Zero
2013
2023
  matches is an empty read (204, `matched: 0`), never a failure. A full-text
2014
2024
  (`~`) or graph (`&`) pattern selects resources, not lines: 400
2015
- `pattern-dialect-unsupported`; a matcher its mimetype cannot run answers the
2025
+ `pattern-dialect-unsupported` ({§pattern-dialect-find-only}); a matcher its mimetype cannot run answers the
2016
2026
  matcher's own 415/400 ({§matcher-dispatch}).
2027
+ - §pattern-dialect-find-only **A `~` or `&` matcher outside FIND is refused as FIND's alone.** Every
2028
+ 400 `pattern-dialect-unsupported` — READ, EDIT, KILL, COPY, MOVE, SEND — names the model's
2029
+ matcher and says only FIND takes it, and its recovery gives both working forms with the
2030
+ model's own target: the FIND carrying that matcher, and the same operation with a text
2031
+ pattern built from the symbol or words it named (regex metacharacters escaped, words joined
2032
+ by `|`): `` Locate it with `FIND (django/urls/resolvers.py) &RoutePattern`, or select lines
2033
+ with a text pattern: `READ (django/urls/resolvers.py) /RoutePattern/`. ``
2017
2034
  - §read-fan-out **A READ over a glob reads every matching path.** `READ (pets_*.md)`
2018
2035
  and `READ (pets_*.md) /dogs/i` keep their glob ({§read-find-normalization} in the
2019
2036
  contracts SPEC) and dispatch fans them out: the ordinary FIND over the same
@@ -2033,9 +2050,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
2033
2050
  FIND failure is that failure on the authored glob. The FIND's resource page bounds
2034
2051
  the fan-out: when more paths matched than were read, one `read_fanout_bounded`
2035
2052
  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.
2053
+ resources, not lines, so that READ dispatches as the FIND survey.
2039
2054
  - §read-bytes A binary channel, and the `#bytes` view of
2040
2055
  any resource whose scheme supplies bytes, reads as the source bytes one hexadecimal
2041
2056
  octet per line: coordinate = line = byte, so `<a,b>` selects bytes, the markerless
@@ -2136,8 +2151,8 @@ turn admitted under {§empty-turn}, one runtime turn of the same loop
2136
2151
  reasoning source; its receipt renders in the next packet like any other log row.
2137
2152
  `PLURNK_REASONING_EMPTY_TURN_LINES` (alias-scoped, default in `.env.defaults`) selects the scope on the same
2138
2153
  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.
2154
+ a turn whose emission or reasoning carries a foreign tool-call grammar or leaked template token
2155
+ (`KnownToxins` names them); the strike and its error row are unchanged.
2141
2156
 
2142
2157
  ### §log-kill-scope KILL on the log: whole items and scoped bodies
2143
2158
 
@@ -2145,6 +2160,8 @@ AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMar
2145
2160
 
2146
2161
  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
2162
 
2163
+ §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.
2164
+
2148
2165
  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
2166
 
2150
2167
  §log-readable-projection Log content has two independent projections:
@@ -2316,10 +2333,10 @@ single line past the end, a reversed range, empty content, a command's log row
2316
2333
 
2317
2334
  | Surface | Contract |
2318
2335
  |---|---|
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. |
2336
+ | 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
2337
  | 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
2338
  | 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. |
2339
+ | 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
2340
  | 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
2341
  | Index | Source text uses the existing derivation, FTS and graph machinery; only its derivation attachment is replaceable. |
2325
2342
  | 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. |
@@ -2550,9 +2567,10 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2550
2567
 
2551
2568
  - §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
2569
  - §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.
2570
+ - §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
2571
  - §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
2572
 
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.
2573
+ - §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
2574
 
2557
2575
  Resource-authority globs select authorities independently of the path scope.
2558
2576
  Matching resources retain their full addresses through pattern matching,
@@ -2567,6 +2585,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2567
2585
  matches the selected channel's content or derivation; path globs select
2568
2586
  resources through `(target)` ({§path-glob}).
2569
2587
  - §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.
2588
+ - §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
2589
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
2571
2590
  - §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
2591
 
@@ -2654,7 +2673,7 @@ same durable liveness.
2654
2673
  | Fresh operation/parser failure, without an authored WAIT | Continue before any automatic parking. |
2655
2674
  | Neither an authored WAIT nor an eligible completion request ({§kill-conclusion}) | Continue, regardless of earlier replies or live work. |
2656
2675
  | 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). |
2676
+ | 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
2677
  | Unanswered messages | Continue. |
2659
2678
  | Unobserved operation results, failures, child results or stream conclusions | Continue; the next packet presents them. |
2660
2679
  | Eligible completion request with no outstanding messages, live work or unobserved results | Conclude successfully. |
@@ -2709,8 +2728,8 @@ accounting and model-visible failure evidence remain separately owned by
2709
2728
  contains exactly one KILL without a target, scope, matcher or metadata, no hard
2710
2729
  parse error or lost boundary, and was not cut at the provider's output allowance.
2711
2730
  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:
2731
+ SEND, NOTE and log-targeted KILL may accompany it, as may outside text ({§outside-text});
2732
+ every other operation requires continuation. This tolerance is unadvertised:
2714
2733
  model teaching requests KILL alone. Reasoning-side NOTEs remain ordinary notes.
2715
2734
  An aside is allowed. After the program settles, {§wait-obligation-matrix} admits the
2716
2735
  completion or returns a non-striking continuation/parking receipt explaining the
@@ -2722,23 +2741,23 @@ accounting and model-visible failure evidence remain separately owned by
2722
2741
  completion. New arrivals still guard the terminal transition atomically
2723
2742
  ({§completion-defers-to-messages}); an arrival concurrent with an accepted reply
2724
2743
  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}).
2744
+ - §outside-text **Text outside the operations is the turn's `outside` source: stored, weighed, never a row.**
2745
+ The spans {§response-text} supplies are stored verbatim as one immutable `outside` turn source
2746
+ per admitted emission, in source order joined by a blank line ({§turn-source-resources}); no
2747
+ operation is minted for them, a repetitive or length-cut response stays one source, and nothing
2748
+ filters what is stored. The model hears only the weight: the next packet's Notices section
2749
+ carries `outside_text: N tokens emitted outside OPs. Discarded.`, N by {§tokenomics-agnostic-ruler};
2750
+ the text itself never enters a packet, is never delivered and never concludes.
2751
+ {§empty-turn} still strikes a turn that holds only text, with unchanged reasoning recovery
2752
+ ({§reasoning-empty-turn-read}), reply accounting and completion rules. A log-entry heading in
2753
+ outside text still rejects the attempt ({§fabricated-log-entry}); an unfenced operation line is
2754
+ not response text ({§unfenced-operation}) and so never reaches the source; `KnownToxins` guards
2755
+ only the read-back ({§reasoning-empty-turn-read}). Clients receive the text once through
2756
+ `outside/event` ({§notifications-outside-event}, {§agui-outside-text}); FORK snapshots the
2757
+ source with the turn's others; the digest names its weight. It has no address: there is no
2758
+ `outside://` scheme, and exact emissions remain at `ops://`. This is evidence, not an alternate
2759
+ authoring format: the `plurnk.md` requirement to use only valid Plurnk OPs remains, and
2760
+ {§response-text} alone owns which bytes are operations, quotations or outside text.
2742
2761
  - §loop-answer **A loop's address is what it said.** READ `ops://<worker>/<loop>` resolves to
2743
2762
  the latest reply the loop gave to the message that started it: the body of a SEND
2744
2763
  or accepted final KILL that answered that message. A running loop without one is 425; a loop that
@@ -2748,10 +2767,9 @@ accounting and model-visible failure evidence remain separately owned by
2748
2767
  remains that turn's emission. A concluded child's `loop_termination` row to its parent
2749
2768
  READs this same loop resource. Witness: `test/intg/loop-answer.test.ts`.
2750
2769
  - §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`
2770
+ Count parsed response operations before reasoning NOTEs join them; neither they nor outside
2771
+ text ({§outside-text}) enter the count. When none exist and no boundary was lost, retain the turn
2772
+ 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
2773
  error row, `422` `The turn performed no operation.`, which rides the next packet's errors like
2756
2774
  any failure ({§operation-result-uniform-error-channel}), and its reasoning is read back to the
2757
2775
  model under {§reasoning-empty-turn-read}; the threshold terminal still says why ({§engine-rails}).
@@ -2777,8 +2795,8 @@ accounting and model-visible failure evidence remain separately owned by
2777
2795
  empty one. Attachment is owned by the one terminal seam.
2778
2796
  - §metadata-ignored **Options a scheme does not take are dropped, not refused.** A READ, FIND,
2779
2797
  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
2798
+ and the packet carries one `metadata_ignored` notice naming the scheme (a warning,
2799
+ never a refusal). The `pattern` option never reaches this
2782
2800
  path; it is lifted into the matcher at parse time ({§matcher-option}). SEND recipients,
2783
2801
  executions, WORK and FORK own their input and receive it whole ({§send-resource-attachments},
2784
2802
  {§env-option}); a key they do not take is their own 400.
@@ -2858,9 +2876,7 @@ the slot contract in its recovery — the resource is the program and the body i
2858
2876
  a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
2859
2877
  names the working directory only when it is not the project root, and then in the
2860
2878
  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
2879
+ 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
2880
  family — and neither a command nor a working directory is ever a target. The default
2865
2881
  shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
2866
2882
 
@@ -2873,6 +2889,10 @@ streams are eight hex digits) is the writer's name for the run: with a body, the
2873
2889
  body runs as if targetless; without one, the source read refuses as before. A
2874
2890
  real stream id is always the program source.
2875
2891
 
2892
+ §exec-target-documentation **Generated reference is never a program.** A resource target under
2893
+ `worker:///_plurnk/` — the executor and scheme documentation the harness generates — is refused
2894
+ 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.
2895
+
2876
2896
  §exec-tool-fall-through **A tool run as a shell command is named at the failure
2877
2897
  site.** A bare shell command whose program is the name of a tool published by
2878
2898
  another enabled runtime (`brave_web_search {…}` under the default shell) exits
@@ -2915,14 +2935,29 @@ its filename, extension, sibling imports, and source-relative assets remain
2915
2935
  intact. This neither bypasses admission nor changes the executor's working
2916
2936
  directory. A disappeared native source fails; it never runs a stale projection.
2917
2937
  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
2938
+ Core creates one file under the scratch directory ({§exec-scratch-directory}), preserving the
2939
+ source extension, with an exclusive, process- and database-coordinate-independent identity. No
2940
+ sibling tree is copied and no relative-resource filesystem is emulated. The file lives through
2921
2941
  the executor run and core removes it after the subscription's terminal result
2922
2942
  has settled. A removal failure is reported to daemon diagnostics with its
2923
2943
  complete cause; it cannot rewrite the execution result, stream state, or
2924
2944
  completion wake.
2925
2945
 
2946
+ §exec-scratch-directory **A realized source is readable by its executor for the execution's
2947
+ lifetime.** The standalone file is written under the one scratch directory
2948
+ `PLURNK_SERVICE_EXEC_SCRATCH` names: empty, it is `$XDG_RUNTIME_DIR/plurnk` when
2949
+ `XDG_RUNTIME_DIR` is set and the platform temporary directory otherwise; an explicit value is
2950
+ an absolute directory (`~` expands), and a relative one fails at boot by name. The directory
2951
+ is created on first use with mode `0700`; each file is created exclusively with mode `0600`
2952
+ under a unique name. An executor that runs in another filesystem namespace — a container that
2953
+ mounts only the repository — is given a directory both sides can see by pointing the knob at
2954
+ it. Admission ensures the directory: one the daemon cannot create or write refuses the
2955
+ execution, 400 `scratch-unavailable`, whose detail names the directory, the knob and the
2956
+ failure code, and whose recovery has the operator point the knob at a writable absolute
2957
+ directory the executor can also read and, meanwhile, has the writer target a program file the
2958
+ executor can reach or run the command beneath a targetless heading. An execution never fails
2959
+ mid-run for this reason.
2960
+
2926
2961
  Loop-flag authority follows the selected runtime's declaration:
2927
2962
 
2928
2963
  | Target realization | Schemes that must be active |
@@ -3050,7 +3085,7 @@ two states and no others:
3050
3085
 
3051
3086
  | state | what the model receives |
3052
3087
  |---|---|
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). |
3088
+ | 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
3089
  | 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
3090
 
3056
3091
  §stream-observation-result **One liveness fact.** The durable READ result owns
@@ -3060,11 +3095,13 @@ observations alike, independently of mimetype: `active` gives false, `closed` or
3060
3095
  projection preserves that Boolean, any included
3061
3096
  integer `exitCode`, and a producer's `page` receipt ({§executor-page-receipt}), even for an empty body. An automatic observation's atomic
3062
3097
  publication transition consumes the same result flag; private log attributes
3063
- retain only the publication offset, not a second liveness value.
3098
+ retain only the publication offset, not a second liveness value. A closed channel
3099
+ publishes only once subscription settlement has installed its terminal result;
3100
+ until then the stream is still in flight and publishes on the turn after it settles.
3064
3101
 
3065
3102
  §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
3103
+ `PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (`-1`
3104
+ unbounded); the scope is the workspace, so neither delegation nor later turns
3068
3105
  bypass it and no other workspace can starve it. Every admitted execution still creates its
3069
3106
  entry, channels, and open subscription before its receipt returns, so queued work is
3070
3107
  cancellable, restart-reconcilable, completion-gated, and observed through the ordinary
@@ -3098,9 +3135,7 @@ channel that holds content, and an empty sibling channel is a fact on that row
3098
3135
  (`channels: {"#stderr": 0}`), never a row of its own; only a stream that printed
3099
3136
  nothing on any channel lands one bodyless conclusion row, on its default
3100
3137
  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
3138
+ completion explicit without invented narration. A skipped channel's publication is still marked
3104
3139
  terminal, so the stream's termination is delivered and never left pending. KILL may curate
3105
3140
  that log row without rewinding the cursor or publishing the terminal result
3106
3141
  again; the exact terminal result and channel content remain READable at the
@@ -3181,7 +3216,7 @@ body prefixes.
3181
3216
  key WORK or FORK does not take is refused the same way. The durable row redacts the block
3182
3217
  wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
3183
3218
  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.
3219
+ - §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.
3185
3220
  - §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.
3186
3221
 
3187
3222
  | Input / effect | Consumer behavior |
@@ -3204,7 +3239,7 @@ body prefixes.
3204
3239
 
3205
3240
  - **Client disposition** ({§methods-proposal-resolve}) — a client interface delivers accept, reject, or cancel; AG-UI uses standard resume entries ({§agui-proposal-resolve}).
3206
3241
  - **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.
3242
+ - §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
3243
 
3209
3244
  **The decision drives a one-way state transition** on `log_entries.state` (resolution is idempotent — `WHERE state='proposed'`, so a second resolution 404s):
3210
3245
 
@@ -3214,7 +3249,9 @@ body prefixes.
3214
3249
  | §proposal-reject-fails reject | `failed` | 400 | `rejected` | none — the action did not occur. |
3215
3250
  | §proposal-cancel-aborts cancel | `cancelled` | 499 | `loop_aborted` | none — the loop is abandoning. |
3216
3251
 
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.
3252
+ §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).
3253
+
3254
+ §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
3255
 
3219
3256
  §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
3257
 
@@ -3425,19 +3462,19 @@ SQLite (`node:sqlite`) with WAL mode and STRICT tables. Hand-written DDL; CI-ali
3425
3462
 
3426
3463
  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
3464
 
3428
- | Concern | Current pre-migration rule |
3465
+ | Concern | Rule |
3429
3466
  |---|---|
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. |
3467
+ | §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. |
3468
+ | §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`). |
3469
+ | §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. |
3470
+ | 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
3471
  | §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
3472
  | §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
3473
  | §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
3474
  | §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`. |
3475
+ | §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). |
3476
+ | §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`. |
3477
+ | §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
3478
 
3442
3479
  - DDL = storage truth; JSON Schemas = wire truth. They are allowed to differ where ergonomics demand.
3443
3480
  - §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`.
@@ -3648,14 +3685,15 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
3648
3685
  | Var | Purpose |
3649
3686
  |---|---|
3650
3687
  | `PLURNK_SERVICE_DB_PATH` | SQLite file path; an explicit non-empty value overrides the derived default. |
3688
+ | `PLURNK_SERVICE_SHARE_FOLDER` | Parent of the shares written when no folder is named ({§share-folder}); empty is `$XDG_STATE_HOME/plurnk/shares`. |
3651
3689
  | §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
3690
  | §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
3653
3691
  | §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
3692
  | `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). |
3693
+ | `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. |
3694
+ | `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
3695
  | §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. |
3696
+ | `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
3697
  | `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF` | First recovery delay (ms); doubles per failure up to `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX` ({§provider-recovery}). |
3660
3698
  | `PLURNK_SERVICE_MAX_STRIKES` | Consecutive turn-contract strike threshold ({§engine-rails}). |
3661
3699
  | `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 +3709,7 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
3671
3709
  | `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
3710
  | `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
3711
  | `PLURNK_SERVICE_EXEC_CONCURRENCY` | Executions admitted at once per workspace; the rest queue FIFO with `202 queued` receipts; `-1` unbounded ({§exec-concurrency}). |
3712
+ | `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
3713
  | `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
3714
  | §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
3715
  | `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 +3718,22 @@ Every core knob listed is enforced at its owning read site; `.env.defaults` is t
3679
3718
 
3680
3719
  **Two override semantics — ceiling vs default.** Which kind a var is determines what "override" means across the cascade:
3681
3720
 
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.
3721
+ - **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
3722
  - **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
3723
 
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
3724
  §operator-config-flag-parity The companion **flag-parity** check binds code and
3692
3725
  template both ways: every `PLURNK_SERVICE_*` the service reads has a
3693
3726
  `.env.defaults` line — a floor, a `--flag`, and a legend entry — and every
3694
3727
  declared `PLURNK_SERVICE_*` is read. A half-landed rename therefore fails a test
3695
3728
  instead of a user's boot, and a dead knob cannot ship.
3696
3729
 
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.
3730
+ Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
3714
3731
 
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:
3732
+ External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
3719
3733
 
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 |
3734
+ §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
3735
 
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.
3736
+ ### Loop limits
3731
3737
 
3732
3738
  §turn-cap-counts-the-tree **The turn ceiling is the worker tree's budget of model
3733
3739
  calls.** The owner is the current loop of the topmost ancestor-or-self worker that has
@@ -3740,7 +3746,9 @@ terminal ({§loop-terminals}) when the ceiling is met; a BARE beyond the budget
3740
3746
  429 `max-turns` before any provider call, so one turn cannot spend past it with a batch. A
3741
3747
  child loop inherits the value and binds the same count.
3742
3748
 
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.
3749
+ §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}).
3750
+
3751
+ ### Workspace settings
3744
3752
 
3745
3753
  §operator-config-workspace-settings **Client open-context (per workspace).**
3746
3754
  `workspace.create({ settings })` accepts only the following fields, normalizes
@@ -3775,18 +3783,55 @@ leak into another.
3775
3783
  floor — the tightest — admitting a plan and disposition with zero actions.
3776
3784
  - §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
3785
  - §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}).
3786
+ - `settings.membersModelScope` narrows `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}).
3779
3787
  - §operator-config-workspace-capabilities `settings.capabilities` is one
3780
3788
  workspace-stable `CapabilityPolicy` layer in {§capability-admission}. It may
3781
3789
  narrow any registered operation, scheme, runtime, tool, access class, or
3782
3790
  trait through the canonical `only`/`deny` selectors; it cannot register a
3783
3791
  capability or restore one removed by the service layer.
3784
3792
 
3785
- Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
3793
+ ### Gate profiles
3786
3794
 
3787
- External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
3795
+ §operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
3796
+ test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
3797
+ the policy renders in exactly one packet section. Every other tier runs the
3798
+ test cascade, so shipped-default regressions are otherwise invisible by
3799
+ construction.
3788
3800
 
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.
3801
+ §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:
3802
+
3803
+ | Owner | Configuration |
3804
+ |---|---|
3805
+ | `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
3806
+ | Live/demo scripts | The repository policy path and runner topology. |
3807
+ | Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
3808
+ | Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
3809
+ | `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
3810
+
3811
+ The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
3812
+ `AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
3813
+ bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
3814
+ none implicitly inherit the daily-driving policy. This does not disable project
3815
+ `AGENTS.md` guidance or modify the operator's file.
3816
+
3817
+ 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.
3818
+
3819
+ §operator-config-zero-pin-gate **Zero-pin is a counterfactual real-model gate,
3820
+ not another configuration source.** The live/demo `:zeropin` scripts load the
3821
+ ordinary environment cascade, then the test floor removes operator model tuning
3822
+ before assembled package defaults fill unset values:
3823
+
3824
+ | Configuration family | Zero-pin treatment |
3825
+ |-----------------------------------------------------------|--------------------|
3826
+ | Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
3827
+ | Alias-specific output and reasoning budgets | Remove |
3828
+ | Model selection, routes, and credentials | Retain |
3829
+ | Bare shipped generation-envelope defaults | Retain |
3830
+ | Unrelated environment | Retain |
3831
+
3832
+ The floor reports every removed key. A gate that succeeds only with those pins
3833
+ is red because provider capacity did not derive for
3834
+ a fresh-user configuration.
3790
3835
 
3791
3836
  ---
3792
3837
 
@@ -3946,8 +3991,8 @@ definition for the submitting client or worker.
3946
3991
  capability-aware operations, scoped module actions, and retained provider work
3947
3992
  lease the workspace's Functionality. Boot, workspace or worker creation,
3948
3993
  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
3994
+ After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` and
3995
+ `PLURNK_SERVICE_WORKSPACE_WARM_MAX` bound idle
3951
3996
  residency. `0` disables the respective grace or allowance; `-1` disables that
3952
3997
  bound. Concurrent demand coalesces; cooling never closes a leased connection.
3953
3998
 
@@ -3963,8 +4008,8 @@ documents and its state. Only a family that holds processes prepares
3963
4008
  `runtimes` (MCP servers today); the field is absent for every other family, so
3964
4009
  warming and cooling bound workspaces, never families, and the two-stage rollback
3965
4010
  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).
4011
+ processes. There is no per-family residency policy: residency is
4012
+ MCP-specific.
3968
4013
 
3969
4014
  The version-1 baseline table `workspace_module_state` stores one JSON value
3970
4015
  per `(workspace_id, namespace_owner)`. It is configuration, not an executable
@@ -4299,8 +4344,8 @@ registration; there is no separate per-tool availability system.
4299
4344
 
4300
4345
  §model-catalog **Model discovery is a bounded local projection, not provider
4301
4346
  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
4347
+ provider-owned `{§model-catalog-readiness}` and {§provider-effort}.
4348
+ Each entry includes the exact route's admitted `efforts`; worker-level
4304
4349
  model/spawn intersections and alias tuning are not catalog facts. The default query includes only
4305
4350
  providers configured enough to attempt; `availability: "all"` includes every
4306
4351
  catalog model with structured missing-configuration causes. Provider and text
@@ -4322,8 +4367,8 @@ cascade. A WORK/FORK child copies the spawning loop's effective spawn model
4322
4367
  no live link and begins with no override, so a later parent change affects
4323
4368
  only that worker's future loops and descendants. Client operation actors and
4324
4369
  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
4370
+ selection; the model, spawn-override, and effort controls refuse them with
4371
+ `409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or effort change while
4327
4372
  the worker holds any queued, running, or parked loop is a precise
4328
4373
  `409 worker-loop-active` ({§worker-lifecycle-live}), independent of a process-local
4329
4374
  drain. The policy write checks liveness atomically, including selections carried
@@ -4333,11 +4378,11 @@ First-time initialization of an unset worker model remains legal and never
4333
4378
  rewrites an existing loop's generation snapshot.
4334
4379
 
4335
4380
  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
4381
+ override, and effort by value alongside its history. It retains no
4337
4382
  live policy link to the source worker.
4338
4383
 
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}`;
4384
+ §worker-effort **Reasoning is a durable worker policy.** Each selected
4385
+ worker model has exactly one member of the shared `{§effort-wire}`;
4341
4386
  a modelless worker has none. A declared alias's scoped environment value—or the
4342
4387
  global provider value for an exact route—seeds the policy only when the worker
4343
4388
  first receives its model. Model identity and reasoning
@@ -4346,17 +4391,17 @@ token ceilings remain separate concerns. An explicit policy change validates
4346
4391
  the exact policy against both the worker model and its optional spawn model and
4347
4392
  is refused while the worker owns a live or parked loop. Effort is identity-grade:
4348
4393
  every client-visible model route carries the worker's durable policy as
4349
- `reasoningPolicy`, omitted only when the cataloged model has no reasoning
4394
+ `effort`, omitted only when the cataloged model has no reasoning
4350
4395
  dimension. Client inspection
4351
4396
  returns the supported-policy intersection of those two routes. Inspection or
4352
4397
  mutation materializes the daemon-default model and policy onto an uninitialized
4353
4398
  model worker before answering; a deliberately modelless daemon remains unset.
4354
4399
 
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
4400
+ §worker-effort-source **A default never masquerades as a choice.** The worker row records
4401
+ `effort_source` beside `effort`: `default` when the value was seeded from the alias
4402
+ or provider configuration, `explicit` only after `worker.effort.set`. `worker.effort.get`
4403
+ returns `source`, and a projected `ModelRoute` carries `effortSource` exactly when it carries
4404
+ `effort`, so a client can render `deepdumb[low]` differently from a seeded `low` without
4360
4405
  inferring anything. Selecting a new model keeps an explicit policy (validated against the new
4361
4406
  model) and re-derives a default one from the new alias, so a seeded value never outlives the alias
4362
4407
  that supplied it; the source itself is not part of the mid-loop generation-change check, because
@@ -4377,7 +4422,7 @@ addressed worker before the loop snapshots it; an omitted selector is not a
4377
4422
  selection and continues the worker's durable model
4378
4423
  ({§worker-model-selection}). The fully resolved provider identity and reasoning
4379
4424
  policy are persisted on the loop and remain immutable through turns, parks,
4380
- wakes, and restart ({§worker-reasoning-policy}).
4425
+ wakes, and restart ({§worker-effort}).
4381
4426
  Injecting into an existing loop with a conflicting explicit selection fails
4382
4427
  before work is accepted. Provider instances are cached; no resume path
4383
4428
  substitutes a boot default for missing or malformed durable selection.
@@ -4436,6 +4481,7 @@ adding a loop to it. LOOK text anchors resolve through the same
4436
4481
  | §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
4482
  | §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
4483
  | §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. |
4484
+ | §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
4485
  | §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
4486
 
4441
4487
  §notifications-stream-event-failure-isolation The plugin-facing
@@ -4538,14 +4584,14 @@ time of measurement.
4538
4584
  | Fact | Owner and unit | Time | Contract |
4539
4585
  |:-----|:---------------|:-----|:---------|
4540
4586
  | 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. |
4587
+ | §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. |
4588
+ | 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
4589
  | 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
4590
 
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.
4591
+ - §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
4592
  - §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
4593
  - §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.
4594
+ - §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
4595
  - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
4550
4596
  `PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
4551
4597
  `(0, 100)`. It allocates that share of the cold-start curation allowance
@@ -4706,8 +4752,7 @@ Stream progress remains owned by {§exec-stream}.
4706
4752
 
4707
4753
  ## §packet Packet shape
4708
4754
 
4709
- §packet-markdown **The packet's Markdown projection, owned here since the packet
4710
- projection package retired (#626).** Core renders the transformed section list
4755
+ §packet-markdown **The packet's Markdown projection (#626).** Core renders the transformed section list
4711
4756
  into one system string and one user string. Within each slot, list order is
4712
4757
  preserved. A nonempty section with a header renders as an H2 immediately followed
4713
4758
  by its JSON object/array content; non-JSON content has one blank line after the
@@ -4769,8 +4814,7 @@ directly with `json_extract`. A packet transformed by a plugin, or any non-log s
4769
4814
  item. Items no composition references are transient data: `retention_collect_packet_items`
4770
4815
  collects them under the retention policy ({§retention-policy}), which is how a deleted worker's or
4771
4816
  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}.
4817
+ and shares the items ({§worker-fork-trigger}).
4774
4818
 
4775
4819
  | Field | Presence | Contract |
4776
4820
  | ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -4793,23 +4837,30 @@ turn receives a note instead of a fabricated response.
4793
4837
  §digest-turn-artifact-identity **Digest packet artifacts project durable turns.**
4794
4838
  After selectors are applied, digest retains every turn with exact program source, a
4795
4839
  valid stored provider request, or malformed stored packet evidence; orders those
4796
- turns by durable chronology; and names them contiguously from `packet000`. The
4840
+ turns by durable chronology; and names each by its log coordinate ({§share-packet-names}). The
4797
4841
  producer does not affect projection.
4798
4842
 
4843
+ §share-packet-names **Packet artifacts carry the coordinate the log uses.** A turn's files are named
4844
+ `<worker>-<loop>-<turn>`, the worker's name and the loop and turn sequences that `log:///<loop>/<turn>/…`
4845
+ addresses: the model's first turn in its first loop is `<worker>-1-2`, because the initialization
4846
+ survey is turn 1 and writes no packet. A digest spanning several workspaces nests each workspace's
4847
+ files in a folder named for it. `digest.json` records each turn's stem as `artifact`, so no
4848
+ consumer reconstructs a name. A name that cannot be a file name, or two turns sharing one, fails.
4849
+
4799
4850
  | Artifact | Present when | Authority |
4800
4851
  |----------|--------------|-----------|
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 |
4852
+ | `<stem>.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
4853
+ | `<stem>.system.md`, `<stem>.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
4803
4854
  | `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 |
4855
+ | `<stem>.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
4856
+ | `<stem>.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
4857
+ | `<stem>.packet.raw.txt` | The stored packet fails typed validation | Exact stored packet text |
4858
+ | `<stem>.packet.invalid.json` | The stored packet fails typed validation | Turn identity and complete validation error chain |
4808
4859
 
4809
4860
  A source-backed turn without provider participation therefore produces only
4810
4861
  `assistant.md`; a request-only turn produces no fabricated assistant. A
4811
4862
  source-less programmatic turn with no provider request has no forensic payload
4812
- to project and reserves no ordinal.
4863
+ to project and writes no files.
4813
4864
 
4814
4865
  The external tokenless draft and transformation boundary is owned by
4815
4866
  {§scheme-packet-transform}. Core alone extends each validated draft with its
@@ -4905,11 +4956,9 @@ producers notify the same settlement path after durable execution; reply wake-up
4905
4956
  shows in Open Messages and in an arrival row's `resource`. A client's own identity for the same
4906
4957
  message — an AG-UI message UUID, an A2A address — stays the durable `path` that correlation,
4907
4958
  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.
4959
+ additional alias. Answering either reaches the same message.
4911
4960
 
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}).
4961
+ §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
4962
 
4914
4963
  §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
4964
 
@@ -4992,21 +5041,30 @@ retain distinct contracts and lifetimes.
4992
5041
 
4993
5042
  §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
5043
 
5044
+ §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.
5045
+
5046
+ §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.
5047
+
5048
+ §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.
5049
+
5050
+ §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`.
5051
+
4995
5052
  §digest-programmatic-surface **The digest is an importable forensic surface.**
4996
5053
 
4997
5054
  | Surface | Contract |
4998
5055
  | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
4999
5056
  | 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
5057
  | `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. |
5058
+ | `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
5059
  | 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. |
5060
+ | §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
5061
  | 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
5062
  | `workerId` | Narrows workers and every dependent loop, turn, turn-attached logical inference, specialization, physical request, and log row to that one worker. |
5005
5063
  | `workspaceId` | Narrows workers plus every logical inference and dependent evidence owned by one workspace, when both selectors are present they intersect. |
5006
5064
 
5007
5065
  §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
5066
 
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.
5067
+ §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
5068
 
5011
5069
  §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
5070
 
@@ -5147,8 +5205,7 @@ or an unknown enabled alias fails the daemon at boot.
5147
5205
  namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). `none`
5148
5206
  refuses every model definition — inclusion or exclusion — as `403
5149
5207
  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.
5208
+ the paths that remain; `root` admits patterns inside the root; `namespace` admits `../` too.
5152
5209
  `auto` loops self-approve proposals, so the ceiling — not the proposal — is the guard
5153
5210
  ({§membership-baseline}). The coordinator hands `admit` the caller (`action` | `operation`)
5154
5211
  so the family bounds the model without a second grammar.
@@ -5188,7 +5245,7 @@ source it rides the definition. The workspace's durable state owns enablement
5188
5245
  model-facing trace.
5189
5246
 
5190
5247
  *Discovery is inert.* `discover {query}` searches the ecosystem registry
5191
- (`PLURNK_SERVICE_SKILLS_REGISTRY_URL`, default `https://skills.sh`; empty disables it
5248
+ (`PLURNK_SERVICE_SKILLS_REGISTRY_URL`; empty disables it
5192
5249
  with 501 `registry-not-configured`) and returns one candidate per hit with
5193
5250
  `registry` provenance and the exact `owner/repo` source. `discover {source}`
5194
5251
  lists the skills one standard package reference contains with `source`
@@ -5204,8 +5261,8 @@ names, rather than the coordinator's generic default.
5204
5261
  *Preparation.* For each enabled alias the adapter selects the host-provided
5205
5262
  tree for `service` scope or locates the directory at the filesystem scope;
5206
5263
  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
5264
+ through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, invoked as
5265
+ `<cli> add <source> --agent universal --skill <name> --yes [--global]`, run with
5209
5266
  `HOME` set to the service's user home so the installer's `~` is the global
5210
5267
  root) and the installed `SKILL.md` — never the installer's output — is the
5211
5268
  evidence.
@@ -5297,11 +5354,11 @@ section because they are language extensions rather than executable tools.
5297
5354
 
5298
5355
  ### §inject system.inject — the operator injection
5299
5356
 
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.
5357
+ §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
5358
 
5302
5359
  ### §policy system.policy — the client's policy injection
5303
5360
 
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}).
5361
+ §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
5362
 
5306
5363
  On first run, and only when `$XDG_CONFIG_HOME/plurnk` itself is absent, the service seeds
5307
5364
  `AGENTS.md` from `@plurnk/plurnk-meta/POLICY.md` ({§teaching-corpus}).
@@ -5461,6 +5518,17 @@ READ/EDIT/COPY/MOVE scopes use the same physical text:
5461
5518
  One/two-coordinate line shorthand is newline-aware so deleting a line does not
5462
5519
  leave an empty line. A terminal position after a final newline is an exact
5463
5520
  insertion anchor, not an additional whole line. `<1,-1>` selects all content.
5521
+
5522
+ §zero-width-column-one-insert **A zero-width region at column 1 inserts whole lines.** The
5523
+ schemes region algebra ({§slicer-text-algebra}) inserts every body verbatim; the missing
5524
+ newline is a fence artifact, so core repairs it where a fenced EDIT body becomes inserted
5525
+ content, through the one schemes helper `wholeLineBody`, at the mutation and again in the
5526
+ receipt and anchor-continuity recomputation so every site sees one body. At `<L,1,L,1>`,
5527
+ an anchored `<@hash,1,@hash,1>`, or `L` = final line + 1 when the content ends with a
5528
+ newline, a non-empty body that does not end in a newline is inserted with the content's
5529
+ line separator appended, so `X` at `<2,1,2,1>` into `a\nb` yields `a\nX\nb`. An empty
5530
+ body inserts nothing. A zero-width region at any other column stays a byte-exact insert with
5531
+ nothing appended. COPY and MOVE transfer source bytes, not a fenced body, and are untouched.
5464
5532
  The runtime also tolerates an authored three-coordinate
5465
5533
  `<startLine,startColumn,endLine>` scope, immediately lowers it to the complete
5466
5534
  four-coordinate region ending after the final code point of `endLine`, and
@@ -5572,7 +5640,7 @@ Carried from the contract walk; durable.
5572
5640
 
5573
5641
  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
5642
 
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`.
5643
+ §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
5644
 
5577
5645
  ---
5578
5646
 
@@ -5617,3 +5685,5 @@ marker file or a sweep; an unstamped invocation is not a special case with its o
5617
5685
  simply an unstamped run with its own directory. A stamped run that passes is reclaimed when it
5618
5686
  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
5687
  caller's own run directory.
5688
+
5689
+ §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.