@plurnk/plurnk-service 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.env.defaults +4 -0
  2. package/SPEC.md +256 -20
  3. package/dist/build-info.json +1 -1
  4. package/dist/content/body-preview.js +1 -1
  5. package/dist/content/body-preview.js.map +1 -1
  6. package/dist/content/edit-receipt.d.ts.map +1 -1
  7. package/dist/content/edit-receipt.js +3 -10
  8. package/dist/content/edit-receipt.js.map +1 -1
  9. package/dist/content/line-anchors.d.ts.map +1 -1
  10. package/dist/content/line-anchors.js +7 -9
  11. package/dist/content/line-anchors.js.map +1 -1
  12. package/dist/content/matcher.js +1 -1
  13. package/dist/content/matcher.js.map +1 -1
  14. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  15. package/dist/core/AdmittedTurnExecutor.js +2 -0
  16. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  17. package/dist/core/BudgetReadout.js +1 -1
  18. package/dist/core/BudgetReadout.js.map +1 -1
  19. package/dist/core/Dispatcher.d.ts +3 -8
  20. package/dist/core/Dispatcher.d.ts.map +1 -1
  21. package/dist/core/Dispatcher.js +8 -16
  22. package/dist/core/Dispatcher.js.map +1 -1
  23. package/dist/core/EditMutations.js +2 -2
  24. package/dist/core/EditMutations.js.map +1 -1
  25. package/dist/core/Engine.d.ts +8 -19
  26. package/dist/core/Engine.d.ts.map +1 -1
  27. package/dist/core/Engine.js +22 -19
  28. package/dist/core/Engine.js.map +1 -1
  29. package/dist/core/Engine.sql +22 -0
  30. package/dist/core/ErrorDetail.d.ts +3 -4
  31. package/dist/core/ErrorDetail.d.ts.map +1 -1
  32. package/dist/core/ErrorDetail.js +3 -18
  33. package/dist/core/ErrorDetail.js.map +1 -1
  34. package/dist/core/ExecutorRegistry.js +1 -1
  35. package/dist/core/ExecutorRegistry.js.map +1 -1
  36. package/dist/core/HostPaths.d.ts +1 -0
  37. package/dist/core/HostPaths.d.ts.map +1 -1
  38. package/dist/core/HostPaths.js +39 -13
  39. package/dist/core/HostPaths.js.map +1 -1
  40. package/dist/core/LogBody.d.ts.map +1 -1
  41. package/dist/core/LogBody.js +9 -8
  42. package/dist/core/LogBody.js.map +1 -1
  43. package/dist/core/LoopDriver.js +1 -1
  44. package/dist/core/LoopDriver.js.map +1 -1
  45. package/dist/core/LoopPolicies.js +1 -1
  46. package/dist/core/LoopPolicies.js.map +1 -1
  47. package/dist/core/PacketBuilder.d.ts.map +1 -1
  48. package/dist/core/PacketBuilder.js +5 -1
  49. package/dist/core/PacketBuilder.js.map +1 -1
  50. package/dist/core/PreviousEmission.d.ts +14 -0
  51. package/dist/core/PreviousEmission.d.ts.map +1 -0
  52. package/dist/core/PreviousEmission.js +24 -0
  53. package/dist/core/PreviousEmission.js.map +1 -0
  54. package/dist/core/ProposalLifecycle.d.ts +9 -13
  55. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  56. package/dist/core/ProposalLifecycle.js +5 -7
  57. package/dist/core/ProposalLifecycle.js.map +1 -1
  58. package/dist/core/ProviderRecovery.d.ts.map +1 -1
  59. package/dist/core/ProviderRecovery.js +2 -7
  60. package/dist/core/ProviderRecovery.js.map +1 -1
  61. package/dist/core/SchemeRegistry.d.ts +2 -1
  62. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  63. package/dist/core/SchemeRegistry.js +10 -2
  64. package/dist/core/SchemeRegistry.js.map +1 -1
  65. package/dist/core/TurnRunner.d.ts +4 -10
  66. package/dist/core/TurnRunner.d.ts.map +1 -1
  67. package/dist/core/TurnRunner.js +22 -41
  68. package/dist/core/TurnRunner.js.map +1 -1
  69. package/dist/core/TurnSources.sql +15 -0
  70. package/dist/core/file-materialization.d.ts.map +1 -1
  71. package/dist/core/file-materialization.js +4 -3
  72. package/dist/core/file-materialization.js.map +1 -1
  73. package/dist/core/git-env.d.ts.map +1 -1
  74. package/dist/core/git-env.js +2 -7
  75. package/dist/core/git-env.js.map +1 -1
  76. package/dist/core/git-state.js +1 -1
  77. package/dist/core/git-state.js.map +1 -1
  78. package/dist/core/notifications.d.ts +17 -0
  79. package/dist/core/notifications.d.ts.map +1 -0
  80. package/dist/core/notifications.js +2 -0
  81. package/dist/core/notifications.js.map +1 -0
  82. package/dist/core/packet-inject.d.ts.map +1 -1
  83. package/dist/core/packet-inject.js +2 -3
  84. package/dist/core/packet-inject.js.map +1 -1
  85. package/dist/core/packet-wire.d.ts +3 -2
  86. package/dist/core/packet-wire.d.ts.map +1 -1
  87. package/dist/core/packet-wire.js +92 -13
  88. package/dist/core/packet-wire.js.map +1 -1
  89. package/dist/core/teaching.js +1 -1
  90. package/dist/core/teaching.js.map +1 -1
  91. package/dist/digest/Digest.d.ts.map +1 -1
  92. package/dist/digest/Digest.js +10 -2
  93. package/dist/digest/Digest.js.map +1 -1
  94. package/dist/digest/DigestRender.d.ts +3 -1
  95. package/dist/digest/DigestRender.d.ts.map +1 -1
  96. package/dist/digest/DigestRender.js +186 -1
  97. package/dist/digest/DigestRender.js.map +1 -1
  98. package/dist/digest/DigestRequiem.d.ts.map +1 -1
  99. package/dist/digest/DigestRequiem.js +2 -10
  100. package/dist/digest/DigestRequiem.js.map +1 -1
  101. package/dist/digest/digest-rows.d.ts +26 -0
  102. package/dist/digest/digest-rows.d.ts.map +1 -1
  103. package/dist/digest/digest-rows.js +1 -1
  104. package/dist/digest/digest-rows.js.map +1 -1
  105. package/dist/digest/digest.sql +10 -0
  106. package/dist/launch/Launch.d.ts +49 -0
  107. package/dist/launch/Launch.d.ts.map +1 -0
  108. package/dist/launch/Launch.js +179 -0
  109. package/dist/launch/Launch.js.map +1 -0
  110. package/dist/observe/spans.d.ts +1 -1
  111. package/dist/observe/spans.d.ts.map +1 -1
  112. package/dist/observe/spans.js +5 -64
  113. package/dist/observe/spans.js.map +1 -1
  114. package/dist/schemes/EffectPolicy.js +1 -1
  115. package/dist/schemes/EffectPolicy.js.map +1 -1
  116. package/dist/schemes/Exec.d.ts +2 -2
  117. package/dist/schemes/Exec.d.ts.map +1 -1
  118. package/dist/schemes/Exec.js +20 -16
  119. package/dist/schemes/Exec.js.map +1 -1
  120. package/dist/schemes/ExecutionInput.d.ts.map +1 -1
  121. package/dist/schemes/ExecutionInput.js +2 -5
  122. package/dist/schemes/ExecutionInput.js.map +1 -1
  123. package/dist/schemes/File.d.ts.map +1 -1
  124. package/dist/schemes/File.js +6 -1
  125. package/dist/schemes/File.js.map +1 -1
  126. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  127. package/dist/schemes/_entry-graph.js +2 -6
  128. package/dist/schemes/_entry-graph.js.map +1 -1
  129. package/dist/schemes/_entry-manifest.js +1 -1
  130. package/dist/schemes/_entry-manifest.js.map +1 -1
  131. package/dist/schemes/_search-index.d.ts.map +1 -1
  132. package/dist/schemes/_search-index.js +2 -8
  133. package/dist/schemes/_search-index.js.map +1 -1
  134. package/dist/schemes/exec-abort.js +1 -1
  135. package/dist/schemes/exec-abort.js.map +1 -1
  136. package/dist/schemes/exec-lifetime.d.ts.map +1 -1
  137. package/dist/schemes/exec-lifetime.js +2 -1
  138. package/dist/schemes/exec-lifetime.js.map +1 -1
  139. package/dist/server/ClientReads.d.ts +1 -1
  140. package/dist/server/ClientReads.d.ts.map +1 -1
  141. package/dist/server/ClientReads.js +1 -1
  142. package/dist/server/ClientReads.js.map +1 -1
  143. package/dist/server/Daemon.d.ts +10 -7
  144. package/dist/server/Daemon.d.ts.map +1 -1
  145. package/dist/server/Daemon.js +12 -3
  146. package/dist/server/Daemon.js.map +1 -1
  147. package/dist/server/DaemonModule.d.ts +2 -63
  148. package/dist/server/DaemonModule.d.ts.map +1 -1
  149. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  150. package/dist/server/DrainSupervisor.js +10 -1
  151. package/dist/server/DrainSupervisor.js.map +1 -1
  152. package/dist/server/EnvFunctionality.d.ts +2 -1
  153. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  154. package/dist/server/EnvFunctionality.js.map +1 -1
  155. package/dist/server/Functionality.d.ts +2 -1
  156. package/dist/server/Functionality.d.ts.map +1 -1
  157. package/dist/server/Functionality.js.map +1 -1
  158. package/dist/server/MembersFunctionality.d.ts +2 -1
  159. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  160. package/dist/server/MembersFunctionality.js +1 -1
  161. package/dist/server/MembersFunctionality.js.map +1 -1
  162. package/dist/server/SkillsFunctionality.d.ts +2 -1
  163. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  164. package/dist/server/SkillsFunctionality.js +1 -1
  165. package/dist/server/SkillsFunctionality.js.map +1 -1
  166. package/dist/server/WorkspaceResidency.d.ts +2 -1
  167. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  168. package/dist/server/WorkspaceResidency.js.map +1 -1
  169. package/dist/server/client-input.d.ts +1 -1
  170. package/dist/server/client-input.d.ts.map +1 -1
  171. package/dist/server/envelope.d.ts +2 -9
  172. package/dist/server/envelope.d.ts.map +1 -1
  173. package/dist/server/envelope.js +2 -2
  174. package/dist/server/envelope.js.map +1 -1
  175. package/dist/server/envelope.sql +6 -3
  176. package/dist/server/logEntry.d.ts +1 -31
  177. package/dist/server/logEntry.d.ts.map +1 -1
  178. package/dist/server/logEntry.js.map +1 -1
  179. package/dist/server/model-catalog.js +1 -1
  180. package/dist/server/model-catalog.js.map +1 -1
  181. package/dist/server/module-discovery.d.ts.map +1 -1
  182. package/dist/server/module-discovery.js +4 -22
  183. package/dist/server/module-discovery.js.map +1 -1
  184. package/dist/service.d.ts.map +1 -1
  185. package/dist/service.js +4 -1
  186. package/dist/service.js.map +1 -1
  187. package/docs/env.md +18 -0
  188. package/package.json +37 -33
  189. package/dist/core/Knob.d.ts +0 -9
  190. package/dist/core/Knob.d.ts.map +0 -1
  191. package/dist/core/Knob.js +0 -50
  192. package/dist/core/Knob.js.map +0 -1
package/.env.defaults CHANGED
@@ -10,6 +10,10 @@
10
10
  # Database path; empty = $XDG_DATA_HOME/plurnk/plurnk.db (~/.local/share by default).
11
11
  # Module operational state: $XDG_STATE_HOME/plurnk (~/.local/state/plurnk by default).
12
12
  PLURNK_SERVICE_DB_PATH=
13
+ # A private daemon's root ({§state-root}): empty = the XDG homes; an absolute path (a leading ~/
14
+ # expands) roots data, state, cache and runtime under it as <root>/{data,state,cache,runtime}/plurnk.
15
+ # Configuration and the shared Agent Skills root never move; PLURNK_SERVICE_DB_PATH still wins.
16
+ PLURNK_SERVICE_STATE_ROOT=
13
17
  # Where `share` writes when no folder is named: each share is a stamped child folder.
14
18
  # A leading ~/ expands; empty = $XDG_STATE_HOME/plurnk/shares (~/.local/state/plurnk/shares by default).
15
19
  PLURNK_SERVICE_SHARE_FOLDER=
package/SPEC.md CHANGED
@@ -224,6 +224,32 @@ before provider or capability initialization can perform external work. Every
224
224
  later startup failure closes resources in reverse ownership order while
225
225
  preserving the originating failure: daemon, observability, database, listener.
226
226
 
227
+ §startup-readiness-line **Readiness is one stdout line.** After the client interface is mounted
228
+ the service prints exactly one line, `plurnk-service agui=<url> db=<json string> route=<json string>`:
229
+ the URL brackets an IPv6 host, and the database path and the route (the active model route or
230
+ `no model`) are JSON strings, so a path or route containing spaces is exact and a consumer parses
231
+ the URL as a URL and the strings as JSON; nothing else the service prints on stdout before it has
232
+ that prefix. Before the line the listener answers `503 service-starting`; after it,
233
+ `discover` is the identity check a launcher uses to tell this daemon from any other listener. A bind
234
+ failure is an exit with the originating address error and means *occupied*, not *foreign* — another
235
+ plurnk-service may be starting there, and only `discover` says which.
236
+
237
+ §daemon-launch **The service ships its own launcher; launchers own policy.** `@plurnk/plurnk-service/launch`
238
+ spawns a daemon argv with the caller's environment, an optional {§state-root}, host and port, and
239
+ resolves on the readiness line with the published address, database path, route and a `stop()` that
240
+ is SIGTERM, a stated grace, then SIGKILL, resolving when the process has ended. It holds no timing of
241
+ its own: the caller states the readiness timeout and the stop grace. A start that fails — spawn error,
242
+ exit before readiness, or timeout — is stopped and awaited before the failure is thrown with its kind
243
+ and both output streams; the helper never creates, keeps or removes state. A shared daemon survives
244
+ the launcher that started it (scheduled deliveries, other clients and inbound A2A depend on it); a
245
+ private daemon is its launcher's child and ends with it under managed shutdown. The launcher
246
+ spawns either: `lifetime: "private"` (the default) pipes both streams to the launcher; `lifetime:
247
+ "shared"` puts the daemon in its own process group with both streams appended to the caller's
248
+ `logFile`, reads readiness from that file, and releases the process once ready, so the launcher may
249
+ exit while the daemon runs on and no output accumulates in a launcher that has left; until
250
+ readiness the launcher owns it either way, and `stop()` ends it while the launcher lives. Shell and container
251
+ launchers consume the same contract by reading the line themselves.
252
+
227
253
  ## §actor-boundary Workers and workspace boundaries
228
254
 
229
255
  ```mermaid
@@ -352,7 +378,7 @@ direct-entry-plus-directory count; `-1` enables the ordinary markerless page;
352
378
  unset / `0` disables previews. `log://` is absent because the current worker's
353
379
  log already renders in present mode.
354
380
 
355
- §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning and program READs in {§reasoning-initial-read} execute under {§op-execution-order}. The full `<1,-1>` READ of its own `ops://<worker>/<loop>/<turn>` source supplies the worked program example; no actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
381
+ §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning READ in {§reasoning-initial-read} execute under {§op-execution-order}. Its program supplies the worked example as the first request's assistant message ({§packet-wire-envelope}); no program READ, actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
356
382
 
357
383
  Incoming messages publish once as inbound SEND rows in the first model turn
358
384
  ({§message-arrival}); initialization neither READs nor archives them. The turn
@@ -574,18 +600,22 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
574
600
  `"parent": null` at a root, so a worker never infers its rank from silence.
575
601
  - §packet-current-turn **The packet says who and which turn, below the log.** The
576
602
  `## Worker` block is the first section after the log, carrying
577
- `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T}`: the actor,
578
- whose child it is, and the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
579
- and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows; a model never infers the
580
- present from the last row's coordinate, which may or may not be its own turn. The block
603
+ `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`:
604
+ the actor, whose child it is, the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
605
+ and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows, and the address of the program
606
+ the envelope's assistant message carries ({§packet-wire-envelope}), so the rows sharing that coordinate
607
+ are its receipts; a model never infers the present from the last row's coordinate, which may or may
608
+ not be its own turn. The block
581
609
  changes every turn, so nothing of it precedes the log, and the packet carries no date, time
582
- or zone anywhere. The coordinate only; the source addresses stay
583
- documented, not taught.
610
+ or zone anywhere. The coordinates and the last program's address only; the other source
611
+ addresses stay documented, not taught.
584
612
 
585
613
  Worker control rides the daemon's inject seam (active→fold, idle→enqueue+drain), so the handler creates/branches the worker and hands off; the daemon owns provider + system prompt. FORK/WORK carry the seed task in the body and are their own ops, dispatched to worker control — never the entry-copy path.
586
614
 
587
615
  ## §membership File membership and project roots
588
616
 
617
+ A file is a member of a workspace when Git tracks it or a members definition includes it and none excludes it; only members can be READ, found by FIND, or changed by EDIT, and every member path resolves under the project root.
618
+
589
619
  The project-file path has two explicit reconciliation gates. Internal entries do
590
620
  not participate in this disk loop.
591
621
 
@@ -1070,6 +1100,15 @@ single operation captures the equivalent boundary before dispatch. This limits
1070
1100
  only log-row selection: operation phasing and same-turn resource effects retain
1071
1101
  their ordinary contracts.
1072
1102
 
1103
+ ### §engine-notifications One bundle of daemon callbacks
1104
+
1105
+ The daemon's observation callbacks — stream, reasoning, outside-text and packet
1106
+ events, worker wake, inject and cancel, operation settlement, notices — are one
1107
+ declared bundle (`EngineNotifications`). The Engine receives them flat, carries
1108
+ them as one value, and every consumer reads the callbacks it uses from that
1109
+ value; adding one is a declaration and a use, never an edit to the constructors
1110
+ between. Scheme contexts still expose the specific callbacks a handler may call.
1111
+
1073
1112
  ### §engine-rails Engine rails
1074
1113
 
1075
1114
  After each admitted turn, one inline verdict decides whether the loop continues.
@@ -1221,7 +1260,7 @@ Three current entry points:
1221
1260
 
1222
1261
  ### Engine → provider guarantees
1223
1262
 
1224
- - `messages` is a complete prompt (the section list, pre-assembled into the system + user messages). Provider does not reorder.
1263
+ - `messages` is a complete prompt (the section list, pre-assembled into the wire envelope, {§packet-wire-envelope}). Provider does not reorder.
1225
1264
  - §provider-guarantees-signal-wired `signal` is wired to the worker's AbortController.
1226
1265
  - §provider-guarantees-serial-attempts Emission attempts for one engine turn are serial. They reuse the exact messages, coordinates, generation limits, and strike state; two attempts for that turn never overlap.
1227
1266
  - BARE calls admitted by one turn launch as one parallel batch; each call retains independent observer and failure state, and the engine awaits the complete batch before committing results in authored order ({§bare-inference}).
@@ -1547,7 +1586,7 @@ Registration precedes loop affinity:
1547
1586
  - §anchor-offset **An anchor offset is tolerated, never taught (#749).** A line mark may carry an offset from its anchor (`@abcde+1`, `@abcde-2`), and a bare `+N` after an anchor counts from that anchor (`<@abcde,+1>`). The anchor resolves as usual and the offset is added; a result before line 1 is an invalid mark, and past the end is the ordinary range refusal. Continuity and current-anchor preconditions check the anchor's own line. No teaching text, scope table or receipt mentions offsets; `plurnk.md` keeps its two anchor forms. A bare `+N` with no anchor before it is refused as before.
1548
1587
  - §edit-batch **One compound operation may require atomic splices.** The scheme's `editBatch` primitive validates all supplied numeric edits against one snapshot and commits one revision or none. Core supplies one statement for an authored EDIT; same-resource MOVE can supply multiple splices as one operation. This primitive does not group separate authored operations. Its replacement, insertion, conflict, and receipt rules remain owned by the shared Slicer.
1549
1588
  - §edit-batch-receipt **A refusal describes its own unapplied work.** An anchor collision lists every distinct unresolved anchor in that EDIT, including both range endpoints, in `unresolvedAnchors` (`anchor`, `kind: missing | ambiguous`, and matching `lines` when ambiguous). Missing is not proof of earlier validity or subsequent change. It carries `editCount: 1`, `applied: 0`, and recovery directing a READ for current coordinates; it makes no claim about other operations. A refused compound splice batch lists all conflicting pairs in `conflicts`, non-conflicting regions in `cleanRegions`, its first pair in `conflictingRegions`, and its own `editCount` and `applied: 0`.
1550
- - §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published `@xxxxx L:` prefixes is stripped when those prefixes verify against current anchors or this worker's preserved READ receipts (`rendered-prefix-stripped`); otherwise it remains literal content (`rendered-prefix-unverified`). Within a single atomic splice batch, the Slicer can deduplicate identical regions/bodies, concatenate same-boundary insertions, assign a shared endpoint to the sole body reproducing that line, or relocate an inner change when its original content occurs exactly once in the outer body. An already-applied inner body can be dropped. Unevidenced overlap remains a collision. These batch resolutions never reinterpret separate authored EDITs. Applied normalizations carry their exact merge facts and a notice; receipts describe only the applied effects.
1589
+ - §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published `L<@xxxxx>` prefixes (the number right-aligned) is stripped when those prefixes verify against current anchors or this worker's preserved READ receipts (`rendered-prefix-stripped`); otherwise it remains literal content (`rendered-prefix-unverified`). Within a single atomic splice batch, the Slicer can deduplicate identical regions/bodies, concatenate same-boundary insertions, assign a shared endpoint to the sole body reproducing that line, or relocate an inner change when its original content occurs exactly once in the outer body. An already-applied inner body can be dropped. Unevidenced overlap remains a collision. These batch resolutions never reinterpret separate authored EDITs. Applied normalizations carry their exact merge facts and a notice; receipts describe only the applied effects.
1551
1590
 
1552
1591
  ### Cross-scheme orchestration
1553
1592
 
@@ -1907,9 +1946,11 @@ anchors from the complete canonical selected channel before applying the
1907
1946
  authored text slice; its durable result retains the canonical derivation
1908
1947
  identity and anchors aligned with returned lines. Packet rendering right-aligns
1909
1948
  `L` to the decimal width of the complete canonical selected channel's final
1910
- addressable line and emits `@xxxxx L:<content>` with one or more ASCII spaces
1911
- before `L`; a source line therefore retains the same prefix across projections
1912
- of one revision.
1949
+ addressable line and emits `L<@xxxxx><content>` with `L` right-aligned to that
1950
+ width, the scope literal as the delimiter, the content beginning after `>`. The anchor
1951
+ stands against its own text and never opens the row after the previous line's text —
1952
+ the placement that reads correctly at long context on every model measured (#893); a
1953
+ source line therefore retains the same prefix across projections of one revision.
1913
1954
  An explicit default-channel fragment and its fragmentless spelling share that
1914
1955
  identity; a selected non-default channel retains its canonical `#channel`.
1915
1956
 
@@ -2270,7 +2311,10 @@ Authored `metadata` retains its opaque ordered block strings under {§scheme-met
2270
2311
  This is retained evidence, not a separate visibility or delivery lifecycle. Source mutation/deletion
2271
2312
  cannot change a retained observation. Explicit READ of its still-active log source can acquire the same media again.
2272
2313
  Every compatible-model packet includes one file part per retained, admitted READ observation, after the
2273
- packet text, in observation order. Model-response settlement never consumes an observation. KILL follows
2314
+ packet text, in observation order, each preceded by a text part that names the observation's log
2315
+ coordinate, source path and projection facts and states that the bytes are that READ's own, retained
2316
+ until its row is KILLed: an uncaptioned native part on the user turn reads as a fresh arrival (#899).
2317
+ Model-response settlement never consumes an observation. KILL follows
2274
2318
  {§log-kill-scope}; forks inherit the snapshot and ordinary projection state independently. Output withholding
2275
2319
  suppresses the complete native part under {§context-output-admission}. Unsupported routes receive only the
2276
2320
  text projection and no native charge; switching back to a compatible route exposes still-retained media.
@@ -2422,7 +2466,7 @@ per-operation projection on `rx`; the aggregate remains inside dispatch.
2422
2466
  | `effect.source`, `result` | `effect` as `<source> -> <result>` | Resolved scopes mapping the source snapshot into the landed body; the admitted marker stays in durable `requested` and `tx`. |
2423
2467
  | `effect.removed`, `inserted` | `change` | Removed and inserted counts in the receipt unit. |
2424
2468
  | `effect.removedText` | `removed` | {§edit-receipt-removed-text}: a pure deletion's removed text, its first `PLURNK_SERVICE_EDIT_RECEIPT_REMOVED_LINES` lines; absent when the edit inserted anything. |
2425
- | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. |
2469
+ | `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. A pattern batch's `last` context follows the first's when it differs, with no blank line between: every line of a row body carries its coordinate. |
2426
2470
  | `disposition`, `requested` | `disposition`, `requested` | A reviewer-replaced batch preserves the authored marker while stating that its attributed effect was superseded. |
2427
2471
  | `replacement` | `replacement`, `change`, canonical proposal-owner body | The one whole-resource effect actually applied by the reviewer replacement; never duplicated across authored rows. |
2428
2472
 
@@ -2870,7 +2914,9 @@ anything spawns — a file is the script; a directory is refused `400 target-not
2870
2914
  pointing at `[{"cwd": "…"}]`; an absent path is refused `400 target-not-found`, giving the
2871
2915
  applicable accepted form without inferring what the model meant. When the target is a
2872
2916
  registered tool of another executor, recovery gives that tool's exact bracketed
2873
- invocation; otherwise it points at an existing script or a bare shell-command body. A non-file resource
2917
+ invocation; when it names another available executor (`sh (python3)` over a Python body, #895),
2918
+ recovery names that executor's fence — `` `python3` is its own executor; use that name on the
2919
+ opening fence and put the program in the body. ``; otherwise it points at an existing script or a bare shell-command body. A non-file resource
2874
2920
  target that cannot be read keeps the owning READ's failure identity (#163) and states
2875
2921
  the slot contract in its recovery — the resource is the program and the body its stdin;
2876
2922
  a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
@@ -3002,7 +3048,8 @@ Per-tool programs such as `go`, `cargo`, `make`, and `npm` do not earn executor
3002
3048
 
3003
3049
  §exec-lifetime **How long a spawn may live is the fence's metadata, one field.**
3004
3050
  `[{"lifetime": …}]` takes a duration (`30s`, `30m`, `2h`), or one of three words;
3005
- absent is `loop`. An execution takes no scope: a numeric coordinate on an
3051
+ absent is `loop`. The key is one of the service's reserved metadata keys, withheld
3052
+ from every owner by the framework ({§service-metadata-keys}). An execution takes no scope: a numeric coordinate on an
3006
3053
  executor target is refused `scope-unsupported` (400), naming the field.
3007
3054
 
3008
3055
  | `lifetime` | The spawn |
@@ -3217,7 +3264,7 @@ body prefixes.
3217
3264
  wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
3218
3265
  provenance as the modifier's.
3219
3266
  - §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits an executor fence, optionally followed by WAIT; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
3220
- - §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address.
3267
+ - §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address. Web acquisition and materialization are the `https` handler's {§web-materialization-contract}, reached through the scheme registry; core names no leaf package.
3221
3268
 
3222
3269
  | Input / effect | Consumer behavior |
3223
3270
  | --- | --- |
@@ -3626,6 +3673,14 @@ and is ignored rather than resolved against the working directory.
3626
3673
  | Reproducible cache | `$XDG_CACHE_HOME` (default `~/.cache`) | Reserved; no directory is created without an owned artifact. |
3627
3674
  | Shared global Agent Skills | User home | `.agents/skills/<name>/SKILL.md` |
3628
3675
 
3676
+ §state-root **A private daemon has one root.** `PLURNK_SERVICE_STATE_ROOT` (absolute; a leading
3677
+ `~/` expands; a relative value fails hard by name) replaces the data, state, cache and runtime homes
3678
+ with `<root>/data`, `<root>/state`, `<root>/cache` and `<root>/runtime`, the database with them
3679
+ (`PLURNK_SERVICE_DB_PATH` still names the database exactly). Configuration stays where the cascade
3680
+ reads it and the shared Agent Skills root stays under the user's home: a state root separates what
3681
+ the daemon *writes*, not what the operator supplies, and is no execution sandbox. What a launcher
3682
+ keeps or removes under a root after the daemon stops is that launcher's retention decision.
3683
+
3629
3684
  The service creates only a directory required by the current command. A newly
3630
3685
  created configuration or data directory uses mode `0700`; a newly seeded
3631
3686
  secret-bearing `.env` uses `0600`. Existing user-owned permissions are not
@@ -4048,7 +4103,12 @@ actions from model operations where the family contract requires it
4048
4103
  outcomes, and a snapshot with `commit`/`abort`. Successful publication commits;
4049
4104
  failure aborts; cooling tears down. Protocol continuations remain ordinary
4050
4105
  module actions. Optional `forget` releases an installed or provisioned
4051
- definition before removal; failure rejects removal ({§skills-remove}).
4106
+ definition before removal; failure rejects removal ({§skills-remove}). The
4107
+ seam's shapes — the identity a verb acts under, its options, definition
4108
+ sources, outcomes, preparation, the prepared result and the family handle —
4109
+ are declared once in `plurnk-contracts` and imported by core and every
4110
+ module; core adds only its own face of the seam, the runtime registration a
4111
+ resident family prepares and the scheme facet it may expose.
4052
4112
 
4053
4113
  An adapter may expose a `scheme` facet beneath its family's runtime namespace
4054
4114
  ({§runtime-resource-binding}). A facet claims a path subtree and is the scheme's
@@ -4224,11 +4284,12 @@ Core's behavior behind them.
4224
4284
  | §methods-conversation-worker Workspace lifecycle | `createConversationWorker({ workspaceId, name? })` | Creates a distinct model-origin root worker with empty history: a fresh conversation over the same world, not a fork or the stable default. |
4225
4285
  | Workspace lifecycle | `forkWorker({ workspaceId, workerId, name? })` | Creates a child worker that branches the source worker's history while sharing workspace state. |
4226
4286
  | §methods-workspace-rename Workspace metadata | `renameWorkspace(workspaceId, name)` | Changes only the world's unique mutable name; workers, log, and membership remain intact. |
4227
- | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?)` | Returns nonempty loop-seed prompts from the workspace's model-origin root conversations, newest-first. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`; spawned and forked child prompts are excluded. |
4287
+ | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?, workerId?)` | Returns nonempty loop-seed prompts a client addressed to the workspace's model workers, newest-first; `workerId` narrows to one worker. Authorship is the seed message's address ({§message-arrival}): a worker-issued seed (WORK, FORK, SEND to a worker) has none and is never history, whichever worker it seeded; a client prompt at a forked conversation worker is. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`. |
4228
4288
  | Workspace metadata | `listWorkspaces()`, `workspaceDerivationStatus(...)` | Reads current workspace identity and derivation progress. |
4229
4289
  | §methods-worker-read Worker topology | `readWorker({ workspaceId, identity })` | Ownership-bounds an exact id-or-name lookup and returns one durable Worker projection or `null` under {§application-worker-observation}. Supplying both identities or neither is invalid. |
4230
4290
  | §methods-worker-list Worker topology | `listWorkers(workspaceId, query?)` | Returns the workspace's durable Worker projections under {§application-worker-observation}. The origin filter is exact; an explicitly present `parentWorkerId` filters roots (`null`) or one immediate parent (id), while omission returns every lineage position. Each projection carries `kind` (`conversation`, `fork` for a child with a fork boundary, `work` for any other child) and `lifecycle`, the representative work loop's status through {§loop-lifecycle-vocabulary} (`idle` with no work loop), so a directory row shows the same lifecycle glyph the bound worker's own status gauge shows; clients infer neither (#523). |
4231
4291
  | §methods-worker-loops Loop lifecycle | `listWorkerLoops({ workspaceId, workerId })` | Ownership-checks the Worker and returns its Loops in sequence order under {§application-loop-observation}, including the validated exact terminal result when one exists. It performs no scheduling or event replay. |
4292
+ | §methods-worker-descendants Descendant spend | `descendantAccounting({ workspaceId, workerId, loopId })` | Ownership-checks the Worker and returns the {§provider-accounting} projection of every settled request on a loop that a descendant of the Worker (`parent_worker_id`, to any depth) ran after `loopId` — this delegation's spend, the same tree the turn cap counts ({§turn-cap-counts-the-tree}). The Worker's own loop is never included: its accounting stays {§notifications-loop-terminated}'s. `loopId: null` is the empty projection, explicit zero. Derived from the request ledger on every read ({§tokenomics-provider-usage}); no rollup, no second store. |
4232
4293
  | Extension actions | `listModuleActions()`, `invokeModuleAction(name, params, context)` | Lists setup-registered `{ name, scope, inputSchema, outputSchema }` descriptors in sorted order. Invocation requires a context matching the registered scope; missing names, forged scope, and missing workspace identity fail before the owner runs. Handler values remain opaque to core. |
4233
4294
 
4234
4295
  §methods-loop-run-fold-consistency **A folded prompt cannot silently reconfigure
@@ -4533,6 +4594,26 @@ flowchart LR
4533
4594
  measure --> rail[Engine budget admission and dispatch]
4534
4595
  ```
4535
4596
 
4597
+ ### §packet-wire-envelope The wire envelope
4598
+
4599
+ The packet reaches the provider under the roles the model was tuned on, its bytes unchanged:
4600
+
4601
+ | Message | Role | Content |
4602
+ |:--|:--|:--|
4603
+ | 1 | `system` | the system slot, as rendered |
4604
+ | 2 … | `user` | the log's records, one message per completed turn in record order; the first opens with `## Log` |
4605
+ | next | `assistant` | the canonical rendering ({§statement-rendering}) of every statement the parser admitted from the worker's most recent program that admitted any ({§turn-source-resources}, kind `ops`), in order and alone: free text and unadmitted forms are absent, a recovered native call ({§native-tool-calls}) appears as the operation it was read as, and an operation whose receipt failed stays, since it produced its row; turn zero's survey ({§worker-initialization-entry}) is the first, so every model request carries one |
4606
+ | last | `user` | the current turn's records, then the remaining user sections in {§packet-cache-monotone} order; native parts ride here ({§packet-attachment-parts}) |
4607
+
4608
+ Only role boundaries are added. Curation governs every record as before, so a KILLed
4609
+ row is absent from its turn's message; the one program is bounded and the model's own last operations,
4610
+ a demonstration of the grammar beside what the log made of it. Whatever sits under the assistant marker
4611
+ is what the model writes next, for better and for worse: shown its own slip, a model repeats it, so the
4612
+ slot carries the grammar's reading and never the bytes as typed. On the first request it is turn zero's
4613
+ survey, the worked example in the model's own place. The prefix through the last completed
4614
+ turn stays reusable across requests; the assistant message and the closing user message are the
4615
+ changing tail. The digest's packet artifacts record the packet; the envelope is its projection (#903).
4616
+
4536
4617
  ### §packet-cache-monotone Default order and cache locality
4537
4618
 
4538
4619
  Conditional absence never reorders the surviving default sections.
@@ -4543,7 +4624,7 @@ Conditional absence never reorders the surviving default sections.
4543
4624
  | 2 | system | `system-policy` | Operator policy; empty content is omitted on the wire. |
4544
4625
  | 3 | system | `inject` | Present only when operator notes are configured. |
4545
4626
  | 4 | user | `log` | Append-mostly model-visible history; the first user section, so the cached prefix ends inside it. |
4546
- | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T}`, the actor and the coordinate this packet's response becomes ({§packet-current-turn}). |
4627
+ | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`, the actor, the coordinate this packet's response becomes and the address of its previous program ({§packet-current-turn}). |
4547
4628
  | 6 | user | `delegation` | `Delegation`: per-turn `{workers, streams}` pointers; always present, each list `[]` when empty ({§packet-empty-sections}). |
4548
4629
  | 7 | user | `errors` | Per-turn failure pointers; empty content is omitted. |
4549
4630
  | 8 | user | `notices` | Per-turn observations; empty content is omitted. |
@@ -5041,6 +5122,8 @@ retain distinct contracts and lifetimes.
5041
5122
 
5042
5123
  §notice-event-notify **Client surface.** Engine Notices broadcast live via the `notice/event` notification — `{ workerId, loopId, notice: { source, kind, level, message?, position?, …kind-specific } }` per the grammar's `Notice` schema — the moment they land. A loop Notice names its owning Worker; workspace derivation progress alone carries `workerId=null, loopId=0`. AG-UI projects the same observation as the custom `plurnk.notice` event. Failures do not broadcast on this surface: they are log rows, and the client reads them through `log.read` / the `log/entry` notification, the durable log.
5043
5124
 
5125
+ §loop-status-notice **The drain beats the loop's lifecycle on the notice channel.** When the drain claims a loop to run it broadcasts `notice/event` `{ workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status: 102 } }`, and when it leaves a loop parked ({§loop-wake-identity}) the same with `status: 202`; a wake that the drain claims again is another `102`. The beat is transient: broadcast to the workspace like any notice ({§notice-event-notify}), never a log row, never in a packet. Terminals stay `loop/terminated`'s; a client that reads the beat has the running/parked edges a parked delegation otherwise never publishes.
5126
+
5044
5127
  §share **A share is the database's record, ready to send.** `plurnk-service share [<file.db>] [<folder>]`, and `npm run share` from a checkout, take a consistent copy of the database (`VACUUM INTO`; a live database is never read in place), and write its digest into `<folder>`, an ordinary folder the user archives or attaches however they like. Without a database the service's own is shared. Nothing is overwritten: a folder that exists and is not empty is refused, and a caller reusing a place removes it first. The share is the user's bug report and our dogfood, benchmark and forensics artifact alike.
5045
5128
 
5046
5129
  §share-snapshot **A database is copied by SQLite, never by the filesystem.** `Share.snapshot(dbPath, copy)`, exported as `@plurnk/plurnk-service/share` with `Share.write`, is the one consistent copy: a byte copy of a WAL-mode database drops every committed page still in its `-wal` file. A harness that keeps the database beside its digest takes it through `snapshot`; an existing `copy` is refused.
@@ -5068,6 +5151,10 @@ retain distinct contracts and lifetimes.
5068
5151
 
5069
5152
  §digest-wire-line **Wire health aggregated.** Each worker summary renders a `Wire:` line — total physical provider requests, error-outcome count, and the error percentage when nonzero. Provider-level failures are absorbed by retries below the packet stream, so without this aggregate a rate-limit storm is invisible in every summary while the model's experience stays clean.
5070
5153
 
5154
+ §digest-cache-ledger **Cacheable versus cached, per request.** For every physical provider request the digest computes its cacheable prefix: the longest common prefix, in characters, between the prompt its turn stored (the packet's rendered `system` text followed by its `user` text — the bytes `.system.md` and `.user.md` carry) and the prompt of the previous provider request in the same loop, taken as that prefix's share of the whole prompt in the packet's own token estimate ({§tokenomics-agnostic-ruler}) and applied to the provider's reported input count, so `cacheableTokens` sits in the same units as the cache read beside it; a loop's first request has 0, and a request whose turn stores no valid packet or whose provider reported no input count has none. Beside it sit the provider's reported cache read as `cachedTokens` (`provider_requests.usage_input_cache_read`) and `inputTokens` (`usage_input`). `digest.json` carries the three on every provider-request row; an inference turn line carries `cache=<cached>/<cacheable>` summed over the turn's requests; each workspace heading is followed by `Cache: <cached> of <cacheable> cacheable tokens reported (<pct>%) over <n> requests`. A provider that reported no cache field at all (null, not 0) renders `?` on the turn line and is counted apart on the workspace line (`· <k> unreported (cached=?)`), outside both sums and the percentage; a request without a stored packet or without a reported input count is likewise counted apart. The prefix measures what the daemon kept identical between consecutive requests; it is no claim about the provider's tokenization or cache-block alignment, so a provider that under-caches an identical prefix reads as such, apart from a prefix the daemon itself broke.
5155
+
5156
+ §digest-edit-census **Every model EDIT by the form it authored, how it landed, and whether it came back.** For each worker the digest reads every model-authored EDIT row and classifies the form from the row's stored marker and the durable statement's pattern: `hash` (one anchor), `line` (one line number), `range` (two marks), `insert` (the zero-width `<L,1,L,1>` form, {§zero-width-column-one-insert}), `column` (any other four-mark region), `prepend` / `append` (`<0>` / `<-1>`), `offset` (a tolerated anchor offset, {§anchor-offset}), `pattern` (a selection matcher), `whole` (no marker: a creation when it lands 201). It counts the EDITs, those refused (status ≥ 400), and the *revisits*: an EDIT of a path the same worker had edited within its previous two model turns — the shape of a repair without the claim of one. Each worker summary renders `EDITs: <n> · <form>=<count>… · refused=<k> · revisits=<r>` (`(no edits)` for none); `digest.json` carries the census as `edit_census` on every worker and stamps every EDIT log entry with its `edit_form` and `edit_revisit`. A form is a fact about what was written, never about intent; the bench sheet reads the counts as friction and leaves the judgement to the reader.
5157
+
5071
5158
  §digest-forensic-fidelity **Forensic fidelity and cardinality.** The digest's machine-readable JSON preserves every log event with its initial and current projection, causal `source`, and structured `attrs`; every exact log-KILL target effect; the exact Problem on every failed row; each loop's exact terminal result, settlement time, scheduled due time, recurring interval, and recurrence lineage; and every ordered physical provider request. Programs still produce chronological `assistant.md` artifacts after every READ receipt is KILLed; source is independent of log curation. Each stored packet validates independently: one malformed historical packet remains exact raw evidence with its complete validation error chain and never prevents healthy turns from being projected. Accounting on broader rows is the shared exact derivation from that ledger, never a second stored fact. A worker's Cost line names how many settled requests carry no usage at all (errored or aborted exchanges) — their server-side spend is unrecorded rather than silently priced as zero. The reasoning chronology distinguishes readable reasoning content from provider-reported reasoning usage: when tokens were reported but no readable content was returned, it states both facts instead of implying that no reasoning occurred. The human Markdown waterfall shows a present causal source and may preview only the Problem detail because it remains a triage projection, not the machine record. Targets reconstruct the model-visible address, including hostname, port, serialized query, and fragment; an authority-bearing URL must never degrade from `https://host/path` to `https:///path`, and durable resource coordinates render back to their authority form. Its human Markdown waterfall groups identical per-turn op outcomes and typed `entry_materialized` narrations, reporting the exact count and sequence span (`xN (seq A-B)`). Grouping keys include source and the complete target, so distinct causes, authorities, or channels never collapse. Thus amplification is conspicuous without making the diagnostic artifact itself pathological; valid packet files remain byte-identical records of what the model saw.
5072
5159
 
5073
5160
  Unrecognized actionless log rows are retained and labelled as such, not
@@ -5687,3 +5774,152 @@ exits; a failed suite's evidence is never touched and stays exactly where the ru
5687
5774
  caller's own run directory.
5688
5775
 
5689
5776
  §fs-world-state **The world-state harness — coverage that closes the class.** Op-outcome tests check what an op returned; the harness checks the resulting world. `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, channel orphan-freedom, the closed admission set (every file row's origin is Git or constraint), and sig-coherence. Generated-pick incorporation and lifecycle require filesystem/Git evidence and are covered by the composed creation matrix rather than a false pure-database proxy. The harness runs as a lifecycle-test epilogue and at every soak turn boundary, where the delta half applies: an idle turn grows the entries table by ZERO. A violation names its law and its row.
5777
+
5778
+ ## Problem codes and pinned wording
5779
+
5780
+ Every Problem code core mints is named here under its family ({§problem-error-carrier} carries it); the root lint (`scripts/problem-codes.mjs`) refuses a code no owning SPEC names.
5781
+
5782
+ §problems-dispatch **Dispatch Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5783
+
5784
+ | code | status | contract |
5785
+ |---|---:|---|
5786
+ | `target-required` | 400 | *OP* requires a target path. Recovery: Write the target in parentheses on the opening fence line: `OP (path)`. |
5787
+ | `scheme-not-found` | 501 | Scheme '*name*' is not registered. |
5788
+ | `scheme-metadata-unsupported` | 400 | *OP* on '*scheme*' does not accept the [metadata] modifier. |
5789
+ | `operation-not-implemented` | 501 | Scheme '*name*' does not implement *OP* (or exec). |
5790
+ | `entry-read-not-implemented` | 501 | The '*scheme*' scheme does not provide entry reads. |
5791
+ | `entry-write-not-implemented` | 501 | The '*scheme*' scheme does not provide entry writes. |
5792
+ | `entry-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide entry deletion. |
5793
+ | `channel-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide channel deletion. |
5794
+ | `scheme-handler-threw` | 500 | The '*scheme*' scheme did not produce a result for *OP*. |
5795
+ | `exec-source-not-data` | 501 | Scheme '*name*' is not a data source for an execution. |
5796
+ | `writer-forbidden` | 403 | Writer '*origin*' cannot modify scheme '*name*'. |
5797
+ | `capability-denied` | 403 | Capability '*route*' is denied by *scope* policy. |
5798
+ | `spawn-prompt-empty` | 422 | *OP* has no prompt text: the resource is empty and there is no body. |
5799
+ | `message-not-found` | 404 | No accepted message exists at *address*. |
5800
+ | `edit-collision` | 409 | EDIT collided with the current resource state ({§edit-collision}). Recovery: *n* of *m* edits applied. READ the target for current coordinates. |
5801
+ | `edit-target-required` | 400 | A line-anchored EDIT requires a target resource. Recovery: Provide the target that rendered the line anchor. |
5802
+ | `kill-target-required` | 400 | KILL requires a target path. |
5803
+ | `kill-target-scheme-required` | 400 | KILL target requires a scheme. |
5804
+ | `worker-not-found` | 404 | Worker '*name*' does not exist in this workspace. |
5805
+ | `entry-operation-unsupported` | 400 | KILL requires an entry-bearing target; '*scheme*' does not provide one. |
5806
+ | `resource-scheme-required` | 400 | Resource selection requires an address. |
5807
+ | `channel-required` | 400 | The '*scheme*' scheme has no default channel. Recovery: Address a named channel with a URI fragment. |
5808
+ | `binary-source-unsupported` | 415 | Channel #*name* is binary and its scheme keeps no bytes to transfer. |
5809
+ | `metadata-unsupported` | 400 | *OP* takes only the env option; '*key*' is not one. |
5810
+ | `worker-name-conflict` | 409 | Worker '*name*' already exists in this workspace. Recovery: To give '*name*' more work, write `SEND (worker://_name_)` with the task as the body; to start another worker, choose a name no worker holds. |
5811
+ | `no-operation` | 422 | The turn performed no operation ({§empty-turn}). |
5812
+ | `send-target-not-a-recipient` | 400 | The addressed scheme is not a SEND recipient. Recovery: A targetless SEND answers the open messages; a directed SEND requires a recipient that implements SEND. |
5813
+
5814
+ §problems-content **Content and transfer Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5815
+
5816
+ | code | status | contract |
5817
+ |---|---:|---|
5818
+ | `handler-crashed` | 415 | The *mimetype* content handler failed on *key*: *cause*. |
5819
+ | `line-anchor-unsupported` | 400 | The byte view of *target* publishes no anchors. Recovery: Use byte coordinates: `<first,last>`. |
5820
+ | `line-anchor-invalid` | 400 | A line anchor in the marker is malformed or names no current line ({§line-anchors}). |
5821
+ | `channel-not-found` | 404 | The addressed channel does not exist at *target*. Recovery: Use one of the available channels: #*a*, #*b*. |
5822
+ | `binary-read-unsupported` | 415 | The representation at *target* is binary and cannot be rendered. |
5823
+ | `pattern-unapplicable` | 422 | The pattern could not be applied to *target*. |
5824
+ | `move-region-overlap` | 409 | MOVE cannot insert a whole channel into itself and then remove that channel. |
5825
+ | `mimetype-mismatch` | 415 | COPY or MOVE cannot write '*source-mimetype*' into a '*destination-mimetype*' channel. |
5826
+ | `binary-region-unsupported` | 415 | Channel #*name* is binary and cannot receive a textual region. |
5827
+ | `copy-destination-exists` | 409 | COPY or MOVE destination *address* already contains different content. |
5828
+ | `proposal-apply-missing` | 500 | The source scheme accepted its MOVE proposal without applying the source mutation. |
5829
+ | `line-anchor-collision` | 409 | READ coordinates collided with current content at *target*. |
5830
+
5831
+ §problems-file **File scheme Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5832
+
5833
+ | code | status | contract |
5834
+ |---|---:|---|
5835
+ | `edit-empty` | 400 | EDIT requires at least one statement. Recovery: Provide an EDIT statement. |
5836
+ | `edit-batch-mismatch` | 400 | The EDIT batch spans multiple resources. Recovery: Submit a separate EDIT batch for each resource. |
5837
+ | `line-marker-required` | 400 | EDIT of an existing file requires a line marker ({§edit-marker-required-on-existing}). Recovery: Name the lines to replace with `<@hash>` or `<@start,@end>` from a READ of the file; `<L,1,L,1>` inserts before line L, and `<1,-1>` replaces the whole file. |
5838
+ | `creation-batch-conflict` | 409 | Multiple EDIT operations attempted to create the same file. Recovery: Create the file with one EDIT before applying additional edits. |
5839
+ | `member-read-only` | 403 | The mounted member '*path*' is read-only. |
5840
+ | `project-root-required` | 400 | The workspace has no project root, so it cannot write files. |
5841
+ | `path-names-no-file` | 403 | The spelling '*path*' does not name a file: it is empty, or it names a directory. |
5842
+ | `path-occupied-by-nonmember` | 403 | A non-member file already occupies '*path*'. Recovery: Choose an unoccupied member path. |
5843
+ | `path-outside-workspace` | 403 | A symlink on '*path*' resolves outside the namespace. |
5844
+ | `binary-write-unsupported` | 415 | A text EDIT cannot author binary '*mimetype*'; COPY or MOVE the bytes instead. |
5845
+ | `file-create-excluded` | 403 | A members exclusion (`!_glob_`) covers '*path*'. Recovery: Remove or disable the excluding members definition, or choose another path. |
5846
+ | `file-create-gitignored` | 403 | Active Git policy ignores '*path*', and no members definition includes it. Recovery: Choose a Git-admitted path or add a members definition that includes it. |
5847
+ | `file-materialization-limit` | 413 | The file exceeds the materialization byte limit and is not read into the workspace. |
5848
+ | `entry-not-found` | 404 | No member of this workspace is at '*path*'. Recovery: Check the path with FIND. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. |
5849
+
5850
+ §problems-exec **Execution Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5851
+
5852
+ | code | status | contract |
5853
+ |---|---:|---|
5854
+ | `invalid-input-target` | 400 | SEND input addresses an execution, without a channel or scope. |
5855
+ | `input-unavailable` | 409 | This execution's input receiver is no longer enabled. |
5856
+ | `stream-not-found` | 404 | No execution exists at the requested address. |
5857
+ | `input-closed` | 410 | Execution input is closed. |
5858
+
5859
+ §problems-entries **Entry scheme Problems (log, worker, entries).** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5860
+
5861
+ | code | status | contract |
5862
+ |---|---:|---|
5863
+ | `read-target-required` | 400 | READ requires a log coordinate. Recovery: Provide one exact log coordinate. |
5864
+ | `coordinate-malformed` | 400 | The log coordinate '*path*' is malformed. Recovery: Use one exact loop/turn/sequence coordinate. |
5865
+ | `worker-target-required` | 400 | EDIT requires a worker:// target. Recovery: Provide the worker target. |
5866
+ | `binary-edit-unsupported` | 415 | The #*channel* channel is binary and cannot be edited. |
5867
+ | `message-not-implemented` | 501 | SEND does not deliver messages to *scheme* entries. Recovery: To reply, SEND to an Open Message address or omit the target. `SEND (worker://<name>)` sends a new message. |
5868
+ | `worker-entity-not-editable` | 400 | A worker entity is not an editable entry. Recovery: EDIT requires an entry path, such as worker:///example.md. |
5869
+ | `message-empty` | 400 | SEND has no message text or attachments. |
5870
+ | `scope-unsupported` | 400 | A worker SEND takes no scope. |
5871
+
5872
+ §problems-functionality **Server and Functionality Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
5873
+
5874
+ | code | status | contract |
5875
+ |---|---:|---|
5876
+ | `service-starting` | 503 | The PLURNK service owns this listener but has not admitted its client interface yet. |
5877
+ | `configuration-unsupported` | 400 | Environment discovery reads this installation's declared configuration; client configuration contributes nothing. |
5878
+ | `name-reserved` | 400 | '*alias*' is plurnk's own: PLURNK_* configuration and provider credential names never reach a subprocess. |
5879
+ | `value-invalid` | 400 | '*alias*' needs a string value. |
5880
+ | `env-invalid` | 400 | `env` must be an object of string values; '*name*' is not a name a shell can export. |
5881
+ | `query-required` | 400 | discover takes a path or a glob. Recovery: Supply `{ "query": "<path or glob>" }`. |
5882
+ | `headless` | 409 | The workspace has no project root, so there are no file members. Recovery: Open the workspace on a project root. |
5883
+ | `definition-invalid` | 400 | A members definition is { glob }: a gitignore-style pattern, `!glob` to exclude; a skill definition names an installable skill. |
5884
+ | `model-scope` | 403 | The model may not change membership here: the members scope is none. Recovery: `git add` the file so git tracks it, or ask the operator to add it (/members add) or raise PLURNK_SERVICE_MEMBERS_MODEL_SCOPE. |
5885
+ | `registry-unreachable` | 502 | Skills registry *url* could not be reached. |
5886
+ | `registry-rejected` | 502 | Skills registry *url* answered *status*. |
5887
+ | `registry-invalid` | 502 | Skills registry *url* returned no skills array. |
5888
+ | `discover-failed` | 502 | Agent Skills source '*source*' could not be listed: *cause*. |
5889
+ | `alias-mismatch` | 400 | Alias '*alias*' must equal the skill name '*name*'. |
5890
+ | `scope-not-installable` | 400 | Service-provided skills can be enabled or disabled; adding a skill requires project or global scope. Recovery: Add it with scope "global" or open a workspace rooted in a project. |
5891
+ | `source-required` | 400 | Adding '*alias*' requires the standard installer source that provides it. |
5892
+ | `uninstall-failed` | 502 | Agent Skill '*name*' could not be removed from its *scope* root: *cause*. |
5893
+ | `workspace-not-found` | 404 | Workspace *id* does not exist. |
5894
+ | `state-not-json` | 400 | Worker module state is not JSON-serializable. |
5895
+ | `workspace-busy` | 409 | Workspace *id* is running an operation or another capability change. Recovery: Settle the current operation and retry the capability change. |
5896
+ | `not-configured` | 503 | No provider is configured for this worker. |
5897
+ | `model-worker-required` | 404 | No model worker exists for prompt injection (or to fork). |
5898
+ | `name-conflict` | 409 | The worker name is taken. Recovery: Choose another worker name. |
5899
+ | `offset-channel-required` | 400 | Recovery: Select the channel to read from the offset. |
5900
+ | `target-invalid` | 400 | Recovery: Use a scheme://path target. |
5901
+ | `proposal-not-pending` | 409 | Recovery: Refresh pending proposals before resolving one. |
5902
+ | `loop-policy-invalid` | 400 | An unattended loop cannot hold a proposal for review: nobody is present to answer. Recovery: State proposals accept or reject, or attend the loop. |
5903
+ | `scope-cancelled` | 499 | The worker scope was cancelled: *reason*. |
5904
+ | `range-not-satisfiable` | 416 | `Range <0,-1>` starts at 0, which is not a line; lines are numbered from 1. Recovery: Write `<1,-1>` to trim every line of the body; `KILL (log:///…/READ)` with no scope retires the item. |
5905
+ | `registry-not-configured` | 501 | Skills registry search is disabled; PLURNK_SERVICE_SKILLS_REGISTRY_URL is empty. |
5906
+ | `install-failed` | 502 | Agent Skill '*name*' could not be installed from '*source*': *cause* (or the installer reported it but its SKILL.md does not exist). |
5907
+ | `skill-missing` | 404 | Agent Skill '*alias*' is not installed under its *scope* root, or is not provided by this service. |
5908
+ | `skill-invalid` | 422 | Agent Skill '*alias*' is not a valid standard skill: *cause*. |
5909
+
5910
+ §pinned-wording-core **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.
5911
+
5912
+ | sentence | arises when |
5913
+ |---|---|
5914
+ | Nothing is in flight. Continuing. | a WAIT (or a premature terminal) with no live work ({§wait-obligation-matrix}) |
5915
+ | Completion deferred. Conclude with KILL alone. | a concluding KILL that carries other operations ({§kill-conclusion}) |
5916
+ | Context Token Budget Overflow: logTokensTotal exceeds logTokensMax; retained context cannot be admitted. | output admission over the retained-context ceiling |
5917
+ | This run is unattended: nobody is present to answer. | a capability that needs a present operator in an unattended loop ({§loop-attendance}) |
5918
+ | Worker name '*name*' must match `[A-Za-z0-9][A-Za-z0-9_-]{0,62}`. Recovery: Use 1–63 ASCII letters, digits, '_' or '-', starting with a letter or digit. | an invalid worker name |
5919
+ | Provide the client identifier. / Provide an absolute project path. / Use a positive integer limit. / prompt is not a non-empty string. | client input validation on the daemon's methods |
5920
+ | The stream was cancelled by KILL. | a stream terminal after KILL |
5921
+ | '*program*' exited with code *n*. | an execution's non-zero exit |
5922
+ | '*path*' is a directory, not a file; READ reads one file. Recovery: List its files with `FIND (_path_/)`, then READ one by its path. | READ of a directory |
5923
+ | The execution at ops://*worker*/*loop* has not concluded. | a bare READ of a running worker's result (425) |
5924
+ | The child provider failed. | a child's provider failure read back by its parent |
5925
+ | '*path*' exists on disk but is not a member of this workspace. Recovery: Admit it with `members (add)` and a `{"glob": "<path>"}` body. | a non-member on disk at the addressed path |
@@ -1 +1 @@
1
- {"package":"@plurnk/plurnk-service","version":"1.22.0","revision":"bd903eeab041b3438ce3c29786895bf8aa70848f","dirty":false}
1
+ {"package":"@plurnk/plurnk-service","version":"1.23.0","revision":"e00d5d950a3dbcffcfb030716253491edcdee430","dirty":false}
@@ -1,5 +1,5 @@
1
1
  import { TextCoordinates } from "@plurnk/plurnk-mimetypes";
2
- import Knob from "../core/Knob.js";
2
+ import { Knob } from "@plurnk/plurnk-meta";
3
3
  // {§body-projection} — one selector for ordinary packet previews and implicit
4
4
  // text acquisition. Explicit operation scopes never pass through this policy.
5
5
  export default class BodyPreview {
@@ -1 +1 @@
1
- {"version":3,"file":"body-preview.js","sourceRoot":"","sources":["../../src/content/body-preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,IAAI,MAAM,iBAAiB,CAAC;AAEnC,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,gGAAgG;IAChG,6FAA6F;IAC7F,MAAM,CAAC,SAAS;QACZ,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,IAAY;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QACjF,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC;YACnF,2EAA2E;YAC3E,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,YAAY,CAAC;gBACjD,CAAC,CAAC,CAAC;gBACH,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAE,CAAC,CAAC,MAAM,CAAC;QACvE,CAAC;QACD,IAAI,YAAY,GAAG,IAAI,CAAC,MAAM,IAAI,YAAY,IAAI,OAAO,EAAE,CAAC;YACxD,MAAM,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC;YAC1G,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACtB,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,YAAY,CAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC;YACvF,CAAC;YACD,MAAM,MAAM,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;YAC9D,IAAI,MAAM,KAAK,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;YACnG,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAC9H,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;IAC9D,CAAC;CACJ"}
1
+ {"version":3,"file":"body-preview.js","sourceRoot":"","sources":["../../src/content/body-preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE3C,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,gGAAgG;IAChG,6FAA6F;IAC7F,MAAM,CAAC,SAAS;QACZ,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,IAAY;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QACjF,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC;YACnF,2EAA2E;YAC3E,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,YAAY,CAAC;gBACjD,CAAC,CAAC,CAAC;gBACH,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAE,CAAC,CAAC,MAAM,CAAC;QACvE,CAAC;QACD,IAAI,YAAY,GAAG,IAAI,CAAC,MAAM,IAAI,YAAY,IAAI,OAAO,EAAE,CAAC;YACxD,MAAM,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC;YAC1G,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACtB,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,YAAY,CAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC;YACvF,CAAC;YACD,MAAM,MAAM,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;YAC9D,IAAI,MAAM,KAAK,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;YACnG,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAC9H,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;IAC9D,CAAC;CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"edit-receipt.d.ts","sourceRoot":"","sources":["../../src/content/edit-receipt.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACR,uBAAuB,EACvB,gBAAgB,EAEhB,WAAW,EAEX,oBAAoB,EACpB,mCAAmC,EACtC,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,YAAY,EACR,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,mCAAmC,GACtC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAClC;AA+DD,eAAO,MAAM,gBAAgB,8FAA8F,CAAC;AAqD5H,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,WAuBlD,CAAC;AAEF,eAAO,MAAM,sBAAsB,UAAW,OAAO,KAAG,gBA4BvD,CAAC;AAEF,eAAO,MAAM,qBAAqB,UAAW,OAAO,KAAG,SAAS,cAAc,EAqC7E,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAAa,gBAAgB,SAAS,MAAM,KAAG,WA0B7E,CAAC;AAyNF,eAAO,MAAM,WAAW,aACV,MAAM,WACP,MAAM,SACR,SAAS,WAAW,EAAE,gBACf,oBAAoB,aACvB,MAAM,KAClB,uBAoBF,CAAC;AAEF,eAAO,MAAM,0BAA0B,YAC1B,gBAAgB,eACZ,oBAAoB,GAAG,SAAS,KAC9C,gBAQF,CAAC;AAEF,eAAO,MAAM,0BAA0B,aACzB,MAAM,WACP,MAAM,YACL,gBAAgB,gBACZ,oBAAoB,aACvB,MAAM,KAClB,mCA4BF,CAAC"}
1
+ {"version":3,"file":"edit-receipt.d.ts","sourceRoot":"","sources":["../../src/content/edit-receipt.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACR,uBAAuB,EACvB,gBAAgB,EAEhB,WAAW,EAEX,oBAAoB,EACpB,mCAAmC,EACtC,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,YAAY,EACR,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,mCAAmC,GACtC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAClC;AA+DD,eAAO,MAAM,gBAAgB,8FAA8F,CAAC;AAqD5H,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,WAuBlD,CAAC;AAEF,eAAO,MAAM,sBAAsB,UAAW,OAAO,KAAG,gBA4BvD,CAAC;AAEF,eAAO,MAAM,qBAAqB,UAAW,OAAO,KAAG,SAAS,cAAc,EAqC7E,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAAa,gBAAgB,SAAS,MAAM,KAAG,WA0B7E,CAAC;AAgNF,eAAO,MAAM,WAAW,aACV,MAAM,WACP,MAAM,SACR,SAAS,WAAW,EAAE,gBACf,oBAAoB,aACvB,MAAM,KAClB,uBAoBF,CAAC;AAEF,eAAO,MAAM,0BAA0B,YAC1B,gBAAgB,eACZ,oBAAoB,GAAG,SAAS,KAC9C,gBAQF,CAAC;AAEF,eAAO,MAAM,0BAA0B,aACzB,MAAM,WACP,MAAM,YACL,gBAAgB,gBACZ,oBAAoB,aACvB,MAAM,KAClB,mCA4BF,CAAC"}
@@ -4,7 +4,7 @@ import { InvalidOperationResultError } from "@plurnk/plurnk-contracts";
4
4
  import LineMarkerOps from "./line-marker.js";
5
5
  import ScopeFormat from "./scope-format.js";
6
6
  import { TextCoordinates } from "@plurnk/plurnk-mimetypes";
7
- import Knob from "../core/Knob.js";
7
+ import { Knob } from "@plurnk/plurnk-meta";
8
8
  const receiptRecord = (value, label) => {
9
9
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
10
10
  throw new InvalidOperationResultError(`${label} must be an object.`);
@@ -314,14 +314,7 @@ const codePointEffects = (original, updated, edits) => {
314
314
  return effect;
315
315
  });
316
316
  };
317
- const contextRadius = () => {
318
- const raw = process.env.PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES;
319
- const value = Number(raw);
320
- if (!Number.isSafeInteger(value) || value < 0) {
321
- throw new Error(`PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES must be a non-negative safe integer, got ${JSON.stringify(raw)}`);
322
- }
323
- return value;
324
- };
317
+ const contextRadius = () => Knob.integer("PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES", 0);
325
318
  // {§edit-receipt-anchored-context} — with the resource's anchor identity, the resulting context
326
319
  // renders exactly as a READ does (`@xxxxx L:text`), so the next batch can cite the landed
327
320
  // lines by anchor without a READ. Without an identity it stays line-numbered.
@@ -332,7 +325,7 @@ const addContext = (effects, updated, identity) => {
332
325
  const width = LineAnchors.lineNumberWidth(updated);
333
326
  const prefix = (line) => anchors === null
334
327
  ? `${line}:`
335
- : `${anchors[line - 1]} ${String(line).padStart(width)}:`;
328
+ : `${String(line).padStart(width)}<${anchors[line - 1]}>`;
336
329
  return effects.map((effect) => {
337
330
  const selected = new Set();
338
331
  const addRange = (first, last) => {