@plurnk/plurnk-service 1.17.0 → 1.18.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 (206) hide show
  1. package/.env.defaults +19 -1
  2. package/README.md +1 -1
  3. package/SPEC.md +1025 -946
  4. package/dist/build-info.json +1 -1
  5. package/dist/content/read-projector.js +2 -2
  6. package/dist/content/read-projector.js.map +1 -1
  7. package/dist/content/read-resolve.d.ts +1 -0
  8. package/dist/content/read-resolve.d.ts.map +1 -1
  9. package/dist/content/read-resolve.js +13 -10
  10. package/dist/content/read-resolve.js.map +1 -1
  11. package/dist/core/AdmittedTurnExecutor.d.ts +1 -2
  12. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  13. package/dist/core/AdmittedTurnExecutor.js +12 -12
  14. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  15. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  16. package/dist/core/CapabilityResolver.js +40 -33
  17. package/dist/core/CapabilityResolver.js.map +1 -1
  18. package/dist/core/ChannelWrite.d.ts +1 -0
  19. package/dist/core/ChannelWrite.d.ts.map +1 -1
  20. package/dist/core/ChannelWrite.js +1 -1
  21. package/dist/core/ChannelWrite.js.map +1 -1
  22. package/dist/core/DataStatementRunner.d.ts.map +1 -1
  23. package/dist/core/DataStatementRunner.js +194 -144
  24. package/dist/core/DataStatementRunner.js.map +1 -1
  25. package/dist/core/Dispatcher.d.ts +6 -1
  26. package/dist/core/Dispatcher.d.ts.map +1 -1
  27. package/dist/core/Dispatcher.js +25 -26
  28. package/dist/core/Dispatcher.js.map +1 -1
  29. package/dist/core/Engine.js +1 -1
  30. package/dist/core/Engine.js.map +1 -1
  31. package/dist/core/ExecutionOutputs.sql +8 -0
  32. package/dist/core/ExecutorRegistry.d.ts +4 -0
  33. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  34. package/dist/core/ExecutorRegistry.js +1 -0
  35. package/dist/core/ExecutorRegistry.js.map +1 -1
  36. package/dist/core/LogBody.d.ts.map +1 -1
  37. package/dist/core/LogBody.js +2 -1
  38. package/dist/core/LogBody.js.map +1 -1
  39. package/dist/core/LogEntryProjection.d.ts.map +1 -1
  40. package/dist/core/LogEntryProjection.js +4 -10
  41. package/dist/core/LogEntryProjection.js.map +1 -1
  42. package/dist/core/LogWriter.d.ts.map +1 -1
  43. package/dist/core/LogWriter.js +9 -7
  44. package/dist/core/LogWriter.js.map +1 -1
  45. package/dist/core/LoopDriver.d.ts.map +1 -1
  46. package/dist/core/LoopDriver.js +1 -3
  47. package/dist/core/LoopDriver.js.map +1 -1
  48. package/dist/core/ModelCall.d.ts.map +1 -1
  49. package/dist/core/ModelCall.js +2 -6
  50. package/dist/core/ModelCall.js.map +1 -1
  51. package/dist/core/ModelCall.sql +7 -36
  52. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  53. package/dist/core/ProposalLifecycle.js +8 -6
  54. package/dist/core/ProposalLifecycle.js.map +1 -1
  55. package/dist/core/RuntimeWorker.d.ts.map +1 -1
  56. package/dist/core/RuntimeWorker.js +6 -1
  57. package/dist/core/RuntimeWorker.js.map +1 -1
  58. package/dist/core/RuntimeWorker.sql +7 -1
  59. package/dist/core/SchemeRegistry.js +1 -1
  60. package/dist/core/SchemeRegistry.js.map +1 -1
  61. package/dist/core/StrikeRail.d.ts +2 -2
  62. package/dist/core/StrikeRail.d.ts.map +1 -1
  63. package/dist/core/StrikeRail.js +7 -5
  64. package/dist/core/StrikeRail.js.map +1 -1
  65. package/dist/core/ToolResources.d.ts.map +1 -1
  66. package/dist/core/ToolResources.js +5 -3
  67. package/dist/core/ToolResources.js.map +1 -1
  68. package/dist/core/TurnDispositionHandler.d.ts +4 -6
  69. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  70. package/dist/core/TurnDispositionHandler.js +88 -80
  71. package/dist/core/TurnDispositionHandler.js.map +1 -1
  72. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  73. package/dist/core/TurnMaterialization.js +7 -9
  74. package/dist/core/TurnMaterialization.js.map +1 -1
  75. package/dist/core/TurnOps.d.ts.map +1 -1
  76. package/dist/core/TurnOps.js +1 -1
  77. package/dist/core/TurnOps.js.map +1 -1
  78. package/dist/core/TurnRunner.d.ts +1 -5
  79. package/dist/core/TurnRunner.d.ts.map +1 -1
  80. package/dist/core/TurnRunner.js +1096 -1062
  81. package/dist/core/TurnRunner.js.map +1 -1
  82. package/dist/core/WorkerControlHandler.d.ts.map +1 -1
  83. package/dist/core/WorkerControlHandler.js +23 -0
  84. package/dist/core/WorkerControlHandler.js.map +1 -1
  85. package/dist/core/WorkerName.d.ts +3 -6
  86. package/dist/core/WorkerName.d.ts.map +1 -1
  87. package/dist/core/WorkerName.js +9 -25
  88. package/dist/core/WorkerName.js.map +1 -1
  89. package/dist/core/WorkspaceGate.js +4 -4
  90. package/dist/core/WorkspaceGate.js.map +1 -1
  91. package/dist/core/env-catalog.d.ts +28 -0
  92. package/dist/core/env-catalog.d.ts.map +1 -0
  93. package/dist/core/env-catalog.js +99 -0
  94. package/dist/core/env-catalog.js.map +1 -0
  95. package/dist/core/file-materialization.js +1 -1
  96. package/dist/core/file-materialization.js.map +1 -1
  97. package/dist/core/git-state.js +1 -1
  98. package/dist/core/git-state.js.map +1 -1
  99. package/dist/core/operation-target-groups.d.ts.map +1 -1
  100. package/dist/core/operation-target-groups.js +2 -1
  101. package/dist/core/operation-target-groups.js.map +1 -1
  102. package/dist/core/packet-wire.d.ts.map +1 -1
  103. package/dist/core/packet-wire.js +320 -295
  104. package/dist/core/packet-wire.js.map +1 -1
  105. package/dist/digest/Digest.d.ts.map +1 -1
  106. package/dist/digest/Digest.js +3 -1
  107. package/dist/digest/Digest.js.map +1 -1
  108. package/dist/digest/DigestRender.d.ts +1 -0
  109. package/dist/digest/DigestRender.d.ts.map +1 -1
  110. package/dist/digest/DigestRender.js +44 -8
  111. package/dist/digest/DigestRender.js.map +1 -1
  112. package/dist/digest/digest-rows.d.ts +6 -0
  113. package/dist/digest/digest-rows.d.ts.map +1 -1
  114. package/dist/digest/digest.sql +11 -2
  115. package/dist/schemes/Exec.d.ts.map +1 -1
  116. package/dist/schemes/Exec.js +68 -33
  117. package/dist/schemes/Exec.js.map +1 -1
  118. package/dist/schemes/ExecScheduler.js +2 -2
  119. package/dist/schemes/ExecScheduler.js.map +1 -1
  120. package/dist/schemes/File.d.ts.map +1 -1
  121. package/dist/schemes/File.js +2 -1
  122. package/dist/schemes/File.js.map +1 -1
  123. package/dist/schemes/Log.js +1 -1
  124. package/dist/schemes/Log.js.map +1 -1
  125. package/dist/schemes/QuestionTool.js +1 -1
  126. package/dist/schemes/QuestionTool.js.map +1 -1
  127. package/dist/schemes/TurnSource.d.ts.map +1 -1
  128. package/dist/schemes/TurnSource.js +7 -6
  129. package/dist/schemes/TurnSource.js.map +1 -1
  130. package/dist/schemes/_entry-crud.sql +18 -0
  131. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  132. package/dist/schemes/_entry-manifest.js +5 -14
  133. package/dist/schemes/_entry-manifest.js.map +1 -1
  134. package/dist/schemes/_entry-manifest.sql +1 -1
  135. package/dist/schemes/_search-index.d.ts +5 -1
  136. package/dist/schemes/_search-index.d.ts.map +1 -1
  137. package/dist/schemes/_search-index.js +96 -56
  138. package/dist/schemes/_search-index.js.map +1 -1
  139. package/dist/schemes/_search-index.sql +17 -0
  140. package/dist/schemes/exec-abort.js +1 -1
  141. package/dist/schemes/exec-abort.js.map +1 -1
  142. package/dist/schemes/exec-env.d.ts +3 -0
  143. package/dist/schemes/exec-env.d.ts.map +1 -1
  144. package/dist/schemes/exec-env.js +66 -15
  145. package/dist/schemes/exec-env.js.map +1 -1
  146. package/dist/schemes/exec-runtime.d.ts +1 -1
  147. package/dist/schemes/exec-runtime.d.ts.map +1 -1
  148. package/dist/schemes/exec-runtime.js +1 -1
  149. package/dist/schemes/exec-runtime.js.map +1 -1
  150. package/dist/server/ClientReads.d.ts.map +1 -1
  151. package/dist/server/ClientReads.js +2 -1
  152. package/dist/server/ClientReads.js.map +1 -1
  153. package/dist/server/Daemon.d.ts +1 -0
  154. package/dist/server/Daemon.d.ts.map +1 -1
  155. package/dist/server/Daemon.js +32 -14
  156. package/dist/server/Daemon.js.map +1 -1
  157. package/dist/server/DaemonModule.d.ts +7 -2
  158. package/dist/server/DaemonModule.d.ts.map +1 -1
  159. package/dist/server/EnvFunctionality.d.ts +43 -0
  160. package/dist/server/EnvFunctionality.d.ts.map +1 -0
  161. package/dist/server/EnvFunctionality.js +194 -0
  162. package/dist/server/EnvFunctionality.js.map +1 -0
  163. package/dist/server/Functionality.d.ts +4 -2
  164. package/dist/server/Functionality.d.ts.map +1 -1
  165. package/dist/server/Functionality.js +188 -79
  166. package/dist/server/Functionality.js.map +1 -1
  167. package/dist/server/FunctionalityManager.d.ts +2 -0
  168. package/dist/server/FunctionalityManager.d.ts.map +1 -1
  169. package/dist/server/FunctionalityManager.js +13 -3
  170. package/dist/server/FunctionalityManager.js.map +1 -1
  171. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  172. package/dist/server/MembersFunctionality.js +0 -1
  173. package/dist/server/MembersFunctionality.js.map +1 -1
  174. package/dist/server/Retention.d.ts +3 -0
  175. package/dist/server/Retention.d.ts.map +1 -1
  176. package/dist/server/Retention.js +7 -4
  177. package/dist/server/Retention.js.map +1 -1
  178. package/dist/server/Retention.sql +23 -0
  179. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  180. package/dist/server/SkillsFunctionality.js +5 -2
  181. package/dist/server/SkillsFunctionality.js.map +1 -1
  182. package/dist/server/WorkspaceResidency.d.ts +2 -0
  183. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  184. package/dist/server/WorkspaceResidency.js +27 -0
  185. package/dist/server/WorkspaceResidency.js.map +1 -1
  186. package/dist/server/client-input.js +1 -1
  187. package/dist/server/client-input.js.map +1 -1
  188. package/dist/server/drain.sql +1 -1
  189. package/dist/server/envelope.d.ts +0 -1
  190. package/dist/server/envelope.d.ts.map +1 -1
  191. package/dist/server/envelope.js +0 -12
  192. package/dist/server/envelope.js.map +1 -1
  193. package/dist/server/lifecycle-recovery.sql +4 -3
  194. package/dist/server/workspace-capabilities.sql +45 -0
  195. package/dist/service.d.ts.map +1 -1
  196. package/dist/service.js +16 -1
  197. package/dist/service.js.map +1 -1
  198. package/docs/env.md +55 -0
  199. package/docs/members.md +4 -4
  200. package/docs/skills.md +1 -1
  201. package/migrations/002_workers.sql +15 -0
  202. package/migrations/004_inference.sql +53 -12
  203. package/migrations/005_entries.sql +8 -0
  204. package/migrations/006_log.sql +18 -15
  205. package/migrations/007_subscriptions.sql +2 -2
  206. package/package.json +42 -32
package/SPEC.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  Canonical contracts plurnk-service exposes, architecture it implements, promises it makes to the constellation (`plurnk-contracts`, `plurnk-providers`, `plurnk-schemes`, `plurnk-mimetypes`, `plurnk-execs`, the user-facing `plurnk` CLI). `AGENTS.md` covers process; this file covers contract.
4
4
 
5
+ ## Contents
6
+
7
+ - [Glossary](#glossary-glossary)
8
+ - [Architecture](#arch-architecture)
9
+ - [Workers and workspace boundaries](#actor-boundary-workers-and-workspace-boundaries)
10
+ - [File membership and project roots](#membership-file-membership-and-project-roots)
11
+ - [Loop scheduling and lifecycle](#worker-loop-lifecycle-loop-scheduling-and-lifecycle)
12
+ - [Provider Contract](#provider-provider-contract)
13
+ - [Scheme Contract](#scheme-scheme-contract)
14
+ - [Mimetype Contract](#mimetype-mimetype-contract)
15
+ - [Search indexing](#persistent-search-index-search-indexing)
16
+ - [Channel Topology](#channels-channel-topology)
17
+ - [Op Surface](#op-op-surface)
18
+ - [Proposals and client interactions](#proposal-proposals-and-client-interactions)
19
+ - [Stream Model](#stream-stream-model)
20
+ - [Storage Model](#storage-storage-model)
21
+ - [Plugin composition](#core-plugin-composition-plugin-composition)
22
+ - [Bundled Set](#bundled-set-bundled-set)
23
+ - [Grammar Dependency](#grammar-grammar-dependency)
24
+ - [Operator Configuration](#operator-config-operator-configuration)
25
+ - [Module seam](#rpc-module-seam)
26
+ - [Workspace Functionality](#functionality-workspace-functionality)
27
+ - [Application interface](#methods-application-interface)
28
+ - [Packet assembly](#packet-assembly-packet-assembly)
29
+ - [Packet shape](#packet-packet-shape)
30
+ - [Matcher selection and text regions](#matcher-matcher-selection-and-text-regions)
31
+ - [Testing and evidence](#test-taxonomy-testing-and-evidence)
32
+
5
33
  ---
6
34
 
7
35
  ## §glossary Glossary
@@ -23,13 +51,13 @@ flowchart LR
23
51
 
24
52
  | Term | Layer | Meaning |
25
53
  |-------------------|-----------------------|---------|
26
- | **agent** | PLURNK | The plurnk runtime. Acts in-workspace as the reserved `plurnk` worker ({§actor-boundary} self-hosting), never a privileged singleton owning its own entries ({§entry-owner}, {§machine-processes}). |
54
+ | **agent** | PLURNK | The plurnk runtime. Acts in-workspace as the `_plurnk` worker ({§actor-boundary} self-hosting), never a privileged singleton owning its own entries ({§entry-owner}, {§machine-processes}). |
27
55
  | **workspace** | Core | Durable user-named shared world. Persists across workers and process restarts. Identity: `workspaces.id` + unique `workspaces.name`. |
28
56
  | **worker** | Core | Durable actor and history over one workspace. Owns its loops and log rows, may carry a `parent_worker_id`, and has one process-local cancellation scope while active. |
29
57
  | **loop** | Core | Queued-to-terminal unit of model or client work within a worker. Status ∈ {100 pending · 102 running · 200 done · 202 waiting (blocked on a live obligation, {§send}) · 413 input-capacity failure · 429 model-turn ceiling · 499 cancelled · 500 failed · 504 execution timeout ({§operator-config-loop-timeout}) · 508 runaway}. Many loops may belong to one worker. |
30
58
  | **turn** | Core | One durable, producer-neutral batch of ordered operations. A turn may be authored by a model, client, plugin, or `_plurnk`; only a model turn assembles a packet and owns an emission call. Many turns may belong to one loop. Identity: `(loop_id, sequence)`. |
31
59
  | **model call** | Core/provider | One logical `provider.generate` invocation. Emission attempts and BARE inferences share this durable accounting owner; provider retries remain cardinal physical requests beneath it. Identity: `(turn_id, sequence)`. |
32
- | **op** | Producer/core | One DSL operation a producer submits, parsed into a `PlurnkStatement`. One admitted source-backed turn produces an ordered disposition-ended program. |
60
+ | **op** | Producer/core | One DSL operation a producer submits, parsed into a `PlurnkStatement`. Admission follows {§turn-ops-admission-path}. |
33
61
  | **statement** | Model/core | A parsed op: the `PlurnkStatement` AST from `@plurnk/plurnk-contracts`. |
34
62
  | **action** | Core | One executed op. Execution normally produces a `log_entries` row at `log:///<L>/<T>/<S>/<op>`; an engine rail may instead record an `op='error'` row ({§operation-results}). A source artifact carries no fabricated operation. |
35
63
  | **dispatch** | Core | Routing a statement to its scheme's op handler. |
@@ -38,55 +66,13 @@ flowchart LR
38
66
  | **`--run`** | Client compatibility | A compatibility-sensitive client spelling, not an internal entity. |
39
67
  | **session** | Retired/unqualified | Not a PLURNK lifecycle noun. Use the actual core noun; a third-party standard may use only its explicitly qualified protocol term. <!-- lexicon-allow: this row defines the retired noun --> |
40
68
 
41
- ### §turn-record Producer-neutral turn record
42
-
43
- A turn is the durable container for one producer's ordered operations. Packet
44
- and provider fields are optional evidence belonging only to model inference;
45
- their absence never makes a client, plugin, or `_plurnk` turn exceptional.
46
-
47
- | Field | Contract |
48
- |---|---|
49
- | `producer` | Required actor class: `model`, `client`, `plugin`, or `_plurnk`. |
50
- | `kind` | Required purpose: `inference`, `initialization`, `operation`, or `maintenance`. Model iff inference; initialization and maintenance require `_plurnk`. Producer and kind are immutable. A maintenance turn's successful rows are packet-suppressed — a receipt answers an asker, and maintenance has none ({§actor-boundary-doc-injection}). |
51
- | `status`, `completed_at` | A new turn is open at status 102 with `completed_at=NULL`. Completion records the exact turn disposition/operation disposition and timestamp; a completed 102 is therefore distinct from an open 102. |
52
- | Operations | Ordered by `(turn_id, sequence)` on one exact worker/loop/turn chain. Each row's `origin` is the turn producer or `_plurnk` making a system observation; the observation does not impersonate the producer. |
53
- | Program source | Every admitted source-backed turn preserves its exact program before dispatch in `turn_sources`, independently of log receipts, under {§turn-ops-entry}. |
54
- | Inference evidence | Model calls, `packet`, model, finish reason, and provider metadata belong only to model/inference turns. Turn fields are nullable until recorded and remain NULL for every other kind. |
55
-
56
- One lifecycle owner opens, optionally records inference evidence, and completes
57
- every turn. Initialization, maintenance, client dispatch, and model
58
- inference use that same path. `plugin` is the producer identity for
59
- plugin-authored operation turns; exposing that path must not introduce a
60
- parallel record or lifecycle. Producer and kind never change. Process-restart
61
- recovery completes any turn whose producer vanished.
62
-
63
- §turn-ops-admission-path **Source acquisition varies; admitted-turn execution does not.**
64
- A provider response, deterministic `_plurnk` program, or future client/plugin
65
- program crosses one admission boundary into the same executor. That executor
66
- parses once, dispatches the admitted statements in order, records their ordinary
67
- outcomes, and completes the turn from its TASK ruling. Exact source is retained before dispatch.
68
- Provider attempts, grammar recovery, reasoning, and accounting end before this
69
- shared seam. A programmatic operation batch that supplied no Plurnk source does
70
- not fabricate verbatim source.
71
-
72
- §turn-ops-selection-snapshot **An admitted program cannot select log rows it emits while executing.**
73
- Immediately before statement dispatch, the shared executor captures that worker's
74
- append-only log high-water mark. Every log-targeted KILL in the program resolves
75
- row membership at or below that same boundary, while prior curation effects still
76
- compose normally. Prompt and other pre-program rows already present in the turn
77
- remain selectable; preceding and later operation rows cannot be captured by
78
- their own program. A directly dispatched
79
- single operation captures the equivalent boundary before dispatch. This limits
80
- only log-row selection: operation phasing and same-turn resource effects retain
81
- their ordinary contracts.
82
-
83
69
  ### §storage-terms Storage terms
84
70
 
85
71
  | Term | Meaning |
86
72
  |---|---|
87
73
  | **entry** | The unit of canonical state. Identity: `(workspace_id, scheme, authority, pathname)` ({§entry-identity-no-null}). Holds one or more `channels` of content plus scheme-private `attributes`. |
88
74
  | **channel** | A named content buffer on an entry. Examples: `body`, `stdout`, `stderr`, `headers`, `symbols`. Each channel has `content`, `mimetype`, curation `weight`, and lifecycle `state`. |
89
- | **scheme** | An addressed capability family + handler. Built-ins include `worker`, `prompt`, `log`, and bare/file paths; discovered schemes and executor-runtime tags extend that set. Internal `exec` routes the EXEC op but is not an addressable model namespace. Consumption surface {§scheme-surface}; author contract: [plurnk-schemes](../plurnk-schemes/SPEC.md). |
75
+ | **scheme** | An addressed capability family + handler. Built-ins include `worker`, `prompt`, `log`, and bare/file paths; discovered schemes and executor-runtime tags extend that set. Internal `exec` routes executions but is not an addressable model namespace. Consumption surface {§scheme-surface}; author contract: [plurnk-schemes](../plurnk-schemes/SPEC.md). |
90
76
  | **mimetype** | A channel's content type. Drives the handler that produces the structural projections (`symbols`, `deepJson`, `deepXml`). Consumption surface {§mimetype-surface}; author contract: [plurnk-mimetypes](../plurnk-mimetypes/SPEC.md). |
91
77
  | **provider** | An LLM transport implementing the `@plurnk/plurnk-providers` `Provider` interface. Core supplies an assembled request and generation context; the provider owns endpoint adaptation and normalized response evidence. Consumption surface {§provider}; author contract: [plurnk-providers](../plurnk-providers/SPEC.md). |
92
78
 
@@ -98,7 +84,7 @@ Independent axes on entries and channels. Confusion across them is a recurring s
98
84
  |---|---|---|
99
85
  | **status** | HTTP int | Outcome of an operation. Carried on `log_entries.status_rx`, returned from op handlers. Per the catalogue ({§send-dispatch}). |
100
86
  | **channel state** | `static \| active \| closed \| errored` | Streaming lifecycle of a channel's content. Metadata, not gating — engine renders content regardless of state. |
101
- | **entry state** | `proposed \| resolved \| failed \| cancelled` | Proposal lifecycle (`log_entries.state`). `proposed` = pending client accept; `resolved` = accepted, side effect happened; `failed` = rejected (no effect); `cancelled` = the proposal was cancelled (loop abandoning). Distinct from channel state. |
87
+ | **proposal state** | `proposed \| resolved \| failed \| cancelled` | Proposal lifecycle (`log_entries.state`) under {§proposal}; distinct from entry identity and channel state. |
102
88
  | **outcome** | `string \| null` | Short reason for `failed`/`cancelled` (`"permission:403"`, `"aborted"`, `"not_found"`). Opaque to most callers. |
103
89
 
104
90
  ### §authority-terms Writer / authority
@@ -109,117 +95,15 @@ Independent axes on entries and channels. Confusion across them is a recurring s
109
95
  | **origin** | Synonym for writer in log_entries (`log_entries.origin`). Historical naming; treat as equivalent. |
110
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). |
111
97
 
112
- ### §engine-rails Engine rails
113
-
114
- After each admitted turn, one inline verdict decides whether the loop continues.
115
- An admitted turn contributes at most one strike, even when several sources fire.
116
- These are the complete strike sources:
117
-
118
- | Strike source | Exact trigger | Model-visible occurrence |
119
- |---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
120
- | Hard result | An admitted non-`EXEC` operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501`. | The originating failure row. |
121
- | Inventory steering | A refused completion at 409 sets the turn's steering ruling ({§send}); an empty TASK is a soft 409 receipt, never a strike. | The TASK receipt. |
122
- | Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
123
-
124
- `EXEC` results remain exact model-visible evidence but are always soft: an
125
- executor error is not a PLURNK contract violation. Cycle and terminal steering
126
- remain independent strike sources.
127
-
128
- A `425` not-ready result describes unfinished work, not a contract violation.
129
- It retains its exact receipt, without scheduling side effects ({§join-blocking-collect});
130
- other violations in the same turn still strike normally.
131
-
132
- §engine-cycle-evidence Cycle identity contains the ordered executed operations
133
- and their dispatch results, including complete operands, scopes, bodies, and
134
- scheme metadata, including the complete TASK inventory and SEND bodies. Source
135
- positions and asides are excluded. Engine-assigned
136
- Problem `instance` addresses are excluded from results. Object member order is
137
- irrelevant; operation and array order are preserved. Only the configured
138
- `MIN_CYCLES × MAX_CYCLE_PERIOD` history window is retained. Repeated addresses
139
- alone are not a cycle: changing inputs or observations distinguish activity.
140
- This is an exact-repetition backstop, not a semantic judgment of task progress;
141
- new asynchronous invocation identities do not prove repetition of their eventual
142
- effects. Ordinary contract strikes and operator budgets remain independent.
143
-
144
- §provider-recovery **A recoverable provider failure never ends a loop.** When a model
145
- call fails with a network failure, rate limit, deadline, or interrupted resource after
146
- the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
147
- notices the client (`engine:provider` / `provider_unavailable`), waits with
148
- exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling, capped at
149
- twelve times itself), and re-issues the same call against the exact frozen model
150
- messages whose response is still outstanding. Each reissue remains a distinct logical
151
- model call with complete physical-request accounting, but the active turn's newly
152
- recorded provider Problems do not recursively enter that request; they surface normally
153
- only in a later genuinely new packet. No emission attempt is consumed and no strike is
154
- scored. Every recovery checkpoint broadcasts live, while the model-facing Notice buffer
155
- retains only the current provider state; the next completed exchange notices
156
- `provider_recovered`. Recovery is bounded by `PLURNK_SERVICE_PROVIDER_RECOVERY`; when it
157
- is spent the turn completes as `202` and the loop parks exactly like a
158
- TASK wait ({§worker-lifecycle-wake-requeue-not-terminal}), resuming on the
159
- next prompt or wake with its log intact. Only a client cancel, the execution allowance
160
- ({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
161
- authorization, quota, an invalid response) settles a loop on a provider failure.
162
-
163
- **Contract Strikes** (operator mandate, 2026-09-01): *Every turn with one or
164
- more contract violations earns a strike. A turn without any contract violations
165
- clears the strikes. Three (not four) strikes and you're out, by default.*
166
- The streak counts consecutive violating turns; `MAX_STRIKES` (default 3) is the
167
- threshold, crossed ON the third strike; the crossing turn terminates at **508
168
- Loop Detected** when cycle-detected, otherwise **500**. Retrieval-only completion
169
- may instead be admitted on that attempt under {§send-final-strike-retrieval}.
170
-
171
- The contracts, and the violation of each that strikes:
172
-
173
- | Contract | Violation that strikes |
174
- |---|---|
175
- | operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
176
- | review contract | a refused completion (turntrieval steer); every other TASK 409 — empty inventory, already-terminal loop — is soft |
177
- | progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
178
- | frame contract | emission attempts exhausted with no admissible turn |
179
- | provider response contract | the provider returned an invalid response |
180
-
181
- Errors and issues are NOT contract violations. Each keeps its own disposition
182
- and never strikes: exploration misses (404, 416) and unsupported capability
183
- (501) are how discovery works; raw 409 outcomes are soft (the review ruling is
184
- steer's alone); EXEC outcomes and `executor/*` problem rows are world evidence;
185
- provider weather (rate limit, network failure, deadline, interruption) recovers
186
- ({§provider-recovery}); provider capacity has its own packet recovery and
187
- terminal ({§provider-capacity-failure}); request rejection
188
- ({§provider-request-rejection}), authorization and quota failures
189
- terminate immediately (configuration, not behavior); rejected private emission
190
- attempts are forensic evidence beneath their turn ({§emission-admission}) —
191
- only their exhaustion surfaces, as one frame-contract violation. The
192
- independent turn ceiling terminates at **429** ({§loop-terminals}). The streak
193
- and cycle verdict are absent from model packets; only the concrete occurrences
194
- in the table are shown. The current streak may ride first-party provider
195
- metadata ({§strikes-first-party-metadata}), which does not make it
196
- model-facing.
197
-
198
- §loop-rail-continuity Rail state belongs to the durable loop, not its execution
199
- segment. The strike streak and bounded cycle history survive driver cleanup and
200
- restart; curation of log evidence cannot alter them.
201
-
202
- | Boundary | Strike streak | Cycle history |
203
- |---|---|---|
204
- | Assessed turn with a violation | Increment once. | Include its exact activity. |
205
- | Clean assessed turn | Reset to zero. | Include its exact activity. |
206
- | Actual park, including an immediate wake/reclaim in the same drain | Preserve the assessed streak. | Close the window; the next turn starts a new one. |
207
- | Recoverable provider outage | No assessment; preserve the streak. | Preserve until an actual park. |
208
- | New loop | Start at zero. | Start empty. |
209
-
210
- The turn belongs to the wait revision under which it began. A rejected waiting TASK or
211
- one resolved without parking does not close a window. Periodic observations
212
- separated by actual waits are not an uninterrupted cycle; cumulative turn and
213
- execution allowances remain independent bounds. A committed terminal result
214
- cannot be replaced by a later rail assessment ({§worker-lifecycle-state-machine}).
98
+ ### §execution-terms Execution terms
215
99
 
216
100
  | Term | Meaning |
217
101
  |------------------------------|---|
218
- | **verdict** | The end-of-turn ruling computed inline in `Engine.runLoop` from the strike rail and independent loop terminals. No filter chain. |
219
- | **strike** | One admitted turn matching at least one source above. |
220
- | **emission attempt** | One completed provider exchange beneath an engine turn. ANTLR admits it when at least one source operation has a trustworthy effective envelope and no boundary-destroying tail. A hard error inside that envelope becomes a failed operation in the admitted turn; a rejected attempt is forensic evidence, not another turn or an engine strike. |
102
+ | **verdict** | The end-of-turn ruling from the strike rail and independent loop terminals ({§loop-terminals}). |
103
+ | **strike** | One admitted turn matching at least one source under {§engine-rails}. |
104
+ | **emission attempt** | One completed provider exchange beneath an engine turn, admitted or rejected under {§emission-admission}. |
221
105
  | **BARE inference** | One isolated child-provider model call whose response becomes an ordinary BARE log result. It has no worker, packet, tools, output grammar, or persistent child state ({§bare-inference}). |
222
- | **cycle** | A repeated turn fingerprint across consecutive turns. Detection strikes silently under the rule above. |
106
+ | **cycle** | Repeated operational inputs and observed results under {§engine-cycle-evidence}. |
223
107
  | **capability policy** | A purely subtractive `only`/`deny` selector layer over routed operation demands. Service and workspace layers compose without granting authority. |
224
108
  | **loop policy** | One immutable `review`, `accept`, or `reject` proposal disposition. |
225
109
  | **proposal** | A deferred side-effecting action. State machine: `proposed → resolved` (accept), `→ failed` (reject), or `→ cancelled` (cancel). Its core-owned disposition says whether the client or loop owns resolution ({§proposal-disposition}). |
@@ -231,83 +115,19 @@ cannot be replaced by a later rail assessment ({§worker-lifecycle-state-machine
231
115
  | ---------- | ------- |
232
116
  | **packet** | A Turn's optional model-exchange record: measured request `sections`, extended with `assistant` and `assistantRaw` only when an emission is admitted. `NULL` means no model request was assembled. |
233
117
  | **log** | The `log` section. Chronological list of `log_entries` in scope this turn. |
234
- | **render** | The act of computing the packet from current DB state at turn boundaries. Mimetype handlers fire at render time. |
235
-
236
- ### §test-taxonomy Test taxonomy
237
-
238
- | Tier | Location | LLM | Substrate |
239
- |---|---|---|---|
240
- | **unit** | `src/**/*.test.ts` | No | Isolated logic, mocked boundaries |
241
- | **intg** | `test/intg/` | No (mock provider) | Real file-backed SqlRite (per-test DB under `test/intg/.tmp/`), real engine |
242
- | **live** | `test/live/` | Real | Wire-level assertions |
243
- | **demo** | `test/demo/` | Real | Holistic outcome assertions |
244
-
245
- §service-worker-composition The service launcher and the live/demo workspace
246
- helper share one registration of default worker-facing modules: MCP and outbound
247
- A2A. Their management families and readable reference documents are present even
248
- with no enabled attachments. Workspace capability policy controls every actor's
249
- surface; registering a family does not enable a remote attachment. Client and
250
- inbound-A2A listeners and host hooks remain launcher-owned.
251
-
252
- §live-harness-deadline The live and demo tiers use `PLURNK_SERVICE_LIVE_TIMEOUT`
253
- as one whole-specimen deadline, including multi-prompt stories. The test's abort
254
- signal reaches the loop wait and invokes ordinary scope cancellation before
255
- teardown. The runner joins the test body's cleanup before starting the next
256
- specimen. The shared workspace and story helpers cover setup, inference and
257
- oracle failures, preserve the primary failure when their cleanup also fails,
258
- and attempt every registered disposal.
259
- Provider attempt/recovery limits remain independent; a harness cancellation is
260
- not evidence that the provider's own deadline expired.
261
-
262
- §provider-conformance-matrix **Every configured model alias is exercised through a
263
- real PLURNK loop: the production packet, a model-selected operation, its
264
- materialized result, and completion.** Transport-only completions are not
265
- conformance evidence. Provider-exposed reasoning must survive in the durable
266
- assistant packet and digest; a provider with no private reasoning is valid when
267
- the observable operation cycle succeeds. One package-owned runner executes the
268
- full tier or exactly one registered specimen (`npm run test:live:specimen --
269
- <exact test name>` in plurnk-core), rejecting absent and duplicate names before
270
- execution. The ledger and classification taxonomy live in
271
- `plurnk-providers/README.md` and report authorization/credential failures
272
- distinct from model failures and repeated stochastic failures separately from
273
- stable ones, never with weakened assertions.
274
-
275
- §test-artifact-retention **File-backed test databases use lane-local current-run
276
- retention.** Each workspace's normal intg runner clears its own
277
- `test/intg/.tmp/` once before the suite, reports that forensic directory, and
278
- retains every artifact the current run creates; a direct `node --test <file>` run bypasses that
279
- runner, so each test process prunes artifacts older than a day once, and never the current run's. A cross-package test may reuse
280
- Core's migration fixture only by passing a path inside the caller's artifact
281
- directory; independently scheduled lanes never share a reset target. A failed
282
- suite therefore leaves its own evidence intact, and the next normal run of that
283
- lane removes it before creating anything. Direct `node --test` invocations
284
- bypass the runner boundary and must invoke the same cleanup procedure
285
- explicitly when isolation matters. Live/demo run directories are benchmark
286
- artifacts outside `.tmp` and retain their separate lifecycle.
118
+ | **render** | Computing the packet from current state under {§packet-assembly} and {§packet-markdown}. |
287
119
 
288
120
  ---
289
121
 
290
122
  ## §arch Architecture
291
123
 
292
- The ecosystem and the in-process shape ({§ecosystem}–{§in-process}), then the two invariants the rest of the spec rests on: isolation by worker ({§actor-boundary}) and the workspace/worker/fork ownership model ({§machine-processes}).
124
+ Daemon composition and startup. Worker attention and workspace state follow
125
+ {§actor-boundary} and {§machine-processes}.
293
126
 
294
127
  ### §ecosystem Ecosystem
295
128
 
296
129
  The root [`ARCHITECTURE.md`](../ARCHITECTURE.md) owns the platform process and
297
- package map. The daemon, contracts, AG-UI module, and bundled capability
298
- families are independently published npm workspaces in this monorepo; the CLI,
299
- TUI, and editor clients are separate repositories.
300
-
301
- ```mermaid
302
- flowchart LR
303
- contracts["plurnk-contracts<br/>language + shared wire"] --> frameworks["providers / schemes<br/>mimetypes / executors"]
304
- meta["plurnk-meta<br/>discovery + teaching corpus"] --> frameworks
305
- contracts --> core["plurnk-core<br/>@plurnk/plurnk-service"]
306
- frameworks --> core
307
- meta --> core
308
- core --> agui["plurnk-agui<br/>external protocol"]
309
- agui --> clients["CLI / TUI / editor clients"]
310
- ```
130
+ package map. The default installed composition is specified in {§bundled-set}.
311
131
 
312
132
  §ecosystem-composed-host Core is the composed runtime: it owns persistence,
313
133
  scheduling, packet assembly, dispatch, and cross-capability orchestration while
@@ -319,18 +139,6 @@ clients render and submit actions but contain no engine logic.
319
139
 
320
140
  OpenTelemetry may observe PLURNK; it never becomes product state, failure transport, scheduler input, model teaching, or client protocol. Domain and client activity remain on AG-UI. Reusable packages depend on the OTel API only; the daemon constructs only the explicitly configured trace and metric providers. An unconfigured or standards-valid disabled process loads no SDK or exporter implementation and keeps the API's no-op behavior with bounded overhead. OTel Logs have no provider or initialization path.
321
141
 
322
- ### §standards-discernment Standards discernment
323
-
324
- Seven principles govern which exterior standards Plurnk conforms to (#299):
325
-
326
- 1. **UVP first.** Never conform away what users chose Plurnk for; the OP grammar, curated log, packet, and worker graph are the product, not a compatibility gap.
327
- 2. **Right-fit.** Hobbyist-first: an enterprise-grade feature is acceptable only when its cost lands on the party that wants it, never on general adoption.
328
- 3. **Traction.** Count running counterparties today; integration horizon must be shorter than the standard's expected half-life. Sockets stay configurable with no default until a candidate earns it.
329
- 4. **POSIX app identity.** Decades-stable host-ecosystem conventions (XDG, NO_COLOR, man, completions, service units) outrank months-stable AI-pipeline fashions.
330
- 5. **Faces, never organs.** A standard adopts as one adapter or projection behind an existing seam; if it cannot, that is the alarm, and it goes to a design gate.
331
- 6. **Deletion is the price of admission.** A standard earns adoption by deleting bespoke surface (the ACP Plan object deleted the Markdown plan microformat); parallel representations, second discovery paths, and compatibility grammars are refused.
332
- 7. **Two arbiters.** Model-facing surfaces change only on measured model evidence; human-facing surfaces follow host-ecosystem convention without ceremony. Standards bodies get a vote on neither.
333
-
334
142
  Configuration uses the standard `OTEL_*` environment: `OTEL_TRACES_EXPORTER` / `OTEL_METRICS_EXPORTER` select `otlp` or `console` per signal (a missing or `none` value keeps that signal off; no SDK default selects an exporter), `OTEL_SERVICE_NAME` names the service (default `plurnk-service`), case-insensitive `true` in `OTEL_SDK_DISABLED` turns the boundary off, and OTLP exporters honor `OTEL_EXPORTER_OTLP_*`. An unknown exporter name fails daemon boot; a typo never silently disables observation. OTel Logs and direct draft semantic-convention use are excluded. HTTP spans carry only an AG-UI-owned bounded route class, never an input pathname or query. Spans otherwise carry high-cardinality identifiers; metric labels stay low-cardinality. Prompts, reasoning, file bodies, arbitrary URLs, secrets, and plugin payloads are never recorded as attributes or metric values by default. Exporter failure cannot change product results or client lifecycle. Daemon, telemetry, and database teardown are independent reverse-ownership phases; every phase runs and aggregate failure preserves every cause.
335
143
 
336
144
  §observability-genai-conventions **GenAI convention projection.** Provider
@@ -346,6 +154,18 @@ boundary is unchanged — no prompts, reasoning, bodies, or URLs. This is the
346
154
  sanctioned exception to the blanket draft-convention exclusion; no other
347
155
  draft convention is projected.
348
156
 
157
+ ### §standards-discernment Standards discernment
158
+
159
+ Seven principles govern which exterior standards Plurnk conforms to (#299):
160
+
161
+ 1. **UVP first.** Never conform away what users chose Plurnk for; the OP grammar, curated log, packet, and worker graph are the product, not a compatibility gap.
162
+ 2. **Right-fit.** Hobbyist-first: an enterprise-grade feature is acceptable only when its cost lands on the party that wants it, never on general adoption.
163
+ 3. **Traction.** Count running counterparties today; integration horizon must be shorter than the standard's expected half-life. Sockets stay configurable with no default until a candidate earns it.
164
+ 4. **POSIX app identity.** Decades-stable host-ecosystem conventions (XDG, NO_COLOR, man, completions, service units) outrank months-stable AI-pipeline fashions.
165
+ 5. **Faces, never organs.** A standard adopts as one adapter or projection behind an existing seam; if it cannot, that is the alarm, and it goes to a design gate.
166
+ 6. **Deletion is the price of admission.** A standard earns adoption by deleting bespoke surface (the ACP Plan object deleted the Markdown plan microformat); parallel representations, second discovery paths, and compatibility grammars are refused.
167
+ 7. **Two arbiters.** Model-facing surfaces change only on measured model evidence; human-facing surfaces follow host-ecosystem convention without ceremony. Standards bodies get a vote on neither.
168
+
349
169
  ### §in-process In-process architecture
350
170
 
351
171
  Composed daemon internals + admin CLI. Four plug points:
@@ -353,7 +173,7 @@ Composed daemon internals + admin CLI. Four plug points:
353
173
  - **Providers** ({§provider}) — LLM transports. Engine sends a turn's messages, receives raw content + usage; engine parses the content into `PlurnkStatement[]`.
354
174
  - **Schemes** ({§scheme}) — addressed capabilities. A scheme handler interprets targets under its prefix and owns its storage substrate.
355
175
  - **Mimetypes** ({§mimetype}) — content interpretation. Render-time handlers consume channel content; framework owns the dispatch.
356
- - **Executors** ({§exec} / {§bundled-set}) — EXEC runtime dispatch for subprocess, data, and pure-computation runtimes; web discovery rides the ordinary MCP surface.
176
+ - **Executors** ({§exec} / {§bundled-set}) — execution dispatch for subprocess, data, and pure-computation runtimes; web discovery rides the ordinary MCP surface.
357
177
 
358
178
  Core's internal owners compose without becoming new package or public seams:
359
179
 
@@ -372,6 +192,13 @@ The contracts package (`@plurnk/plurnk-contracts`) owns the parser and AST contr
372
192
 
373
193
  Server posture: this package is the one long-running runtime process. `plurnk-agui` exposes its external protocol; user-facing clients run separately and do not call core's in-process seam directly.
374
194
 
195
+ §service-worker-composition The service launcher and the live/demo workspace
196
+ helper share one registration of default worker-facing modules: MCP and outbound
197
+ A2A. Their management families and readable reference documents are present even
198
+ with no enabled attachments. Workspace capability policy controls every actor's
199
+ surface; registering a family does not enable a remote attachment. Client and
200
+ inbound-A2A listeners and host hooks remain launcher-owned.
201
+
375
202
  ### §service-package-exports Package export surface
376
203
 
377
204
  | Export path | Current contract |
@@ -410,7 +237,7 @@ before provider or capability initialization can perform external work. Every
410
237
  later startup failure closes resources in reverse ownership order while
411
238
  preserving the originating failure: daemon, observability, database, listener.
412
239
 
413
- ### §actor-boundary The actor boundary: per-worker attention, two doors, self-hosting
240
+ ## §actor-boundary Workers and workspace boundaries
414
241
 
415
242
  ```mermaid
416
243
  flowchart LR
@@ -475,13 +302,13 @@ never wake; they queue until another cause produces a turn ({§env-delta}). The
475
302
  obligation edge is continuation control, not a third door through which
476
303
  arbitrary workspace state can enter.
477
304
 
478
- §actor-boundary-self-hosting **Use the actor path when the work has an operation; retain irreducible rails in the kernel.** The workspace has one reserved `plurnk` Worker. `DispatchAsPlurnk` opens ordinary administrative loops and turns for its work. Generated references are shared entries; the runtime actor has no privileged scratch access.
305
+ §actor-boundary-self-hosting **Use the actor path when the work has an operation; retain irreducible rails in the kernel.** The workspace has one runtime Worker, `_plurnk` (origin `_plurnk`), a name `WORKER_NAME` never admits, so no model or client can mint or resume it ({§worker-name-minting}). `DispatchAsPlurnk` opens ordinary administrative loops and turns for its work. Generated references are shared entries; the runtime actor has no privileged scratch access.
479
306
 
480
307
  | Work | Owning path | Why |
481
308
  | --------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
482
309
  | Workspace reference documents | Runtime actor; `_plurnk` EDIT through engine dispatch. | Maintaining generated workspace scratch is an ordinary operation. |
483
310
  | Git membership and disk materialization | Kernel `GitMembership` / entry CRUD. | Ingesting existing disk state is not a model-authored EDIT. |
484
- | Disk-divergence narration | Kernel writes an EDIT-shaped `source=file` row to the `plurnk` log. | It reports an environment event honestly; no operation is fabricated as having run. |
311
+ | Disk-divergence narration | Kernel writes an EDIT-shaped `source=file` row to the `_plurnk` log. | It reports an environment event honestly; no operation is fabricated as having run. |
485
312
  | Search derivation and catalog render | Kernel. | They are indexes and read-only projections, not entry operations. |
486
313
  | Packet assembly and budget rails | Kernel. | They are the execution substrate on which actor operations depend. |
487
314
 
@@ -496,7 +323,7 @@ file: no entry, no stunt, nothing 404s. The global XDG configuration `AGENTS.md`
496
323
  remains system-prompt policy ({§policy-sections}); the stunt carries only
497
324
  local repo guidance.
498
325
 
499
- §actor-boundary-doc-injection **Generated documents use the actor path.** Workspace references and projected project instructions are materialized in `worker:///_plurnk/` through the reserved actor's ordinary maintenance turns. Their exact programs and operation evidence remain durable in that actor's log; generation is not a hidden database write.
326
+ §actor-boundary-doc-injection **Generated documents use the actor path.** Workspace references and projected project instructions are materialized in `worker:///_plurnk/` through the runtime actor's ordinary maintenance turns. Their exact programs and operation evidence remain durable in that actor's log; generation is not a hidden database write.
500
327
 
501
328
  Maintenance turns create neither lineage nor commons broadcasts. Their loops are not work-lifecycle observations ({§application-worker-observation}, {§application-loop-observation}); `work_loops` excludes loops whose turns are all maintenance, but retains empty queued loops and loops containing any other purpose. Scheduling and forensic history remain intact.
502
329
 
@@ -537,7 +364,7 @@ log already renders in present mode.
537
364
 
538
365
  §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its program is stored before execution and dispatches the orienting READ/FIND surveys, the reasoning and program READs in {§reasoning-initial-read}, and the final TASK (in {§op-execution-order}). The prompt is not READ here: its `prompt` row in the first model turn is the one publication of every prompt ({§prompt-entry}), so the body never appears twice. The full `<1,-1>` READ of its own `ops:///<loop>/<turn>` source supplies the worked program example; no actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The program archives nothing: the prompt entry ({§prompt-entry}) is the durable, addressable copy of every prompt, so no scratch archive of it is written. The three namespace surveys carry asides that name what each space is — `project root member files`, `shared worker Extended Context`, `private worker Extended Context` — and, because the program echo shows them verbatim, those asides are the model's map of its spaces. TASK hands off with one {§plan-value} entry: `{"content":"Address the prompt.","status":"in_progress"}`. The first model request occupies the following turn and therefore begins at database/log turn sequence 2; “turn zero” is the initialization phase's model-facing label, not a zero-based database coordinate. Client and `_plurnk` administrative workers execute operation turns and do not receive model initialization.
539
366
 
540
- ### §machine-processes The machine and its processes: workspace, worker, fork
367
+ ### §machine-processes Workspace and worker state
541
368
 
542
369
  A workspace owns every entry; a worker owns its history and active work.
543
370
 
@@ -627,7 +454,7 @@ shared entries, and membership remain live and uncopied.
627
454
  histories over one workspace are worker forks. A divergent project filesystem
628
455
  or membership overlay requires a new workspace.
629
456
 
630
- ### §worker-scheme The worker:// scheme — the knowledgebase (commons, own space, named spaces) and worker control (spawn, irc, fork, terminate, cap, collect)
457
+ ### §worker-scheme Worker resources and control
631
458
 
632
459
  §worker-authority-carving **Authority is a literal namespace, not a principal.** `worker:///notes.md` is shared scratch; `worker://alice/draft.md` is named scratch. Both belong directly to the workspace ({§entry-owner}). Entry addresses require no namesake Worker row and survive its deletion; only pathless worker controls resolve an actor. The caller never alters an address, and `~` has no alias semantics.
633
460
 
@@ -638,9 +465,8 @@ continues to decompose other authorities without treating them as mintable.
638
465
 
639
466
  | Candidate | Minting result |
640
467
  | ---------------------------------------------- | --------------------------------------------------------------------- |
641
- | `WORKER_NAME` match, not reserved | Admitted as the exact literal worker name. |
642
- | `plurnk` (any case variant) | Refused as reserved before lookup or insertion. |
643
- | Any other spelling | Refused as `name-invalid` before lookup, insertion, or child startup. |
468
+ | `WORKER_NAME` match | Admitted as the exact literal worker name; `plurnk` is one. |
469
+ | `_plurnk`, or any other spelling | Refused as `name-invalid` before lookup, insertion, or child startup. |
644
470
  | Automatic name | Generated, then admitted through the same predicate. |
645
471
 
646
472
  §worker-read-scope **Scratch is workspace-readable.** Any actor reads and searches any named or shared scratch address. Parentage and writer identity do not change resolution. Scratch namespaces do not require a namesake Worker. A pathless actor address requires a named Worker; an unknown actor returns 404.
@@ -649,7 +475,7 @@ continues to decompose other authorities without treating them as mintable.
649
475
 
650
476
  §worker-generated-subtree **Generated documents share `worker:///_plurnk/`.** Project instructions (`agents.md` and subtree-scoped `instructions/**`), scheme/runtime references (`plurnk/**`), tool details (`tools/**`), and family catalogs are workspace resources. Agent Skills retain their own trees at `skill://<name>/` ({§skills-resources}).
651
477
 
652
- The subtree has ordinary scratch access, not an ACL. Runtime maintenance reconciles it from workspace Functionality through the reserved actor's ordinary turns ({§actor-boundary-doc-injection}); reconciliation may replace manual edits. There are no per-Worker copies or fork rederivation. A runtime's `resourcesPath` is relative to this root ({§tools-resource-materialization}).
478
+ The subtree has ordinary scratch access, not an ACL. Runtime maintenance reconciles it from workspace Functionality through the runtime actor's ordinary turns ({§actor-boundary-doc-injection}); reconciliation may replace manual edits. There are no per-Worker copies or fork rederivation. A runtime's `resourcesPath` is relative to this root ({§tools-resource-materialization}).
653
479
 
654
480
  §worker-control-addressing **Explicit worker control addresses are authority-only.**
655
481
  WORK and FORK may omit their address to allocate one ({§worker-auto-name}).
@@ -702,17 +528,19 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
702
528
  - §worker-scheme-fork-scratch **Forked scratch.** Named scratch and evidence are copied under the new name through {§machine-processes-entry-inheritance}. Parent and branch can edit either scratch namespace; their copies diverge independently.
703
529
  - §worker-spawn-no-branch **WORK and FORK take a worker path and a prompt, nothing else.** Branch
704
530
  delegation was removed outright (#396). A model manages git branches through ordinary
705
- EXEC git — never engine machinery.
531
+ the `git` runtime — never engine machinery.
706
532
  - §worker-delegation-inherits-policy **Fresh delegated loops inherit proposal disposition.** WORK, FORK, and SEND to an idle Worker carry the sender's proposal disposition. SEND into an active or parked loop leaves its immutable policy untouched. All workers share live workspace capability policy; delegation creates no capability snapshot or bound.
707
533
  - §worker-lifecycle-wake-requeue-not-terminal **A wake re-queue is not a terminal.** A conclusion-wake resumes a 202-blocked loop by re-queueing it (202 → 100); when that lands while the loop's own live drain is between turns, the drain **re-claims and continues** (atomic 100 → 102; the injected prompt is already the next turn). The internal re-queue is never reported as an outward terminal.
708
534
 
709
535
  - §worker-scheme-collect **Collect** — a worker's loop reaching a terminal status
710
536
  surfaces to its direct parent as an ambient delta ({§env-delta}): a `SEND` from
711
- `worker://<name>` carrying the loop's exact terminal operation result. A
712
- **2xx deliverable is born visible** (its body
713
- materialized into the parent's packet, not body-suppressed): a child's
714
- success must reach the parent visible and awakening, never a bodyless row. An
715
- non-2xx result surfaces body-suppressed; a failure retains its exact status and Problem. Every death-path is stamped uniformly —
537
+ `worker://<name>` carrying the loop's exact terminal operation result. **Every
538
+ conclusion is born visible** (its body materialized into the parent's packet,
539
+ not body-suppressed): a child's last message must reach the parent visible and
540
+ awakening whatever the status, never a bodyless row, because a failure's
541
+ explanation is its deliverable (operator, 2026-09-14: fail is completed with a
542
+ frowny face); a failure retains its exact status and Problem beside that
543
+ message. Every death-path is stamped uniformly —
716
544
  including a spawn that dies before its first turn — so no child termination is
717
545
  silent to its owner; collection is lineage
718
546
  supervision, never a
@@ -758,32 +586,227 @@ EXEC git — never engine machinery.
758
586
 
759
587
  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.
760
588
 
761
- ### §worker-loop-lifecycle Worker and loop lifecycle: drain, reap, and passive wake
762
-
763
- - §join-blocking-collect **Collection and scheduling are independent.** A path-absent ```` ```READ (worker://<running-child>) ```` returns **425** (Too Early), without a strike or scheduler side effect. TASK with waiting intent joins live obligations; an `in_progress` inventory keeps working. A child reaching any terminal status wakes a waiting parent with its result, including completion racing the park boundary. Children retain their own limits; TASK timing may additionally bound the parent's wait. Collection never arms an implicit disposition override.
589
+ ## §membership File membership and project roots
764
590
 
765
- A worker is a **log plus a cancellation scope** — one `AbortController` per worker, reused while live and replaced only once aborted, so a cancel ends the worker as a unit and a later `runLoop` request is never born cancelled. A worker's queued loops are advanced by a **drain**: a single per-worker drain that claims loops atomically (status 100→102) and runs each under the worker's scope. A loop may spawn **streams** (execs) that outlive it; each is a row in the subscription registry ({§subscriptions}) — the durable record of what the worker holds open. Cancellation and conclusion are defined against these structures, never wall-clock timing.
591
+ The project-file path has two explicit reconciliation gates. Internal entries do
592
+ not participate in this disk loop.
766
593
 
767
594
  ```mermaid
768
- stateDiagram-v2
769
- [*] --> Queued: runLoop request
770
- Queued --> Running: due task claimed by drain
771
- Running --> Parked: wait with live obligations
772
- Parked --> Queued: obligation settles or arrival
773
- Running --> Terminal: conclude or fail
774
- Parked --> Terminal: cancel
775
- Queued --> Terminal: cancel
776
- Terminal --> [*]
595
+ flowchart LR
596
+ git["Git tracked"] --> resolve["Resolve workspace membership"]
597
+ include["include"] --> resolve
598
+ exclude["exclude"] -->|subtract| resolve
599
+ resolve --> materialize["Pre-turn materialize<br/>disk → file snapshot"]
600
+ materialize --> read["READ snapshot"]
601
+ materialize --> edit["EDIT against snapshot"]
602
+ edit --> proposal["Proposal"]
603
+ proposal -->|"client accepts or loop auto"| cas["synced_sig compare-and-swap"]
604
+ cas -->|"file snapshot → disk"| project["Project file"]
605
+ project --> materialize
777
606
  ```
778
607
 
779
- §worker-lifecycle-state-machine The lifecycle store admits only the guarded transitions shown above: `100 → 102`, `102 → 202`, `202 → 100`, and any unresolved state (`100`, `102`, `202`) to a terminal status. Terminal state is immutable. `DrainSupervisor` owns claim, wake, and cancellation; the dispatcher owns model-requested park/conclusion; the daemon owns boot-recovery orchestration; and the engine owns policy terminals. A racing transition that loses observes the durable winner; it does not overwrite it or report the requested state as fact.
780
-
781
- §worker-lifecycle-live **Worker liveness is existential, not latest-state.** A
782
- Worker is live while ANY of its loops is unresolved (`100`, `102`, or `202`). A
783
- newer terminal loop cannot mask older queued, running, or parked work. Name
784
- collision, workspace worker caps, child obligations, orientation, and recovery
785
- all use that one definition.
786
-
608
+ | Concern | Owner and representation |
609
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
610
+ | Workspace identity | `workspaces.project_root`; null is headless. There is no separate project entity. |
611
+ | File visibility | Workspace-tier resolved membership: `(tracked files ∪ include) − exclude` ({§membership-baseline}). Every worker sees the same result. |
612
+ | File reads | READ returns the materialized file snapshot stored in the entry body channel; it does not read disk directly. |
613
+ | File writes | EDIT proposes against that snapshot. Only accepted resolution with the captured `synced_sig` writes the project file. |
614
+ | Internal entries | Workspace or worker entries are canonical store state. Writing one never implies a project-file write. |
615
+ | Authority | Service flags set the membership ceiling; the `members` family's definitions include and exclude within it; client or loop auto resolves proposals. `origin` is attribution. |
616
+
617
+ §web-search-retrieval **Web discovery is an ordinary MCP concern; retrieval is a first-class composition.** PLURNK owns no search runtime: a search-capable MCP server (e.g. Brave Search) participates through the ordinary MCP contract — admission, read-effect classification, tool documentation, and packet projection are identical to every other MCP tool ({§mcp-tool-presentation}). An executor that wants to materialize discovered pages uses the generic `content: null` `entry()` request ({§exec-entry-sink}): the guarded `WebFetcher` sink fetches candidates in parallel, off the write-serialization chain, and materializes successful bodies as ordinary HTTP entries. Every candidate whose `entry()` call rejects, regardless of failure reason, is mechanically omitted from the model-facing result directory; survivors retain upstream order. Without an entry sink the executor cannot test materialization and omits the verdict.
618
+
619
+ Search prefetch and direct HTTP READ materialize the same resource contract:
620
+ protocol + canonical authority (including a non-default port) + path + serialized
621
+ query is the absolute identity ({§scheme-address-network}); the sanitized
622
+ readable projection is the fragmentless default, while faithful DOM, origin
623
+ media type, and projection identity remain explicit auxiliary evidence. A
624
+ normal
625
+ ```` ```READ (https://host/path?query) ```` therefore publishes only the sanitized body
626
+ under that exact URL—never raw HTML, response headers, or a channel-selection
627
+ lesson. FIND consumes the addressed stored channel representation
628
+ and never re-fetch a match.
629
+
630
+ §web-retrieval-live Coverage protects the composition at distinct seams: HTTP unit tests pin fragmentless-body publication and explicit auxiliary selection; integration tests pin materialize→FIND and persistence/publication separation. A live positive-control demo requires a materialized HTTPS body and a substantive answer from a real sanitized page; live discovery demos remain diagnostic and may expose model judgment failures without weakening these assertions.
631
+
632
+ **Git is the substrate and the repository is the boundary:**
633
+
634
+ - §membership-baseline **The baseline contract — chiseled (#400).** To a workspace a
635
+ project file is exactly one of three things: **invisible**, **added**, or **tracked
636
+ by git**. There is no fourth category. Membership — what the model can READ and
637
+ FIND, what is materialized into the store, what a packet can ship to a provider — is
638
+ the allowlist `(tracked ∪ include) − exclude` and nothing else. No file is a member because
639
+ it exists on disk, because git does not ignore it, or because a model would find it
640
+ convenient: ambient admission of untracked files is prohibited, so a workspace rooted
641
+ in a home directory or a monorepo exposes exactly what was committed or added (the
642
+ non-member rule, {§fs-write-nonmember}: no read, no leak, no overwrite). Every
643
+ exception is a named clause in the register ({§membership-model-universe}), admits
644
+ files by an exact creation record with recorded provenance, and never by `git add`.
645
+ Changing this clause, the register, or the composition is an operator ruling recorded
646
+ on the issue that lands it — never an implementation convenience, never a side effect
647
+ of making a file visible to solve the problem at hand. The 2026-07-12 – 2026-08-27
648
+ "untracked-but-not-ignored" ambient admission is retired.
649
+ - §membership-model-universe **The exception register — files in the model's universe.**
650
+ Admitted by exact creation records (`source: "create"`, origin `constraint`), never
651
+ staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
652
+ ({§membership-create-parents}). Admitted by a published standard as projected
653
+ instruction documents — never as members: (3) the project's `AGENTS.md` and nested
654
+ `AGENTS.md` files ({§turn0-agents-stunt}, #346), read from disk regardless of git status
655
+ and materialized as `worker:///_plurnk/agents.md` and
656
+ `worker:///_plurnk/instructions/<subtree>/AGENTS.md`; the file itself is a member
657
+ only when tracked or added, and the standard never overrides the operator's
658
+ exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
659
+ is not projected. (4) A definition the model proposes through the `members`
660
+ family ({§members-functionality}), admitted only under the operator's ceiling
661
+ `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (shipped `namespace`), projected with source
662
+ `model`, and never admitted past the repository's ignore rules or an exclusion.
663
+ Nothing else.
664
+ - §membership-git-membership The workspace owns the Git repository containing
665
+ `project_root`. Its tracked files (`git ls-files` semantics) are members with
666
+ no explicit overlay; when the root is a package inside a monorepo, the
667
+ repository's other packages are members at root-relative paths. An unrelated
668
+ or nested independent repository is not discovered or managed by this
669
+ workspace. When Git is absent there is no filesystem walk; member definitions are
670
+ then the sole source.
671
+ - §git-native-default **Core Git reads use native Git.** Membership and status
672
+ execute the installed Git binary. An absent or failed binary yields no
673
+ automatic Git membership or status; core has no alternate implementation or
674
+ fallback.
675
+ - §membership-git-hermetic Native Git runs with ambient `GIT_*` and
676
+ global/system config scrubbed, and with the repository's own program-running
677
+ keys pinned off at the highest precedence (`core.fsmonitor=false`,
678
+ `core.hooksPath=/dev/null`), so repository identity follows `project_root`,
679
+ never the daemon's launch environment, and inspecting a supplied repository
680
+ never runs a program its `.git/config` names. Other repository-local
681
+ configuration is still read (#568). The one program no key can pin off is a
682
+ `filter.<name>.clean` / `filter.<name>.process` driver, which `git status`
683
+ index refresh may run: automatic inspection asks the repository's own config
684
+ first (`git config --get-regexp`, no program runs) and, when any such key is
685
+ declared, refuses the repository as a warning-and-skip — Git status and
686
+ automatic Git membership answer exactly as for a non-repository, and one
687
+ `engine:membership` / `git_inspection_refused` notice names the key, once per
688
+ workspace until it changes or clears. User- and model-requested Git commands
689
+ stay on their explicit execution path.
690
+ - §membership-edit-membership-gate **Membership-gated edits.** EDIT is bounded by membership exactly as READ is. An existing **member**'s baseline is its entry snapshot — the body channel the model READ, not a fresh disk read — so the diff is naive against the view the model saw, never empty (the write-side CAS, {§membership-edit-write-cas}, prevents the silent overwrite of out-of-band drift). An existing **non-member** is refused (403) *before* any read or write: the model never reads a file it can't see (no leak into the proposal) and never overwrites one (no wiping a gitignored `.env` it never added). A **new path** crosses the creation matrix in {§fs-write-surface}; proposal acceptance cannot bypass its scope, exclusion, or incorporation rules. Shell execution reaches beyond file membership; the file scheme does not.
691
+ - §membership-create-parents **Parent-complete creation.** An accepted File creation—whether authored as EDIT or as a COPY/MOVE destination—recursively creates missing parent directories before writing and registering the new member.
692
+
693
+ **The overlay — `include | exclude`.** `workspace_constraints` holds the `members` family's projected definitions and the engine's creation records ({§members-projection}). Resolved membership is `(project repository files ∪ include) − exclude`.
694
+
695
+ - §membership-auto-add **Auto-add** — the project repository's ambient membership is its tracked `ls-files`, with `git` origin; an untracked file is never an ambient member ({§membership-baseline}). An accepted creation is incorporated by an exact creation record, never by `git add` ({§membership-model-universe}); a record that cannot be written fails the creation transaction, never an orphan ({§file-create-no-orphans}).
696
+ - §membership-overlay-include **`include`** — admit a file Git misses through a targeted pattern scan (files only), with `constraint` origin. `source: "members"` is a projected human definition, `source: "model"` a projected model definition, `source: "create"` the exact durable record of an accepted creation. Only `members` inclusions override active Git ignore. In a Git-absent root, inclusions are the sole file-membership source.
697
+ - §membership-overlay-exclude **`exclude`** — a `!glob` definition removes a tracked or included file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-oversized or sensitive tracked file; exclusions mask creation records without deleting their provenance ({§fs-create-masked}).
698
+ - §membership-reconcile-sets **Reconciliation is two set statements.** Once the desired set is composed — `(git ls-files ∪ include) − exclude`, glob evaluation and Git shell-outs being the process's own — it lands as one `INSERT … SELECT FROM json_each` with the same idempotent provenance update as a single registration, and every overlay-owned member outside it leaves in one `DELETE … WHERE pathname NOT IN (json_each) RETURNING`, its prior body riding out of the statement. A path that also left disk truth (no longer a candidate at all) becomes a divergence from that prior content; an exclusion is silent. No per-path statement, no set difference outside the database.
699
+ - §membership-glob-in-sql **A constraint is evaluated where it lives.** `glob_match(pathname, glob)` is `node:path.matchesGlob` registered into SQLite (`src/core/glob_match.ts`, deterministic), the one matcher every overlay decision uses, so a lookup that asks the constraints table a question — which exclusion covers this key, whether a members definition includes it, which inclusion owns each untracked path — is one statement over `workspace_constraints`, never a listing filtered in the process. Transient inputs the process already holds (a `git ls-files` listing against the exclude globs) stay filtered in the process; the function exists so the database's own rows can be asked, not so process data makes a round trip.
700
+
701
+ **File ops act on the entry, not the disk; the two reconcile only at gates.** A `file:///` member is a row whose body channel holds its *materialized model-readable snapshot*. READ returns that channel; EDIT diffs against editable text snapshots — neither reaches the filesystem directly. Entry and disk reconcile at exactly two gates: the **pre-turn materialize** (disk → entry, below) and the **accept-time write-back** (entry → disk, {§proposal}). Between the gates the entry is the truth the model curates against, and `synced_sig` — the member's last-synced disk stat (`mtime:size`) — is the version token both gates compare on.
702
+
703
+ §membership-source-projection Binary acquisition is transient and bounded by
704
+ {§mimetype-binary-input}; durable entry channels remain Unicode text. Core-private
705
+ `sourceProjection` attributes preserve the source mimetype, opaque projection
706
+ identity, and terminal disposition without exposing raw bytes or a base64 lane.
707
+
708
+ | Disk source | Durable body | Operation effect |
709
+ | ---------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
710
+ | Text | Verbatim Unicode under the detected textual mimetype | READ and EDIT use the snapshot. |
711
+ | Binary with readable projection | Derived Unicode as `text/markdown` | READ uses the projection; source-aware EDIT remains 415. |
712
+ | Binary without projection/over cap | Empty marker under the source binary mimetype | READ and EDIT return 415; private metadata distinguishes unavailable from limit. |
713
+
714
+ §membership-materialization-limit **A pathological member degrades, never the
715
+ workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
716
+ byte ceiling over one disk source before Core reads it into the canonical file
717
+ snapshot. Its valid range is `1..104857600`, bounded by the channel storage
718
+ contract, and it ships at that 100 MiB maximum. An oversized path remains a real member
719
+ with an empty body channel carrying a durable 413 producer result; no diagnostic
720
+ sentinel impersonates file content. READ therefore names the path, observed bytes,
721
+ ceiling, and recovery through the ordinary result contract, while EDIT returns the
722
+ same 413 instead of diffing against a fictitious empty baseline. Core records the
723
+ materialization disposition and ceiling privately, so an unchanged disk member is
724
+ reconsidered when the operator changes the policy and otherwise remains a stat-only
725
+ no-op. The file write gate independently stats the source against the same ceiling,
726
+ so safety does not depend on a background warm winning a client-operation race.
727
+
728
+ §derivation-dedup-parallel **The index dedups then parallelizes.** The derivation identity hashes the exact READ channel representation, mimetype, reader behavior, and applicable search exclusion. A channel or log projection attaches the immutable artifact only after it is complete; identical projections therefore share one FTS row and one symbol graph without copying. The identity hashes the representation's own SHA-256 ({§tokenomics-content-hash-identity}), never its body, so a maintenance pass judges an entry channel from its stored `content_hash` without the body crossing into the process; a body is acquired only for a derivation that runs, under that same identity (a representation that moved on since it was judged returns nothing and is judged again next pass), or for a channel with no stored identity, an open or streamed channel, which must be read to be judged. Log rows and turn sources still arrive whole. The pass reports the bytes it acquired beside the derivations it ran, and an unchanged workspace acquires none of its channel bodies at any concurrency. Distinct artifacts run with bounded producer concurrency (`PLURNK_SERVICE_DERIVE_CONCURRENCY`). Pending artifacts sort by readable content length before entering that pool, so small resources start first while every outlier still derives fully. Unset uses a host-relative square-root fan-out; a positive integer is an exact operator budget and `-1` claims every core. Graph persistence writes at most `PLURNK_SERVICE_DERIVE_STORE_BATCH` definitions or references per SQLite statement. Every launched worker settles before the maintenance pass reports success or failure, so one failed artifact cannot orphan sibling derivations. Every representation completed by a successful pass attaches a terminal classified artifact, identically at concurrency 1 and N. A changed pass emits one immediate `preparing` state, intermediate `indexing` heartbeats at `PLURNK_SERVICE_DERIVE_PROGRESS_HEARTBEAT_MS`, and one immediate `complete` or `failed` state. A no-op pass emits no lifecycle, an indexing heartbeat never claims 100%, and the model-facing Notice buffer retains only the current derivation state while live clients observe each heartbeat.
729
+
730
+ The artifact also retains a positive `{§mimetype-parse-issues}` count and the
731
+ full normalized `{§mimetype-summary}` when the exact parsed channel reported
732
+ either. Both remain advisory alongside a normally completed search
733
+ disposition; zero, empty, and unavailable evidence persist as absence. Catalog
734
+ projection attaches either only to that channel, never to a sibling whose
735
+ content the artifact does not describe.
736
+
737
+ Every completed artifact records one terminal disposition: `indexed`, `excluded`
738
+ (the configured search-exclusion table), `unsearchable` (empty or binary), or
739
+ `failed` (a typed {§mimetype-error-policy} invalid-source failure, or the
740
+ handler's own defect on that one member — {§derivation-member-failure}).
741
+
742
+ §derivation-member-failure **One member's derivation failure never ends the
743
+ pass or the model's turn.** A handler defect on one member — a
744
+ `MimetypeDerivationError` under {§mimetype-derivation-evidence}, the exception
745
+ being a cancellation — is that member's terminal `failed` disposition, whose
746
+ `reason` is the handler's invocation context followed by the exact original
747
+ cause (`Mimetype derivation failed for "/x.js" ("text/javascript").
748
+ RuntimeError: …`). The pass continues, attaches every other member, and
749
+ completes; its terminal `search_progress` notice is `complete` at `level: warn`,
750
+ naming the first failed member and carrying the count. A failed member is
751
+ terminal for that exact content, handler revision, and configuration identity
752
+ ({§derivation-dedup-parallel}); a change to any of those derives it again.
753
+ Everything that is not a handler's derivation of one member stays fatal to the
754
+ pass exactly as before: a grammar that is not installed, an index-persistence
755
+ or contract failure, and cancellation, each leaving the artifact `building` for
756
+ retry.
757
+ Cancellation and implementation, loading, database and index-persistence failures
758
+ remain `building`, unattached, and retryable; Core never guesses that an arbitrary
759
+ projection exception is bad content. The digest reports exceptional dispositions
760
+ with their reasons. Successful optional projection degradations continue indexing
761
+ and surface their framework Notice once per identical observation in a maintenance
762
+ pass.
763
+
764
+ §membership-change-gated-sync **Sync is idempotent and change-gated.** Per turn, membership materializes every member's model-readable snapshot into its entry. Text with an unchanged disk signature and materialization policy is a stat-only no-op. The version token is either the observed `mtime:size` or the explicit `absent` state; an observed deletion removes the stale readable channels, and a later reappearance is therefore a new divergence rather than a first-sight materialization. Binary sources additionally compare the cached per-mimetype projection identity; unchanged bytes are never reacquired, while changed reader behavior rematerializes without fabricating a filesystem-divergence event. Coverage is exhaustive across the project repository while work is proportional to source or projection change. After a pass every member carries the current representation defined by {§membership-source-projection} and {§membership-materialization-limit}.
765
+
766
+ §membership-emi-divergence-signal **EMI divergence evidence.** The detector that gates the work *is* the one that records this — one mechanism, not a second full read. When change detection finds a member moved out-of-band, the runtime actor records an `EDIT`-shaped row naming the file with `source="file"`; it does not broadcast that workspace change into unrelated workers' logs ({§env-delta-filesystem-narration}). The model's own edits are write-through (the entry equals disk after a File write), so the scan never mis-attributes them as external divergence. The current file remains ordinarily addressable. A stale anchored edit rejects under {§line-anchors}; a disk race after proposal rejects under {§membership-edit-write-cas}.
767
+
768
+ §membership-edit-write-cas **The write-back is a compare-and-swap — never a clobber, never a clever merge.** EDIT is *naive against the editable text snapshot*: it diffs the model's change onto the entry's body channel — the exact Unicode the model READ — and the proposal carries the `synced_sig` that snapshot was taken at. Binary sources are refused before this path ({§membership-source-projection}). At accept, `applyResolution` re-stats disk and lands the proposed content only if that signature still matches. If disk moved out-of-band in the propose→accept window — a sibling worker, the user's editor, a build step — the write is **refused** with the same neutral `edit-collision` as {§edit-collision}, and **nothing is written**. The engine neither blind-writes over the ambient change (a *clobber*) nor silently re-diffs the model's edit against a state it never saw (getting *clever*) — both would bury a stale-view contract violation under a fallback. The collision surfaces instead: a ≥400 apply downgrades to a reject ({§proposal}), so the model sees that EDIT **did not occur** (400; the `edit_collision` outcome is forensics-only). Reconciliation aligns the current file projection and records the `source=file` evidence in the runtime log ({§membership-emi-divergence-signal}); the model re-reads and re-proposes against the fresh snapshot.
769
+
770
+ The version travels *with the proposal*, never re-read from the entry at accept: a sibling worker in the same workspace may reconcile while this proposal sits paused, advancing the entry's `synced_sig` to the drifted disk — comparing against the *current* entry sig would wave that clobber through, so the comparison is always against the sig the proposal was computed at. A proposal that assumed an **absent** path (a create) conflicts only if a file has since appeared; a member with **no recorded snapshot** (an un-materialized entry, null `synced_sig`) has no baseline to guard and writes through — the two are told apart by the proposal's `existed` flag, not by a null sig alone. On a clean landing the entry refreshes to the written content and `synced_sig` is **restamped** to it, so the next reconcile recognizes the model's own write (not an external divergence) and a second same-turn edit bases on the landed bytes, not a stale sig. This is the write-side twin of the read-side change-gate ({§membership-change-gated-sync}): one `synced_sig`, gating both the re-read and the write.
771
+
772
+ 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.
773
+
774
+ §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`.
775
+
776
+ **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.
777
+
778
+ **Schema.** The version-1 baseline stores the normalized {§inference-ledger},
779
+ its model-response evidence, emission admission, and cardinal
780
+ physical requests. Its constraints distinguish pending calls, response
781
+ evidence, and response-less errors while monetary classification remains
782
+ explicit.
783
+
784
+ ## §worker-loop-lifecycle Loop scheduling and lifecycle
785
+
786
+ - §join-blocking-collect **Collection and scheduling are independent.** A path-absent ```` ```READ (worker://<running-child>) ```` returns **425** (Too Early), without a strike or scheduler side effect. TASK with waiting intent joins live obligations; an `in_progress` inventory keeps working. A child reaching any terminal status wakes a waiting parent with its result, including completion racing the park boundary. Children retain their own limits; TASK timing may additionally bound the parent's wait. Collection never arms an implicit disposition override.
787
+
788
+ A worker is a **log plus a cancellation scope** — one `AbortController` per worker, reused while live and replaced only once aborted, so a cancel ends the worker as a unit and a later `runLoop` request is never born cancelled. A worker's queued loops are advanced by a **drain**: a single per-worker drain that claims loops atomically (status 100→102) and runs each under the worker's scope. A loop may spawn **streams** (execs) that outlive it; each is a row in the subscription registry ({§subscriptions}) — the durable record of what the worker holds open. Cancellation and conclusion are defined against these structures, never wall-clock timing.
789
+
790
+ ```mermaid
791
+ stateDiagram-v2
792
+ [*] --> Queued: runLoop request
793
+ Queued --> Running: due task claimed by drain
794
+ Running --> Parked: wait with live obligations
795
+ Parked --> Queued: obligation settles or arrival
796
+ Running --> Terminal: conclude or fail
797
+ Parked --> Terminal: cancel
798
+ Queued --> Terminal: cancel
799
+ Terminal --> [*]
800
+ ```
801
+
802
+ §worker-lifecycle-state-machine The lifecycle store admits only the guarded transitions shown above: `100 → 102`, `102 → 202`, `202 → 100`, and any unresolved state (`100`, `102`, `202`) to a terminal status. Terminal state is immutable. `DrainSupervisor` owns claim, wake, and cancellation; the dispatcher owns model-requested park/conclusion; the daemon owns boot-recovery orchestration; and the engine owns policy terminals. A racing transition that loses observes the durable winner; it does not overwrite it or report the requested state as fact.
803
+
804
+ §worker-lifecycle-live **Worker liveness is existential, not latest-state.** A
805
+ Worker is live while ANY of its loops is unresolved (`100`, `102`, or `202`). A
806
+ newer terminal loop cannot mask older queued, running, or parked work. Name
807
+ collision, workspace worker caps, child obligations, orientation, and recovery
808
+ all use that one definition.
809
+
787
810
  ### §worker-scheduled-send Scheduled worker tasks
788
811
 
789
812
  A numeric scope on `SEND (worker://name)` queues a new task with the authored
@@ -954,7 +977,7 @@ stateDiagram-v2
954
977
  Polling observes a still-open stream; it never changes ownership or manufactures
955
978
  completion. Closure is always a wake edge regardless of polling mode.
956
979
 
957
- | §worker-lifecycle-poll-matrix EXEC poll marker | While open | On closure |
980
+ | §worker-lifecycle-poll-matrix execution poll marker | While open | On closure |
958
981
  |------------------------------------------------|---|---|
959
982
  | omitted | exponential-backoff observation wakes | resume once with terminal observation |
960
983
  | positive `P` | fixed-cadence observation wakes every `P` minutes | cancel cadence; resume once |
@@ -962,7 +985,7 @@ completion. Closure is always a wake edge regardless of polling mode.
962
985
  | turn-scoped `<0>` | reap at the next pre-turn boundary | surface the terminal outcome |
963
986
 
964
987
  The structured-concurrency sequence is identical whether a child performs an
965
- EXEC, retrieval, or pure inference. Intermediate child status is private to the
988
+ execution, retrieval, or pure inference. Intermediate child status is private to the
966
989
  child. Only the child's terminal loop result crosses the parent edge.
967
990
 
968
991
  ```mermaid
@@ -972,7 +995,7 @@ sequenceDiagram
972
995
  participant S as Child stream
973
996
  P->>C: WORK or FORK
974
997
  P->>P: TASK waiting parks on live child
975
- C->>S: EXEC opens subscription
998
+ C->>S: execution opens subscription
976
999
  C->>C: TASK waiting parks on live stream
977
1000
  loop backoff, fixed cadence, or explicit arrival
978
1001
  S-->>C: optional progress observation
@@ -1011,49 +1034,194 @@ it is the parked lifecycle state, not a terminal. No product surface may infer o
1011
1034
  reconstruct a result from that projection. Active rows have no terminal result;
1012
1035
  terminal rows must have one, and database triggers enforce both directions.
1013
1036
 
1014
- ```mermaid
1015
- flowchart TD
1016
- W[Worker cancellation] --> L[Terminalize unresolved loops]
1017
- W --> C[Cancel descendants]
1018
- W --> S[Enumerate durable open subscriptions]
1019
- S --> H[Invoke each live cancellation handle]
1020
- H --> T[Persist terminal channel and subscription state]
1021
- T --> N[Publish conclusion without resurrection]
1022
- C --> L
1023
- ```
1037
+ ```mermaid
1038
+ flowchart TD
1039
+ W[Worker cancellation] --> L[Terminalize unresolved loops]
1040
+ W --> C[Cancel descendants]
1041
+ W --> S[Enumerate durable open subscriptions]
1042
+ S --> H[Invoke each live cancellation handle]
1043
+ H --> T[Persist terminal channel and subscription state]
1044
+ T --> N[Publish conclusion without resurrection]
1045
+ C --> L
1046
+ ```
1047
+
1048
+ Restart applies the same ownership rule: accepted queued loops are reclaimable;
1049
+ in-flight provider calls and subscriptions belonged to the vanished process and
1050
+ become explicit failures. A park whose child remains live stays parked; a park
1051
+ whose obligations settled during reconciliation is requeued in place so it can
1052
+ observe their terminal results. No effect is replayed across an unknown
1053
+ boundary.
1054
+
1055
+ - §worker-lifecycle-single-drain **One drain advances a worker.** At most one drain is registered for a worker at any instant: a `runLoop` request or wake on a worker with a live drain folds in (active→next-turn) or enqueues a loop that drain claims, never a second parallel drain. A drain's start and its empty-queue teardown relinquish the worker under one per-worker lock, so the teardown's re-claim cannot race a concurrent start into a double-drain. Fresh-loop sequence allocation and insertion are one mutation under that same lock; concurrent accepted prompts remain distinct ordered queue items.
1056
+ - §worker-lifecycle-total-reap **Cancellation is recursive and reaps every held stream.** `loop.cancel`, worker `KILL`, and a failed TASK terminalize every unresolved loop in the cancelled worker subtree and iterate each worker's durable open-subscription rows, invoking each exact callable owner from the process-local live registry. The durable rows answer *what is held*; the live registry answers *how this process tears it down*; the abort signal is a fast-path optimization. There is no implicit detachment. Shutdown reaps process-local streams while preserving parked work under {§worker-lifecycle-durable-disposition}. Before shutdown awaits drains, it cancels every process-local proposal waiter through {§proposal-cancel-aborts} with outcome `daemon_stopping`, so a stopped-world dispatch cannot hold teardown open. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. The teardown abort is bounded: the executor sends a polite signal then SIGKILL after a consumer-set grace (`PLURNK_SERVICE_EXEC_KILL_GRACE_MS`). A model ```` ```KILL [code] ```` on one live stream instead delivers exactly that signal once (bare KILL uses the executor's SIGHUP default; ```` ```KILL [9] ```` uses SIGKILL).
1057
+ - §worker-lifecycle-exec-epoch-bound **A stream's kill binds to the scope it captured at spawn.** A stream captures the worker's cancellation scope as it registers and wires its kill to it, re-checking `aborted` AFTER wiring — no check-then-listen gap can drop an abort that lands mid-registration. Because the scope is replaced only once aborted, a captured-then-replaced scope is necessarily already aborted, so replacement never strands a live stream.
1058
+ - §worker-lifecycle-no-resurrection **Cancelled work does not revive its scope.** A cancelled worker cannot be woken by its torn-down streams, stale timers, or cancelled undelivered prompt frames. The evidence remains readable. A `499` result from cancelling only one stream is still a completion owed to a live waiting worker: result status is not proof of worker cancellation. Only an explicit new arrival admits new work after scope cancellation; terminal loops themselves remain immutable.
1059
+ - §worker-cancel-trigger **A cancellation is one bound statement.** `lifecycle_cancel_workers` writes the causal cutoff and the cancellation Problem onto every worker of the scope; `workers_cancel_live_loops` (an `INIT` process trigger beside the lifecycle statements, {§db-process-triggers}) retires each worker's live loops inside that statement — 499, waits cleared, the delivered response kept as the result's `content`, the Problem instanced `loop:///<id>`, `terminated_by = 'cancel'` — so cutoff and cancellation cannot land apart and no value is string-interpolated. Execution consumption is measured by the process-local monotonic timers ({§loop-execution-allowance}) and lands first through `lifecycle_checkpoint_executions`; a wall clock cannot stand in for it, so the timer stays outside the database by design.
1060
+ - §worker-causal-admission **Admission and cancellation have one ordering.** DrainSupervisor serializes message admission, prompt promotion, and subtree cancellation within the workspace, taking the worker queue lock inside that control boundary. No provider, tool, fork-history copying, or stream reap holds it. WORK, FORK, and directed SEND identify their originating loop; admission requires that task still running. An accepted message to an independent recipient is a committed effect, not retroactively withdrawn by cancelling its sender.
1061
+
1062
+ | Boundary outcome | Durable consequence |
1063
+ |---|---|
1064
+ | Admission before cancellation | Owned work is included in cancellation. |
1065
+ | Recurring success during cancellation | Its successor is cancelled and reported from the durable cutoff, not a previously sampled task list. |
1066
+ | Cancellation before admission | The cancelled task cannot deliver further messages or start children. Created identities and evidence are retained. |
1067
+ | Cancellation with unread prompts on completed tasks | Atomically record each worker's greatest admitted loop sequence as `cancelled_through_sequence` and cancel unresolved tasks. Prompt promotion, including boot recovery, excludes sources at or below that cutoff; completed results and prompt evidence are unchanged. |
1068
+ | Independent arrival after cancellation | Admit a new loop above the cutoff using the ordinary worker policy. |
1069
+ | Slow stream teardown after new admission | Reap only subscription identities captured by cancellation; late old-scope spawns follow {§worker-lifecycle-exec-epoch-bound}. |
1070
+
1071
+ - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** The stream first persists its terminal state. A worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) then **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. An already-active worker needs no injected prompt or second wake because its next packet reads the durable terminal state. A concluded worker receives no synthetic loop from ambient stream closure. The result remains available in the stream's own state under every case.
1072
+ - §worker-lifecycle-child-wake **Each child task completion notifies its parent.** Terminal-task publication, including failure and cancellation of a parked task, notifies the direct parent without injecting a prompt. Other unfinished tasks or streams in that child remain independent obligations; they cannot suppress notification. The parent's eligible waits requeue in place under {§loop-wake-identity} and the bounded {§worker-optimistic-settlement} opportunity. Durable revisioning covers completion-before-park and restart; drain teardown and whole-worker quiescence are not completion identities.
1073
+ - §worker-optimistic-settlement **Asynchronous settlement receives one bounded worker-local opportunity before model dispatch.** An initiating turn lets only the streams it started settle before its turn disposition; separately, a stream or direct-child conclusion persists and publishes immediately but holds eligible parked loops' `202→100` requeues while another stream or direct child remains live. Both use `PLURNK_SERVICE_OPTIMISTIC_WAIT_MS`, shipped at five seconds; zero disables the opportunity. The wake hold ends as soon as no sibling obligation remains, never extends its original deadline, and coalesces conclusions within that window into at most one requeue per eligible loop. With no sibling obligation the wake is immediate; at the deadline, surviving work follows the ordinary monitored lifecycle. A conclusion that lands after provider dispatch begins retains its next wake, while poll, park-deadline, prompt, and operator wakes never open this hold. Only packet/provider dispatch waits: terminal state, client events, cancellation, and child execution do not. One redaction-safe span records elapsed time, quiescence versus deadline, and conclusion count without entering the packet.
1074
+ - §worker-lifecycle-idle-is-concluded **No implicit completion.** A waiting inventory without a finite deadline, positive poll or live obligation continues under {§wait-obligation-matrix}. Only a terminal inventory with at least one completed task claims success. A concluded worker retains durable history; a later addressed arrival starts a new loop.
1075
+ - §worker-lifecycle-no-lost-loop **A loop is never stranded by a drain's exit.** A drain relinquishes its registry slot only after a lock-held re-claim confirms the queue is empty; a loop enqueued during that teardown is either re-claimed by the exiting drain or claimed by a fresh drain that a later inject starts. The relinquish and the start are serialized, so neither the lost-loop hang nor a transient double-drain can occur.
1076
+ - §worker-lifecycle-durable-disposition **Durable disposition wins cancellation races.** At a turn boundary, the engine reads the loop's durable status before interpreting a process-local abort. A committed `202` park survives a later daemon-shutdown signal; only a loop still durably running at `102` can be terminalized by that cancellation. Wake selection rechecks shutdown and worker cancellation before requeuing each parked loop.
1077
+ - §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request first settles as an error with absent usage and explicitly unknown cost; then its logical model call closes. Recovery never fabricates zero evidence. Every durable proposed operation likewise lost its process-local resolution waiter and settles as a visible `500 owner_vanished` occurrence rather than an unresolvable interrupt ({§proposal-list}). A pending client interaction also lost its exact awaiting operation, so boot removes the orphan instead of replaying work or inventing a response ({§client-interactions}). Every durable-open subscription belonged to a vanished callable: active channels become errored and its row closes `500`. A `202` continuation preserves its requested deadline and observation interval. An unseen completion requeues it; an untimed join whose obligations vanished also requeues. Otherwise a future timed wait remains parked and an expired due time wakes it through the same guarded scheduler. Child terminalization wakes its parked parent on every outcome, including provider exceptions, cancellation, and restart interruption, recursively through the durable parent edges. These operations are idempotent, so an interrupted recovery safely repeats.
1078
+
1079
+ ---
1080
+
1081
+ ### §turn-record Producer-neutral turn record
1082
+
1083
+ A turn is the durable container for one producer's ordered operations. Packet
1084
+ and provider fields are optional evidence belonging only to model inference;
1085
+ their absence never makes a client, plugin, or `_plurnk` turn exceptional.
1086
+
1087
+ | Field | Contract |
1088
+ |---|---|
1089
+ | `producer` | Required actor class: `model`, `client`, `plugin`, or `_plurnk`. |
1090
+ | `kind` | Required purpose: `inference`, `initialization`, `operation`, or `maintenance`. Model iff inference; initialization and maintenance require `_plurnk`. Producer and kind are immutable. A maintenance turn's successful rows are packet-suppressed — a receipt answers an asker, and maintenance has none ({§actor-boundary-doc-injection}). |
1091
+ | `status`, `completed_at` | A new turn is open at status 102 with `completed_at=NULL`. Completion records the exact turn disposition/operation disposition and timestamp; a completed 102 is therefore distinct from an open 102. |
1092
+ | Operations | Ordered by `(turn_id, sequence)` on one exact worker/loop/turn chain. Each row's `origin` is the turn producer or `_plurnk` making a system observation; the observation does not impersonate the producer. |
1093
+ | Program source | Every admitted source-backed turn preserves its exact program before dispatch in `turn_sources`, independently of log receipts, under {§turn-ops-entry}. |
1094
+ | Inference evidence | Model calls, `packet`, model, finish reason, and provider metadata belong only to model/inference turns. Turn fields are nullable until recorded and remain NULL for every other kind. |
1095
+
1096
+ One lifecycle owner opens, optionally records inference evidence, and completes
1097
+ every turn. Initialization, maintenance, client dispatch, and model
1098
+ inference use that same path. `plugin` is the producer identity for
1099
+ plugin-authored operation turns; exposing that path must not introduce a
1100
+ parallel record or lifecycle. Producer and kind never change. Process-restart
1101
+ recovery completes any turn whose producer vanished.
1102
+
1103
+ §turn-ops-admission-path **Source acquisition varies; admitted-turn execution does not.**
1104
+ A provider response, deterministic `_plurnk` program, or future client/plugin
1105
+ program crosses one admission boundary into the same executor. That executor
1106
+ parses once, dispatches the admitted statements in order, records their ordinary
1107
+ outcomes, and completes the turn from its TASK ruling. Exact source is retained before dispatch.
1108
+ Provider attempts, grammar recovery, reasoning, and accounting end before this
1109
+ shared seam. A programmatic operation batch that supplied no Plurnk source does
1110
+ not fabricate verbatim source.
1111
+
1112
+ §turn-ops-selection-snapshot **An admitted program cannot select log rows it emits while executing.**
1113
+ Immediately before statement dispatch, the shared executor captures that worker's
1114
+ append-only log high-water mark. Every log-targeted KILL in the program resolves
1115
+ row membership at or below that same boundary, while prior curation effects still
1116
+ compose normally. Prompt and other pre-program rows already present in the turn
1117
+ remain selectable; preceding and later operation rows cannot be captured by
1118
+ their own program. A directly dispatched
1119
+ single operation captures the equivalent boundary before dispatch. This limits
1120
+ only log-row selection: operation phasing and same-turn resource effects retain
1121
+ their ordinary contracts.
1122
+
1123
+ ### §engine-rails Engine rails
1124
+
1125
+ After each admitted turn, one inline verdict decides whether the loop continues.
1126
+ An admitted turn contributes at most one strike, even when several sources fire.
1127
+ These are the complete strike sources:
1128
+
1129
+ | Strike source | Exact trigger | Model-visible occurrence |
1130
+ |---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
1131
+ | 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. |
1132
+ | Inventory steering | Retired (2026-09-14): a completion claimed over live work joins it ({§completion-joins-live-work}) and one over settled results defers ({§completion-defers-to-results}); every answer to a TASK claim, an empty inventory's soft 409 included, is a receipt and never a strike; a malformed TASK (`400 wait-timing-invalid`) is a hard result like any other operation's. | The TASK receipt. |
1133
+ | Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
1134
+
1135
+ Execution results remain exact model-visible evidence but are always soft: an
1136
+ executor error is not a PLURNK contract violation. Cycle detection remains an
1137
+ independent strike source.
1138
+
1139
+ A `425` not-ready result describes unfinished work, not a contract violation.
1140
+ It retains its exact receipt, without scheduling side effects ({§join-blocking-collect});
1141
+ other violations in the same turn still strike normally.
1142
+
1143
+ §engine-cycle-evidence Cycle identity contains the ordered executed operations
1144
+ and their dispatch results, including complete operands, scopes, bodies, and
1145
+ scheme metadata, including the complete TASK inventory and SEND bodies. Source
1146
+ positions and asides are excluded. Engine-assigned
1147
+ Problem `instance` addresses are excluded from results. Object member order is
1148
+ irrelevant; operation and array order are preserved. Only the configured
1149
+ `MIN_CYCLES × MAX_CYCLE_PERIOD` history window is retained. Repeated addresses
1150
+ alone are not a cycle: changing inputs or observations distinguish activity.
1151
+ This is an exact-repetition backstop, not a semantic judgment of task progress;
1152
+ new asynchronous invocation identities do not prove repetition of their eventual
1153
+ effects. Ordinary contract strikes and operator budgets remain independent.
1154
+
1155
+ §provider-recovery **A recoverable provider failure never ends a loop.** When a model
1156
+ call fails with a network failure, rate limit, deadline, or interrupted resource after
1157
+ the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
1158
+ notices the client (`engine:provider` / `provider_unavailable`), waits with
1159
+ exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling, capped at
1160
+ twelve times itself), and re-issues the same call against the exact frozen model
1161
+ messages whose response is still outstanding. Each reissue remains a distinct logical
1162
+ model call with complete physical-request accounting, but the active turn's newly
1163
+ recorded provider Problems do not recursively enter that request; they surface normally
1164
+ only in a later genuinely new packet. No emission attempt is consumed and no strike is
1165
+ scored. Every recovery checkpoint broadcasts live, while the model-facing Notice buffer
1166
+ retains only the current provider state; the next completed exchange notices
1167
+ `provider_recovered`. Recovery is bounded by `PLURNK_SERVICE_PROVIDER_RECOVERY`; when it
1168
+ is spent the turn completes as `202` and the loop parks exactly like a
1169
+ TASK wait ({§worker-lifecycle-wake-requeue-not-terminal}), resuming on the
1170
+ next prompt or wake with its log intact. Only a client cancel, the execution allowance
1171
+ ({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
1172
+ authorization, quota, an invalid response) settles a loop on a provider failure.
1173
+
1174
+ **Contract Strikes** (operator mandate, 2026-09-01): *Every turn with one or
1175
+ more contract violations earns a strike. A turn without any contract violations
1176
+ clears the strikes. Three (not four) strikes and you're out, by default.*
1177
+ The streak counts consecutive violating turns; `MAX_STRIKES` (default 3) is the
1178
+ threshold, crossed ON the third strike; the crossing turn terminates at **508
1179
+ Loop Detected** when cycle-detected, otherwise **500**.
1024
1180
 
1025
- Restart applies the same ownership rule: accepted queued loops are reclaimable;
1026
- in-flight provider calls and subscriptions belonged to the vanished process and
1027
- become explicit failures. A park whose child remains live stays parked; a park
1028
- whose obligations settled during reconciliation is requeued in place so it can
1029
- observe their terminal results. No effect is replayed across an unknown
1030
- boundary.
1181
+ The contracts, and the violation of each that strikes:
1031
1182
 
1032
- - §worker-lifecycle-single-drain **One drain advances a worker.** At most one drain is registered for a worker at any instant: a `runLoop` request or wake on a worker with a live drain folds in (active→next-turn) or enqueues a loop that drain claims, never a second parallel drain. A drain's start and its empty-queue teardown relinquish the worker under one per-worker lock, so the teardown's re-claim cannot race a concurrent start into a double-drain. Fresh-loop sequence allocation and insertion are one mutation under that same lock; concurrent accepted prompts remain distinct ordered queue items.
1033
- - §worker-lifecycle-total-reap **Cancellation is recursive and reaps every held stream.** `loop.cancel`, worker `KILL`, and a failed TASK terminalize every unresolved loop in the cancelled worker subtree and iterate each worker's durable open-subscription rows, invoking each exact callable owner from the process-local live registry. The durable rows answer *what is held*; the live registry answers *how this process tears it down*; the abort signal is a fast-path optimization. There is no implicit detachment. Shutdown reaps process-local streams while preserving parked work under {§worker-lifecycle-durable-disposition}. Before shutdown awaits drains, it cancels every process-local proposal waiter through {§proposal-cancel-aborts} with outcome `daemon_stopping`, so a stopped-world dispatch cannot hold teardown open. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. The teardown abort is bounded: the executor sends a polite signal then SIGKILL after a consumer-set grace (`PLURNK_SERVICE_EXEC_KILL_GRACE_MS`). A model ```` ```KILL [code] ```` on one live stream instead delivers exactly that signal once (bare KILL uses the executor's SIGHUP default; ```` ```KILL [9] ```` uses SIGKILL).
1034
- - §worker-lifecycle-exec-epoch-bound **A stream's kill binds to the scope it captured at spawn.** A stream captures the worker's cancellation scope as it registers and wires its kill to it, re-checking `aborted` AFTER wiring — no check-then-listen gap can drop an abort that lands mid-registration. Because the scope is replaced only once aborted, a captured-then-replaced scope is necessarily already aborted, so replacement never strands a live stream.
1035
- - §worker-lifecycle-no-resurrection **Cancelled work does not revive its scope.** A cancelled worker cannot be woken by its torn-down streams, stale timers, or cancelled undelivered prompt frames. The evidence remains readable. A `499` result from cancelling only one stream is still a completion owed to a live waiting worker: result status is not proof of worker cancellation. Only an explicit new arrival admits new work after scope cancellation; terminal loops themselves remain immutable.
1036
- - §worker-cancel-trigger **A cancellation is one bound statement.** `lifecycle_cancel_workers` writes the causal cutoff and the cancellation Problem onto every worker of the scope; `workers_cancel_live_loops` (an `INIT` process trigger beside the lifecycle statements, {§db-process-triggers}) retires each worker's live loops inside that statement — 499, waits cleared, the delivered response kept as the result's `content`, the Problem instanced `loop:///<id>`, `terminated_by = 'cancel'` — so cutoff and cancellation cannot land apart and no value is string-interpolated. Execution consumption is measured by the process-local monotonic timers ({§loop-execution-allowance}) and lands first through `lifecycle_checkpoint_executions`; a wall clock cannot stand in for it, so the timer stays outside the database by design.
1037
- - §worker-causal-admission **Admission and cancellation have one ordering.** DrainSupervisor serializes message admission, prompt promotion, and subtree cancellation within the workspace, taking the worker queue lock inside that control boundary. No provider, tool, fork-history copying, or stream reap holds it. WORK, FORK, and directed SEND identify their originating loop; admission requires that task still running. An accepted message to an independent recipient is a committed effect, not retroactively withdrawn by cancelling its sender.
1183
+ | Contract | Violation that strikes |
1184
+ |---|---|
1185
+ | operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
1186
+ | review contract | none since 2026-09-14: a completion claimed over live work joins it ({§completion-joins-live-work}), one over settled results defers ({§completion-defers-to-results}), and an empty inventory or an already-terminal loop is a soft receipt |
1187
+ | progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
1188
+ | frame contract | emission attempts exhausted with no admissible turn |
1189
+ | provider response contract | the provider returned an invalid response |
1038
1190
 
1039
- | Boundary outcome | Durable consequence |
1040
- |---|---|
1041
- | Admission before cancellation | Owned work is included in cancellation. |
1042
- | Recurring success during cancellation | Its successor is cancelled and reported from the durable cutoff, not a previously sampled task list. |
1043
- | Cancellation before admission | The cancelled task cannot deliver further messages or start children. Created identities and evidence are retained. |
1044
- | Cancellation with unread prompts on completed tasks | Atomically record each worker's greatest admitted loop sequence as `cancelled_through_sequence` and cancel unresolved tasks. Prompt promotion, including boot recovery, excludes sources at or below that cutoff; completed results and prompt evidence are unchanged. |
1045
- | Independent arrival after cancellation | Admit a new loop above the cutoff using the ordinary worker policy. |
1046
- | Slow stream teardown after new admission | Reap only subscription identities captured by cancellation; late old-scope spawns follow {§worker-lifecycle-exec-epoch-bound}. |
1191
+ Errors and issues are NOT contract violations. Each keeps its own disposition
1192
+ and never strikes: exploration misses (404, 416) and unsupported capability
1193
+ (501) are how discovery works; raw 409 outcomes are soft (the review ruling is
1194
+ steer's alone); execution outcomes and `executor/*` problem rows are world evidence;
1195
+ provider weather (rate limit, network failure, deadline, interruption) recovers
1196
+ ({§provider-recovery}); provider capacity has its own packet recovery and
1197
+ terminal ({§provider-capacity-failure}); request rejection
1198
+ ({§provider-request-rejection}), authorization and quota failures
1199
+ terminate immediately (configuration, not behavior); rejected private emission
1200
+ attempts are forensic evidence beneath their turn ({§emission-admission}) —
1201
+ only their exhaustion surfaces, as one frame-contract violation. The
1202
+ independent turn ceiling terminates at **429** ({§loop-terminals}). The streak
1203
+ and cycle verdict are absent from model packets; only the concrete occurrences
1204
+ in the table are shown. The current streak may ride first-party provider
1205
+ metadata ({§strikes-first-party-metadata}), which does not make it
1206
+ model-facing.
1047
1207
 
1048
- - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** The stream first persists its terminal state. A worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) then **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. An already-active worker needs no injected prompt or second wake because its next packet reads the durable terminal state. A concluded worker receives no synthetic loop from ambient stream closure. The result remains available in the stream's own state under every case.
1049
- - §worker-lifecycle-child-wake **Each child task completion notifies its parent.** Terminal-task publication, including failure and cancellation of a parked task, notifies the direct parent without injecting a prompt. Other unfinished tasks or streams in that child remain independent obligations; they cannot suppress notification. The parent's eligible waits requeue in place under {§loop-wake-identity} and the bounded {§worker-optimistic-settlement} opportunity. Durable revisioning covers completion-before-park and restart; drain teardown and whole-worker quiescence are not completion identities.
1050
- - §worker-optimistic-settlement **Asynchronous settlement receives one bounded worker-local opportunity before model dispatch.** An initiating turn lets only the streams it started settle before its turn disposition; separately, a stream or direct-child conclusion persists and publishes immediately but holds eligible parked loops' `202→100` requeues while another stream or direct child remains live. Both use `PLURNK_SERVICE_OPTIMISTIC_WAIT_MS`, shipped at five seconds; zero disables the opportunity. The wake hold ends as soon as no sibling obligation remains, never extends its original deadline, and coalesces conclusions within that window into at most one requeue per eligible loop. With no sibling obligation the wake is immediate; at the deadline, surviving work follows the ordinary monitored lifecycle. A conclusion that lands after provider dispatch begins retains its next wake, while poll, park-deadline, prompt, and operator wakes never open this hold. Only packet/provider dispatch waits: terminal state, client events, cancellation, and child execution do not. One redaction-safe span records elapsed time, quiescence versus deadline, and conclusion count without entering the packet.
1051
- - §worker-lifecycle-idle-is-concluded **No implicit completion.** A waiting inventory without a finite deadline, positive poll or live obligation continues under {§wait-obligation-matrix}. Only a terminal inventory with at least one completed task claims success. A concluded worker retains durable history; a later addressed arrival starts a new loop.
1052
- - §worker-lifecycle-no-lost-loop **A loop is never stranded by a drain's exit.** A drain relinquishes its registry slot only after a lock-held re-claim confirms the queue is empty; a loop enqueued during that teardown is either re-claimed by the exiting drain or claimed by a fresh drain that a later inject starts. The relinquish and the start are serialized, so neither the lost-loop hang nor a transient double-drain can occur.
1053
- - §worker-lifecycle-durable-disposition **Durable disposition wins cancellation races.** At a turn boundary, the engine reads the loop's durable status before interpreting a process-local abort. A committed `202` park survives a later daemon-shutdown signal; only a loop still durably running at `102` can be terminalized by that cancellation. Wake selection rechecks shutdown and worker cancellation before requeuing each parked loop.
1054
- - §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request first settles as an error with absent usage and explicitly unknown cost; then its logical model call closes. Recovery never fabricates zero evidence. Every durable proposed operation likewise lost its process-local resolution waiter and settles as a visible `500 owner_vanished` occurrence rather than an unresolvable interrupt ({§proposal-list}). A pending client interaction also lost its exact awaiting operation, so boot removes the orphan instead of replaying work or inventing a response ({§client-interactions}). Every durable-open subscription belonged to a vanished callable: active channels become errored and its row closes `500`. A `202` continuation preserves its requested deadline and observation interval. An unseen completion requeues it; an untimed join whose obligations vanished also requeues. Otherwise a future timed wait remains parked and an expired due time wakes it through the same guarded scheduler. Child terminalization wakes its parked parent on every outcome, including provider exceptions, cancellation, and restart interruption, recursively through the durable parent edges. These operations are idempotent, so an interrupted recovery safely repeats.
1208
+ §loop-rail-continuity Rail state belongs to the durable loop, not its execution
1209
+ segment. The strike streak and bounded cycle history survive driver cleanup and
1210
+ restart; curation of log evidence cannot alter them.
1055
1211
 
1056
- ---
1212
+ | Boundary | Strike streak | Cycle history |
1213
+ |---|---|---|
1214
+ | Assessed turn with a violation | Increment once. | Include its exact activity. |
1215
+ | Clean assessed turn | Reset to zero. | Include its exact activity. |
1216
+ | Actual park, including an immediate wake/reclaim in the same drain | Preserve the assessed streak. | Close the window; the next turn starts a new one. |
1217
+ | Recoverable provider outage | No assessment; preserve the streak. | Preserve until an actual park. |
1218
+ | New loop | Start at zero. | Start empty. |
1219
+
1220
+ The turn belongs to the wait revision under which it began. A rejected waiting TASK or
1221
+ one resolved without parking does not close a window. Periodic observations
1222
+ separated by actual waits are not an uninterrupted cycle; cumulative turn and
1223
+ execution allowances remain independent bounds. A committed terminal result
1224
+ cannot be replaced by a later rail assessment ({§worker-lifecycle-state-machine}).
1057
1225
 
1058
1226
  ## §provider Provider Contract
1059
1227
 
@@ -1069,7 +1237,7 @@ Three current entry points:
1069
1237
 
1070
1238
  §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}).
1071
1239
 
1072
- §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 response/failure and capacity evidence. 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.
1240
+ §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.
1073
1241
 
1074
1242
  §meta-passthrough **Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag. The service stores it unenforced per turn (`turns.meta`, `json_valid` only — no schema) and forwards the latest turn's blob in `loop/terminated.usage` ({§notifications}). The service never reads a field within it. Providers own their metadata shapes; monetary values carry an explicit amount and currency rather than an implied unit. Absent → `{}`. The mirror direction (client → provider, the self-identified `client` id) rides `generate({client})` ({§attribution}).
1075
1243
 
@@ -1365,9 +1533,9 @@ Registration precedes loop affinity:
1365
1533
  | Provider | Exactly the loop's WORK/FORK child provider; durable inherit policy falls back to the parent. |
1366
1534
  | Execution | A contiguous group's prompt acquisition precedes model-call creation; interrupted acquisition leaves no unstarted inference records. Admitted calls acquire identities in authored order and launch concurrently under the loop cancellation signal. Core awaits the group and records results and notifications in authored order, regardless of completion order. An intervening operation is an execution boundary. |
1367
1535
  | Failure | A source/admission/provider failure affects its own operation, not successful siblings. Accounting or persistence failure is internal and fails hard. |
1368
- | Observation | Responses are unseen retrieval work: submit an unfinished TASK inventory; same-turn completion is refused until the next packet presents them. |
1536
+ | Observation | Responses are unseen retrieval work; completion follows {§send-premature-terminate}. |
1369
1537
 
1370
- - §op-synchronous **Decisive operations settle before the next operation.** The dispatcher awaits each operation and its proposal resolution. Work remains in flight only when the operation's contract deliberately creates concurrency: FORK, WORK, stream-producing EXEC, and streaming READ after acquisition. Such a READ first establishes its durable subscription and returns `102`; a later operation may address that live owner. Dispatching EXEC before KILL does not wait for the process to finish using a resource. KILL of a worker synchronously ends its live loops before disposition checks the pending set; physical scope cleanup remains asynchronous.
1538
+ - §op-synchronous **Decisive operations settle before the next operation.** The dispatcher awaits each operation and its proposal resolution. Work remains in flight only when the operation's contract deliberately creates concurrency: FORK, WORK, a stream-producing execution, and streaming READ after acquisition. Such a READ first establishes its durable subscription and returns `102`; a later operation may address that live owner. Dispatching an execution before KILL does not wait for the process to finish using a resource. KILL of a worker synchronously ends its live loops before disposition checks the pending set; physical scope cleanup remains asynchronous.
1371
1539
  - §edit-execution **One authored EDIT is one mutation.** Each EDIT resolves against current resource state when dispatch reaches it, owns its proposal when gated, and records its own resulting revision. No later EDIT is prepared or applied in advance. Numeric scopes address current coordinates; an earlier EDIT may change what those numbers select. Rejection applies only to that operation, not its successful siblings.
1372
1540
  - §edit-anchor-continuity **Own EDITs preserve untouched hash targets within one program.** Core carries an anchor through exact, successfully applied EDIT splices when its line survives unchanged, even if its ordinal or neighborhood changes. Scoped entry KILL uses the same deletion path. Target-line replacement or deletion invalidates that binding. Continuity is private to the admitted program and canonical resource/channel; it is not a new published anchor format. The complete normalized line content must match the expected result of the preceding recorded EDIT, otherwise retained bindings are discarded and ordinary current-state validation applies. Reviewer replacement and results without an applied EDIT receipt do not carry bindings forward. Normalization is the same line-content representation used by READ and line hashing; file-write revision checks remain independent. No approximate text matching is used. Lowered coordinates retain a current-anchor precondition at the mutation owner; ambiguous matches and concurrent changes remain collisions.
1373
1541
  - §edit-batch **One compound operation may require atomic splices.** The scheme's `editBatch` primitive validates all supplied numeric edits against one snapshot and commits one revision or none. Core supplies one statement for an authored EDIT; same-resource MOVE can supply multiple splices as one operation. This primitive does not group separate authored operations. Its replacement, insertion, conflict, and receipt rules remain owned by the shared Slicer.
@@ -1376,29 +1544,11 @@ Registration precedes loop affinity:
1376
1544
 
1377
1545
  ### §orchestration Cross-scheme orchestration
1378
1546
 
1379
- COPY and MOVE independently resolve a source and destination resource
1380
- selection. Each selection contains one scheme resource, one channel (URI
1381
- fragment or scheme default), and an optional text scope.
1382
-
1383
- ```mermaid
1384
- flowchart LR
1385
- A[resolve source selection] --> B[read selected source channel]
1386
- B --> C[apply optional source text scope]
1387
- C --> D[resolve destination selection]
1388
- D --> E{destination scoped?}
1389
- E -->|yes| F[destination editBatch]
1390
- E -->|no| G[write selected destination channel]
1391
- F --> H{MOVE?}
1392
- G --> H
1393
- H -->|no| I[complete COPY]
1394
- H -->|yes| J[remove selected source region or channel]
1395
- ```
1396
-
1397
- The same orchestrator covers same- and cross-scheme resources. A same-channel
1398
- regional MOVE lowers both replacements into one `editBatch` against one source
1399
- snapshot. Cross-resource MOVE is ordered destination-then-source and cannot be
1400
- globally atomic; if source removal fails after destination success, its Problem
1401
- Details state `destinationWritten: true` and identify the destination.
1547
+ Core owns same- and cross-scheme transfers under {§copy} and {§move}; handlers
1548
+ provide the underlying resource operations. Each operand independently selects
1549
+ a resource, channel, and optional text scope ({§transfer-resource-selections}).
1550
+ Source acquisition follows {§universal-read-composition}; landed mutation
1551
+ effects follow {§edit-result-copy-move-effects}.
1402
1552
 
1403
1553
  ### §send-dispatch SEND dispatch (a message to a recipient)
1404
1554
 
@@ -1407,60 +1557,17 @@ A recipient SEND (non-null path — {§turn-disposition}) routes to the scheme's
1407
1557
  message, a worker's next prompt. TASK never reaches a scheme: it controls the
1408
1558
  turn ({§send}). Cancelling a stream and deleting an entry are KILL ({§stream}, {§move}).
1409
1559
 
1410
- - §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.
1411
- - §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.
1412
- - §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.
1413
- - §readable-channel **A readable projection is a channel, never a hidden matching surface.**
1414
- When an entry's source channel lands — an EDIT that creates or changes it, a COPY or MOVE
1415
- landing, a member materialized from disk — and its mimetype handler owns a readable
1416
- projection that differs from the source ({§mimetype-content}), that projection lands beside
1417
- it as the `readable` channel, `text/markdown`, in its own line coordinates; a source without
1418
- a projection keeps no sibling, and a source channel's deletion takes the sibling with it. A
1419
- scheme that supplies `readable` in its own write owns it — a fetched page's curated Markdown
1420
- arrives with its producer outcome ({§html-materialization} in the http scheme) — and core
1421
- derives the sibling only for a write that supplies none.
1422
- Worker and file entries declare `readable`. Every operation addresses the channel it names
1423
- and matches in that channel's own text ({§mimetype-content-query}): a regex or glob on
1424
- `page.html` sees the markup and reports the markup's lines, on `page.html#readable` it sees
1425
- the Markdown and reports the Markdown's lines; FIND lists both channels per path
1426
- ({§channel-selection-visibility}) and full-text search indexes each. The projection is
1427
- derived, so no operation writes it: EDIT of `#readable`, a COPY or MOVE landing on it, and a
1428
- MOVE out of it are 400 `channel-derived`; COPY from it is an ordinary read. Binary sources
1429
- keep {§membership-source-projection}, where the source is not text and the projection is the
1430
- body; a fetched web page keeps its scheme's own two channels ({§html-materialization}).
1431
- - §channel-selection-visibility **Channel selection is decision-time information, not a guess** — every multi-channel resource presents its channels with extents wherever FIND presents the resource: broad results list each channel's path, projection `mimetype`, tokens, and lines (default channel first), and matcher locations name the channel their line coordinates address. A READ of a multi-channel resource names its other channels with their tokens in `channels`, keyed by the fragment the model appends (`{"#readable": 812}`), so first contact — a fetched page, a stream's stdout — carries the same choice without a listing; a single-channel resource names none. When the default channel is a readable projection of a differently typed source, it also names `sourceMimetype` once; this is representation evidence, not a different READ workflow. The packet never presents channels as equal and indistinguishable; extents derive from the stored channels by construction. Budget enforcement stays with {§context-output-admission} — this is information, not a second guard.
1432
-
1433
- - §matcher-selection-signal **Matching carries navigation evidence** - a matcher is a boolean resource predicate. Internally, each selected resource carries `matches: MatchEvidence[]`, where `MatchEvidence` is `{channel?,locator?,region?}`; `channel` names the entry channel the finding was located in and is absent for channel-less resources such as log rows, so line coordinates cannot be mis-attributed across channels of the same resource ({§channel-selection-visibility}). `locator` preserves a structural address without overloading the resource row's `path`; `region` is a complete four-coordinate `TextRegion` only when the finding maps honestly into the exact text the model can READ. Exact duplicate evidence deduplicates. Relation findings map their indexed source spans through the same readable text coordinate index. FIND alone decides whether that grouped selection projects as resource rows or flat locations ({§find-result-projection}); the engine never fabricates a region or guesses which surgical READ the model wants.
1434
-
1435
1560
  §send-dispatch-entry-schemes-501 An entry-bearing scheme carries no messages: a recipient SEND aimed at one returns 501.
1436
1561
 
1437
1562
  Null-path SEND is broadcast ({§send}), engine-handled.
1438
1563
 
1439
1564
  ### §scheme-surface Consumption surface
1440
1565
 
1441
- Per-call context (`src/core/scheme-types.ts`):
1442
-
1443
- ```ts
1444
- interface PlurnkSchemeContext {
1445
- readonly db: Db;
1446
- readonly workspaceId: number;
1447
- readonly workerId: number;
1448
- readonly loopId: number;
1449
- readonly turnId: number;
1450
- readonly writer: "model" | "client" | "_plurnk" | "plugin"; // WriterTier
1451
- readonly signal: AbortSignal | undefined;
1452
- readonly streamEventNotify?: StreamEventNotify;
1453
- readonly wakeWorkerNotify?: WakeWorkerNotify;
1454
- readonly injectWorker?: InjectWorkerNotify; // worker:// spawn/fork/irc loop-start (§worker-scheme)
1455
- readonly mimetypes?: Mimetypes;
1456
- readonly executors?: ExecutorRegistry; // boot-discovered EXEC runtimes (§exec)
1457
- readonly tokenize?: (text: string) => number; // write-time tokenizer (§tokenomics)
1458
- readonly defaultChannelFor?: (scheme: string) => string;
1459
- readonly pushNotice?: (notice: Notice) => void; // → next packet Notices + notice/event (§operation-results)
1460
- }
1461
- ```
1462
-
1463
- The optional engine-/daemon-populated capabilities (the notifiers, `injectWorker`, `executors`, `tokenize`, `defaultChannelFor`, `pushNotice`) are absent in bare test fixtures; a handler that needs one **fail-hards** rather than silently degrading (no default runtime, no silent zero-token write).
1566
+ Every public handler receives `SchemeCtx` under {§capability-ctx} and
1567
+ {§scheme-ctx-lifetime}. Core's private `PlurnkSchemeContext`
1568
+ ([`scheme-types.ts`](src/core/scheme-types.ts)) is not an extension API; core
1569
+ projects it into those public capabilities before invocation. Bundled adapters
1570
+ receive any additional daemon collaborators separately, under the same contract.
1464
1571
 
1465
1572
  Engine → scheme guarantees:
1466
1573
 
@@ -1503,14 +1610,6 @@ Author-facing contract: [`@plurnk/plurnk-mimetypes`](../plurnk-mimetypes/SPEC.md
1503
1610
 
1504
1611
  §mimetype-schemes-do-not-invoke-handlers **Firing semantics.** Scheme writes are verbatim: the source channel lands exactly as authored, and the one handler call a write makes is the readable projection that lands beside it as `readable` ({§readable-channel}); no write invokes a handler's query or structural projections. `SearchIndex.maintain` processes the current readable projections before model execution and attaches complete search artifacts. Catalog rendering independently asks handlers for extents. Fetch-time materialization is earlier still: the web-fetch sink converts guarded HTTP HTML or supported binary input into derived Unicode that READ serves and search indexes, retaining faithful DOM and origin/projection evidence only in explicit auxiliary channels. An authored workspace HTML file remains verbatim; its markup is data, and its Markdown rides beside it as `#readable`.
1505
1612
 
1506
- ### §mimetype-manifest Manifest
1507
-
1508
- Per the author contract, a package declares `kind: "mimetype"` and one or more
1509
- handler entries with a name plus optional glyph and extensions. Discovery
1510
- injects that metadata into one handler instance per declared mimetype.
1511
- Discovery order and collisions follow {§mimetype-discovery}; resolution
1512
- failures follow {§mimetype-error-policy}.
1513
-
1514
1613
  ### §mimetype-methods Methods
1515
1614
 
1516
1615
  The author contract is owned by plurnk-mimetypes. Core and its sibling adapters
@@ -1529,26 +1628,11 @@ dialects pass to `Mimetypes.query` without reclassification. Mimetype handlers
1529
1628
  own content-to-structure interpretation. Core owns candidate-set composition
1530
1629
  and the persistent full-text/graph relation indexes.
1531
1630
 
1532
- Cross-cutting promises service relies on:
1533
-
1534
- - Storage writes do not implicitly project; query, indexing, and presentation
1535
- invoke the exact public surface they need.
1536
- - Handler projections are deterministic for a given `(content, mimetype, projection identity)` tuple.
1537
- - Validation errors propagate (fail-hard).
1538
- - Degraded projection (a `grammarMissing` marker) rather than throw when a grammar is absent.
1539
-
1540
- ### §handler-bounds What handlers do NOT do
1541
-
1542
- - **Tokenization** — outside the mimetype projection pipeline; core owns packet and stored-weight accounting ({§tokenomics-agnostic-ruler}).
1543
- - **Storage** — handlers receive content values and own no entry persistence.
1544
- - **Streaming** — handlers see whatever content is current; subscription registry lives between schemes and {§stream}.
1545
-
1546
- ### §handler-bundling Bundled vs sibling handlers
1547
-
1548
- No format handler ships inside `@plurnk/plurnk-service`; the framework and
1549
- format handlers are sibling workspaces and independently published packages.
1550
- The service manifest, not the framework manifest, owns the dependency edges
1551
- that compose the default install ({§bundled-set}).
1631
+ Handler authority, discovery, projection identity, and failures follow
1632
+ {§mimetype-handler-authority}, {§mimetype-discovery},
1633
+ {§mimetype-projection-identity}, and {§mimetype-error-policy}. Core owns
1634
+ persistence, packet accounting ({§tokenomics-agnostic-ruler}), and subscriptions
1635
+ ({§subscriptions}); handlers do not.
1552
1636
 
1553
1637
  ### §mimetype-surface Consumption surface
1554
1638
 
@@ -1573,12 +1657,6 @@ internal contract failure, never a reason to substitute the pure heuristic.
1573
1657
  | READ/EDIT and COPY/MOVE scope | Admit text regions or return 415. |
1574
1658
  | Search derivation | Build graph/FTS artifacts or mark unsearchable. |
1575
1659
 
1576
- The default service installation includes its structured, document and image
1577
- handlers and all registered tree-sitter grammar leaves through the service
1578
- manifest. Exact tokenizer vocabularies and third-party handlers remain independently
1579
- installable and resolve from the same consumer-visible package graph under
1580
- trust-gated discovery ({§mimetype-discovery}).
1581
-
1582
1660
  **Token accounting.** The daemon injects no tokenizer into `Mimetypes`; content
1583
1661
  projection is independent of packet budgeting. Core uses the stable
1584
1662
  model-independent ruler for stored/catalog weights and the model-facing curation budget
@@ -1586,7 +1664,11 @@ model-independent ruler for stored/catalog weights and the model-facing curation
1586
1664
  confined to provider-owned physical capacity assessment
1587
1665
  ({§tokenomics-context-envelope-admission}).
1588
1666
 
1589
- §persistent-search-index **Persistent search index.** `SearchIndex.maintain` is the pre-model engine pass. Every addressable entry channel supplies the exact readable representation its READ exposes; `LogBody` resolves each log row's canonical full body from its durable tx/rx envelope. Acquisition schemes project remote source material before storing addressable channels; search never introduces a second hidden text projection. The channel content, mimetype, resolved text/binary classification, mimetype projection identity, and applicable search exclusion form a content hash. Complete artifacts own FTS, symbol definitions, and references; each `entry_channels` row or log row holds only its own attachment hash. Binary, empty, and excluded derivations do not invoke handler projections and therefore use one fixed no-projection identity.
1667
+ **Conformance.** Mimetype-specific behavioral tests live in each handler's own surface. plurnk-service intg covers integration: the engine routes through `Mimetypes.process` with the right hint and the catalog reflects `totalLines`; tests use auto-discovery (production handler set); a custom-handler test injects a stub `BaseHandler` via `loader + discovery`.
1668
+
1669
+ ## §persistent-search-index Search indexing
1670
+
1671
+ `SearchIndex.maintain` is the pre-model engine pass. Every addressable entry channel supplies the exact readable representation its READ exposes; `LogBody` resolves each log row's canonical full body from its durable tx/rx envelope. Acquisition schemes project remote source material before storing addressable channels; search never introduces a second hidden text projection. The channel content, mimetype, resolved text/binary classification, mimetype projection identity, and applicable search exclusion form a content hash. Complete artifacts own FTS, symbol definitions, and references; each `entry_channels` row or log row holds only its own attachment hash. Binary, empty, and excluded derivations do not invoke handler projections and therefore use one fixed no-projection identity.
1590
1672
 
1591
1673
  §search-exclusion **File-search eligibility is Core policy.**
1592
1674
  `PLURNK_SERVICE_SEARCH_EXCLUDE` is a comma-separated table of anchored
@@ -1628,8 +1710,6 @@ rescan. Progress exposes `preparing`, `indexing`, `complete`, or `failed` throug
1628
1710
  `search_progress` Notices. Producer concurrency and heartbeat interval are
1629
1711
  operator knobs in `.env.defaults`. Search indexing performs no inference.
1630
1712
 
1631
- **Conformance.** Mimetype-specific behavioral tests live in each handler's own surface. plurnk-service intg covers integration: the engine routes through `Mimetypes.process` with the right hint and the catalog reflects `totalLines`; tests use auto-discovery (production handler set); a custom-handler test injects a stub `BaseHandler` via `loader + discovery`.
1632
-
1633
1713
  ---
1634
1714
 
1635
1715
  ## §channels Channel Topology
@@ -1668,7 +1748,7 @@ Rules:
1668
1748
  2. §channel-selection-fragment-selects-named-channel Paths with a fragment target the named channel.
1669
1749
  3. §channel-selection-missing READ, FIND, EDIT, COPY, and MOVE report unknown channel selections as `404 channel-not-found`. READ and transfer source selection also report 404 when a declared channel is absent on the entry; this does not prohibit creating a permitted destination channel. These Problems name `requestedChannel` and `availableChannels`: existing exposed channels when a representation was read, otherwise the scheme's declared names, including its default. A channel miss is a discovery miss under {§engine-rails}, not malformed syntax. Object prototype properties are not channels. The error never invents another intended resource or claims the containing entry is absent.
1670
1750
  4. Schemes without `defaultChannel` reject fragment-less EDIT/READ.
1671
- - §log-channel-miss-names-stream A channel READ on a log EXEC item is such a miss, and the receipt resolves it: the item's recorded stream link (`attrs.stream`, `<runtime>:///<loop>/<turn>/<item>/<runtime>`) rides as representation data, so the 404 names `<stream>#<channel>` in its detail and `recovery` and carries it as `stream`. The log item and the stream share their coordinate, which is exactly why the model conflates them (#502).
1751
+ - §log-channel-miss-names-stream A channel READ on a log execution item is such a miss, and the receipt resolves it: the item's recorded stream link (`attrs.stream`, `<runtime>:///<claim>` per {§execution-output-identity}) rides as representation data, so the 404 names `<stream>#<channel>` in its detail and `recovery` and carries it as `stream`. The log item's coordinate and the stream's claim address name the same execution, which is exactly why the model conflates them (#502).
1672
1752
  5. §channel-selection-fragment-on-nonexistent-404 Non-default channel EDIT requires entry to exist (404 if absent); default-channel EDIT creates.
1673
1753
  | URI | Channel |
1674
1754
  | ------------------------------------ | ------------------------------------ |
@@ -1687,6 +1767,26 @@ Client-interface target parameters carry fragments inline (`{ target: "sh:///1/1
1687
1767
 
1688
1768
  **Wire rendering: default channel is path-only.** A rendered target omits `#channel` when channel matches `defaultChannel`. Single-channel entries render path-only; multi-channel entries render the default path-only and only non-default carries `#name`.
1689
1769
 
1770
+ - §readable-channel **A readable projection is a channel, never a hidden matching surface.**
1771
+ When an entry's source channel lands — an EDIT that creates or changes it, a COPY or MOVE
1772
+ landing, a member materialized from disk — and its mimetype handler owns a readable
1773
+ projection that differs from the source ({§mimetype-content}), that projection lands beside
1774
+ it as the `readable` channel, `text/markdown`, in its own line coordinates; a source without
1775
+ a projection keeps no sibling, and a source channel's deletion takes the sibling with it. A
1776
+ scheme that supplies `readable` in its own write owns it — a fetched page's curated Markdown
1777
+ arrives with its producer outcome ({§html-materialization} in the http scheme) — and core
1778
+ derives the sibling only for a write that supplies none.
1779
+ Worker and file entries declare `readable`. Every operation addresses the channel it names
1780
+ and matches in that channel's own text ({§mimetype-content-query}): a regex or glob on
1781
+ `page.html` sees the markup and reports the markup's lines, on `page.html#readable` it sees
1782
+ the Markdown and reports the Markdown's lines; FIND lists both channels per path
1783
+ ({§channel-selection-visibility}) and full-text search indexes each. The projection is
1784
+ derived, so no operation writes it: EDIT of `#readable`, a COPY or MOVE landing on it, and a
1785
+ MOVE out of it are 400 `channel-derived`; COPY from it is an ordinary read. Binary sources
1786
+ keep {§membership-source-projection}, where the source is not text and the projection is the
1787
+ body; a fetched web page keeps its scheme's own two channels ({§html-materialization}).
1788
+ - §channel-selection-visibility **Channel selection is decision-time information, not a guess** — every multi-channel resource presents its channels with extents wherever FIND presents the resource: broad results list each channel's path, projection `mimetype`, tokens, and lines (default channel first), and matcher locations name the channel their line coordinates address. A READ of a multi-channel resource names its other channels with their tokens in `channels`, keyed by the fragment the model appends (`{"#readable": 812}`), so first contact — a fetched page, a stream's stdout — carries the same choice without a listing; a single-channel resource names none. When the default channel is a readable projection of a differently typed source, it also names `sourceMimetype` once; this is representation evidence, not a different READ workflow. The packet never presents channels as equal and indistinguishable; extents derive from the stored channels by construction. Budget enforcement stays with {§context-output-admission} — this is information, not a second guard.
1789
+
1690
1790
  ### §channel-state Channel state — metadata, not gating
1691
1791
 
1692
1792
  §channel-state-state-is-metadata Each channel has `state ∈ {static, active, closed, errored}`. Metadata only, not an engine gate.
@@ -1870,6 +1970,9 @@ READ is the one fan-out core performs ({§read-fan-out}).
1870
1970
  scope, matcher and aside ({§read-selection-projection}, {§read-pattern}), one
1871
1971
  receipt row per path in the FIND's order, so every rendered line keeps its path,
1872
1972
  physical ordinal and anchor and remains a coordinate source for EDIT and KILL.
1973
+ Each such row carries `attrs.fanout` (`target`, the authored glob; `matched`, the
1974
+ survey's matching path count; `index`; `count`), so a client presents the authored
1975
+ statement once and folds the paths beneath it without inferring the group.
1873
1976
  Without a scope each path renders its ordinary `<1,16>` preview; with a pattern
1874
1977
  only the matching lines, `grep -n` style. The authored statement contributes
1875
1978
  `rowsWritten`, the receipt count, to its turn's sequence. No path is one 204
@@ -1888,7 +1991,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
1888
1991
  anchors do not exist there (400). Bytes are read from the source at READ time, sized
1889
1992
  then windowed, never stored: `file:` supplies them from the member on disk, and a
1890
1993
  scheme that keeps no bytes answers 501 `bytes-unavailable` for `#bytes` and 415 for a
1891
- binary channel, as before. An EXEC whose target is a file member already runs the
1994
+ binary channel, as before. An execution whose target is a file member already runs the
1892
1995
  bytes on disk. Byte selection affects only this hexadecimal projection: when the
1893
1996
  resource also qualifies for a native attachment, a successful ranged byte READ carries
1894
1997
  the complete source attachment independently under {§packet-attachment-parts}.
@@ -2096,7 +2199,7 @@ ordinary bounded bodies expose their displayed and complete chunk extents there.
2096
2199
 
2097
2200
  §rejected-emission-entry A rejected provider response is not `turnOps`: it never became an admitted turn program. The one bounded invalid-emission recovery item under {§emission-admission} has `attrs.kind="emissionAttempt"`, `origin="model"`, the canonical model-facing `/attempt` leaf, and the exact latest rejected response. The packet does not duplicate that identity as `kind` metadata. It is born durably body-suppressed and projected visibly only in the informed recovery packet; every other rejected attempt remains forensic-only.
2098
2201
 
2099
- - §log-coordinate-hierarchy **Log coordinates are a hierarchical prefix; the trailing slash is optional** — a coordinate is `loop/turn/sequence`, and a PARTIAL coordinate selects its descendants: `log:///1` = loop 1's rows, `log:///1/2` = turn 1/2's rows, `log:///1/2/3` = the one row. A full coordinate is always three parts, so a one- or two-part path is unambiguously a prefix — the trailing slash is an optional alias (`log:///1/2` ≡ `log:///1/2/`), uniform with ```` ```READ (worker:///docs/) ````. A complete `[start-end]` segment in any numeric coordinate slot selects that inclusive decimal interval; brackets elsewhere retain ordinary path-glob meaning. Every rendered row appends one canonical model-facing leaf: the native operation name or invoked executor name, `/attempt` for a rejected emission. An executor leaf is derived from the durable submitted statement (`executor`, default `sh`), never the internal `EXEC` dispatch type or the current tool registry. Digits and punctuation in executor names remain part of the leaf. The leaf names identity rather than adding a resource level. Exact consumers tolerate the unsuffixed three-part shorthand; when supplied, the case-insensitive leaf is authoritative and a disagreement resolves 404. READ anchors use the canonical suffixed identity even when addressed by shorthand. Typed entry materialization therefore resolves as `/READ` while retaining its durable `EDIT` event ({§exec-entry-sink}). `log:///1/2/*` still selects the turn's item rows, while `log:///**/READ`, `log:///**/python3`, and `log:///**/attempt` deliberately filter canonical leaves. An executor's output stream lives at that same item address under its runtime scheme — `sh:///1/2/3/sh#stdout` — so one `loop/turn/item/invocation` schema addresses log rows and streams. Error pointers, Problem instances, source attribution, and search use this same identity; client stream coordinates retain the numeric triple. Within a turn, sequence is arrival order, and the turn's prompt rows arrive first: the prompt publication row and any injected prompt rows are materialized before the program runs, so a turn that received a prompt holds it at `log:///L/T/1/prompt` (further prompts follow, oldest first) and the model's own operations come after — a contract, not an accident of dispatch order ({§packet-current-turn} names `L/T`).
2202
+ - §log-coordinate-hierarchy **Log coordinates are a hierarchical prefix; the trailing slash is optional** — a coordinate is `loop/turn/sequence`, and a PARTIAL coordinate selects its descendants: `log:///1` = loop 1's rows, `log:///1/2` = turn 1/2's rows, `log:///1/2/3` = the one row. A full coordinate is always three parts, so a one- or two-part path is unambiguously a prefix — the trailing slash is an optional alias (`log:///1/2` ≡ `log:///1/2/`), uniform with ```` ```READ (worker:///docs/) ````. A complete `[start-end]` segment in any numeric coordinate slot selects that inclusive decimal interval; brackets elsewhere retain ordinary path-glob meaning. Every rendered row appends one canonical model-facing leaf: the native operation name or invoked executor name, `/attempt` for a rejected emission. An executor leaf is derived from the durable submitted statement (its `runtime`), never an internal dispatch type or the current tool registry. Digits and punctuation in executor names remain part of the leaf. The leaf names identity rather than adding a resource level. Exact consumers tolerate the unsuffixed three-part shorthand; when supplied, the case-insensitive leaf is authoritative and a disagreement resolves 404. READ anchors use the canonical suffixed identity even when addressed by shorthand. Typed entry materialization therefore resolves as `/READ` while retaining its durable `EDIT` event ({§exec-entry-sink}). `log:///1/2/*` still selects the turn's item rows, while `log:///**/READ`, `log:///**/python3`, and `log:///**/attempt` deliberately filter canonical leaves. An executor's output stream lives at that same item address under its runtime scheme — `sh:///1/2/3/sh#stdout` — so one `loop/turn/item/invocation` schema addresses log rows and streams. Error pointers, Problem instances, source attribution, and search use this same identity; client stream coordinates retain the numeric triple. Within a turn, sequence is arrival order, and the turn's prompt rows arrive first: the prompt publication row and any injected prompt rows are materialized before the program runs, so a turn that received a prompt holds it at `log:///L/T/1/prompt` (further prompts follow, oldest first) and the model's own operations come after — a contract, not an accident of dispatch order ({§packet-current-turn} names `L/T`).
2100
2203
  - §log-curation-folder-idiom **Log curation speaks the folder idiom; a zero-match sweep is a no-op success** — KILL takes a concrete coordinate or a path-glob, and a **trailing slash or a partial coordinate means "the contents"** ({§log-coordinate-hierarchy}), like a folder-scoped FIND: ```` ```KILL (log:///1/2) <1,-1> ```` suppresses turn 1/2's bodies. A **well-formed selection that matches nothing is 204 with `matched: 0`**; a successful sweep's rx carries `matched: N`. A targetless KILL is 400.
2101
2204
  - §log-curation-set-selection **Row selection and body scope are independent** — target/glob and an optional heading pattern (```` ```KILL (log:///**) [{"pattern": "~stale"}] ````, every dialect a FIND over rows accepts) compose by intersection into the affected row set. An optional `<L>` or `<SL,EL>` then intersects each selected canonical body; it never paginates or changes the selected set. Thus ```` ```KILL (log:///**/READ) <17,-1> ```` may change long READs and no-op on short ones while reporting every selected row in `matched`.
2102
2205
 
@@ -2122,19 +2225,122 @@ Ordinary operation rows store a normalized statement projection, not exact
2122
2225
  provider evidence. This is a structural credential-slot rule, not general
2123
2226
  secret detection.
2124
2227
 
2125
- | Surface | Durable rule |
2126
- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2127
- | Operation target | Each non-null URL username or password becomes `__redacted__`; `raw` is rebuilt from that pure projected target. |
2128
- | Scheme metadata modifier | Every present block becomes `__redacted__` wholesale; only ordered block count and represented-slot presence survive ({§scheme-metadata-modifier}). |
2129
- | COPY/MOVE destination | The nested destination target receives the identical URL-credential projection. URI component columns derive from the projected primary target, so columns and `tx` cannot disagree. |
2130
- | Query and authored body | Preserved exactly; they are authored content and URI identity, not structurally identifiable credential slots. |
2131
- | Parser failure | Preserves the structural diagnosis and source position without quoting scheme-metadata contents ({§scheme-metadata-modifier}). |
2132
- | Client, fork, packet, and digest | Consume the stored projection; none owns a second redaction policy. |
2133
- | Model-call evidence and source artifacts | `model_calls.response` under {§emission-admission}, `turnOps` under {§turn-ops-log-curation}, and `emissionAttempt` under {§rejected-emission-entry} remain exact forensic evidence and are the explicit exception. |
2228
+ | Surface | Durable rule |
2229
+ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2230
+ | Operation target | Each non-null URL username or password becomes `__redacted__`; `raw` is rebuilt from that pure projected target. |
2231
+ | Scheme metadata modifier | Every present block becomes `__redacted__` wholesale; only ordered block count and represented-slot presence survive ({§scheme-metadata-modifier}). |
2232
+ | COPY/MOVE destination | The nested destination target receives the identical URL-credential projection. URI component columns derive from the projected primary target, so columns and `tx` cannot disagree. |
2233
+ | Query and authored body | Preserved exactly; they are authored content and URI identity, not structurally identifiable credential slots. |
2234
+ | Parser failure | Preserves the structural diagnosis and source position without quoting scheme-metadata contents ({§scheme-metadata-modifier}). |
2235
+ | Client, fork, packet, and digest | Consume the stored projection; none owns a second redaction policy. |
2236
+ | Model-call evidence and source artifacts | `model_call_responses.response` under {§emission-admission}, `turnOps` under {§turn-ops-log-curation}, and `emissionAttempt` under {§rejected-emission-entry} remain exact forensic evidence and are the explicit exception. |
2237
+
2238
+ ### §edit-result-render Mutation log rows render truthful effects
2239
+
2240
+ A mutation row keeps request and outcome separate: `tx` is the admitted
2241
+ statement; `rx` is its resolved result. Only state that actually landed may
2242
+ appear there as an effect.
2243
+
2244
+ ```mermaid
2245
+ flowchart LR
2246
+ authored["Authored EDIT / scoped entry KILL / COPY / MOVE"] --> snapshot["Resolve addressed channel(s)<br/>against pre-mutation snapshots"]
2247
+ snapshot --> apply["Apply synchronously<br/>or settle proposal"]
2248
+ apply --> landed{"Did state land?"}
2249
+ landed -->|no| rx["Persist structured rx"]
2250
+ landed -->|yes| kind{"Operation?"}
2251
+ kind -->|EDIT / scoped entry KILL| receipt["Project one EDIT receipt<br/>for this authored row"]
2252
+ kind -->|COPY / MOVE| effects["Compose ordered effects<br/>after application"]
2253
+ receipt --> rx
2254
+ effects --> rx
2255
+ rx --> meta["Packet projection<br/>status · operands · optional effect metadata"]
2256
+ rx --> body["Canonical log body<br/>bounded receipt context or empty"]
2257
+ body --> recall["READ log:///…<br/>selects untrimmed content"]
2258
+ ```
2259
+
2260
+ §edit-receipt-removed-text **A pure deletion's receipt quotes what it removed.** An applied effect that inserted nothing and removed at least one line carries `removedText` — the removed text, first 40 lines — projected on the wire as `removed`; an effect that inserted anything carries no such field, its resulting context shows the change.
2261
+
2262
+ §edit-receipt-anchored-context **An applied EDIT's resulting context carries anchors.** The bounded resulting context each effect renders (`PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES` around and inside the landed region) is rendered exactly as a READ renders — `@xxxxx L:text`, hashed with the resource's READ identity ({§line-anchors}) — so a later operation can cite the landed lines by anchor without a READ. A scheme that supplies no identity keeps the line-numbered form.
2263
+
2264
+ §edit-result-receipt-projection **EDIT and scoped entry KILL project the
2265
+ scheme-owned batch receipt.** The scheme framework owns the exact aggregate
2266
+ shape ({§scheme-edit-batch-receipt}). Each operation supplies one splice; Core
2267
+ validates and projects its one applied effect or superseded disposition.
2268
+ That row carries any reviewer replacement effect. Core stores only the
2269
+ per-operation projection on `rx`; the aggregate remains inside dispatch.
2270
+
2271
+ | Durable receipt fact | Packet projection | Meaning |
2272
+ | -------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
2273
+ | Full `revision` | not projected | SHA-256 identity of the complete landed channel body, retained for forensics. No operation takes a revision; it is neither a lookup nor a compare-and-swap token. |
2274
+ | `unit`, `before`, `after` | `extent` | Whole-line batches use line counts. A batch containing any exact four-coordinate edit uses Unicode code-point counts. |
2275
+ | `parseIssues.before`, `parseIssues.after` | `parseIssues` as `before→after` | Parser-recovery counts for complete source and landed revisions; omitted when both are clean or either is unavailable. |
2276
+ | `effect.requested`, `source`, `result` | `range` | The admitted marker and its normalized mapping from the common source snapshot into the landed body. |
2277
+ | `effect.removed`, `inserted` | `change` | Removed and inserted counts in the receipt unit. |
2278
+ | `effect.removedText` | `removed` | {§edit-receipt-removed-text}: a pure deletion's removed text, first 40 lines; absent when the edit inserted anything. |
2279
+ | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. |
2280
+ | `disposition`, `requested` | `disposition`, `requested` | A reviewer-replaced batch preserves the authored marker while stating that its attributed effect was superseded. |
2281
+ | `replacement` | `replacement`, `change`, canonical proposal-owner body | The one whole-resource effect actually applied by the reviewer replacement; never duplicated across authored rows. |
2282
+
2283
+ §edit-result-receipt-truth **Receipts describe committed state.** Each EDIT
2284
+ carries its own landed revision, extent, and optional `parseIssues` transition
2285
+ for its complete source and landed revisions. When the proposal lands
2286
+ unchanged, the row also carries its requested
2287
+ marker, source/result mapping, counts, and context. For configured count `C`,
2288
+ the context contains up to `C` surrounding lines and the first and last `C`
2289
+ landed lines at the result boundaries. Overlapping windows coalesce; coordinate
2290
+ jumps expose an omitted middle. A deletion instead shows up to `C` lines on
2291
+ each side of its join.
2292
+
2293
+ §edit-result-reviewer-replacement **A resolver replacement is one effect, not a
2294
+ guess at authorship.** An arbitrary accepted body replaces that operation's
2295
+ proposed body. It cannot be attributed to the authored span: the row retains
2296
+ its requested marker with disposition `superseded` and carries the one
2297
+ whole-resource replacement effect with bounded landed context. Subsequent
2298
+ EDITs address that landed state independently under {§edit-execution}.
2299
+
2300
+ | Acceptance | Per-authored-row receipt | Applied effect |
2301
+ | ------------------------------ | -------------------------------------- | ------------------------------------------------- |
2302
+ | Proposed body unchanged | Requested marker and its exact mapping | One per authored EDIT |
2303
+ | Resolver body replaced proposal | Requested marker plus `superseded` | One whole-resource replacement, carried once |
2304
+
2305
+ Durable `tx` always remains the model's admitted statement. There is no JSON
2306
+ row/item receipt mode. A deliberate READ observes its authored execution point
2307
+ ({§op-execution-order}) and remains the universal request for arbitrary current
2308
+ content.
2309
+
2310
+ System-narrated environment EDITs are state-diff events rather than authored
2311
+ mutation receipts. They carry the resulting span defined by
2312
+ {§env-delta-filesystem-narration}.
2313
+
2314
+ §edit-result-copy-move-effects **Core composes COPY/MOVE effects only after
2315
+ application.** Operands remain owned by the durable statement and render
2316
+ independently under {§copy-move-observation}; effects describe only state that
2317
+ landed.
2318
+
2319
+ | Durable effect field | Contract |
2320
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
2321
+ | `target` | Canonical model-facing address. The default channel is path-only; an explicitly selected non-default channel retains its fragment. |
2322
+ | `action` | Exactly `create`, `update`, or `delete`. |
2323
+ | `receipt` | Optional validated EDIT projection. Only textual `create` and `update` effects may carry one; a creation receipt has `before=0`. |
2324
+
2325
+ | Outcome | Ordered `effects` |
2326
+ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
2327
+ | Landed COPY | Its destination effect. |
2328
+ | Landed MOVE between different resource-channel selections | Any destination effect, then any source effect. |
2329
+ | Landed regional MOVE within one resource channel, unchanged by its resolver | Insertion, then removal; both name the same target because they are distinct effects in one atomic batch. |
2330
+ | Resolver body replaces a proposed COPY/MOVE mutation | One actual replacement effect. Cross-resource MOVE still appends an independently landed source effect. |
2331
+ | Textual create/update caused by a scope on either operand | The effect carries the ordinary bounded EDIT receipt. |
2332
+ | Textual create/update with no scoped operand; binary mutation; channel delete | Structural effect only; no invented text receipt. Scoped source removal is an `update` with a receipt. |
2333
+ | `304`, rejection, or cancellation with no landed mutation | `effects` omitted. |
2334
+ | Cross-selection MOVE source failure after destination success | The failure retains every destination effect that landed. |
2335
+
2336
+ Core validates the complete ordered array before exposing it. Parser-recovery
2337
+ inspection is advisory and occurs against complete resulting text after
2338
+ successful application. A handler or parser failure emits a Notice, omits
2339
+ `parseIssues`, and never changes the mutation outcome.
2134
2340
 
2135
2341
  ### §copy COPY (engine-orchestrated)
2136
2342
 
2137
- AST operands: `{ op: "COPY", source: ResourceSelection, destination: ResourceSelection }`.
2343
+ Operand syntax: {§transfer-resource-selections}. Result projection: {§copy-move-observation}.
2138
2344
 
2139
2345
  1. §copy-missing-source-404 Resolve source path, channel, and optional text scope; missing resource or
2140
2346
  channel is 404. Entry sources follow {§membership-source-projection}; active
@@ -2177,8 +2383,6 @@ COPY use this one orchestrator.
2177
2383
 
2178
2384
  ### §move MOVE (engine-orchestrated)
2179
2385
 
2180
- AST operands: `{ op: "MOVE", source: ResourceSelection, destination: ResourceSelection }`.
2181
-
2182
2386
  - §move-relocation-deletes-source MOVE first performs the destination mutation under {§copy}, then removes only
2183
2387
  the selected source region or channel. A whole-channel MOVE deletes the
2184
2388
  source entry only when that was its final channel.
@@ -2186,10 +2390,9 @@ AST operands: `{ op: "MOVE", source: ResourceSelection, destination: ResourceSel
2186
2390
  reads its source exactly as COPY does, writes the destination, and then
2187
2391
  retires the source through the source scheme's own KILL: an entry scheme
2188
2392
  deletes the entry or edits the region out, and the **log** curates — a scoped
2189
- MOVE from a log region (`### MOVE_ (log:///1/5/3/READ) <123,456>
2190
- (worker://analyst/notes/Q4-insights.md)`) copies the readable lines and trims them
2191
- from the projection like the same scoped KILL; an unscoped MOVE retires the
2192
- row like an unscoped KILL. The recorded evidence is never written or erased
2393
+ MOVE copies the readable lines and trims them from the projection like the
2394
+ same scoped KILL; an unscoped MOVE retires the row like an unscoped KILL.
2395
+ The recorded evidence is never written or erased
2193
2396
  ({§log-readable-projection}), so relocating reasoning or results into a
2194
2397
  scratch note is a first-class curation move, not a refused write. A stream's
2195
2398
  KILL is process control, not content curation, and is not a MOVE source
@@ -2204,13 +2407,11 @@ AST operands: `{ op: "MOVE", source: ResourceSelection, destination: ResourceSel
2204
2407
  preconditions compose against that snapshot, and overlapping regions are 409.
2205
2408
  - A cross-resource destination failure leaves the source untouched. A source
2206
2409
  failure after destination success is an explicit partial failure with
2207
- `destinationWritten: true`. Proposal acceptance/rejection follows the same
2208
- ordered rule.
2410
+ `destinationWritten: true` and the destination's identity. Proposal
2411
+ acceptance/rejection follows the same ordered rule.
2209
2412
  - §move-cross-scheme-move Same- and cross-scheme resources use the same
2210
2413
  contract; there is no global cross-scheme transaction.
2211
2414
  - §move-missing-source-404 A missing source is 404.
2212
- - §move-null-body-400 **MOVE is not a delete operation.** A null body returns
2213
- 400 because a destination is required.
2214
2415
  - §move-dev-null-not-special `/dev/null` carries no special meaning; KILL is
2215
2416
  the canonical standalone delete.
2216
2417
 
@@ -2218,7 +2419,9 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2218
2419
 
2219
2420
  ### §find FIND
2220
2421
 
2221
- AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal: tags | null, lineMarker? }`.
2422
+ - §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.
2423
+ - §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.
2424
+ - §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.
2222
2425
 
2223
2426
  - §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.
2224
2427
 
@@ -2231,12 +2434,14 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
2231
2434
  identity-bearing: `https://example.com/page` queries
2232
2435
  `(https, example.com, /page)`, never an empty-authority row at `/page`.
2233
2436
  - §find-channel-selection The target selects a channel under {§channel-selection}. That channel controls candidate eligibility, every matcher dialect's content or derivation, match-evidence coordinates, and exact producer-result composition. A selected channel absent from an exact entry is 404; a broad scope simply excludes entries lacking it. Successful resource-mode results remain complete default-first channel groups, so sibling channels are navigable catalog metadata rather than additional matches.
2234
- - §find-glob-filter-on-content `body` matcher operates on the addressed entry channel (glob/regex/jsonpath/xpath), per `plurnk.md` "Pattern Filtering"; the path-glob lives in the (target), not the body.
2437
+ - §find-glob-filter-on-content The heading's `pattern` option ({§matcher-option})
2438
+ matches the selected channel's content or derivation; path globs select
2439
+ resources through `(target)` ({§path-glob}).
2235
2440
  - §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 defaults to `<1,16>`, `<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.
2236
2441
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
2237
2442
  - §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 }`:
2238
2443
 
2239
- | Target | Matcher body | `range.unit` | Result rows |
2444
+ | Target | Matcher | `range.unit` | Result rows |
2240
2445
  |---|---|---|---|
2241
2446
  | exact | absent | `resource` | the one catalog channel group |
2242
2447
  | glob or folder | absent | `resource` | catalog channel groups |
@@ -2247,7 +2452,7 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
2247
2452
  target remains location mode when it has many locations. A valid exact match
2248
2453
  with no addressable location is status 200 with `matchingPathCount: 1`,
2249
2454
  `matchLocationCount: 0`, and no fabricated row; a matcher selecting no
2250
- resource is 204. A body-less broad empty catalog survey is status 200; an
2455
+ resource is 204. A matcher-less broad empty catalog survey is status 200; an
2251
2456
  absent exact resource is 404. Every entry-channel location names its `channel`
2252
2457
  ({§channel-selection-visibility}); log rows carry none.
2253
2458
 
@@ -2255,7 +2460,7 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
2255
2460
  complete selection before pagination; the packet curates those facts under
2256
2461
  {§retrieval-packet-metadata}. `path` is reserved for resource or channel identity;
2257
2462
  broad results never nest locations, and exact location rows never repeat the
2258
- resource path. A **body-less** FIND is the **catalog**. Its outer result array
2463
+ resource path. A **matcher-less** FIND is the **catalog**. Its outer result array
2259
2464
  contains one nonempty, flat channel array per resource. Element `[0]` is always
2260
2465
  the default channel and carries the bare resource path; later elements carry
2261
2466
  their complete `path#channel` addresses. Each channel is
@@ -2314,18 +2519,19 @@ SEND AST: `{ op: "SEND", target: ParsedPath | null, body: SendBody | null, metad
2314
2519
  | TASK omitted | At least one authored operation | 102; no synthetic TASK, warning, or strike | None |
2315
2520
  | explicit empty inventory | Always | 409 receipt; continue, no strike (operator, 2026-09-12) | `No tasks were supplied. Submit a nonempty TASK inventory.` |
2316
2521
  | continue | Always | 102; no implicit join or idle strike | None |
2317
- | pending | Always | 102 | `Pending tasks remain. Review their dependencies.` |
2522
+ | todo | Always | 102; no strike | None |
2318
2523
  | wait | Finite timeout, positive poll, or live obligation | 202; durable park and wake of the same loop | Wait timing metadata |
2319
2524
  | wait | No wait obligation; results or curation await the next packet | 102 | Existing result evidence |
2320
2525
  | wait | No wait obligation or unobserved result | 102; no strike | `Nothing is in flight and no timed or polled wait is set. Continuing.` |
2321
- | complete | The loop holds prompt frames it has not yet published ({§completion-defers-to-prompts}) | 102; no strike; the next packet publishes them | `Completion deferred: 1 new prompt arrived during this turn. It is in this packet; a response and a TASK now complete.` |
2322
- | complete | Model fired an operation other than SEND/TASK/KILL, or has unobserved failures or pending work/results | 409; continue with one strike, except {§send-final-strike-retrieval} | Factual pending-result Problem |
2526
+ | complete, fail | The loop holds prompt frames it has not yet published ({§completion-defers-to-prompts}) | 102; no strike; the next packet publishes them | `Completion deferred: 1 new prompt arrived during this turn. It is in this packet; a response and a TASK now complete.` |
2527
+ | complete | Live work: an open stream or a live child worker ({§completion-joins-live-work}) | 202; durable park and wake of the same loop; no strike | Join detail naming the work, read when the wake lands |
2528
+ | complete, fail | Same-turn failures, or settled results the next packet carries — this turn's receipts, a concluded stream, a terminated child ({§completion-defers-to-results}) | 102; no strike; the next packet carries them | Read-time deferral detail naming them |
2323
2529
  | complete | No blocking obligation, or administrative producer | 200 | None |
2324
- | fail | Always | 499; cancel unresolved descendant scope | `All tasks in the final inventory failed.` |
2530
+ | fail | Otherwise; live work is cancelled, never waited for | 499; cancel unresolved descendant scope | `All tasks in the final inventory failed.` |
2325
2531
 
2326
2532
  A timing scope on a non-waiting inventory is ignored with `Wait timing was not applied because no waiting intent was selected.` It does not override the inventory. Every continuation retains the same loop's budgets and strike rail. No-op waiting never invents success.
2327
2533
 
2328
- §loop-response-messages **Replies and outcome are independent.** Each successful targetless SEND, and a SEND to one of this loop's own prompts ({§send-prompt-acceptance}), contributes its complete authored body to the loop's response, in turn and operation order, separated by a blank line. Other directed SEND, TASK, asides, interstitial text, inherited rows and ambient observations do not contribute. Collection reads immutable executed operation evidence, not the curated log projection. KILL cannot retract a delivered message. A later failure, cancellation or refused completion preserves prior messages; a task inventory never becomes a synthetic answer. Terminal status and Problem Details remain independent of this response content.
2534
+ §loop-response-messages **The response is the last message.** The loop's response is the complete authored body of its last successful targetless SEND, or SEND to one of this loop's own prompts ({§send-prompt-acceptance}), in turn and operation order. Earlier messages reach the client as they are delivered and stay log rows; they are not part of the response, so a corrected answer or a repeated one after a refused completion delivers once. Other directed SEND, TASK, asides, interstitial text, inherited rows and ambient observations never count. The projection reads immutable executed operation evidence, not the curated log projection: KILL cannot retract a delivered message, and a later failure, cancellation or refused completion keeps the last message. A task inventory never becomes a synthetic answer. Terminal status and Problem Details remain independent of this response content. A parent receives its child's response as the child's conclusion (operator, 2026-09-11).
2329
2535
 
2330
2536
  §loop-terminal-authorship **Terminal authorship is explicit when external.**
2331
2537
 
@@ -2336,21 +2542,10 @@ A timing scope on a non-waiting inventory is ignored with `Wait timing was not a
2336
2542
 
2337
2543
  The engine's failure terminals — **500** (strike threshold) and **508** (cycle), {§engine-rails} — are never the model's to pick; they are the engine ruling the loop failed. The surface is small on purpose: the model says done, waiting, or giving up, and is never asked to hold a correct opinion about *how* it failed or *whether* it can be woken — the engine decides those from state.
2338
2544
 
2339
- **Rail accounting is separate from model-facing evidence.** The model sees the
2340
- specific correction on its next packet, never the private strike count
2341
- ({§rail-accounting-private}). Each state below contributes one strike and lets
2342
- the loop continue; repeated offenses terminate through the engine's 500.
2343
-
2344
- | state | model-facing evidence | accounting |
2345
- |---------------------|------------------------------------------------------------|------------|
2346
- | Explicit empty inventory | The TASK's 409 row; preceding valid operations remain executed | No strike (soft 409) |
2347
- | Refused disposition | The disposition's 409 row with its exact Problem Detail | One strike |
2348
-
2349
- Executor results are evidence, never strikes: a command's nonzero exit — surfaced
2350
- as its completion READ ({§exec-stream}) or read by the model from the stream —
2351
- carries an `executor/*` problem identity and does not enter the streak. Structural
2352
- violations follow the current admission and strike contracts
2353
- ({§emission-admission}, {§engine-rails}).
2545
+ Disposition outcomes follow {§wait-obligation-matrix},
2546
+ {§completion-joins-live-work}, and {§completion-defers-to-results}. Strike
2547
+ accounting and model-visible failure evidence remain separately owned by
2548
+ {§engine-rails} and {§rail-accounting-private}.
2354
2549
 
2355
2550
  - §send-target-recipient **A SEND target is a recipient.** A model's directed SEND
2356
2551
  addresses a worker (```` ```SEND (worker://<name>) ````), an outbound agent (`a2a://`),
@@ -2364,8 +2559,8 @@ violations follow the current admission and strike contracts
2364
2559
  lists `prompt://<worker>/<loop>/<id>` addresses under Active Prompts, and a model that
2365
2560
  addresses one of them means what an untargeted SEND means. The engine accepts a model
2366
2561
  SEND to a prompt of the current worker and loop as exactly that response: it dispatches
2367
- as the untargeted case, the row keeps the address the model wrote, and the body joins the
2368
- loop's response under {§loop-response-messages}. Nothing is taught about the form and it
2562
+ as the untargeted case, the row keeps the address the model wrote, and the body counts as
2563
+ the loop's response under {§loop-response-messages}. Nothing is taught about the form and it
2369
2564
  creates no per-prompt result structure; a prompt of another loop or another worker stays
2370
2565
  `400 send-target-not-a-recipient`. An undocumented acceptance in the same spirit as KILL
2371
2566
  in the completion turn, not a recipient.
@@ -2386,7 +2581,8 @@ violations follow the current admission and strike contracts
2386
2581
  EDIT or KILL carrying `[metadata]` for a scheme whose manifest takes none runs without it,
2387
2582
  and the packet carries one `metadata_ignored` notice naming the scheme (operator,
2388
2583
  2026-09-12: a gentle warning, never a refusal). The `pattern` option never reaches this
2389
- path; it is lifted into the matcher at parse time ({§matcher-option}).
2584
+ path; it is lifted into the matcher at parse time ({§matcher-option}). Executions, WORK and FORK
2585
+ own their slot and receive it whole ({§env-option}); a key they do not take is their own 400.
2390
2586
  - §send-looks-like-operation **A reply never begins with an operation heading.** When a model's
2391
2587
  untargeted SEND has, as its first non-blank line, a line that parses alone as one clean
2392
2588
  heading naming an operation this worker could perform — a Plurnk operation, or a registered
@@ -2416,36 +2612,56 @@ violations follow the current admission and strike contracts
2416
2612
  results** (every same-turn non-SEND/TASK/KILL operation, terminal stream output
2417
2613
  without a terminal foisted READ, and child results queued for the next packet).
2418
2614
  The set is judged at the disposition's own dispatch, after
2419
- earlier operations in the emission. `[200]` over a pending member is refused
2420
- 409 and the loop continues, except {§send-final-strike-retrieval}; every refusal
2421
- strikes uniformly, including a retrieval-only refusal. Its Problem reports the bounded pending kinds
2422
- `streams`, `workers`, `receipts`, `failed-stream-results`, and
2423
- `worker-results`. A receipts-only refusal also names the distinct blocking
2424
- operations in execution order using their model-facing log names
2615
+ earlier operations in the emission. `[200]` over a **live** member joins it
2616
+ ({§completion-joins-live-work}); a claim over only completed-but-unobserved
2617
+ members is a deferral ({§completion-defers-to-results}). Neither is a refusal
2618
+ and neither strikes. The pending kinds are `streams`, `workers`, `receipts`,
2619
+ `failed-stream-results`, and `worker-results`; a receipt names them and never
2620
+ embeds commands, stream handles, result bodies, or a presumed recovery. A
2621
+ nonempty all-`failed` inventory crosses the same deferral and then abandons
2622
+ regardless of live work, which it cancels rather than waits for.
2623
+ - §completion-joins-live-work **A completion over live work is a join.** A scope
2624
+ that says done while its own work runs — an open stream, a live child worker —
2625
+ is asking to leave with that work unfinished, and the two exits structured
2626
+ concurrency allows are join and cancel. The `completed` inventory takes the
2627
+ join: the TASK answers 202, the loop parks untimed on the live obligation
2628
+ exactly as a `waiting` inventory would, and the settle edge wakes it with what
2629
+ concluded in the packet; the model's next TASK decides with that result in
2630
+ front of it, so nothing completes on an answer written before the work
2631
+ finished. The `failed` inventory takes the cancel. The join's `detail` is read
2632
+ when the wake lands and speaks from that moment; its `attrs` carry `waiting`
2633
+ and the pending kinds. It is never a strike: the review contract has no
2634
+ violation left, and the rail's remaining sources are hard results, cycles and
2635
+ empty turns ({§engine-rails}). A stream the model meant to leave running parks
2636
+ the loop until it ends, as a model-written `waiting` would; KILL before the
2637
+ claim is the way to leave it running (operator, 2026-09-14: "politely
2638
+ converting completed into waiting when there are streams or workers in-flight").
2639
+ - §completion-defers-to-results **Settled results defer a terminal; they never strike.**
2640
+ A completion or abandonment claimed over results the model could not yet have
2641
+ seen — this turn's failed operations, this turn's receipts (successful execution
2642
+ results included, not only READ/FIND), a concluded stream's result, a terminated
2643
+ child's result — is deferred: the TASK answers 102 with no Problem and no strike,
2644
+ the loop continues, and the next packet carries what deferred it. The engine
2645
+ owns every observe edge, so such a claim is early in the observation order, not
2646
+ false about the world. After observing the results, the model can continue work,
2647
+ revise its response, or conclude with TASK alone while retaining its last SEND
2648
+ ({§loop-response-messages}, {§send-undelivered-child-term}).
2649
+ The deferral's `detail` is read one packet later, beside the results it names,
2650
+ and speaks from that moment: a receipts-only deferral names the distinct
2651
+ blocking operations in execution order using their model-facing log names
2425
2652
  ({§log-coordinate-hierarchy}), plus `stream completion` for undelivered terminal
2426
- stream results. The receipt is read one packet later, beside the results it
2427
- names, and speaks from that moment: a receipts-only or results-only refusal
2428
- says the deferred results are in the packet being read and that a TASK now
2429
- completes; a live obligation names the wait (a TASK with a pending task, or
2430
- KILL for an execution); a same-turn failure says the failure is in the packet
2431
- and asks for it to be addressed or completed over. Every completion refusal
2432
- is `retryable: true`, because the same TASK is the correct next request;
2433
- none of them names an action to observe, and none describes a persistent
2434
- unacknowledged obligation.
2435
- That list obeys the configured error-detail limit; it never
2436
- embeds commands, stream handles, result bodies, or a presumed recovery.
2437
- The pending kind changes the factual Problem class, not
2438
- rail accounting. A nonempty all-`failed` inventory deliberately abandons regardless.
2439
- - §send-final-strike-retrieval **Receipt-only completion at the final strike.**
2440
- If refusing a model's completion TASK would reach the loop's existing
2441
- consecutive-strike limit, accept it when `receipts` is the only pending kind
2442
- and this turn has no failed operations. Receipts include successful execution
2443
- results, not only READ/FIND. The same streak and configured limit
2444
- apply; a clean turn resets the streak and there is no separate refusal counter.
2445
- Live work, undelivered child results, and failed stream results remain blocking.
2446
- Decide at TASK dispatch: record the accepted TASK and terminal normally,
2447
- retain all earlier refusals and retrieval results unchanged, and do not invent
2448
- another model turn or an observation of the pending receipts.
2653
+ stream results; a results deferral names the landed kinds; a failure deferral
2654
+ counts the failures. These deferrals and the live-work join receipt share the
2655
+ conditional guidance: `If your final response has already been sent and these
2656
+ results require no further work or response revision, submit only TASK.` This
2657
+ avoids repeating a delivered response, not delivering one; newly arrived prompts
2658
+ retain their distinct feedback under {§completion-defers-to-prompts}. Their
2659
+ `attrs` carry the pending kinds or the failure count. The rail's streak never
2660
+ enters the decision: a deferral is admissible at any streak, and a loop that
2661
+ keeps issuing operations before each claim pays one packet per claim, never a
2662
+ strike, until the cycle detector rules its repetition ({§engine-cycle-evidence}).
2663
+ Operator, 2026-09-14: the barrier is safety and the rail is liveness; fail is
2664
+ completed with a frowny face and crosses the same barrier.
2449
2665
  - §send-administrative-terminal **An administrative terminal closes its own
2450
2666
  transaction.** A client, plugin, or `_plurnk` operation program runs in its
2451
2667
  own administrative loop. Its all-`completed` TASK concludes exactly that loop;
@@ -2477,9 +2693,9 @@ of which Worker launched it ({§execution-output-identity}).
2477
2693
  Stored runtime entries retain plugin-only writers. Input and termination are
2478
2694
  control capabilities, not exceptions granting write access to stdout/resources.
2479
2695
 
2480
- ### §exec EXEC
2696
+ ### §exec Executions
2481
2697
 
2482
- AST: `{ op: "EXEC", target (optional runtime-specific target), body: string | null (runtime-specific input), signal: string | null (runtime tag), lineMarker (timeout/poll) }`.
2698
+ AST: `{ runtime (the fence name in its registered lowercase spelling; there is no operation keyword), target (optional runtime-specific target), body: string | null (runtime-specific input), lineMarker (timeout/poll) }`.
2483
2699
 
2484
2700
  §exec-target-routing Engine routes unconditionally to the `exec` scheme,
2485
2701
  resolves the runtime first, selects its static {§executor-invocation} or exact
@@ -2505,9 +2721,9 @@ names the working directory only when it is not the project root, and then in th
2505
2721
  model's own project-relative form ({§fs-namespace}: the root is the model's `/`, so it
2506
2722
  is never rendered, and no receipt or Problem carries a host-absolute path — the
2507
2723
  batch of 2026-08-29 showed the absolute `cwd` copied back into the target slot as
2508
- `(cwd: /host/path)`). The EXEC `(path)` is a program — a script for an interpreter, a tool name for a tool
2724
+ `(cwd: /host/path)`). The `(path)` is a program — a script for an interpreter, a tool name for a tool
2509
2725
  family — and neither a command nor a working directory is ever a target. The default
2510
- shell is taught as targetless bare `EXEC`; `[sh]` remains the explicit form.
2726
+ shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
2511
2727
 
2512
2728
  §exec-tool-fall-through **A tool run as a shell command is named at the failure
2513
2729
  site.** A bare shell command whose program is the name of a tool published by
@@ -2529,7 +2745,7 @@ registry does not know keeps the plain exit-127 receipt.
2529
2745
 
2530
2746
  Body and target requirements come from the same runtime declaration. A runtime
2531
2747
  with no target declaration refuses a target; required body or target fields are
2532
- enforced independently; every EXEC requires at least one of them; and an
2748
+ enforced independently; every execution requires at least one of them; and an
2533
2749
  `exclusive` declaration refuses an invocation containing both. A target retains
2534
2750
  its one declared role whether the body is empty or non-empty. Runtime selection,
2535
2751
  target validation, and body/target relation failures therefore occur before
@@ -2567,7 +2783,7 @@ Loop-flag authority follows the selected runtime's declaration:
2567
2783
  | Non-file `resource` | `exec` and the addressed source scheme |
2568
2784
 
2569
2785
  Worker and runtime-stream authorities, query, fragment, and every other
2570
- component of a `resource` address retain their owning READ semantics. EXEC's
2786
+ component of a `resource` address retain their owning READ semantics. The execution's
2571
2787
  metadata is not part of that address: the internal READ has no metadata, and
2572
2788
  the selected executor receives the exact original blocks separately from its
2573
2789
  body. A failed source READ is preserved as the proposal-application
@@ -2601,7 +2817,7 @@ catalogue.
2601
2817
 
2602
2818
  Per-tool programs such as `go`, `cargo`, `make`, and `npm` do not earn executor tags merely because they are executables; they are complete shell commands in a `sh` executable fence. Registered tags exist only for tools that own a distinct body, target, or output contract. {§exec-registry-resolves}
2603
2819
 
2604
- **Timeout and poll — `<T,P>` on the `<L>` slot (grammar 0.74.20).** EXEC
2820
+ **Timeout and poll — `<T,P>` on the `<L>` slot (grammar 0.74.20).** An execution
2605
2821
  repurposes the line-marker slot as `<timeout, poll>` in **minutes** — agentic
2606
2822
  latencies make a sub-minute horizon a trap — converted at the parse boundary to the
2607
2823
  catalog's internal `stream.seconds`. The TASK `<T>` wait horizon is minutes too.
@@ -2700,7 +2916,7 @@ read effects). Unlisted effects keep the default. An invalid entry — unknown
2700
2916
  effect, unknown policy, or a non-`<effect>:<policy>` shape — fails daemon boot
2701
2917
  loudly rather than degrading admission.
2702
2918
 
2703
- After all non-SEND operations dispatch, the initiating turn applies {§worker-optimistic-settlement} to only the EXEC streams it started, then dispatches its turn disposition against the refreshed lifecycle state. An older stream receives no renewed opportunity merely because another turn began. This is a settlement barrier before disposition, not sibling-operation serialization: dependent EXECs remain separate observed turns.
2919
+ After all non-SEND operations dispatch, the initiating turn applies {§worker-optimistic-settlement} to only the execution streams it started, then dispatches its turn disposition against the refreshed lifecycle state. An older stream receives no renewed opportunity merely because another turn began. This is a settlement barrier before disposition, not sibling-operation serialization: dependent EXECs remain separate observed turns.
2704
2920
 
2705
2921
  §exec-stream **Stream surfacing.** An exec's output is *observed, not fetched*, in
2706
2922
  two states and no others:
@@ -2713,7 +2929,7 @@ two states and no others:
2713
2929
  §exec-concurrency **Bounded admission per workspace (#389).** At most
2714
2930
  `PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (shipped `12`;
2715
2931
  `-1` unbounded); the scope is the workspace, so neither delegation nor later turns
2716
- bypass it and no other workspace can starve it. Every admitted EXEC still creates its
2932
+ bypass it and no other workspace can starve it. Every admitted execution still creates its
2717
2933
  entry, channels, and open subscription before its receipt returns, so queued work is
2718
2934
  cancellable, restart-reconcilable, completion-gated, and observed through the ordinary
2719
2935
  stream mechanics ({§exec-stream}). The receipt tells the truth once and never rewrites
@@ -2722,7 +2938,7 @@ it: an immediate slot is `200 { outcome: "started" }`; delayed work is
2722
2938
  `active` in the existing live sense — output growth and terminal settlement are the
2723
2939
  current truth. Admission is FIFO within the workspace; queue residence does not consume
2724
2940
  the execution timeout; a KILL while queued never invokes the executor and closes the
2725
- stream through the normal 499 path. The scheduler is the EXEC scheme's; the knob is the
2941
+ stream through the normal 499 path. The scheduler is the exec scheme's; the knob is the
2726
2942
  service's ({§operator-config}), fail-hard on any other value.
2727
2943
 
2728
2944
  §exec-stream-page **An unrequested delivery never exceeds the retrieval page.** The
@@ -2745,7 +2961,7 @@ transition commit atomically. A concluded stream lands one conclusion row per
2745
2961
  channel that holds content, and an empty sibling channel is a fact on that row
2746
2962
  (`channels: {"#stderr": 0}`), never a row of its own; only a stream that printed
2747
2963
  nothing on any channel lands one bodyless conclusion row, on its default
2748
- channel, whose terminal fact, causal EXEC link, and available exit code make
2964
+ channel, whose terminal fact, causal execution link, and available exit code make
2749
2965
  completion explicit without invented narration (operator, 2026-09-13: the
2750
2966
  per-channel empty row was "a useless packet bomb" — 131 of 298 conclusion rows
2751
2967
  in the candidate4 run). A skipped channel's publication is still marked
@@ -2755,7 +2971,7 @@ again; the exact terminal result and channel content remain READable at the
2755
2971
  stream address. Every READ then obeys {§body-projection} and therefore renders
2756
2972
  its selected result complete. A stream that closes before a same-turn wait
2757
2973
  remains pending until every selected channel's terminal READ crosses the next
2758
- packet boundary. The EXEC row separately records the authored invocation.
2974
+ packet boundary. The execution row separately records the authored invocation.
2759
2975
 
2760
2976
  ```` ```KILL (<runtime>:///<eight-hex-id>) ```` cancels an active subprocess via
2761
2977
  the subscription registry's stored controller. A terminal stream is immutable:
@@ -2764,9 +2980,49 @@ the subscription registry's stored controller. A terminal stream is immutable:
2764
2980
  The runtime scheme participates in the durable lookup; a completed `sh:///`
2765
2981
  stream cannot fall through an internal `exec`-only query. {§stream-control}
2766
2982
 
2767
- §exec-env-scoped **Scoped environment.** An EXEC subprocess inherits the *project's* environment — its `.env`, the standard shell vars — so the model's commands run as the project expects; but never plurnk's own secrets: the provider API keys and `PLURNK_*` config are stripped before the spawn, so a model-executed command can't `printenv` the engine's keys. The service owns the scoping policy (the denylist); the executor spawns with the env it is handed.
2768
-
2769
- - §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 EXEC followed by TASK; 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.
2983
+ §exec-env-scoped **Scoped environment.** An execution subprocess receives a composed
2984
+ environment, never the host's. Two mechanisms apply in order, and they are different kinds
2985
+ of thing. First the **ambient policy**, a ceiling: `PLURNK_SERVICE_EXEC_ENV_INHERIT` names
2986
+ what the host's environment may contribute at all and `_EXCLUDE` narrows that, both taking
2987
+ exact names or one trailing-`*` prefix glob, both ordinary operator knobs under
2988
+ {§operator-config-env-defaults}. A worker document or a heading modifier narrows the ceiling
2989
+ further; nothing downstream widens it. The worker document is the Worker's `env` state
2990
+ ({§env-functionality}), read at the spawn: an enabled worker entry sets its own value, a disabled
2991
+ entry of either origin withholds the name, and what `list` projects is what the command
2992
+ receives. Then the **invariant**, which is not a knob:
2993
+ `PLURNK_*` config and every provider credential name are stripped last and unconditionally,
2994
+ so no policy, document or modifier can readmit plurnk's own secrets. The service composes;
2995
+ the executor spawns with the environment it is handed. Each spawn records the environment it
2996
+ received on the output it produces — every name with its provenance: the host through the
2997
+ ceiling, this Worker, an ancestor by name, or masked — and the digest renders it beside the
2998
+ operation, which closes the host-versus-container confound where it starts.
2999
+
3000
+ The ceiling exists because the invariant protects the wrong secrets. It knows plurnk's
3001
+ credentials and nothing about the operator's, so a denylist alone hands `SSH_AUTH_SOCK` and
3002
+ `NPM_TOKEN` to every command a model writes. Membership is an allowlist: the shipped
3003
+ `INHERIT` is what a shell, git, node and python need to run, and an operator who wants a
3004
+ tool's credential reachable by model-written commands adds it by name. An empty policy admits
3005
+ nothing ambient — the allowlist is declared in `.env.defaults`, so an empty one is a cleared
3006
+ policy rather than an unconfigured install.
3007
+
3008
+ The full composition for one spawn, nearest setter winning for defaults and ceilings immune,
3009
+ is package floors → operator cascade → ambient policy → worker document → the op's modifier →
3010
+ body prefixes.
3011
+
3012
+ - §env-option **The op's environment.** `env` is the service's key in the heading's `[metadata]`
3013
+ ({§scheme-metadata-modifier}): `[{"env": {"NAME": "value"}}]`, an object of string values.
3014
+ On an executor fence it is that process's environment over the Worker's own
3015
+ ({§env-functionality}) — the layer nearest the spawn, never entering the registry; a runtime
3016
+ that runs in-process ignores the environment it is handed, so there it changes nothing. On
3017
+ WORK and FORK it is the child's starting environment: after the copy the child inherits
3018
+ ({§functionality-scope}), each name lands as the child's own entry through the family's `add`,
3019
+ so a parent hands down its registry and overrides specific names for one child in one heading.
3020
+ Names follow the family's admission — a name a shell can export, never plurnk's own — and a
3021
+ refusal names the key and the reason at the operation, before anything runs or is created; a
3022
+ key WORK or FORK does not take is refused the same way. The durable row redacts the block
3023
+ wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
3024
+ provenance as the modifier's.
3025
+ - §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 followed by TASK; 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.
2770
3026
  - §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.
2771
3027
 
2772
3028
  | Input / effect | Consumer behavior |
@@ -2776,14 +3032,14 @@ stream cannot fall through an internal `exec`-only query. {§stream-control}
2776
3032
  | Supplied bytes | Retain the original bytes and declared mimetype in an ordinary channel ({§binary-parity}). |
2777
3033
  | Supplied text | Preserve text resources; HTTP/HTML materialization retains source and derived channels under {§html-materialization}. |
2778
3034
  | Null content | Acquire an HTTP(S) resource through the checked WebFetcher, using the same configured materializers as exact HTTP acquisition ({§http-materializer-plugins}). |
2779
- | Durable evidence | One typed EDIT event in the reserved runtime actor, with the calling Worker as causal source. Binary evidence describes the resource; its complete bytes live in the resource channel. |
3035
+ | Durable evidence | One typed EDIT event in the runtime actor, with the calling Worker as causal source. Binary evidence describes the resource; its complete bytes live in the resource channel. |
2780
3036
  | Model orientation | The executor includes returned addresses in its result. Publication does not independently wake inference or broadcast an observer row ({§env-delta-entry-materialization}). READ controls content acquisition and native delivery. |
2781
3037
 
2782
3038
  A failed acquisition, projection, or write rejects the sink with its cause; it does not erase the upstream operation or imply no external effect. Parallel acquisition begins before the per-invocation serialized write chain. A rejected publication leaves that chain usable. Execution completion and shutdown await the whole chain; one lazily created runtime narration turn owns the invocation's publication evidence.
2783
3039
 
2784
- ### §proposal The proposal lifecycle
3040
+ ## §proposal Proposals and client interactions
2785
3041
 
2786
- §proposal-202-pauses A side-effecting op does not execute on dispatch — it **proposes**. The scheme returns **202** (an EXEC `host` runtime {§exec}, an EDIT to a member file {§membership}); the engine writes the log row `state='proposed'`, registers a waiter keyed by `logEntryId`, and **pauses `dispatch`** awaiting a resolution. The provider exchange and emitted operation are already durable, while the turn remains open until dispatch settles; {§engine-rails} therefore sees the *resolved* status, never the provisional 202. On accept the status becomes 200 and the scheme's effect runs.
3042
+ §proposal-202-pauses A side-effecting op does not execute on dispatch — it **proposes**. The scheme returns **202** (an execution on a `host` runtime {§exec}, an EDIT to a member file {§membership}); the engine writes the log row `state='proposed'`, registers a waiter keyed by `logEntryId`, and **pauses `dispatch`** awaiting a resolution. The provider exchange and emitted operation are already durable, while the turn remains open until dispatch settles; {§engine-rails} therefore sees the *resolved* status, never the provisional 202. On accept the status becomes 200 and the scheme's effect runs.
2787
3043
 
2788
3044
  **Resolution arrives through one lifecycle:**
2789
3045
 
@@ -2841,8 +3097,30 @@ execution signal, including KILL and deadline, not just its enclosing loop.
2841
3097
  Reconnect discovery intersects durable rows with live waiters; restart
2842
3098
  removes ownerless rows without fabricating cancellation, payload, or replay.
2843
3099
 
3100
+ ### §proposal-ownership Loop disposition and client YOLO
3101
+
3102
+ Side-effecting operations propose ({§exec}) and pause dispatch at 202 for an
3103
+ authority decision ({§engine-rails}, {§methods}). Automatic acceptance has two
3104
+ distinct owners:
3105
+
3106
+ | Mechanism | Authority path | Intended use |
3107
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
3108
+ | §proposal-ownership-loop-auto **Loop disposition** | `runLoop({ policy: { proposals: "accept" } })` persists a loop-owned disposition; core resolves proposals in process without a client. | Headless automation, benchmarks, CI, fixtures, and unattended use. |
3109
+ | **Client-side YOLO** (`--yolo` / `PLURNK_YOLO`) | A `proposals: "review"` loop emits the ordinary `loop/proposal`; the client returns an accepted proposal through its standard resolution path. | Interactive automatic review. |
3110
+
3111
+ Core cannot distinguish client-side YOLO from a fast human acceptance and does
3112
+ not need to. Loop auto keeps authority inside the loop; client-side YOLO acts
3113
+ only after authority crosses the client boundary.
3114
+
3115
+ §proposal-ownership-notification **The notification carries disposition, not policy inputs.** `loop/proposal` carries the core-owned `ProposalDisposition` ({§notifications}, {§proposal-disposition}). A connected client presents only `owner="client"`; it never reimplements policy from operation or attrs.
3116
+
2844
3117
  ### §proposal-disposition Settlement authority and precedence
2845
3118
 
3119
+ §loop-policy-effective-read `loops.policy` persists one complete immutable
3120
+ `LoopPolicy`; every runtime policy read validates that snapshot before use.
3121
+ Missing rows or invalid values fail with the owning loop coordinate and cause.
3122
+ Raw archival copies and forensic rendering do not interpret policy.
3123
+
2846
3124
  `ProposalDisposition` is either `{ owner: "client" }` or `{ owner: "loop", decision: "accept" | "reject", outcome? }`. The persisted loop policy determines it exactly:
2847
3125
 
2848
3126
  | `policy.proposals` | Disposition |
@@ -2935,7 +3213,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
2935
3213
  | §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. |
2936
3214
  | §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. |
2937
3215
  | §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}); no explicit checkpoint and no periodic `ANALYZE` run. |
2938
- | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads five knobs from `.env.defaults` once at daemon construction and runs three 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` (1) collects items no composition references. `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). Under the shipped defaults nothing that is information leaves; only what no row references. A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
3216
+ | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads seven knobs from `.env.defaults` once at daemon construction 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` (1) collects items no composition references. `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` (-1) 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 nothing that is information leaves; only what no row references. A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
2939
3217
 
2940
3218
  - **Schema-alignment test**: loads `@plurnk/plurnk-contracts/schema/*.json`, parses DDL via `node:sqlite` introspection, asserts every required schema field has a corresponding `NOT NULL` column. Contract drift fails CI.
2941
3219
  - DDL = storage truth; JSON Schemas = wire truth. Tested-aligned, allowed to differ where ergonomics demand.
@@ -3060,23 +3338,11 @@ service manifest edit.
3060
3338
 
3061
3339
  ## §grammar Grammar Dependency
3062
3340
 
3063
- `@plurnk/plurnk-contracts` is authoritative; surface gaps through its owning issue and adopt what lands. Do not redesign the contract from core.
3064
-
3065
- ### §grammar-provides What grammar provides
3066
-
3067
- - Parser (`PlurnkParser`, ANTLR4) — DSL text → `PlurnkStatement[]`.
3068
- - AST types — exported TypeScript interfaces.
3069
- - JSON schemas (`schema/*.json`, draft 2020-12) for every wire shape.
3070
- - `plurnk.md` — canonical model-facing DSL description.
3071
-
3072
- ### §service-tracks What plurnk-service tracks (NOT in grammar)
3073
-
3074
- - Channel state (`static`/`active`/`closed`/`errored`) — persisted channel metadata owned by core and exposed through the schemes capability contract ({§channel-state}).
3075
- - Backpressure caps — none ({§stream-constraints}).
3076
- - Stream cancel — `KILL` of the stream address ({§stream-control}).
3077
- - Delete — `KILL` (entry-KILL, the canonical delete, {§move}); a scoped entry KILL deletes one span through the EDIT path ({§kill-scope-entry}).
3078
- - §loop-policy-effective-read Per-loop policy — `loops.policy` persists one complete immutable `LoopPolicy`; every runtime policy read validates that snapshot before use. Missing rows or invalid values fail with the owning loop coordinate and cause. Raw archival copies and forensic rendering do not interpret policy.
3079
- - Default-channel wire rendering — {§channel-selection}.
3341
+ Core consumes the language, schemas, and generated types under
3342
+ {§contract-authority} and {§contract-representations}. Provider emissions cross
3343
+ {§emission-admission}; admitted programs execute through
3344
+ {§turn-ops-admission-path}. Core owns execution and persisted state, not a second
3345
+ language definition.
3080
3346
 
3081
3347
  ---
3082
3348
 
@@ -3138,7 +3404,7 @@ Model selection uses one selector vocabulary in `ProviderRegistry` ({§provider-
3138
3404
  | `PLURNK_SERVICE_DB_PATH` | `$XDG_DATA_HOME/plurnk/plurnk.db` | SQLite file path; an explicit non-empty value overrides the derived default. |
3139
3405
  | `PLURNK_HOST` | `127.0.0.1` | Bind address for the listener. Local-only by default. |
3140
3406
  | `PLURNK_PORT` | `1066` | TCP port for THE client surface — the AG-UI+ listener (the plurnk-agui plugin module binds it at boot). Production is single-listener. |
3141
- | §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | `1` | Hard service ceiling: only `1` admits Git membership, status, and branch batching; every other value denies them. |
3407
+ | §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | `1` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
3142
3408
  | §operator-config-file-create-scope `PLURNK_SERVICE_FILE_CREATE_SCOPE` | `root` | 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. |
3143
3409
  | `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` | `104857600` | Byte ceiling in `1..104857600` for one workspace-file snapshot ({§membership-materialization-limit}). |
3144
3410
  | `PLURNK_SERVICE_MAX_TURNS` | `-1` | 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. |
@@ -3158,7 +3424,7 @@ Model selection uses one selector vocabulary in `ProviderRegistry` ({§provider-
3158
3424
  | `PLURNK_SERVICE_REQUIEM_MAX_TOKENS` | `16384` | Initial forensic witness output allowance ({§digest-requiem}). |
3159
3425
  | `PLURNK_SERVICE_REQUIEM_RETRY_MAX_TOKENS` | `32768` | Retry allowance; must be at least the initial requiem allowance ({§digest-requiem}). |
3160
3426
  | `PLURNK_SERVICE_FILES_ITEMS` | `-1` | 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}). |
3161
- | `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` | `none` | Ceiling for a model's `members` definitions in the lattice `none < root < namespace`; `none` refuses every model definition ({§members-model-scope}). |
3427
+ | `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` | `namespace` | Ceiling for a model's `members` definitions in the lattice `none < root < namespace`; `none` refuses every model definition ({§members-model-scope}). |
3162
3428
  | `PLURNK_SERVICE_EXEC_CONCURRENCY` | `12` | Executions admitted at once per workspace; the rest queue FIFO with `202 queued` receipts; `-1` unbounded ({§exec-concurrency}). |
3163
3429
  | `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` | (empty — waits indefinitely) | Finite positive milliseconds before cancellation with outcome `timeout`; empty waits, and every other explicit value fails ({§proposal-timeout-cancels}). |
3164
3430
  | §operator-config-worker-warm `PLURNK_SERVICE_WORKSPACE_WARM_MS` | `900000` | Milliseconds a lease-free workspace Functionality snapshot remains warm; `0` cools without grace and `-1` disables time-based cooling ({§module-workspace-residency}). |
@@ -3364,7 +3630,7 @@ flowchart LR
3364
3630
  | `registerScheme(name, handler)` | Adds one process-wide addressable scheme handler; scheme readiness and model-facing capability publication remain core-owned. |
3365
3631
  | §module-action-registration `registerModuleAction({ name, scope, inputSchema, outputSchema, handler })` | Adds one non-empty, extension-unique action with resolvable JSON Schemas. `scope` is exactly `worldless`, `workspace`, or `worker`; the handler receives schema-validated params and a separate matching context. Scoped contexts contain trusted bound identifiers, never client parameters. A client-interface module decides whether and how the name becomes public, validates successful output, and owns collisions with its built-ins. |
3366
3632
  | §module-workspace-provider `registerWorkspaceCapabilityProvider(namespaceOwner, provider)` | Registers one extension-unique Functionality provider. `activate({ workspaceId, retain })` reconstructs the workspace snapshot; idempotent `deactivate({ workspaceId })` releases process resources. Core coalesces demand and supplies residency leases for work that outlives its caller. |
3367
- | §module-workspace-state `readWorkspaceModuleState(workspaceId, namespaceOwner)` | Reads one nullable JSON state value per workspace and provider. Core owns storage and lifecycle; the provider owns its schema. Store symbolic credential references, not copied secrets. |
3633
+ | §module-workspace-state `readWorkspaceModuleState(workspaceId, namespaceOwner)` | Reads one nullable JSON state value per workspace and provider. Core owns storage and lifecycle; the provider owns its schema. Store symbolic credential references, not copied secrets. A worker-scoped family's coordinator reads and replaces the same shape per worker in `worker_module_state` ({§functionality-scope}). |
3368
3634
  | §module-functionality-adapter `registerFunctionalityAdapter(adapter)` | Registers one family beneath the shared coordinator ({§functionality-coordinator}). |
3369
3635
  | §module-workspace-capabilities `replaceWorkspaceCapabilities({ workspaceId, namespaceOwner, state, runtimes })` | Atomically replaces one provider's durable state and runtime/scheme snapshot at the workspace operation boundary. Namespace claims are validated before mutation. Failure restores the prior state and publication. |
3370
3636
 
@@ -3412,11 +3678,21 @@ workspace boundary. Durable configuration, history, and saved entries remain.
3412
3678
  New demand reconstructs the one shared snapshot. Shutdown cancels warm timers
3413
3679
  and closes module resources through their owning provider.
3414
3680
 
3681
+ §module-workspace-residency-facet **Residency is the workspace's; processes are
3682
+ one family's facet.** The workspace is the lease unit, and every family's
3683
+ publication rides the same replacement: its manager executor, its generated
3684
+ documents and its state. Only a family that holds processes prepares
3685
+ `runtimes` (MCP servers today); the field is absent for every other family, so
3686
+ warming and cooling bound workspaces, never families, and the two-stage rollback
3687
+ guards the manager registration of every family alongside the one family's
3688
+ processes. There is no per-family residency policy (operator, 2026-09-14:
3689
+ residency is MCP-specific and is not a family policy system).
3690
+
3415
3691
  The version-1 baseline table `workspace_module_state` stores one JSON value
3416
3692
  per `(workspace_id, namespace_owner)`. It is configuration, not an executable
3417
3693
  registry. Deleting the workspace cascades its state; worker lifecycle does not.
3418
3694
 
3419
- ### §functionality Workspace Functionality
3695
+ ## §functionality Workspace Functionality
3420
3696
 
3421
3697
  §functionality-coordinator **One coordinator owns the common lifecycle.**
3422
3698
  Agent Skills, MCP, outbound A2A agents, and membership are adapters beneath
@@ -3439,7 +3715,11 @@ Retryability describes the actual failed condition, not its numeric status.
3439
3715
 
3440
3716
  §functionality-adapter **An adapter owns protocol truth.** It declares its
3441
3717
  family, namespace owner, definition schema, contributed defaults, discovery,
3442
- admission, preparation, and teardown. Admission distinguishes explicit client
3718
+ admission, preparation, and teardown, and its alias grammar when that is not
3719
+ the shared lowercase-hyphen one: the coordinator enforces whichever grammar the
3720
+ family declares, at admission, in the service projection, and on persisted
3721
+ state, so an environment variable's name is an alias exactly as a skill name
3722
+ is. Admission distinguishes explicit client
3443
3723
  actions from model operations where the family contract requires it
3444
3724
  ({§members-model-scope}). Preparation returns runtimes, documents, per-alias
3445
3725
  outcomes, and a snapshot with `commit`/`abort`. Successful publication commits;
@@ -3447,17 +3727,79 @@ failure aborts; cooling tears down. Protocol continuations remain ordinary
3447
3727
  module actions. Optional `forget` releases an installed or provisioned
3448
3728
  definition before removal; failure rejects removal ({§skills-remove}).
3449
3729
 
3730
+ §env-functionality **Environment is the fourth family, owned per Worker.** Its two origins
3731
+ are the ambient names the operator's ceiling admits ({§exec-env-scoped}, service origin, enabled
3732
+ until a Worker disables one for itself) and the Worker's own entries (worker origin). `add` takes
3733
+ the name as the alias and `{ "value": "…" }` as the definition, used verbatim with no
3734
+ interpolation. `disable` withdraws a name from the Worker's spawns while retaining it, which is
3735
+ how `CI=1` goes away for one Worker without an operator change; `remove` forgets a Worker's own
3736
+ entry and a same-name service baseline reappears disabled, so removal never silently changes what
3737
+ the next spawn sees. Service definitions are disable-only.
3738
+
3739
+ `list` projects effective values with their origin. Values are shown: the ceiling is the security
3740
+ boundary, not the projection, and any admitted name is already readable by every command the
3741
+ Worker runs — withholding it here would be theatre and would make `list` lie about the
3742
+ environment its commands receive. A name the invariant reserves (`PLURNK_*`, provider credential
3743
+ names) is refused at **admission**, not dropped at the spawn, so the model learns why.
3744
+
3745
+ `discover` is this installation's configuration catalog: every name an installed package declares
3746
+ under {§operator-config-env-defaults} that a Worker may set, projected as candidates whose summary
3747
+ is the declaration's own comment and whose provenance is the declaring package. The names the
3748
+ invariant reserves — plurnk's own configuration and provider credentials — are the operator's and
3749
+ never appear, so the catalog stays short enough to read whole. `query` matches a name or the
3750
+ comment that documents it, never a value. It is not a permissions list beyond that — a Worker may
3751
+ set any name it lists or none of them — it answers which names have a **consumer**, and it is how
3752
+ a Worker learns the name of a value only the operator can supply. The catalog projects
3753
+ declarations, never the host environment, so a credential the operator has filled in appears by
3754
+ name with its documentation and an empty value ({§exec-env-scoped}: referred to by name, never
3755
+ read). `configuration` is refused: a client's own environment contributing candidates would be a
3756
+ second door past the ceiling.
3757
+
3758
+ The family publishes no runtimes. An environment is read at the spawn that uses it, never held
3759
+ resident, so it never enters the warmed capability snapshot ({§module-workspace-residency}).
3760
+
3761
+ §functionality-scope **A family declares who owns its definitions.** Skills, MCP, members
3762
+ and outbound A2A describe what exists in a **workspace**: a capability, resident or installable,
3763
+ that every Worker there shares. Environment describes how one **Worker** works — context rather
3764
+ than capability — so its definitions are owned per Worker. The adapter declares `scope`
3765
+ (absent means workspace) and the coordinator keys durable state and the locally-owned `origin`
3766
+ by it: a workspace-scoped family's own entries carry origin `workspace`, a worker-scoped
3767
+ family's carry `worker`. Origin names ownership, never scope, so a projection never claims the
3768
+ workspace set a value one Worker set for itself. Nothing else in the contract varies: the six
3769
+ verbs, the two projections, enabledness, and the service-baseline rules are one implementation
3770
+ across every family, which is what keeps their idioms from drifting apart.
3771
+
3772
+ A worker-scoped family projects `worker.<family>.<verb>` in place of
3773
+ `workspace.<family>.<verb>`; the action's context names the Worker, as every execution
3774
+ does. Its durable value is the same shape per (worker, family) in `worker_module_state`, read
3775
+ at each verb and at each spawn rather than held in the workspace snapshot. Its `list` and
3776
+ mutations serialize on the Worker's own lane and take no workspace exclusivity: nothing
3777
+ resident changes, and the next spawn reads the state, so a Worker shapes its own environment
3778
+ while its siblings run. Its preparation yields outcomes only — no runtimes, documents or
3779
+ snapshot — and the coordinator refuses one that does more.
3780
+
3781
+ A Worker created with a parent — WORK and FORK alike — starts with a copy of the parent's
3782
+ worker-scoped state, taken at creation. The child owns its copy: neither side's later edits
3783
+ reach the other, and depth is transitive with no further rule. Each copied entry carries
3784
+ `inherited`, the Worker that set it, preserved across generations until the child changes that
3785
+ entry, so `list` never claims the child set what it inherited; a parent's masking of an
3786
+ ambient name travels the same way. WORK and FORK may hand the child more: the heading's `env`
3787
+ option lands as the child's own entries through `add`, after the copy ({§env-option}).
3788
+
3450
3789
  §functionality-state **One durable value per workspace and family.**
3451
3790
  `{ version: 1, definitions: { [alias]: { origin, enabled, definition? } } }`
3452
- is stored under the provider namespace in `workspace_module_state`.
3453
- A `service` entry persists enabledness; a `workspace` entry persists its exact
3454
- definition. Active, unavailable, and authorization-required are preparation
3791
+ is stored under the provider namespace in `workspace_module_state`; a
3792
+ worker-scoped family ({§functionality-scope}) stores the same value per worker
3793
+ and family in `worker_module_state`.
3794
+ A `service` entry persists enabledness; a `workspace` or `worker` entry persists
3795
+ its exact definition. Active, unavailable, and authorization-required are preparation
3455
3796
  outcomes, not durable desired state. The configuration cascade contributes
3456
3797
  defaults; one workspace snapshot is effective authority.
3457
3798
 
3458
3799
  §functionality-publication **One replacement publishes a family.** The
3459
3800
  coordinator prepares, then replaces state and runtimes through
3460
- {§module-workspace-capabilities}, with the manager followed by adapter runtimes.
3801
+ {§module-workspace-capabilities}, with the manager followed by any runtimes the
3802
+ adapter prepared ({§module-workspace-residency-facet}).
3461
3803
  Admission, tools, documents, Turn 0, and client status consume that publication.
3462
3804
  The coordinator's synchronous `publish` participates in the registry commit;
3463
3805
  its returned undo restores the previous view before rollback reconciles documents.
@@ -3482,6 +3824,10 @@ effects and use normal proposals. Summary, signatures, and deep docs derive
3482
3824
  from the same registry ({§tools-resource-materialization},
3483
3825
  {§executor-input-schema-preview}). Outcomes stream into the invoking operation's
3484
3826
  output entry. The manager closes over workspace identity, not worker identity.
3827
+ A worker-scoped family's verbs nonetheless act for the invoking Worker: Core
3828
+ binds that Worker to the manager at the operation, so the published manager
3829
+ stays one per workspace and the executor framework's arguments carry no
3830
+ identity ({§functionality-scope}).
3485
3831
 
3486
3832
  §functionality-document-body **A family's teaching is an authored file beneath
3487
3833
  its generated header.** The adapter names its package directory (`docsDir`);
@@ -3507,7 +3853,7 @@ a failed preparation, and fails 409 while the workspace is held
3507
3853
  ({§module-workspace-quiescence}). Rejecting a proposal prepares, persists, and
3508
3854
  publishes nothing.
3509
3855
 
3510
- ### §methods ApplicationPort function set
3856
+ ## §methods Application interface
3511
3857
 
3512
3858
  `ApplicationPort` is the contracts-owned interface implemented by `Daemon` and
3513
3859
  consumed by every exterior adapter ({§application-port}). Its function names are
@@ -3524,7 +3870,7 @@ Core's behavior behind them.
3524
3870
  | §methods-loop-run Loops | `runLoop({ workspaceId, workerId, prompt, source?, maxTurns?, policy?, openPaths?, selector?, childSelector? })` | Validates a model worker and complete loop policy, persists it with the effective turn ceiling, then returns an immediate status-100 acknowledgement with `loopId` and `action`. A trusted adapter may identify the prompt's causal actor with one canonical `source`; ordinary clients cannot author it through their protocol surface. The exact terminal result arrives only through `loop/terminated`; parking and resuming do not replace the loop. |
3525
3871
  | §methods-loop-cancel Loops | `cancelDrain(workerId, reason?)`; `cancelWorker({ workspaceId, workerId, reason? })` | `cancelDrain` begins durable structured cancellation and reports whether process-local work existed when called; queued or parked durable work is still terminalized when it is `false`. The ownership-bounded `cancelWorker` awaits that same tree cancellation and stream reap, so an exterior protocol can project the settled durable result without polling or fabricating state. |
3526
3872
  | §methods-op-mirror Client dispatch | `dispatchClientAction({ workspaceId, workerId, statements })` | Dispatches already-parsed grammar statements as one client action in one administrative loop in the client worker, executing in the workspace's Functionality ({§actor-boundary-attached-functionality}). Every statement is an ordered client/operation turn, and every committed `log/entry` is emitted before the action promise resolves; a proposal may keep its turn, loop, and action promise open until resolution. Core exposes no per-op method family. |
3527
- | Client observation | `look({ workspaceId, workerId, statement })` | Runs an already-parsed READ through the full resolver in the workspace's Functionality without a log row. A non-READ statement is rejected ({§op-look}). |
3873
+ | Client observation | `look({ workspaceId, workerId, statement, perspectiveWorkerId? })` | Runs an already-parsed READ through the full resolver in the workspace's Functionality without a log row. A non-READ statement is rejected ({§op-look}). |
3528
3874
  | §methods-log-read Reads | `readLog({ workspaceId, workerId, ...coordinate })` | Ownership-checks the worker, then reads by ids, recency, or the complete `loopSeq`/`turnSeq`/`sequence` display coordinate. `limit` defaults to 100 and is capped at 1000. |
3529
3875
  | §methods-entry-read Reads | `readEntry({ workspaceId, workerId, target, channel?, offset? })` | Resolves the selector from that worker's perspective and returns {§entry-read-result}, either complete or as one channel suffix, without creating action evidence. |
3530
3876
  | Providers | `listProviders()` | Lists configured aliases with provider/model identity, active state, and the effective provider-derived `inputCapacity` when known. |
@@ -3582,11 +3928,11 @@ later create or attach result without requiring a new transport; it resolves the
3582
3928
  conversation worker separately, while each client action allocates its own
3583
3929
  administrative loop under {§connection-lifecycle}.
3584
3930
 
3585
- §methods-worker-name-reserved **Client worker-name admission.** Attach,
3931
+ §methods-worker-name-admission **Client worker-name admission.** Attach,
3586
3932
  fresh-conversation, and fork apply {§worker-name-minting} before lookup or
3587
- creation. A client therefore cannot forge or resume an internal worker, insert
3588
- a non-mintable spelling, or make the client registry diverge from model worker
3589
- control.
3933
+ creation. A client therefore cannot forge or resume the runtime actor (its
3934
+ name lies outside `WORKER_NAME`), insert a non-mintable spelling, or make the
3935
+ client registry diverge from model worker control.
3590
3936
 
3591
3937
  §capability-admission **One admission path owns external authority.** Core
3592
3938
  derives one or more `CapabilityDescriptor` demands from each routed statement,
@@ -3595,7 +3941,7 @@ survive before execution or proposal creation. A denial is an exact terse 403
3595
3941
  identifying the denied descriptor and owning policy scope; it never guesses the
3596
3942
  model's intent or recommends an alternate operation. COPY demands observation
3597
3943
  of its source and mutation of its destination; MOVE additionally demands
3598
- mutation of its source; resource-backed EXEC demands its runtime plus source
3944
+ mutation of its source; a resource-backed execution demands its runtime plus source
3599
3945
  observation. Unknown schemes, runtimes,
3600
3946
  and tools continue to their ordinary resolver so capability policy cannot turn
3601
3947
  absence into a misleading restriction. The same resolver shapes generated
@@ -3639,7 +3985,7 @@ client-interaction lifecycle — durable pause, reconnect discovery,
3639
3985
  cancellation, and the answer-as-resolution all come from
3640
3986
  {§client-interactions}; there is no loopback MCP and no proposal masquerade.
3641
3987
  Answer and cancellation resume the same waiting loop whether resolved before
3642
- or after it parks; the next packet contains the result without replaying EXEC.
3988
+ or after it parks; the next packet contains the result without replaying the execution.
3643
3989
  Effect `read`: the tool observes the human's answer and is never
3644
3990
  proposal-gated. Its runtime declares the `interaction` trait, which the shared
3645
3991
  resolver projects as access class `interact`; any capability-policy layer may
@@ -3679,7 +4025,8 @@ cascade. A WORK/FORK child copies the spawning loop's effective spawn model
3679
4025
  no live link and begins with no override, so a later parent change affects
3680
4026
  only that worker's future loops and descendants. Client operation actors and
3681
4027
  Plurnk-owned bookkeeping workers run no model loops and own no model
3682
- selection. An explicit model, spawn-override, or reasoning-policy change while
4028
+ selection; the model, spawn-override, and reasoning controls refuse them with
4029
+ `409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or reasoning-policy change while
3683
4030
  the worker holds any queued, running, or parked loop is a precise
3684
4031
  `409 worker-loop-active` ({§worker-lifecycle-live}), independent of a process-local
3685
4032
  drain. The policy write checks liveness atomically, including selections carried
@@ -3774,7 +4121,10 @@ spelling and grammar parsing. It rewrites a valid LOOK statement to READ and
3774
4121
  hands the AST to core's `look`; core owns the full resolver and the no-log
3775
4122
  invariant. The internal closed, rowless observation segment supplies an honest
3776
4123
  numeric loop coordinate for relative `log:///` addressing without leaving
3777
- active lifecycle behind. LOOK text anchors resolve through the same
4124
+ active lifecycle behind. The segment belongs to the acting worker (`workerId`);
4125
+ the READ resolves as `perspectiveWorkerId` when one is given — `log:///`, `reasoning:///`,
4126
+ and `ops:///` as that worker sees them — so a client can look at a conversation without
4127
+ adding a loop to it. LOOK text anchors resolve through the same
3778
4128
  {§line-anchors} path as READ.
3779
4129
 
3780
4130
  ### §notifications Core events
@@ -3834,12 +4184,7 @@ module that publishes that protocol.
3834
4184
 
3835
4185
  ---
3836
4186
 
3837
- ## §decisions Architectural decisions
3838
-
3839
- Tagged decisions state the durable contract and the reason for it. Investigation,
3840
- implementation status, and superseded alternatives belong in forge issues.
3841
-
3842
- ### §packet-assembly Packet assembly: engine builds the default list, plugins transform it
4187
+ ## §packet-assembly Packet assembly
3843
4188
 
3844
4189
  `PacketBuilder.buildRequestPacket` owns the engine's default ordered section
3845
4190
  list. Trusted scheme plugins may transform that first-class list before it is
@@ -3853,7 +4198,7 @@ flowchart LR
3853
4198
  measure --> rail[Engine budget admission and dispatch]
3854
4199
  ```
3855
4200
 
3856
- #### §packet-cache-monotone Default order and cache locality
4201
+ ### §packet-cache-monotone Default order and cache locality
3857
4202
 
3858
4203
  Conditional absence never reorders the surviving default sections.
3859
4204
 
@@ -3870,265 +4215,70 @@ Conditional absence never reorders the surviving default sections.
3870
4215
  | 9 | user | `git` | Per-turn workspace status; empty content is omitted. |
3871
4216
  | 10 | user | `budget` | `Context Curation`; omitted when capacity is unknown. |
3872
4217
  | 11 | user | `prompt` | Current prompt-entry pointers. |
3873
- | 12 | user | `recap` | Optional authored operational recap. |
3874
-
3875
- The order favors prefix-cache locality where semantics permit: the definition
3876
- and privileged policy lead operator notes, while the append-mostly
3877
- log leads the volatile user-status clump. It does **not** claim that every system byte is
3878
- immutable or that the complete packet is globally monotone in volatility:
3879
- operator notes and policies can change. Trust is a separate
3880
- admission rule. The system slot contains trusted control-plane material;
3881
- attacker-reachable content stays in the user slot.
3882
-
3883
- #### §packet-plugin-transform Trusted whole-list extension seam
3884
-
3885
- `SchemeRegistry.transformSections` pipes the complete default list through
3886
- every registered scheme implementing `transformSections(sections) -> sections`,
3887
- in registration order, before rendering and measurement. The schemes-owned
3888
- `PacketSectionDraft` contains only `name`, `slot`, `header`, and `content`.
3889
- Each initial or returned list passes the schemes-owned validator, including
3890
- unique-name enforcement, before the next transformer or renderer. Each
3891
- transformer may inspect the section content and add, remove, or reorder
3892
- sections. It receives no separate engine, database, actor, or request context.
3893
-
3894
- This is strictly a trusted in-process seam, admitted through the common plugin
3895
- trust gate; an external client action cannot invoke it. Whole-list transformation is
3896
- the fork-avoidance valve for alternate packet shapes ({§ecosystem}), while
3897
- overflow recovery and packet projection remain closed engine concerns.
3898
-
3899
- ### §tokenomics Tokenomics: four facts, one curation ruler
3900
-
3901
- Token accounting distinguishes the artifact being measured, the unit, and the
3902
- time of measurement.
3903
-
3904
- | Fact | Owner and unit | Time | Contract |
3905
- |:-----|:---------------|:-----|:---------|
3906
- | 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. |
3907
- | §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. |
3908
- | 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. |
3909
- | 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. |
3910
-
3911
- - §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.
3912
- - §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.
3913
- - §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}).
3914
- - §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.
3915
- - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
3916
- `PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
3917
- `(0, 100)`. It allocates that share of the cold-start curation allowance
3918
- (the capacity conversion with factor 1) to the aggregate automatic prompt-body
3919
- projection. Rolling calibration does not resize existing prompt bodies; only
3920
- the overall curation ceiling adapts. This does not bound stored prompt
3921
- size, provider capacity, an explicit READ/FIND result, or the complete packet.
3922
- Basing the share on configured capacity rather than current free weight or
3923
- sampled conversion keeps one prompt's projection byte-stable as the log and
3924
- calibration samples evolve.
3925
- - §tokenomics-window-unpollable-deliberate **Unknown provider capacity stays unknown.** When the provider cannot derive `inputCapacity`, Core omits denominator-dependent curation telemetry and uses the ordinary bounded prompt projection. The provider still sends requests whose measurement or limits are estimates or unavailable: ambiguity defers to the upstream capacity oracle rather than becoming a local rejection.
3926
-
3927
- §tokenomics-client-gauge **Clients receive curation and physical occupancy as separate pairs.** `loop/terminated.usage` carries latest packet-bearing model-turn `curationWeight`/`curationBudget` and latest-emission-call `contextTokens`/`contextCapacity`; each unknown fact is `null`. The curation pair is the packet's measured weight and captured allowance, exactly as displayed to the model, not a recalculation using newer usage evidence. Packetless chronology cannot erase an assembled-request gauge. Both physical facts bind to that same call: a preflight rejection may report capacity while its absent physical request leaves `contextTokens=null`, never borrowed from an earlier call. Clients never divide provider-reported physical tokens by Core curation weight. `providers.list` exposes each instantiated alias's `inputCapacity`. A model switch replaces the latest-turn facts together; aggregate provider accounting remains cardinal monetary evidence, not a gauge input.
3928
-
3929
- - **Derivation is exhaustive and demand-led.** Explicit searchable-resource changes may start one coalesced warm. Passive creation and attachment do not. The first model turn starts or joins that warm; later turns derive intervening changes before dispatch. No model operation observes partial graph or full-text coverage. Progress notices make the wait visible. {§derivation-exhaustive}
3930
- - §membership-binary-sniff **Binary truth beats a text label.** Filesystem source acquisition, including tracked members and installed skill resources, inspects up to the first 8192 bytes when extension detection does not identify a binary type. NUL marks `application/octet-stream`; existing binary types retain their declared type. Member projections follow {§membership-source-projection}; installed skill projections follow {§skills-resources}.
3931
- - §tokenomics-agnostic-ruler **One model-agnostic curation ruler.** The daemon runs workers on different models in one workspace concurrently, while catalog and log accounting are workspace-wide. `contentWeight = ceil(chars/2)` therefore gives one content one stable number without per-model workspace state or recount passes. It controls curation only; every provider call independently measures the complete request as well as it can.
3932
- - §tokenomics-neutral-telemetry **Curation telemetry is state, not response allowance.** The model-facing `Context Curation` section is one JSON object carrying `logTokensTotal` and `logTokensMax` (and `tokensResponseMax` when an output floor is disclosed), so the block opens as a JSON payload like ```` ```TASK ````. It never presents their difference as free response tokens. The protocol definition directly requires KILL of irrelevant log items and ranges to keep the next packet within the maximum. Per-entry weights remain on log rows where they describe visible cost and curation savings. Generic packet composition and physical-token speculation are absent.
3933
- - §tokenomics-pressure-inventory **Pressure identifies its reclaimable concentration.** At 80% of `logTokensMax`, a Markdown `> [!WARNING]` block follows the JSON with `> YOU MUST KILL superseded, stale, or irrelevant log items and ranges.` New output withholding replaces that mandate under {§context-output-warning}. The JSON may include `logTokensLargest`: at most five retained log items, each `{path, logTokens}`, ranked by that charge descending and then path. Native-only, suppressed, and metadata-only items remain eligible: whole-item KILL can reclaim their actual contribution. Include the largest prefix that fits; drop the optional list before the warning. Both participate in the final fixed-point total and complete request admission check.
3934
- - §tokenomics-content-hash-identity **Content identity, not per-tokenizer counts.** Static channel writes stamp `content_hash` (SHA-256) as stable content identity. `weight` is stored beside that content and is never keyed or recomputed by model.
3935
- - §tokenomics-provider-usage **Provider accounting is physical-request evidence, not curation state.** Every issued physical request has one durable pre-I/O `provider_requests` identity beneath the normalized {§inference-ledger} and settles once as response or error. Each record preserves conventional {§provider-usage} quantities and required {§provider-cost} evidence; an unreported quantity remains absent, including on response-less failures, and is never replaced by zero. `model_calls` own response/failure evidence, `turn_attempts` specialize emission admission, and `provider_requests` are the sole durable accounting representation. Emissions, BARE calls, rejected responses, retries, failovers, and errors therefore remain cardinal and ordered. Turn, loop, worker, workspace, digest, and protocol accounting are derived from those records through the shared {§provider-accounting} projection; only emission calls contribute the latest-packet context gauge. The baseline stores no floating-point money, denormalized totals, or rollup triggers. A documented direct charge becomes `charged`; otherwise the provider may compute an exact-decimal USD `estimated` amount from complete usage and the exact model's Models.dev rates; insufficient evidence becomes `unknown`. Derived `costUsd` sums every USD-expressible request and is `null` only when no request is expressible; a response-less failure or an uncataloged model is skipped, never allowed to erase the expressible evidence. Each derived aggregate usage field independently sums its reported quantity, so heterogeneous detail coverage remains partial rather than becoming fictitiously complete. This is operational request accounting, not invoice reconciliation. Output and reasoning are quantities the model cannot KILL, so they never alter the model-facing Budget ledger.
3936
- - §tokenomics-negative-pressure **Negative curation pressure is honest but never submitted.** The provisional readout may report `logTokensTotal` above `logTokensMax`. Crossing the maximum withholds new returned output under {§context-output-admission}; no over-ceiling packet reaches `provider.generate`. Output admission creates neither a strike nor another turn.
3937
-
3938
- ### §membership Workspace identity, membership, disk co-location
3939
-
3940
- The project-file path has two explicit reconciliation gates. Internal entries do
3941
- not participate in this disk loop.
3942
-
3943
- ```mermaid
3944
- flowchart LR
3945
- git["Git tracked"] --> resolve["Resolve workspace membership"]
3946
- include["include"] --> resolve
3947
- exclude["exclude"] -->|subtract| resolve
3948
- resolve --> materialize["Pre-turn materialize<br/>disk → file snapshot"]
3949
- materialize --> read["READ snapshot"]
3950
- materialize --> edit["EDIT against snapshot"]
3951
- edit --> proposal["Proposal"]
3952
- proposal -->|"client accepts or loop auto"| cas["synced_sig compare-and-swap"]
3953
- cas -->|"file snapshot → disk"| project["Project file"]
3954
- project --> materialize
3955
- ```
3956
-
3957
- | Concern | Owner and representation |
3958
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
3959
- | Workspace identity | `workspaces.project_root`; null is headless. There is no separate project entity. |
3960
- | File visibility | Workspace-tier resolved membership: `(tracked files ∪ include) − exclude` ({§membership-baseline}). Every worker sees the same result. |
3961
- | File reads | READ returns the materialized file snapshot stored in the entry body channel; it does not read disk directly. |
3962
- | File writes | EDIT proposes against that snapshot. Only accepted resolution with the captured `synced_sig` writes the project file. |
3963
- | Internal entries | Workspace or worker entries are canonical store state. Writing one never implies a project-file write. |
3964
- | Authority | Service flags set the membership ceiling; the `members` family's definitions include and exclude within it; client or loop auto resolves proposals. `origin` is attribution. |
3965
-
3966
- §web-search-retrieval **Web discovery is an ordinary MCP concern; retrieval is a first-class composition.** PLURNK owns no search runtime: a search-capable MCP server (e.g. Brave Search) participates through the ordinary MCP contract — admission, read-effect classification, tool documentation, and packet projection are identical to every other MCP tool ({§mcp-tool-presentation}). An executor that wants to materialize discovered pages uses the generic `content: null` `entry()` request ({§exec-entry-sink}): the guarded `WebFetcher` sink fetches candidates in parallel, off the write-serialization chain, and materializes successful bodies as ordinary HTTP entries. Every candidate whose `entry()` call rejects, regardless of failure reason, is mechanically omitted from the model-facing result directory; survivors retain upstream order. Without an entry sink the executor cannot test materialization and omits the verdict.
3967
-
3968
- Search prefetch and direct HTTP READ materialize the same resource contract:
3969
- protocol + canonical authority (including a non-default port) + path + serialized
3970
- query is the absolute identity ({§scheme-address-network}); the sanitized
3971
- readable projection is the fragmentless default, while faithful DOM, origin
3972
- media type, and projection identity remain explicit auxiliary evidence. A
3973
- normal
3974
- ```` ```READ (https://host/path?query) ```` therefore publishes only the sanitized body
3975
- under that exact URL—never raw HTML, response headers, or a channel-selection
3976
- lesson. FIND consumes the addressed stored channel representation
3977
- and never re-fetch a match.
3978
-
3979
- §web-retrieval-live Coverage protects the composition at distinct seams: HTTP unit tests pin fragmentless-body publication and explicit auxiliary selection; integration tests pin materialize→FIND and persistence/publication separation. A live positive-control demo requires a materialized HTTPS body and a substantive answer from a real sanitized page; live discovery demos remain diagnostic and may expose model judgment failures without weakening these assertions.
3980
-
3981
- **Git is the substrate and the repository is the boundary:**
3982
-
3983
- - §membership-baseline **The baseline contract — chiseled (#400).** To a workspace a
3984
- project file is exactly one of three things: **invisible**, **added**, or **tracked
3985
- by git**. There is no fourth category. Membership — what the model can READ and
3986
- FIND, what is materialized into the store, what a packet can ship to a provider — is
3987
- the allowlist `(tracked ∪ include) − exclude` and nothing else. No file is a member because
3988
- it exists on disk, because git does not ignore it, or because a model would find it
3989
- convenient: ambient admission of untracked files is prohibited, so a workspace rooted
3990
- in a home directory or a monorepo exposes exactly what was committed or added (the
3991
- non-member rule, {§fs-write-nonmember}: no read, no leak, no overwrite). Every
3992
- exception is a named clause in the register ({§membership-model-universe}), admits
3993
- files by an exact creation record with recorded provenance, and never by `git add`.
3994
- Changing this clause, the register, or the composition is an operator ruling recorded
3995
- on the issue that lands it — never an implementation convenience, never a side effect
3996
- of making a file visible to solve the problem at hand. The 2026-07-12 – 2026-08-27
3997
- "untracked-but-not-ignored" ambient admission is retired.
3998
- - §membership-model-universe **The exception register — files in the model's universe.**
3999
- Admitted by exact creation records (`source: "create"`, origin `constraint`), never
4000
- staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
4001
- ({§membership-create-parents}). Admitted by a published standard as projected
4002
- instruction documents — never as members: (3) the project's `AGENTS.md` and nested
4003
- `AGENTS.md` files ({§turn0-agents-stunt}, #346), read from disk regardless of git status
4004
- and materialized as `worker:///_plurnk/agents.md` and
4005
- `worker:///_plurnk/instructions/<subtree>/AGENTS.md`; the file itself is a member
4006
- only when tracked or added, and the standard never overrides the operator's
4007
- exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
4008
- is not projected. (4) A definition the model proposes through the `members`
4009
- family ({§members-functionality}), admitted only under the operator's ceiling
4010
- `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (shipped `none`), projected with source
4011
- `model`, and never admitted past the repository's ignore rules or an exclusion.
4012
- Nothing else.
4013
- - §membership-git-membership The workspace owns the Git repository containing
4014
- `project_root`. Its tracked files (`git ls-files` semantics) are members with
4015
- no explicit overlay; when the root is a package inside a monorepo, the
4016
- repository's other packages are members at root-relative paths. An unrelated
4017
- or nested independent repository is not discovered or managed by this
4018
- workspace. When Git is absent there is no filesystem walk; member definitions are
4019
- then the sole source.
4020
- - §git-native-default **Core Git reads use native Git.** Membership and status
4021
- execute the installed Git binary. An absent or failed binary yields no
4022
- automatic Git membership or status; core has no alternate implementation or
4023
- fallback.
4024
- - §membership-git-hermetic Native Git runs with ambient `GIT_*` and
4025
- global/system config scrubbed, and with the repository's own program-running
4026
- keys pinned off at the highest precedence (`core.fsmonitor=false`,
4027
- `core.hooksPath=/dev/null`), so repository identity follows `project_root`,
4028
- never the daemon's launch environment, and inspecting a supplied repository
4029
- never runs a program its `.git/config` names. Other repository-local
4030
- configuration is still read (#568). The one program no key can pin off is a
4031
- `filter.<name>.clean` / `filter.<name>.process` driver, which `git status`
4032
- index refresh may run: automatic inspection asks the repository's own config
4033
- first (`git config --get-regexp`, no program runs) and, when any such key is
4034
- declared, refuses the repository as a warning-and-skip — Git status and
4035
- automatic Git membership answer exactly as for a non-repository, and one
4036
- `engine:membership` / `git_inspection_refused` notice names the key, once per
4037
- workspace until it changes or clears. User- and model-requested Git commands
4038
- stay on their explicit execution path.
4039
- - §membership-edit-membership-gate **Membership-gated edits.** EDIT is bounded by membership exactly as READ is. An existing **member**'s baseline is its entry snapshot — the body channel the model READ, not a fresh disk read — so the diff is naive against the view the model saw, never empty (the write-side CAS, {§membership-edit-write-cas}, prevents the silent overwrite of out-of-band drift). An existing **non-member** is refused (403) *before* any read or write: the model never reads a file it can't see (no leak into the proposal) and never overwrites one (no wiping a gitignored `.env` it never added). A **new path** crosses the creation matrix in {§fs-write-surface}; proposal acceptance cannot bypass its scope, exclusion, or incorporation rules. Shell execution reaches beyond file membership; the file scheme does not.
4040
- - §membership-create-parents **Parent-complete creation.** An accepted File creation—whether authored as EDIT or as a COPY/MOVE destination—recursively creates missing parent directories before writing and registering the new member.
4041
-
4042
- **The overlay — `include | exclude`.** `workspace_constraints` holds the `members` family's projected definitions and the engine's creation records ({§members-projection}). Resolved membership is `(project repository files ∪ include) − exclude`.
4043
-
4044
- - §membership-auto-add **Auto-add** — the project repository's ambient membership is its tracked `ls-files`, with `git` origin; an untracked file is never an ambient member ({§membership-baseline}). An accepted creation is incorporated by an exact creation record, never by `git add` ({§membership-model-universe}); a record that cannot be written fails the creation transaction, never an orphan ({§file-create-no-orphans}).
4045
- - §membership-overlay-include **`include`** — admit a file Git misses through a targeted pattern scan (files only), with `constraint` origin. `source: "members"` is a projected human definition, `source: "model"` a projected model definition, `source: "create"` the exact durable record of an accepted creation. Only `members` inclusions override active Git ignore. In a Git-absent root, inclusions are the sole file-membership source.
4046
- - §membership-overlay-exclude **`exclude`** — a `!glob` definition removes a tracked or included file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-oversized or sensitive tracked file; exclusions mask creation records without deleting their provenance ({§fs-create-masked}).
4047
- - §membership-reconcile-sets **Reconciliation is two set statements.** Once the desired set is composed — `(git ls-files ∪ include) − exclude`, glob evaluation and Git shell-outs being the process's own — it lands as one `INSERT … SELECT FROM json_each` with the same idempotent provenance update as a single registration, and every overlay-owned member outside it leaves in one `DELETE … WHERE pathname NOT IN (json_each) RETURNING`, its prior body riding out of the statement. A path that also left disk truth (no longer a candidate at all) becomes a divergence from that prior content; an exclusion is silent. No per-path statement, no set difference outside the database.
4048
- - §membership-glob-in-sql **A constraint is evaluated where it lives.** `glob_match(pathname, glob)` is `node:path.matchesGlob` registered into SQLite (`src/core/glob_match.ts`, deterministic), the one matcher every overlay decision uses, so a lookup that asks the constraints table a question — which exclusion covers this key, whether a members definition includes it, which inclusion owns each untracked path — is one statement over `workspace_constraints`, never a listing filtered in the process. Transient inputs the process already holds (a `git ls-files` listing against the exclude globs) stay filtered in the process; the function exists so the database's own rows can be asked, not so process data makes a round trip.
4049
-
4050
- **File ops act on the entry, not the disk; the two reconcile only at gates.** A `file:///` member is a row whose body channel holds its *materialized model-readable snapshot*. READ returns that channel; EDIT diffs against editable text snapshots — neither reaches the filesystem directly. Entry and disk reconcile at exactly two gates: the **pre-turn materialize** (disk → entry, below) and the **accept-time write-back** (entry → disk, {§proposal}). Between the gates the entry is the truth the model curates against, and `synced_sig` — the member's last-synced disk stat (`mtime:size`) — is the version token both gates compare on.
4051
-
4052
- §membership-source-projection Binary acquisition is transient and bounded by
4053
- {§mimetype-binary-input}; durable entry channels remain Unicode text. Core-private
4054
- `sourceProjection` attributes preserve the source mimetype, opaque projection
4055
- identity, and terminal disposition without exposing raw bytes or a base64 lane.
4056
-
4057
- | Disk source | Durable body | Operation effect |
4058
- | ---------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
4059
- | Text | Verbatim Unicode under the detected textual mimetype | READ and EDIT use the snapshot. |
4060
- | Binary with readable projection | Derived Unicode as `text/markdown` | READ uses the projection; source-aware EDIT remains 415. |
4061
- | Binary without projection/over cap | Empty marker under the source binary mimetype | READ and EDIT return 415; private metadata distinguishes unavailable from limit. |
4062
-
4063
- §membership-materialization-limit **A pathological member degrades, never the
4064
- workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
4065
- byte ceiling over one disk source before Core reads it into the canonical file
4066
- snapshot. Its valid range is `1..104857600`, bounded by the channel storage
4067
- contract, and it ships at that 100 MiB maximum. An oversized path remains a real member
4068
- with an empty body channel carrying a durable 413 producer result; no diagnostic
4069
- sentinel impersonates file content. READ therefore names the path, observed bytes,
4070
- ceiling, and recovery through the ordinary result contract, while EDIT returns the
4071
- same 413 instead of diffing against a fictitious empty baseline. Core records the
4072
- materialization disposition and ceiling privately, so an unchanged disk member is
4073
- reconsidered when the operator changes the policy and otherwise remains a stat-only
4074
- no-op. The file write gate independently stats the source against the same ceiling,
4075
- so safety does not depend on a background warm winning a client-operation race.
4076
-
4077
- §derivation-dedup-parallel **The index dedups then parallelizes.** The derivation identity hashes the exact READ channel representation, mimetype, reader behavior, and applicable search exclusion. A channel or log projection attaches the immutable artifact only after it is complete; identical projections therefore share one FTS row and one symbol graph without copying. Distinct artifacts run with bounded producer concurrency (`PLURNK_SERVICE_DERIVE_CONCURRENCY`). Pending artifacts sort by readable content length before entering that pool, so small resources start first while every outlier still derives fully. Unset uses a host-relative square-root fan-out; a positive integer is an exact operator budget and `-1` claims every core. Graph persistence writes at most `PLURNK_SERVICE_DERIVE_STORE_BATCH` definitions or references per SQLite statement. Every launched worker settles before the maintenance pass reports success or failure, so one failed artifact cannot orphan sibling derivations. Every representation completed by a successful pass attaches a terminal classified artifact, identically at concurrency 1 and N. A changed pass emits one immediate `preparing` state, intermediate `indexing` heartbeats at `PLURNK_SERVICE_DERIVE_PROGRESS_HEARTBEAT_MS`, and one immediate `complete` or `failed` state. A no-op pass emits no lifecycle, an indexing heartbeat never claims 100%, and the model-facing Notice buffer retains only the current derivation state while live clients observe each heartbeat.
4078
-
4079
- The artifact also retains a positive `{§mimetype-parse-issues}` count and the
4080
- full normalized `{§mimetype-summary}` when the exact parsed channel reported
4081
- either. Both remain advisory alongside a normally completed search
4082
- disposition; zero, empty, and unavailable evidence persist as absence. Catalog
4083
- projection attaches either only to that channel, never to a sibling whose
4084
- content the artifact does not describe.
4218
+ | 12 | user | `recap` | Optional authored operational recap. |
4085
4219
 
4086
- Every completed artifact records one terminal disposition: `indexed`, `excluded`
4087
- (the configured search-exclusion table), `unsearchable` (empty or binary), or
4088
- `failed` (a typed {§mimetype-error-policy} invalid-source failure, or the
4089
- handler's own defect on that one member — {§derivation-member-failure}).
4220
+ The order favors prefix-cache locality where semantics permit: the definition
4221
+ and privileged policy lead operator notes, while the append-mostly
4222
+ log leads the volatile user-status clump. It does **not** claim that every system byte is
4223
+ immutable or that the complete packet is globally monotone in volatility:
4224
+ operator notes and policies can change. Trust is a separate
4225
+ admission rule. The system slot contains trusted control-plane material;
4226
+ attacker-reachable content stays in the user slot.
4090
4227
 
4091
- §derivation-member-failure **One member's derivation failure never ends the
4092
- pass or the model's turn.** A handler defect on one member — a
4093
- `MimetypeDerivationError` under {§mimetype-derivation-evidence}, the exception
4094
- being a cancellation — is that member's terminal `failed` disposition, whose
4095
- `reason` is the handler's invocation context followed by the exact original
4096
- cause (`Mimetype derivation failed for "/x.js" ("text/javascript").
4097
- RuntimeError: …`). The pass continues, attaches every other member, and
4098
- completes; its terminal `search_progress` notice is `complete` at `level: warn`,
4099
- naming the first failed member and carrying the count. A failed member is
4100
- terminal for that exact content, handler revision, and configuration identity
4101
- ({§derivation-dedup-parallel}); a change to any of those derives it again.
4102
- Everything that is not a handler's derivation of one member stays fatal to the
4103
- pass exactly as before: a grammar that is not installed, an index-persistence
4104
- or contract failure, and cancellation, each leaving the artifact `building` for
4105
- retry.
4106
- Cancellation and implementation, loading, database and index-persistence failures
4107
- remain `building`, unattached, and retryable; Core never guesses that an arbitrary
4108
- projection exception is bad content. The digest reports exceptional dispositions
4109
- with their reasons. Successful optional projection degradations continue indexing
4110
- and surface their framework Notice once per identical observation in a maintenance
4111
- pass.
4228
+ ### §packet-plugin-transform Trusted whole-list extension seam
4112
4229
 
4113
- §membership-change-gated-sync **Sync is idempotent and change-gated.** Per turn, membership materializes every member's model-readable snapshot into its entry. Text with an unchanged disk signature and materialization policy is a stat-only no-op. The version token is either the observed `mtime:size` or the explicit `absent` state; an observed deletion removes the stale readable channels, and a later reappearance is therefore a new divergence rather than a first-sight materialization. Binary sources additionally compare the cached per-mimetype projection identity; unchanged bytes are never reacquired, while changed reader behavior rematerializes without fabricating a filesystem-divergence event. Coverage is exhaustive across the project repository while work is proportional to source or projection change. After a pass every member carries the current representation defined by {§membership-source-projection} and {§membership-materialization-limit}.
4230
+ `SchemeRegistry.transformSections` pipes the complete default list through
4231
+ every registered scheme implementing `transformSections(sections) -> sections`,
4232
+ in registration order, before rendering and measurement. The schemes-owned
4233
+ `PacketSectionDraft` contains only `name`, `slot`, `header`, and `content`.
4234
+ Each initial or returned list passes the schemes-owned validator, including
4235
+ unique-name enforcement, before the next transformer or renderer. Each
4236
+ transformer may inspect the section content and add, remove, or reorder
4237
+ sections. It receives no separate engine, database, actor, or request context.
4114
4238
 
4115
- §membership-emi-divergence-signal **EMI divergence evidence.** The detector that gates the work *is* the one that records this — one mechanism, not a second full read. When change detection finds a member moved out-of-band, the runtime actor records an `EDIT`-shaped row naming the file with `source="file"`; it does not broadcast that workspace change into unrelated workers' logs ({§env-delta-filesystem-narration}). The model's own edits are write-through (the entry equals disk after a File write), so the scan never mis-attributes them as external divergence. The current file remains ordinarily addressable. A stale anchored edit rejects under {§line-anchors}; a disk race after proposal rejects under {§membership-edit-write-cas}.
4239
+ This is strictly a trusted in-process seam, admitted through the common plugin
4240
+ trust gate; an external client action cannot invoke it. Whole-list transformation is
4241
+ the fork-avoidance valve for alternate packet shapes ({§ecosystem}), while
4242
+ overflow recovery and packet projection remain closed engine concerns.
4116
4243
 
4117
- §membership-edit-write-cas **The write-back is a compare-and-swap — never a clobber, never a clever merge.** EDIT is *naive against the editable text snapshot*: it diffs the model's change onto the entry's body channel — the exact Unicode the model READ — and the proposal carries the `synced_sig` that snapshot was taken at. Binary sources are refused before this path ({§membership-source-projection}). At accept, `applyResolution` re-stats disk and lands the proposed content only if that signature still matches. If disk moved out-of-band in the propose→accept window — a sibling worker, the user's editor, a build step — the write is **refused** with the same neutral `edit-collision` as {§edit-collision}, and **nothing is written**. The engine neither blind-writes over the ambient change (a *clobber*) nor silently re-diffs the model's edit against a state it never saw (getting *clever*) — both would bury a stale-view contract violation under a fallback. The collision surfaces instead: a ≥400 apply downgrades to a reject ({§proposal}), so the model sees that EDIT **did not occur** (400; the `edit_collision` outcome is forensics-only). Reconciliation aligns the current file projection and records the `source=file` evidence in the runtime log ({§membership-emi-divergence-signal}); the model re-reads and re-proposes against the fresh snapshot.
4244
+ ### §tokenomics Tokenomics: four facts, one curation ruler
4118
4245
 
4119
- The version travels *with the proposal*, never re-read from the entry at accept: a sibling worker in the same workspace may reconcile while this proposal sits paused, advancing the entry's `synced_sig` to the drifted disk — comparing against the *current* entry sig would wave that clobber through, so the comparison is always against the sig the proposal was computed at. A proposal that assumed an **absent** path (a create) conflicts only if a file has since appeared; a member with **no recorded snapshot** (an un-materialized entry, null `synced_sig`) has no baseline to guard and writes through — the two are told apart by the proposal's `existed` flag, not by a null sig alone. On a clean landing the entry refreshes to the written content and `synced_sig` is **restamped** to it, so the next reconcile recognizes the model's own write (not an external divergence) and a second same-turn edit bases on the landed bytes, not a stale sig. This is the write-side twin of the read-side change-gate ({§membership-change-gated-sync}): one `synced_sig`, gating both the re-read and the write.
4246
+ Token accounting distinguishes the artifact being measured, the unit, and the
4247
+ time of measurement.
4120
4248
 
4121
- 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.
4249
+ | Fact | Owner and unit | Time | Contract |
4250
+ |:-----|:---------------|:-----|:---------|
4251
+ | 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. |
4252
+ | §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. |
4253
+ | 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. |
4254
+ | 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. |
4122
4255
 
4123
- §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`.
4256
+ - §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.
4257
+ - §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.
4258
+ - §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}).
4259
+ - §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.
4260
+ - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
4261
+ `PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
4262
+ `(0, 100)`. It allocates that share of the cold-start curation allowance
4263
+ (the capacity conversion with factor 1) to the aggregate automatic prompt-body
4264
+ projection. Rolling calibration does not resize existing prompt bodies; only
4265
+ the overall curation ceiling adapts. This does not bound stored prompt
4266
+ size, provider capacity, an explicit READ/FIND result, or the complete packet.
4267
+ Basing the share on configured capacity rather than current free weight or
4268
+ sampled conversion keeps one prompt's projection byte-stable as the log and
4269
+ calibration samples evolve.
4270
+ - §tokenomics-window-unpollable-deliberate **Unknown provider capacity stays unknown.** When the provider cannot derive `inputCapacity`, Core omits denominator-dependent curation telemetry and uses the ordinary bounded prompt projection. The provider still sends requests whose measurement or limits are estimates or unavailable: ambiguity defers to the upstream capacity oracle rather than becoming a local rejection.
4124
4271
 
4125
- **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.
4272
+ §tokenomics-client-gauge **Clients receive curation and physical occupancy as separate pairs.** `loop/terminated.usage` carries latest packet-bearing model-turn `curationWeight`/`curationBudget` and latest-emission-call `contextTokens`/`contextCapacity`; each unknown fact is `null`. The curation pair is the packet's measured weight and captured allowance, exactly as displayed to the model, not a recalculation using newer usage evidence. Packetless chronology cannot erase an assembled-request gauge. Both physical facts bind to that same call: a preflight rejection may report capacity while its absent physical request leaves `contextTokens=null`, never borrowed from an earlier call. Clients never divide provider-reported physical tokens by Core curation weight. `providers.list` exposes each instantiated alias's `inputCapacity`. A model switch replaces the latest-turn facts together; aggregate provider accounting remains cardinal monetary evidence, not a gauge input.
4126
4273
 
4127
- **Schema.** The version-1 baseline stores the normalized {§inference-ledger},
4128
- its model-response evidence, emission admission, and cardinal
4129
- physical requests. Its constraints distinguish pending calls, response
4130
- evidence, and response-less errors while monetary classification remains
4131
- explicit.
4274
+ - **Derivation is exhaustive and demand-led.** Explicit searchable-resource changes may start one coalesced warm. Passive creation and attachment do not. The first model turn starts or joins that warm; later turns derive intervening changes before dispatch. No model operation observes partial graph or full-text coverage. Progress notices make the wait visible. {§derivation-exhaustive}
4275
+ - §membership-binary-sniff **Binary truth beats a text label.** Filesystem source acquisition, including tracked members and installed skill resources, inspects up to the first 8192 bytes when extension detection does not identify a binary type. NUL marks `application/octet-stream`; existing binary types retain their declared type. Member projections follow {§membership-source-projection}; installed skill projections follow {§skills-resources}.
4276
+ - §tokenomics-agnostic-ruler **One model-agnostic curation ruler.** The daemon runs workers on different models in one workspace concurrently, while catalog and log accounting are workspace-wide. `contentWeight = ceil(chars/2)` therefore gives one content one stable number without per-model workspace state or recount passes. It controls curation only; every provider call independently measures the complete request as well as it can.
4277
+ - §tokenomics-neutral-telemetry **Curation telemetry is state, not response allowance.** The model-facing `Context Curation` section is one JSON object carrying `logTokensTotal` and `logTokensMax` (and `tokensResponseMax` when an output floor is disclosed), so the block opens as a JSON payload like ```` ```TASK ````. It never presents their difference as free response tokens. The protocol definition directly requires KILL of irrelevant log items and ranges to keep the next packet within the maximum. Per-entry weights remain on log rows where they describe visible cost and curation savings. Generic packet composition and physical-token speculation are absent.
4278
+ - §tokenomics-pressure-inventory **Pressure identifies its reclaimable concentration.** At 80% of `logTokensMax`, a Markdown `> [!WARNING]` block follows the JSON with `> YOU MUST KILL superseded, stale, or irrelevant log items and ranges.` New output withholding replaces that mandate under {§context-output-warning}. The JSON may include `logTokensLargest`: at most five retained log items, each `{path, logTokens}`, ranked by that charge descending and then path. Native-only, suppressed, and metadata-only items remain eligible: whole-item KILL can reclaim their actual contribution. Include the largest prefix that fits; drop the optional list before the warning. Both participate in the final fixed-point total and complete request admission check.
4279
+ - §tokenomics-content-hash-identity **Content identity, not per-tokenizer counts.** Static channel writes stamp `content_hash` (SHA-256) as stable content identity. `weight` is stored beside that content and is never keyed or recomputed by model.
4280
+ - §tokenomics-provider-usage **Provider accounting is physical-request evidence, not curation state.** Every issued physical request has one durable pre-I/O `provider_requests` identity beneath the normalized {§inference-ledger} and settles once as response or error. Each record preserves conventional {§provider-usage} quantities and required {§provider-cost} evidence; an unreported quantity remains absent, including on response-less failures, and is never replaced by zero. `model_calls` own response/failure evidence, `turn_attempts` specialize emission admission, and `provider_requests` are the sole durable accounting representation. Emissions, BARE calls, rejected responses, retries, failovers, and errors therefore remain cardinal and ordered. Turn, loop, worker, workspace, digest, and protocol accounting are derived from those records through the shared {§provider-accounting} projection; only emission calls contribute the latest-packet context gauge. The baseline stores no floating-point money, denormalized totals, or rollup triggers. A documented direct charge becomes `charged`; otherwise the provider may compute an exact-decimal USD `estimated` amount from complete usage and the exact model's Models.dev rates; insufficient evidence becomes `unknown`. Derived `costUsd` sums every USD-expressible request and is `null` only when no request is expressible; a response-less failure or an uncataloged model is skipped, never allowed to erase the expressible evidence. Each derived aggregate usage field independently sums its reported quantity, so heterogeneous detail coverage remains partial rather than becoming fictitiously complete. This is operational request accounting, not invoice reconciliation. Output and reasoning are quantities the model cannot KILL, so they never alter the model-facing Budget ledger.
4281
+ - §tokenomics-negative-pressure **Negative curation pressure is honest but never submitted.** The provisional readout may report `logTokensTotal` above `logTokensMax`. Crossing the maximum withholds new returned output under {§context-output-admission}; no over-ceiling packet reaches `provider.generate`. Output admission creates neither a strike nor another turn.
4132
4282
 
4133
4283
  ### §context-output-admission Budget enforcement: returned-output admission
4134
4284
 
@@ -4160,7 +4310,7 @@ flowchart TD
4160
4310
  | Admitted | Normal projection, thereafter controlled only by deliberate curation and existing delivery rules. |
4161
4311
  | Withheld | Body/native parts stay absent in the original receipt; freeing space does not silently restore them. |
4162
4312
 
4163
- §context-output-receipt A withheld receipt adds `overflow: "N output lines not shown; logTokensTotal exceeds logTokensMax"`, counting the output lines its normal current projection would show, not unrelated source lines. Withheld native media is named as `"N output lines and native content not shown; ..."` or, without a text body, `"native content not shown; ..."`. Its original result status and Problem are unchanged. FORK inherits admission state with the copied log. Explicit KILL still controls readable/active content independently.
4313
+ §context-output-receipt A withheld receipt adds `overflow: "N output lines not shown; the log exceeded logTokensMax when this row was withheld"`, counting the output lines its normal current projection would show, not unrelated source lines. Withheld native media is named as `"N output lines and native content not shown; ..."` or, without a text body, `"native content not shown; ..."`. Its original result status and Problem are unchanged. FORK inherits admission state with the copied log. Explicit KILL still controls readable/active content independently.
4164
4314
 
4165
4315
  §context-output-warning **New omission escalates the one curation warning.** The admitting request renders `> [!WARNING]` followed by `> YOU MUST ONLY KILL superseded, stale, or irrelevant log content in bulk.` beneath its JSON readout, replacing the ordinary pressure mandate even when withholding brings usage below 80%. Historical omission alone does not retrigger it. The warning participates in exact packet measurement; optional largest-items entries yield space first.
4166
4316
 
@@ -4244,126 +4394,6 @@ uses the voice door ({§actor-boundary-two-doors}); direct-child terminal
4244
4394
  disposition alone carries the structured-concurrency wake owned by
4245
4395
  {§worker-scheme-collect}. Stream progress remains owned by {§exec-stream}.
4246
4396
 
4247
- ### §edit-result-render Mutation log rows render truthful effects
4248
-
4249
- A mutation row keeps request and outcome separate: `tx` is the admitted
4250
- statement; `rx` is its resolved result. Only state that actually landed may
4251
- appear there as an effect.
4252
-
4253
- ```mermaid
4254
- flowchart LR
4255
- authored["Authored EDIT / scoped entry KILL / COPY / MOVE"] --> snapshot["Resolve addressed channel(s)<br/>against pre-mutation snapshots"]
4256
- snapshot --> apply["Apply synchronously<br/>or settle proposal"]
4257
- apply --> landed{"Did state land?"}
4258
- landed -->|no| rx["Persist structured rx"]
4259
- landed -->|yes| kind{"Operation?"}
4260
- kind -->|EDIT / scoped entry KILL| receipt["Project one EDIT receipt<br/>for this authored row"]
4261
- kind -->|COPY / MOVE| effects["Compose ordered effects<br/>after application"]
4262
- receipt --> rx
4263
- effects --> rx
4264
- rx --> meta["Packet projection<br/>status · operands · optional effect metadata"]
4265
- rx --> body["Canonical log body<br/>bounded receipt context or empty"]
4266
- body --> recall["READ log:///…<br/>selects untrimmed content"]
4267
- ```
4268
-
4269
- §edit-receipt-removed-text **A pure deletion's receipt quotes what it removed.** An applied effect that inserted nothing and removed at least one line carries `removedText` — the removed text, first 40 lines — projected on the wire as `removed`; an effect that inserted anything carries no such field, its resulting context shows the change.
4270
-
4271
- §edit-receipt-anchored-context **An applied EDIT's resulting context carries anchors.** The bounded resulting context each effect renders (`PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES` around and inside the landed region) is rendered exactly as a READ renders — `@xxxxx L:text`, hashed with the resource's READ identity ({§line-anchors}) — so a later operation can cite the landed lines by anchor without a READ. A scheme that supplies no identity keeps the line-numbered form.
4272
-
4273
- §edit-result-receipt-projection **EDIT and scoped entry KILL project the
4274
- scheme-owned batch receipt.** The scheme framework owns the exact aggregate
4275
- shape ({§scheme-edit-batch-receipt}). Each operation supplies one splice; Core
4276
- validates and projects its one applied effect or superseded disposition.
4277
- That row carries any reviewer replacement effect. Core stores only the
4278
- per-operation projection on `rx`; the aggregate remains inside dispatch.
4279
-
4280
- | Durable receipt fact | Packet projection | Meaning |
4281
- | -------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
4282
- | Full `revision` | not projected | SHA-256 identity of the complete landed channel body, kept in the durable receipt for forensics; no operation takes a revision, so the packet carries none (operator, 2026-09-13). Formerly `rev`, abbreviated; the display knob is gone. Never a lookup or compare-and-swap token. |
4283
- | `unit`, `before`, `after` | `extent` | Whole-line batches use line counts. A batch containing any exact four-coordinate edit uses Unicode code-point counts. |
4284
- | `parseIssues.before`, `parseIssues.after` | `parseIssues` as `before→after` | Parser-recovery counts for complete source and landed revisions; omitted when both are clean or either is unavailable. |
4285
- | `effect.requested`, `source`, `result` | `range` | The admitted marker and its normalized mapping from the common source snapshot into the landed body. |
4286
- | `effect.removed`, `inserted` | `change` | Removed and inserted counts in the receipt unit. |
4287
- | `effect.removedText` | `removed` | {§edit-receipt-removed-text}: a pure deletion's removed text, first 40 lines; absent when the edit inserted anything. |
4288
- | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. |
4289
- | `disposition`, `requested` | `disposition`, `requested` | A reviewer-replaced batch preserves the authored marker while stating that its attributed effect was superseded. |
4290
- | `replacement` | `replacement`, `change`, canonical proposal-owner body | The one whole-resource effect actually applied by the reviewer replacement; never duplicated across authored rows. |
4291
-
4292
- §edit-result-receipt-truth **Receipts describe committed state.** Each EDIT
4293
- carries its own landed revision, extent, and optional `parseIssues` transition
4294
- for its complete source and landed revisions. When the proposal lands
4295
- unchanged, the row also carries its requested
4296
- marker, source/result mapping, counts, and context. For configured count `C`,
4297
- the context contains up to `C` surrounding lines and the first and last `C`
4298
- landed lines at the result boundaries. Overlapping windows coalesce; coordinate
4299
- jumps expose an omitted middle. A deletion instead shows up to `C` lines on
4300
- each side of its join.
4301
-
4302
- §edit-result-reviewer-replacement **A resolver replacement is one effect, not a
4303
- guess at authorship.** An arbitrary accepted body replaces that operation's
4304
- proposed body. It cannot be attributed to the authored span: the row retains
4305
- its requested marker with disposition `superseded` and carries the one
4306
- whole-resource replacement effect with bounded landed context. Subsequent
4307
- EDITs address that landed state independently under {§edit-execution}.
4308
-
4309
- | Acceptance | Per-authored-row receipt | Applied effect |
4310
- | ------------------------------ | -------------------------------------- | ------------------------------------------------- |
4311
- | Proposed body unchanged | Requested marker and its exact mapping | One per authored EDIT |
4312
- | Resolver body replaced proposal | Requested marker plus `superseded` | One whole-resource replacement, carried once |
4313
-
4314
- Durable `tx` always remains the model's admitted statement. There is no JSON
4315
- row/item receipt mode. A deliberate READ observes its authored execution point
4316
- ({§op-execution-order}) and remains the universal request for arbitrary current
4317
- content.
4318
-
4319
- System-narrated environment EDITs are state-diff events rather than authored
4320
- mutation receipts. They carry the resulting span defined by
4321
- {§env-delta-filesystem-narration}.
4322
-
4323
- §edit-result-copy-move-effects **Core composes COPY/MOVE effects only after
4324
- application.** Operands remain owned by the durable statement and render
4325
- independently under {§copy-move-observation}; effects describe only state that
4326
- landed.
4327
-
4328
- | Durable effect field | Contract |
4329
- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
4330
- | `target` | Canonical model-facing address. The default channel is path-only; an explicitly selected non-default channel retains its fragment. |
4331
- | `action` | Exactly `create`, `update`, or `delete`. |
4332
- | `receipt` | Optional validated EDIT projection. Only textual `create` and `update` effects may carry one; a creation receipt has `before=0`. |
4333
-
4334
- | Outcome | Ordered `effects` |
4335
- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
4336
- | Landed COPY | Its destination effect. |
4337
- | Landed MOVE between different resource-channel selections | Any destination effect, then any source effect. |
4338
- | Landed regional MOVE within one resource channel, unchanged by its resolver | Insertion, then removal; both name the same target because they are distinct effects in one atomic batch. |
4339
- | Resolver body replaces a proposed COPY/MOVE mutation | One actual replacement effect. Cross-resource MOVE still appends an independently landed source effect. |
4340
- | Textual create/update caused by a scope on either operand | The effect carries the ordinary bounded EDIT receipt. |
4341
- | Textual create/update with no scoped operand; binary mutation; channel delete | Structural effect only; no invented text receipt. Scoped source removal is an `update` with a receipt. |
4342
- | `304`, rejection, or cancellation with no landed mutation | `effects` omitted. |
4343
- | Cross-selection MOVE source failure after destination success | The failure retains every destination effect that landed. |
4344
-
4345
- Core validates the complete ordered array before exposing it. Parser-recovery
4346
- inspection is advisory and occurs against complete resulting text after
4347
- successful application. A handler or parser failure emits a Notice, omits
4348
- `parseIssues`, and never changes the mutation outcome.
4349
-
4350
- ### §proposal-ownership Loop disposition and client YOLO
4351
-
4352
- Side-effecting operations propose ({§exec}) and pause dispatch at 202 for an
4353
- authority decision ({§engine-rails}, {§methods}). Automatic acceptance has two
4354
- distinct owners:
4355
-
4356
- | Mechanism | Authority path | Intended use |
4357
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
4358
- | §proposal-ownership-loop-auto **Loop disposition** | `runLoop({ policy: { proposals: "accept" } })` persists a loop-owned disposition; core resolves proposals in process without a client. | Headless automation, benchmarks, CI, fixtures, and unattended use. |
4359
- | **Client-side YOLO** (`--yolo` / `PLURNK_YOLO`) | A `proposals: "review"` loop emits the ordinary `loop/proposal`; the client returns an accepted proposal through its standard resolution path. | Interactive automatic review. |
4360
-
4361
- Core cannot distinguish client-side YOLO from a fast human acceptance and does
4362
- not need to. Loop auto keeps authority inside the loop; client-side YOLO acts
4363
- only after authority crosses the client boundary.
4364
-
4365
- §proposal-ownership-notification **The notification carries disposition, not policy inputs.** `loop/proposal` carries the core-owned `ProposalDisposition` ({§notifications}, {§proposal-disposition}). A connected client presents only `owner="client"`; it never reimplements policy from operation or attrs.
4366
-
4367
4397
  ---
4368
4398
 
4369
4399
  ## §packet Packet shape
@@ -4522,7 +4552,7 @@ remain exact. Automatic stream delivery uses that markerless selector too;
4522
4552
  its range or region describes the selected content and the complete stream
4523
4553
  remains addressable. This selection is not a second rendering-time cut.
4524
4554
 
4525
- READ and FIND own their range or pagination before packet rendering; the packet never applies a second hidden substring bound to their selected result. TASK inventory is likewise complete while visible: the model's task inventory is serialized once as compact JSON, never preview-clipped. Reasoning arrives through ordinary scoped READs ({§reasoning-history}). Prompt rows follow their separate adaptive projection contract. Structured mutation contexts already carry the receipt-owned bound in {§edit-result-receipt-truth}, so packet rendering does not preview them again. Rejected-emission artifacts, SEND/WORK/FORK bodies, EXEC commands, environment-delta EDIT spans, and extension-produced bodies use the ordinary fixed bound. When a visible projection differs from its canonical body, metadata carries `chunk` with the exact selected and complete extents defined by {§log-wire-format}; complete and fully suppressed bodies omit it. ```` ```READ (log:///<coordinate>/<OP>) ```` selects untrimmed lines in original coordinates under {§log-readable-projection}; the unsuffixed exact shorthand and authoritative suffix behavior are defined by {§log-coordinate-hierarchy}. ```` ```FIND (log:///...) ```` and search match that same readable view. System/policy sections are not log bodies. Notices are transient non-log observations; they share the ordinary line/character bounds but have no durable body or recovery URI.
4555
+ READ and FIND own their range or pagination before packet rendering; the packet never applies a second hidden substring bound to their selected result. TASK inventory is likewise complete while visible: the model's task inventory is serialized once as compact JSON, never preview-clipped. Reasoning arrives through ordinary scoped READs ({§reasoning-history}). Prompt rows follow their separate adaptive projection contract. Structured mutation contexts already carry the receipt-owned bound in {§edit-result-receipt-truth}, so packet rendering does not preview them again. Rejected-emission artifacts, SEND/WORK/FORK bodies, execution commands, environment-delta EDIT spans, and extension-produced bodies use the ordinary fixed bound. When a visible projection differs from its canonical body, metadata carries `chunk` with the exact selected and complete extents defined by {§log-wire-format}; complete and fully suppressed bodies omit it. ```` ```READ (log:///<coordinate>/<OP>) ```` selects untrimmed lines in original coordinates under {§log-readable-projection}; the unsuffixed exact shorthand and authoritative suffix behavior are defined by {§log-coordinate-hierarchy}. ```` ```FIND (log:///...) ```` and search match that same readable view. System/policy sections are not log bodies. Notices are transient non-log observations; they share the ordinary line/character bounds but have no durable body or recovery URI.
4526
4556
 
4527
4557
  §prompt-entry **Prompt as a first-class entry and log row.** Each prompt is stored once at `prompt://<worker>/<loop>/<id>` as an explicitly addressed text/markdown entry — written before any turn of its loop executes — then published to its first model turn as one actionless lowercase `prompt` log row; that row, not the entry, records publication. No synthetic EDIT or READ operation is invented. The row is born visible and obeys {§body-projection}. The **Active Prompts** section closes the user-slot status clump as a paths-only list (`* prompt://<worker>/<loop>/<id>`), so every frame remains directly READable after its log row's body is suppressed or its active projection is retired.
4528
4558
 
@@ -4692,7 +4722,7 @@ Tool-result/output schemas remain ordinary evidence, not teaching.
4692
4722
  flowchart LR
4693
4723
  Survey["Turn 0 FIND<br/>tools/*.md"] --> Families["family paths + summaries"]
4694
4724
  Families --> Read["READ selected family<br/>only when needed"]
4695
- Read --> Exec["EXEC invocation with an aside"]
4725
+ Read --> Exec["execution with an aside"]
4696
4726
  Read --> Schema["READ linked input schema<br/>when the preview is insufficient"]
4697
4727
  Schema --> Exec
4698
4728
  ```
@@ -4757,10 +4787,11 @@ or an unknown enabled alias fails the daemon at boot.
4757
4787
 
4758
4788
  §members-model-scope *The model's authority.* A model's `add` is admitted against
4759
4789
  `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` in the file-creation lattice `none < root <
4760
- namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). Shipped `none`
4790
+ namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). `none`
4761
4791
  refuses every model definition — inclusion or exclusion — as `403
4762
4792
  members/functionality/model-scope`, naming `git add` and the operator's `/members add` as
4763
- the paths that remain; `root` admits patterns inside the root; `namespace` admits `../` too.
4793
+ the paths that remain; `root` admits patterns inside the root; `namespace`, the shipped
4794
+ default, admits `../` too.
4764
4795
  `auto` loops self-approve proposals, so the ceiling — not the proposal — is the guard
4765
4796
  ({§membership-baseline}). The coordinator hands `admit` the caller (`action` | `operation`)
4766
4797
  so the family bounds the model without a second grammar.
@@ -4886,7 +4917,7 @@ only through the generated ```` ```skills ```` family
4886
4917
  The catalog and Turn0 describe Functionality under the current workspace
4887
4918
  capability policy and service ceiling. A direct denied attempt receives the
4888
4919
  same exact 403 from dispatch rather than a second documentation policy.
4889
- Optional non-EXEC operations remain a separate `## Enabled Optional Operations`
4920
+ Optional non-execution operations remain a separate `## Enabled Optional Operations`
4890
4921
  section because they are language extensions rather than executable tools.
4891
4922
 
4892
4923
  ### §schemes Scheme-reference discovery
@@ -4959,10 +4990,9 @@ contract.
4959
4990
 
4960
4991
  ## §matcher Matcher selection and text regions
4961
4992
 
4962
- Body matchers and text scopes are independent. Matcher prefixes choose a
4963
- dialect (`//` xpath, `/` regex, `$` jsonpath, `~` full-text, `&` graph, otherwise
4964
- glob); they select resources and report evidence. A text scope always addresses
4965
- the exact readable text, regardless of mimetype.
4993
+ Matchers select resources and report evidence; text scopes independently
4994
+ address the exact readable text, regardless of mimetype. Syntax belongs to
4995
+ {§matcher-option}, {§matcher-prefix-claims}, and {§scope-slot}.
4966
4996
 
4967
4997
  ### §matcher-dispatch Matcher dispatch
4968
4998
 
@@ -4988,7 +5018,7 @@ authored target still constrains every resource returned. Outgoing
4988
5018
  references belong to a definition through the handler-reported fully qualified
4989
5019
  container identity.
4990
5020
 
4991
- | Matcher body | Selected resources | Match evidence |
5021
+ | Matcher | Selected resources | Match evidence |
4992
5022
  | ------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
4993
5023
  | `&<symbol` | In-scope resources that reference `symbol` | Each matching reference's source span |
4994
5024
  | `&>symbol` | In-scope resources defining names referenced by each definition of `symbol` | Each referenced symbol's definition span |
@@ -5022,6 +5052,8 @@ Glob anchoring (`TODO*` starts-with, `*TODO*` contains, `*.log` ends-with,
5022
5052
 
5023
5053
  ### §matcher-result Matcher selection and evidence
5024
5054
 
5055
+ - §matcher-selection-signal **Matching carries navigation evidence** - a matcher is a boolean resource predicate. Internally, each selected resource carries `matches: MatchEvidence[]`, where `MatchEvidence` is `{channel?,locator?,region?}`; `channel` names the entry channel the finding was located in and is absent for channel-less resources such as log rows, so line coordinates cannot be mis-attributed across channels of the same resource ({§channel-selection-visibility}). `locator` preserves a structural address without overloading the resource row's `path`; `region` is a complete four-coordinate `TextRegion` only when the finding maps honestly into the exact text the model can READ. Exact duplicate evidence deduplicates. Relation findings map their indexed source spans through the same readable text coordinate index. FIND alone decides whether that grouped selection projects as resource rows or flat locations ({§find-result-projection}); the engine never fabricates a region or guesses which surgical READ the model wants.
5056
+
5025
5057
  §matcher-result-resource-selection **A matcher selects resources; it never extracts a value or chooses a retrieval
5026
5058
  window.** Every dialect answers whether a resource matches and may return
5027
5059
  `MatchEvidence { locator?, region? }` ({§matcher-selection-signal}). `locator` is a
@@ -5115,7 +5147,7 @@ presentation aid, never part of canonical content; matchers and mutations
5115
5147
  consume canonical bytes before rendering. A producer may set `startLine: null`
5116
5148
  only when its content is already source-numbered, such as an effect receipt.
5117
5149
 
5118
- §render-rule-find-renders-result A log row's canonical full body is resolved once by `LogBody`: READ/FIND, actionless source artifacts, prompt, and extension result content comes from `rx.content`; EDIT and scoped entry KILL use their structured receipt, while environment-delta EDIT uses its resulting span; COPY/MOVE concatenate the textual receipt contexts in their ordered `effects`; TASK serializes their canonical inventory through the shared {§json-result-rendering} spread as `application/json`; EXEC and SEND/WORK/FORK use their statement body. Whole-channel COPY/MOVE effects are bodyless rather than fabricating a text projection. Packet rendering applies {§body-projection} and the coordinate projection in {§render-rule-line-navigable-prefix}. READ/FIND over `log:///` and search use the same body with deliberate trims applied under {§log-readable-projection}, without packet-only suppression or preview limits. Status and content are orthogonal: a failed terminal stream READ retains its Problem Details and failure status while rendering captured diagnostic output; failure never erases evidence.
5150
+ §render-rule-find-renders-result A log row's canonical full body is resolved once by `LogBody`: READ/FIND, actionless source artifacts, prompt, and extension result content comes from `rx.content`; EDIT and scoped entry KILL use their structured receipt, while environment-delta EDIT uses its resulting span; COPY/MOVE concatenate the textual receipt contexts in their ordered `effects`; TASK serializes their canonical inventory through the shared {§json-result-rendering} spread as `application/json`; executions and SEND/WORK/FORK use their statement body. Whole-channel COPY/MOVE effects are bodyless rather than fabricating a text projection. Packet rendering applies {§body-projection} and the coordinate projection in {§render-rule-line-navigable-prefix}. READ/FIND over `log:///` and search use the same body with deliberate trims applied under {§log-readable-projection}, without packet-only suppression or preview limits. Status and content are orthogonal: a failed terminal stream READ retains its Problem Details and failure status while rendering captured diagnostic output; failure never erases evidence.
5119
5151
 
5120
5152
  An EDIT or scoped entry KILL log row renders its bounded effect receipt (`rx.receipt`) as row
5121
5153
  metadata and join context, not its input statement. Proposal-gated file EDITs
@@ -5174,3 +5206,50 @@ Carried from the contract walk; durable.
5174
5206
  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.
5175
5207
 
5176
5208
  §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`.
5209
+
5210
+ ---
5211
+
5212
+ ## §test-taxonomy Testing and evidence
5213
+
5214
+ | Tier | Location | LLM | Substrate |
5215
+ |---|---|---|---|
5216
+ | **unit** | `src/**/*.test.ts` | No | Isolated logic, mocked boundaries |
5217
+ | **intg** | `test/intg/` | No (mock provider) | Real file-backed SqlRite (per-test DB under `test/intg/.tmp/`), real engine |
5218
+ | **live** | `test/live/` | Real | Wire-level assertions |
5219
+ | **demo** | `test/demo/` | Real | Holistic outcome assertions |
5220
+
5221
+ §live-harness-deadline The live and demo tiers use `PLURNK_SERVICE_LIVE_TIMEOUT`
5222
+ as one whole-specimen deadline, including multi-prompt stories. The test's abort
5223
+ signal reaches the loop wait and invokes ordinary scope cancellation before
5224
+ teardown. The runner joins the test body's cleanup before starting the next
5225
+ specimen. The shared workspace and story helpers cover setup, inference and
5226
+ oracle failures, preserve the primary failure when their cleanup also fails,
5227
+ and attempt every registered disposal.
5228
+ Provider attempt/recovery limits remain independent; a harness cancellation is
5229
+ not evidence that the provider's own deadline expired.
5230
+
5231
+ §provider-conformance-matrix **Every configured model alias is exercised through a
5232
+ real PLURNK loop: the production packet, a model-selected operation, its
5233
+ materialized result, and completion.** Transport-only completions are not
5234
+ conformance evidence. Provider-exposed reasoning must survive in the durable
5235
+ assistant packet and digest; a provider with no private reasoning is valid when
5236
+ the observable operation cycle succeeds. One package-owned runner executes the
5237
+ full tier or exactly one registered specimen (`npm run test:live:specimen --
5238
+ <exact test name>` in plurnk-core), rejecting absent and duplicate names before
5239
+ execution. The ledger and classification taxonomy live in
5240
+ `plurnk-providers/README.md` and report authorization/credential failures
5241
+ distinct from model failures and repeated stochastic failures separately from
5242
+ stable ones, never with weakened assertions.
5243
+
5244
+ §test-artifact-retention **File-backed test databases use lane-local current-run
5245
+ retention.** Each workspace's normal intg runner clears its own
5246
+ `test/intg/.tmp/` once before the suite, reports that forensic directory, and
5247
+ retains every artifact the current run creates; a direct `node --test <file>` run bypasses that
5248
+ runner, so each test process prunes artifacts older than a day once, and never the current run's. A cross-package test may reuse
5249
+ Core's migration fixture only by passing a path inside the caller's artifact
5250
+ directory; independently scheduled lanes never share a reset target. A failed
5251
+ suite therefore leaves its own evidence intact, and the next normal run of that
5252
+ lane removes it before creating anything. Direct `node --test` invocations
5253
+ bypass the runner boundary and must invoke the same cleanup procedure
5254
+ explicitly when isolation matters. Live/demo run directories are benchmark
5255
+ artifacts outside `.tmp` and retain their separate lifecycle.