@plurnk/plurnk-service 0.25.0 → 0.27.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 (119) hide show
  1. package/SPEC.md +66 -46
  2. package/dist/Paths.d.ts +0 -1
  3. package/dist/Paths.d.ts.map +1 -1
  4. package/dist/Paths.js +3 -6
  5. package/dist/Paths.js.map +1 -1
  6. package/dist/content/path-mimetype.js +2 -2
  7. package/dist/content/path-mimetype.js.map +1 -1
  8. package/dist/core/ChannelWrite.d.ts +17 -2
  9. package/dist/core/ChannelWrite.d.ts.map +1 -1
  10. package/dist/core/ChannelWrite.js +11 -5
  11. package/dist/core/ChannelWrite.js.map +1 -1
  12. package/dist/core/Engine.d.ts +4 -5
  13. package/dist/core/Engine.d.ts.map +1 -1
  14. package/dist/core/Engine.js +149 -49
  15. package/dist/core/Engine.js.map +1 -1
  16. package/dist/core/ExecutorRegistry.d.ts +3 -0
  17. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  18. package/dist/core/ExecutorRegistry.js +8 -1
  19. package/dist/core/ExecutorRegistry.js.map +1 -1
  20. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  21. package/dist/core/SchemeRegistry.js +39 -15
  22. package/dist/core/SchemeRegistry.js.map +1 -1
  23. package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
  24. package/dist/core/caps/DbNotifyCaps.js +4 -1
  25. package/dist/core/caps/DbNotifyCaps.js.map +1 -1
  26. package/dist/core/caps/DbSubscriptionCaps.d.ts +1 -1
  27. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  28. package/dist/core/caps/DbSubscriptionCaps.js +2 -2
  29. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  30. package/dist/core/fork.js +1 -1
  31. package/dist/core/fork.js.map +1 -1
  32. package/dist/core/git-membership.d.ts.map +1 -1
  33. package/dist/core/git-membership.js +107 -46
  34. package/dist/core/git-membership.js.map +1 -1
  35. package/dist/core/git-state.js +2 -2
  36. package/dist/core/packet-wire.js +5 -5
  37. package/dist/core/packet-wire.js.map +1 -1
  38. package/dist/core/plugin-trust.d.ts +4 -0
  39. package/dist/core/plugin-trust.d.ts.map +1 -0
  40. package/dist/core/plugin-trust.js +23 -0
  41. package/dist/core/plugin-trust.js.map +1 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/schemes/Exec.d.ts.map +1 -1
  47. package/dist/schemes/Exec.js +78 -20
  48. package/dist/schemes/Exec.js.map +1 -1
  49. package/dist/schemes/File.d.ts.map +1 -1
  50. package/dist/schemes/File.js +17 -14
  51. package/dist/schemes/File.js.map +1 -1
  52. package/dist/schemes/Log.js +6 -6
  53. package/dist/schemes/Log.js.map +1 -1
  54. package/dist/schemes/Plurnk.js +4 -4
  55. package/dist/schemes/Plurnk.js.map +1 -1
  56. package/dist/schemes/_entry-chunk.d.ts +11 -0
  57. package/dist/schemes/_entry-chunk.d.ts.map +1 -0
  58. package/dist/schemes/_entry-chunk.js +100 -0
  59. package/dist/schemes/_entry-chunk.js.map +1 -0
  60. package/dist/schemes/_entry-find.d.ts.map +1 -1
  61. package/dist/schemes/_entry-find.js +6 -2
  62. package/dist/schemes/_entry-find.js.map +1 -1
  63. package/dist/schemes/_entry-graph.js +1 -1
  64. package/dist/schemes/_entry-graph.js.map +1 -1
  65. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  66. package/dist/schemes/_entry-manifest.js +14 -6
  67. package/dist/schemes/_entry-manifest.js.map +1 -1
  68. package/dist/schemes/_entry-ops.js +2 -2
  69. package/dist/schemes/_entry-ops.js.map +1 -1
  70. package/dist/schemes/_entry-semantic.d.ts +22 -2
  71. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  72. package/dist/schemes/_entry-semantic.js +96 -9
  73. package/dist/schemes/_entry-semantic.js.map +1 -1
  74. package/dist/schemes/exec-env.d.ts +5 -0
  75. package/dist/schemes/exec-env.d.ts.map +1 -0
  76. package/dist/schemes/exec-env.js +29 -0
  77. package/dist/schemes/exec-env.js.map +1 -0
  78. package/dist/server/Daemon.d.ts +1 -1
  79. package/dist/server/Daemon.d.ts.map +1 -1
  80. package/dist/server/Daemon.js +11 -1
  81. package/dist/server/Daemon.js.map +1 -1
  82. package/dist/server/dsl.d.ts +2 -2
  83. package/dist/server/dsl.js +2 -2
  84. package/dist/server/envelope.d.ts +1 -0
  85. package/dist/server/envelope.d.ts.map +1 -1
  86. package/dist/server/envelope.js +5 -5
  87. package/dist/server/envelope.js.map +1 -1
  88. package/dist/server/methods/entry_read.d.ts.map +1 -1
  89. package/dist/server/methods/entry_read.js +29 -7
  90. package/dist/server/methods/entry_read.js.map +1 -1
  91. package/dist/server/methods/log_read.d.ts.map +1 -1
  92. package/dist/server/methods/log_read.js +14 -1
  93. package/dist/server/methods/log_read.js.map +1 -1
  94. package/dist/server/methods/loop_inject.d.ts +5 -0
  95. package/dist/server/methods/loop_inject.d.ts.map +1 -0
  96. package/dist/server/methods/loop_inject.js +58 -0
  97. package/dist/server/methods/loop_inject.js.map +1 -0
  98. package/dist/server/methods/loop_run.d.ts.map +1 -1
  99. package/dist/server/methods/loop_run.js +6 -3
  100. package/dist/server/methods/loop_run.js.map +1 -1
  101. package/dist/server/methods/op_edit.js +1 -1
  102. package/dist/server/methods/op_edit.js.map +1 -1
  103. package/dist/server/methods/op_find.js +1 -1
  104. package/dist/server/methods/op_find.js.map +1 -1
  105. package/dist/server/methods/op_fold.js +1 -1
  106. package/dist/server/methods/op_open.js +1 -1
  107. package/dist/server/methods/run_fork.d.ts +5 -0
  108. package/dist/server/methods/run_fork.d.ts.map +1 -0
  109. package/dist/server/methods/run_fork.js +42 -0
  110. package/dist/server/methods/run_fork.js.map +1 -0
  111. package/dist/server/methods/session_constraints.js +6 -6
  112. package/dist/server/methods/session_constraints.js.map +1 -1
  113. package/dist/server/methods/session_create.d.ts +1 -0
  114. package/dist/server/methods/session_create.d.ts.map +1 -1
  115. package/dist/server/methods/session_create.js +32 -0
  116. package/dist/server/methods/session_create.js.map +1 -1
  117. package/migrations/0000-00-00.01_schema.sql +29 -15
  118. package/package.json +108 -111
  119. package/requirements.md +1 -2
package/SPEC.md CHANGED
@@ -21,7 +21,7 @@ Canonical meanings. When a doc, comment, test name, or commit message uses one o
21
21
  | **turn** | One round-trip with the LLM (or one client RPC dispatch). One assembled prompt sent, one parsed response handled. Many turns per loop. Identity: `(loop_id, sequence)`. |
22
22
  | **op** | One DSL operation the model emits. Parsed into a `PlurnkStatement`. Examples: `EDIT`, `READ`, `SEND`, `FIND`, `COPY`, `MOVE`, `OPEN`, `FOLD`, `EXEC`. One turn produces zero or more ops. |
23
23
  | **statement** | Synonym for parsed op. The AST shape `PlurnkStatement` from `@plurnk/plurnk-grammar`. |
24
- | **action** | One executed op. Action and op are the same thing in different states (op = parsed; action = executed). The execution produces a log_entries row at `log://<L>/<T>/<S>/<op>`. |
24
+ | **action** | One executed op. Action and op are the same thing in different states (op = parsed; action = executed). The execution produces a log_entries row at `log:///<L>/<T>/<S>/<op>`. |
25
25
  | **dispatch** | The engine routing a statement to its scheme's op handler. |
26
26
 
27
27
  ### §storage-terms Storage terms
@@ -128,7 +128,7 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
128
128
 
129
129
  **Question.** A session holds many runs — model, client, plurnk (§lifecycle-terms, §authority-terms) — over one shared manifest. What keeps one run's activity out of another's conversation; what are the *only* ways a run's work reaches another; and does the engine's own work obey the boundary or get a privileged back channel?
130
130
 
131
- **Decision — isolation by run; the model is not privileged.** A packet renders exactly one run's log — the assembling run's — against the session's shared manifest (§packet). A run cannot see another's log: isolation is *structural*, a consequence of "a run owns its log entries" (§lifecycle-terms) and "one packet, one run," never an `origin` filter at render time. `origin` (§authority-terms) is **attribution** — the delta's provenance (§env-delta) — never read to hide a row. {§actor-boundary-isolation} {§actor-boundary-origin-not-filter}
131
+ **Decision — isolation by run; the model is not privileged.** A packet renders exactly one run's log — the assembling run's — against the session's shared manifest (§packet). A run cannot see another's log: isolation is *structural*, a consequence of "a run owns its log entries" (§lifecycle-terms) and "one packet, one run," never an `origin` filter at render time. `origin` (§authority-terms) is **attribution** — the delta's provenance (§env-delta) — never read to filter a row. {§actor-boundary-isolation} {§actor-boundary-origin-not-filter}
132
132
 
133
133
  **Two doors, and only two.** A run's work reaches another run by exactly two channels, and a private log is reachable no other way:
134
134
  - the **environment door** — a write to a *shared entry* surfaces to every run sharing it as a folded, attributed delta (§env-delta). *State.*
@@ -144,25 +144,31 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
144
144
 
145
145
  **Migration path.** Largely realized: `Engine.dispatch` is origin-agnostic; client ops run in a per-connection client loop (`_dispatchAsClient`); plurnk EDITs already carry `origin=plurnk`. The keystone is **built** — `dispatchAsPlurnk` spawns the session's reserved `plurnk` run and fires ops through dispatch, mirroring `_dispatchAsClient`. What remains is *repatriation* — the inline plurnk dispatches still bolted into the model's loop (the §env-delta materialization, the manifest build, git auto-add) move onto it.
146
146
 
147
- **The keystone's first use: operator reference docs.** `PLURNK_MD_<ALIAS>=<path>` (§operator-config) materializes `<path>` as a `plurnk://<ALIAS>.md` entry — a `dispatchAsPlurnk` EDIT in the plurnk run, **not** the model's — and the model's turn-0 foists a READ of it. The model reads the doc inline while the materializing EDIT stays out of its log: idiomatic context injection, an ordinary entry + READ rather than a bespoke packet section. The same `PLURNK_MD_*` convention cascades to clients. {§actor-boundary-doc-injection}
147
+ **The keystone's first use: operator reference docs.** `PLURNK_MD_<ALIAS>=<path>` (§operator-config) materializes `<path>` as a `plurnk:///<ALIAS>.md` entry — a `dispatchAsPlurnk` EDIT in the plurnk run, **not** the model's — and the model's turn-0 foists a READ of it. The model reads the doc inline while the materializing EDIT stays out of its log: idiomatic context injection, an ordinary entry + READ rather than a bespoke packet section. The same `PLURNK_MD_*` convention cascades to clients. {§actor-boundary-doc-injection}
148
+
149
+ **Manifest preview.** `PLURNK_MANIFEST_ITEMS` foists a turn-0 READ of `plurnk:///manifest.json` into the model's first turn — the same plurnk-origin foist as the docs — so a run opens with the session catalog instead of blank. `-1` reads the full manifest; a positive `N` slices to the first N items (jsonpath `$[0:N]` — the catalog is JSON); unset / `0` foists nothing. The READ is sequenced *after* the per-turn manifest write, so it hits the catalog rather than 404ing. {§actor-boundary-manifest-preview}
148
150
 
149
151
  ### §machine-processes The machine and its processes: session, run, fork
150
152
 
151
- **Question.** §actor-boundary isolates runs and lets the runtime self-host, but it stands on an ownership model it never states: what does a *session* own versus a *run*; what is shared versus private; and what does a fork carry? Unstated, the downstream questions — which run `log.read` reads, what a fork copies, where a per-client view of the workspace would live — grow subtle, then metastasize. Drawn once, they vanish.
153
+ **Question.** §actor-boundary isolates runs and lets the runtime self-host, but it stands on an ownership model it never states: what does a *session* own versus a *run*; what is shared versus private; and what does a fork carry? Unstated, the downstream questions — which run `log.read` reads, what a fork copies, where a per-client window onto the workspace would live — grow subtle, then metastasize. Drawn once, they vanish.
152
154
 
153
- **Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk://manifest.json` (§packet) — under one membership overlay (§membership). Exactly one filesystem and one overlay per session; neither is per-run. A **run** is a process whose entire private memory is its **log** (§lifecycle-terms) — its loops, turns, and rows, each row carrying its own content, attribution (`origin`/`source`, §env-delta), and fold-state (`indexed`). A run owns **no entries** and **no membership**; even its visibility is not a possession but a bit on its own rows. It is a *history over the shared world, not a world*.
155
+ **Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk:///manifest.json` (§packet) — under one membership overlay (§membership). Exactly one filesystem and one overlay per session; neither is per-run. A **run** is a process whose entire private memory is its **log** (§lifecycle-terms) — its loops, turns, and rows, each row carrying its own content, attribution (`origin`/`source`, §env-delta), and fold-state (`indexed`). A run owns **no entries** and **no membership**; even its visibility is not a possession but a bit on its own rows. It is a *history over the shared world, not a world*.
154
156
 
155
157
  **One filesystem.** The entries are the session's: `entries.session_id`, never a run. A write by any run is a write to the one filesystem every run reads; there is no per-run entry set. {§machine-processes-one-filesystem}
156
158
 
157
159
  **One overlay.** Membership — `git ls-files ∪ add − ignore` with read-only (§membership) — is the session's: `session_constraints.session_id`, never a run. It is workspace *curation*, and the workspace *is* the session; two runs are two conversations about one curated workspace and see the same one. Divergent membership is a different session, never a per-run overlay. {§machine-processes-one-overlay}
158
160
 
159
- **A run is its log — and nothing beside.** The run-private state is the log and only the log. *What I am looking at* (OPEN/FOLD) is `log_entries.indexed`, a bit on the run's own rows, toggled by ordinary `log://` ops — not a second store, and never membership (§open-fold). *What I last saw* needs no shadow either: a run learns its world moved through log entries (§env-delta) — a sibling's write broadcast into its log, an out-of-band disk change detected against the entry's own content and broadcast the same way — never through a per-run snapshot the run cannot see. The log is the whole of a run's memory. {§machine-processes-run-is-its-log}
161
+ **A run is its log — and nothing beside.** The run-private state is the log and only the log. *What I am looking at* (OPEN/FOLD) is `log_entries.indexed`, a bit on the run's own rows, toggled by ordinary `log:///` ops — not a second store, and never membership (§open-fold). *What I last saw* needs no shadow either: a run learns its world moved through log entries (§env-delta) — a sibling's write broadcast into its log, an out-of-band disk change detected against the entry's own content and broadcast the same way — never through a per-run snapshot the run cannot see. The log is the whole of a run's memory. {§machine-processes-run-is-its-log}
162
+
163
+ **A run's log is private to packets, not to the session.** Isolation (§actor-boundary) governs what an *actor* sees — its own run, never a sibling's. It does not wall off the *wire*: any connection may read any run's log in its session by id — `log.read({ runId })`, ownership-verified, defaulting to the connection's own run. This is how a conversation client reads the **model** run, where the conversation lives: `loop.run` returns its `modelRunId`, and `session.runs` enumerates a session's runs for a connection that did not drive it live. The read is observation, never packet membership — no actor sees it. {§machine-processes-model-run-readable}
164
+
165
+ **A run carries its actor.** Each run records its `origin` — `model` (the conversation), `client` (a connection's own run), or `plurnk` (the runtime's self-hosting run) — set once at creation and inherited by a fork. `session.runs` returns it, so a conversation client identifies the model run by its actor, not by parsing a renameable name. {§machine-processes-run-origin}
160
166
 
161
167
  **Fork — copy the log, share the world.** A fork is a new run in the *same* session (`runs.parent_run_id`, §lifecycle-terms). It copies the **log** — the rows, their fold-state riding along — so the branch inherits everything the parent observed (§env-delta makes a run's timeline self-contained for exactly this) and diverges freely after. {§machine-processes-fork-copies-the-log} It shares the **world** — the one filesystem, the one overlay — live and uncopied, because the run never owned it. {§machine-processes-fork-shares-the-world}
162
168
 
163
169
  **A session cannot be forked.** There is nothing to branch — a session *is* the shared ground. `runs` carries `parent_run_id`; `sessions` carries no parent. Parallel histories over one workspace are forks of its runs; a divergent workspace is a new session. {§machine-processes-no-fork-session}
164
170
 
165
- **Rationale.** The model falls out of one correction: *a run is a history over a shared world, not a world.* Entries are the world (session); the log is the history (run); forking a history need not copy the world, and a run accumulates nothing the log does not already hold. The overlay's session home is forced the same way — it is the world's curation, and the world is shared; per-run it fragments the one manifest, forks the membership read-gate (the §membership security line), and duplicates what FOLD already does at the right level. Every "which run / what's copied / where's the per-client view" answers itself once the world/log line is drawn.
171
+ **Rationale.** The model falls out of one correction: *a run is a history over a shared world, not a world.* Entries are the world (session); the log is the history (run); forking a history need not copy the world, and a run accumulates nothing the log does not already hold. The overlay's session home is forced the same way — it is the world's curation, and the world is shared; per-run it fragments the one manifest, forks the membership read-gate (the §membership security line), and duplicates what FOLD already does at the right level. Every "which run / what's copied / where's the per-client window" answers itself once the world/log line is drawn.
166
172
 
167
173
  **Migration path.** Mostly stating what the schema already carries: `runs.parent_run_id` and the parentless `sessions` exist (§lifecycle-terms); `session_constraints` is session-level (§membership); §env-delta already makes a run's timeline self-contained, so a fork's log copy suffices. Additive: `run.fork` over the wire (the engine fork is built). Two repatriations: §actor-boundary's "read-only overlay scopes a run's writable surface" becomes a *session* policy bounding every run uniformly; and the §env-delta environment door has shed its per-run snapshot — a run's only memory is its log, so drift is pulled from the shared log (other actors' edits since the run's last turn) and the filesystem narrates its own through the `plurnk` run, both already log entries, never a per-run shadow.
168
174
 
@@ -358,7 +364,7 @@ entry.channels[r.channel] = { mimetype: r.mimetype, tokens: tokenize(r.content),
358
364
  if (isBody) await EntryGraph.populateFrom(db, sessionId, r.entry_id, result.symbols ?? [], result.references ?? []);
359
365
  ```
360
366
 
361
- `hint` short-circuits detection. The service consumes `totalLines` (extent), `symbols`/`references` (the `@graph` index), and `deepJson`/`deepXml` (matcher dispatch); never a rendered preview — content reaches the model on READ. Because this pass runs every assembly over every entry, any content change — by any writer — is reflected in the next packet's index. The `@graph` index is NOT engine *ranking* (the anti-pattern): it's a complete, unranked index the model queries via `FIND @<sym`, the manifest paradigm applied to structure, uniform across schemes (`file://` is the primary case).
367
+ `hint` short-circuits detection. The service consumes `totalLines` (extent), `symbols`/`references` (the `@graph` index), and `deepJson`/`deepXml` (matcher dispatch); never a rendered preview — content reaches the model on READ. Because this pass runs every assembly over every entry, any content change — by any writer — is reflected in the next packet's index. The `@graph` index is NOT engine *ranking* (the anti-pattern): it's a complete, unranked index the model queries via `FIND @<sym`, the manifest paradigm applied to structure, uniform across schemes (`file:///` is the primary case).
362
368
 
363
369
  **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`.
364
370
 
@@ -378,7 +384,7 @@ Schemes MAY declare multiple channels (`exec`: stdout/stderr/stdin; `http`: body
378
384
 
379
385
  ### §no-visibility Entries carry no visibility
380
386
 
381
- Every entry is uniformly listed in `plurnk://manifest.json` (§packet) and READable — entries have no per-run open/folded state. Context curation is the model's, on the **log** (via OPEN/FOLD, §open-fold), never on entries.
387
+ Every entry is uniformly listed in `plurnk:///manifest.json` (§packet) and READable — entries have no per-run open/folded state. Context curation is the model's, on the **log** (via OPEN/FOLD, §open-fold), never on entries.
382
388
 
383
389
  ### §channel-mimetype Mimetype is a (scheme, channel) property — never a default
384
390
 
@@ -399,27 +405,27 @@ Rules:
399
405
  5. Non-default channel EDIT requires entry to exist (404 if absent); default-channel EDIT creates. {§channel-selection-fragment-on-nonexistent-404}
400
406
  | URI | Channel |
401
407
  |---|---|
402
- | `known://france/capital` | body (default) |
403
- | `known://france/capital#symbols` | symbols |
404
- | `exec://sh/1/1/2#stdout` | stdout |
405
- | `exec://sh/1/1/2#stderr` | stderr |
408
+ | `known:///france/capital` | body (default) |
409
+ | `known:///france/capital#symbols` | symbols |
410
+ | `exec:///sh/1/1/2#stdout` | stdout |
411
+ | `exec:///sh/1/1/2#stderr` | stderr |
406
412
  | `sse://feed/y#data` | data |
407
- | `log://N/T/A` | (no channel concept; atomic log row) |
413
+ | `log:///N/T/A` | (no channel concept; atomic log row) |
408
414
 
409
415
  Op implications:
410
416
 
411
417
  - EDIT to undeclared channel → 400; read-only channel → 405.
412
418
  - COPY/MOVE with fragment is per-channel; design deferred until needed.
413
419
 
414
- RPC params carry fragments inline via the `target` string (`{ target: "known://x#stderr" }`).
420
+ RPC params carry fragments inline via the `target` string (`{ target: "known:///x#stderr" }`).
415
421
 
416
422
  **Wire rendering: default channel is path-only.** Heredoc fence 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`.
417
423
 
418
424
  ```
419
425
  <<notes.md:...:notes.md — file scheme (bare)
420
- <<exec://sh/1/1/2:...:exec://sh/1/1/2 — exec default (stdout)
421
- <<exec://sh/1/1/2#stderr:...:exec://sh/1/1/2#stderr — non-default
422
- <<log://1/1/0:...:log://1/1/0 — atomic log row
426
+ <<exec:///sh/1/1/2:...:exec:///sh/1/1/2 — exec default (stdout)
427
+ <<exec:///sh/1/1/2#stderr:...:exec:///sh/1/1/2#stderr — non-default
428
+ <<log:///1/1/0:...:log:///1/1/0 — atomic log row
423
429
  ```
424
430
 
425
431
  ### §channel-state Channel state — metadata, not gating
@@ -462,7 +468,7 @@ AST: `{ op: "READ", target, body: MatcherBody | null, signal: tags | null, lineM
462
468
 
463
469
  AST: `{ op: "OPEN"|"FOLD", target, body: MatcherBody | null, signal: tags | null, lineMarker? }`.
464
470
 
465
- OPEN/FOLD operate on the **log** (`log://`) — the model's context-curation surface (§packet). FOLD collapses a log row to its path; OPEN restores its body. Non-destructive: rows and bodies persist, re-OPENable. Entries carry no visibility (§no-visibility), so OPEN/FOLD against an entry scheme returns 501.
471
+ OPEN/FOLD operate on the **log** (`log:///`) — the model's context-curation surface (§packet). FOLD collapses a log row to its path; OPEN restores its body. Non-destructive: rows and bodies persist, re-OPENable. Entries carry no visibility (§no-visibility), so OPEN/FOLD against an entry scheme returns 501.
466
472
 
467
473
  ### §copy COPY (engine-orchestrated)
468
474
 
@@ -510,11 +516,13 @@ AST: `{ op: "EXEC", target (cwd), body: string | null (command), signal: string
510
516
 
511
517
  Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI). The runtime slot (`signal`) selects an executor, resolved against the boot-time `ExecutorRegistry` — siblings discovered and probed at startup, availability cached, default `sh`. Unknown or unavailable runtime → 501 carrying the probe `detail`. {§exec-registry-resolves}
512
518
 
513
- **Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose** (lifecycle §proposal): the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec://<runtime>/<loop>/<turn>/<seq>` entry (the executor is the URI authority; the coordinate that follows matches the op's log-row coordinate, e.g. `exec://sh/1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive the model's view at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
519
+ **Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose** (lifecycle §proposal): the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec:///<runtime>/<loop>/<turn>/<seq>` entry (the executor is the URI authority; the coordinate that follows matches the op's log-row coordinate, e.g. `exec:///sh/1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive what the model sees at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
514
520
 
515
521
  A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run** in-process: no proposal, no human gate, no notification. The run is awaited synchronously and its channel content rides back as the EXEC result body the same turn — not streamed to the entry for a next-turn read. {§exec-readpure-inline}
516
522
 
517
- `SEND[499](exec://<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§stream-control).
523
+ `SEND[499](exec:///<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§stream-control).
524
+
525
+ **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-run 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. {§exec-env-scoped}
518
526
 
519
527
  ### §proposal The proposal lifecycle
520
528
 
@@ -550,6 +558,8 @@ READ on a streaming scheme is a subscription, not a one-shot. Scheme opens the c
550
558
 
551
559
  Subscription registry is plurnk-service runtime state (its own SQLite table). Exists ONLY for cancellation routing. Channel state (§channel-state) + log entries (§no-chunk-rows) carry lifecycle.
552
560
 
561
+ FOLD/OPEN toggles `log_entries.indexed` (§open-fold) — a per-run render bit, never the subscription registry. FOLDing a streaming entry's log row collapses its body out of the packet but leaves the live stream running: curation is render-only, never cancellation. {§subscriptions-fold-keeps-subscription}
562
+
553
563
  ### §chunk-accumulation Chunk accumulation
554
564
 
555
565
  SSE event types, WS message types, exec stdout/stderr each map to a named channel. Channel record (`ChannelContent`): `content`, `mimetype`, `tokens`. Active-connection state lives in the subscription registry, not on the channel. Chunks accumulate into the channel as they arrive — not buffered until close. {§chunk-accumulation-chunks-accumulate}
@@ -653,9 +663,9 @@ Plugin discovery (§plugin-discovery) registers whatever's in `node_modules/@plu
653
663
  | `Known.ts` | `@plurnk/plurnk-schemes-known` | Primary narrative entries; session-scoped. |
654
664
  | `Unknown.ts` | `@plurnk/plurnk-schemes-unknown` | Open questions / decomposition. |
655
665
  | `Skill.ts` | `@plurnk/plurnk-schemes-skill` | Skill docs; same shape as known. |
656
- | `Plurnk.ts` | may stay in-tree | `plurnk://prompt/<loop_id>` carries each loop's prompt. Model-origin writes to `plurnk://prompt/*` rejected in-handler. |
657
- | `Log.ts` | may stay in-tree | Read-only coordinate-addressed (`log://<L>/<T>/<S>`). Renders as JSON meta line in packet log; status ≥ 400 mirrors to `packet.user.telemetry.errors[]` (§telemetry). |
658
- | `File.ts` | `@plurnk/plurnk-schemes-file` | Filesystem-backed. **Model is never trained on `file://` and never sees it.** Bare paths are model-facing; `file://` accepted as input, renders bare. |
666
+ | `Plurnk.ts` | may stay in-tree | `plurnk:///prompt/<loop_id>` carries each loop's prompt. Model-origin writes to `plurnk:///prompt/*` rejected in-handler. |
667
+ | `Log.ts` | may stay in-tree | Read-only coordinate-addressed (`log:///<L>/<T>/<S>`). Renders as JSON meta line in packet log; status ≥ 400 mirrors to `packet.user.telemetry.errors[]` (§telemetry). |
668
+ | `File.ts` | `@plurnk/plurnk-schemes-file` | Filesystem-backed. **Model is never trained on `file:///` and never sees it.** Bare paths are model-facing; `file:///` accepted as input, renders bare. |
659
669
  | `Exec.ts` | stays in-tree | Dispatches EXEC op to runtime executors registered via [plurnk-execs](https://github.com/plurnk/plurnk-execs). |
660
670
 
661
671
  **Executors in-tree:** none. Framework + every runtime are siblings. `Exec.ts` dispatches by the EXEC op's `runtime` slot (`sh` default, `node`, `python`) to the matching sibling. Today's registry is hardcoded; plugin discovery migration tracked in [plurnk-execs#1](https://github.com/plurnk/plurnk-execs/issues/1).
@@ -703,7 +713,8 @@ Model selection: separate alias cascade in `ProviderRegistry` (§provider-instan
703
713
  | `PLURNK_MIN_CYCLES` | `3` | enforced | Min repetitions before cycle detection fires (§engine-rails). |
704
714
  | `PLURNK_MAX_CYCLE_PERIOD` | `4` | enforced | Max period length cycle detection examines (§engine-rails). |
705
715
  | `PLURNK_PERSONA` | `persona.md` | enforced | Path to the default persona file. Tail of the persona cascade: loops.persona > runs.persona > sessions.persona > this file. |
706
- | `PLURNK_MD_<ALIAS>` | (unset) | enforced | Operator reference doc: materializes `<path>` as `plurnk://<ALIAS>.md`, auto-READ into every model run's turn 0 (§actor-boundary). `~` expands to home. |
716
+ | `PLURNK_MD_<ALIAS>` | (unset) | enforced | Operator reference doc: materializes `<path>` as `plurnk:///<ALIAS>.md`, auto-READ into every model run's turn 0 (§actor-boundary). `~` expands to home. |
717
+ | `PLURNK_MANIFEST_ITEMS` | `0` | enforced | Turn-0 manifest preview foisted into the model's first turn. `-1` = full `plurnk:///manifest.json`; positive `N` = the first N items (jsonpath slice); `0` / unset = off (§actor-boundary-manifest-preview). |
707
718
  | `PLURNK_PROPOSAL_TIMEOUT_MS` | `300000` | enforced | ms wait for a proposed entry (status=202) to be resolved before timing out. |
708
719
  | `PLURNK_PROVIDERS_REASON_LEVEL` | `0` | enforced | Reasoning **magnitude** sent to the providers: `0` = none, positive = effort/budget the provider module translates to wire format (o-series tiers, Anthropic `budget_tokens`). The on/off is the `PLURNK_PROVIDERS_REASONING` gate. |
709
720
  | `PLURNK_PLAN` | `0` | enforced | Enable the grammar's `<<PLAN` op — advertised in the `# Plurnk System Tools` packet section (§tools). `1` on, `0` off. |
@@ -976,7 +987,7 @@ Each entry: question, answer, rationale, migration path.
976
987
  - **Provider tokens, stored at write.** `provider.countTokens` is the source of truth; `entry_channels.tokens` (via `_entry-crud`) and `log_entries.tokens` (via `Engine.#writeLog`) are populated at write as a write-time snapshot. A `ceil(len/DIVISOR)` fallback (the divisor tripwire) applies only when no provider tokenizer is wired. {§tokenomics-tokens-stored-at-write}
977
988
  - **Render-weight budget.** The budget headline — `ceiling`, `tokenUsage`, `tokensFree` — is measured from the *assembled packet* (placeholders substituted after measuring), so it reflects what the model actually receives. A `SUM` of stored content-depth would mis-price the rendered packet; render-weight is the accurate measure. {§tokenomics-render-weight-budget}
978
989
  - **Per-turn weight.** A markdown table groups render-weight by turn — the `loop/turn` coordinate prefix — oldest first, the grinder's rollback unit. The model sees which turns are fat and what the rail folds first, and can FOLD ahead of it. {§tokenomics-turn-totals}
979
- - **Heaviest entries.** A second table lists the ten heaviest log entries by render-weight, each by its `log://<coord>/<op>` handle — the FOLD targets behind the turn weight. The handle carries the turn, so the two tables interlock. {§tokenomics-largest-entries}
990
+ - **Heaviest entries.** A second table lists the ten heaviest log entries by render-weight, each by its `log:///<coord>/<op>` handle — the FOLD targets behind the turn weight. The handle carries the turn, so the two tables interlock. {§tokenomics-largest-entries}
980
991
  - **Context-window percent.** The headline carries usage as a percent of the ceiling — `usage Y (P%)` — a fullness gauge beside the absolutes. Reads the ceiling already in hand; no extra provider call. {§tokenomics-context-percent}
981
992
  - **Depth re-counted at render.** The manifest re-tokenizes each entry's `tokens` through the live provider at build — never the write-time snapshot — so a model change between loops can't stale the catalog. Every token figure in the packet is render-fresh, manifest and budget alike; nothing trusts a cross-loop cached total.
982
993
  - **Over-budget is honest.** When usage exceeds the ceiling, `free` floors at 0 and the percent passes 100 — the readout shows the overshoot rather than a negative free, so the model knows it's over and curates down. {§tokenomics-over-budget-floor}
@@ -996,21 +1007,30 @@ Each entry: question, answer, rationale, migration path.
996
1007
 
997
1008
  **The boundary is the client's.** The client owns the model's filesystem access in both directions: reads are membership-gated (a file is invisible to the model unless it is a member), and writes are proposals the client accepts or rejects (`yolo` auto-accepts). Writing an entry never implies writing to disk — entries are canonical in the store; disk only moves when the client accepts a side-effecting proposal, and only where `project_root` is set (null = headless, client owns materialization).
998
1009
 
999
- **Built — git-substrate membership.** {§membership-git-membership}
1010
+ **Tiersession is the world; permissions are the session's.** Membership, the overlay, and the git flags are **session-tier** (`session_constraints.session_id`, service/session config) — never per-run. Every run in a session shares one world (§machine-processes: one filesystem, one overlay); a run is a *log* — a perspective over that world — owning no membership of its own. A declaration reshapes the one world for every run, never per-connection. `runs.origin` is attribution (whose perspective), not a permission.
1000
1011
 
1001
- - **Identity on the session.** No `projects` table; `sessions.project_root TEXT` (nullable = headless). `entries.scope` unchanged (`∈ {'agent','session'}`). Workspace = session; no users/auth/multi-tenant.
1002
- - **git-ls-files membership.** git present → tracked files (`git ls-files`) are members with no explicit `add` — channel-less markers, disk is truth. git absent → no fs-walk (non-git/headless get no substrate membership).
1003
- - **EMI eager + exhaustive.** Materializes *every* active git member at prompt-composition, re-reading disk so a divergent member reflects current content. git's `ls-files` is the whole membership bound the engine adds no relevance pass of its own. Allowing git *is* the curation.
1012
+ **Workspace identity.** No `projects` table; `sessions.project_root TEXT` (nullable = headless) anchors the workspace. `entries.scope` unchanged (`∈ {'agent','session'}`). Workspace = session; no users/auth/multi-tenant.
1013
+
1014
+ **git is the substrate.** {§membership-git-membership} git-tracked files (`git ls-files`) are members with no explicit overlay channel-less markers, disk is truth. git absent no fs-walk (non-git/headless get no substrate membership); `pick` is then the sole source.
1015
+
1016
+ **Membership is a declared forest of repos.** {§membership-forest} A workspace is not one git repo but a **forest**: membership is the union, over a session-declared set of repos, of each repo's `ls-files` (gitlinks/mode-160000 filtered), each path-prefixed by the repo's path relative to `project_root`. The root need not itself be a repo — a non-git parent of ninety repos resolves to all ninety. A worktree, a submodule, a buried repo are not special cases: each is just another declared repo, resolved `rev-parse --show-toplevel` → `ls-files` in the tree it points at.
1004
1017
  - **Membership-gated edits.** {§membership-edit-membership-gate} EDIT is bounded by membership exactly as READ is. An existing **member** is read from disk before diffing, so its baseline is real content, never empty — silent overwrite is structurally prevented. 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** stays open — proposal→accept adds it to the manifest. Reaching past membership is `EXEC[sh]`'s job, not the file scheme's.
1005
1018
 
1006
- - **Constraint overlay — `ignore`.** {§membership-constraint-ignore} A `session_constraints` table (effect ∈ {add, ignore, read-only}, glob) is the client's supersede over git. `ignore` drops tracked matches: resolution computes `git ls-files − ignore` (`node:path.matchesGlob`) and **reconciles** — registers the desired, un-registers any git-origin member no longer desired (untracked or newly ignored) so the entry set *equals* the member set. The lever to exclude a committed-but-sensitive tracked file; `entries.membership_origin` keeps reconciliation off model-created members.
1007
- - **Constraint overlay — `read-only`.** {§membership-constraint-readonly} A `read-only` glob keeps a matching member readable but refuses `File.edit` 403'd at the membership check, before any diff. A file admitted for reading without granting writes. (Admitting an *untracked* file as read-only rides on `add`'s scan.)
1008
- - **Constraint overlay — `add`.** {§membership-constraint-add} `add` globs admit members git misses: a targeted, client-dictated `node:fs` glob scan enumerates untracked matches (files only), registered with 'constraint' origin and reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `add` is the *sole* membership source — the whole mechanism without git.
1009
- - **EMI divergence signal.** {§membership-emi-divergence-signal} When git membership re-reads a member whose disk content changed out-of-band, the build-time delta detector (§env-delta) surfaces it as a system `EDIT` log row naming the file, `source="file"` the model sees what changed without diffing the manifest against memory. The model's own edits are write-through (the entry equals disk after a File write), so the disk-vs-entry scan never mis-attributes them as external divergence.
1019
+ **The overlay — `pick | view | hide | repo`, removed by `drop`.** A `session_constraints` table (effect ∈ {pick, view, hide, repo}, target) is the client's supersede over git; `drop` removes any declaration. Resolved membership is `( repo ls-files pick) hide`, with `view` enforced at the edit gate.
1020
+ - **`repo`** {§membership-overlay-repo} declare a git repo (a folder); its `ls-files` join membership, path-prefixed. Submodules/nested repos are separate `repo` declarations no recursion; the client owns the scan and the security call.
1021
+ - **`pick`** {§membership-overlay-pick} admit an untracked file git misses: a targeted client-dictated `node:fs` glob scan over untracked matches (files only), 'constraint' origin, reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `pick` is the *sole* membership source.
1022
+ - **`hide`** {§membership-overlay-hide} exclude a tracked file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-sensitive tracked file; `entries.membership_origin` keeps reconciliation off model-created members.
1023
+ - **`view`** {§membership-overlay-view} — keep a member readable but refuse `File.edit`, 403'd at the membership check before any diff. (Admitting an untracked file as `view` rides on `pick`'s scan.)
1024
+
1025
+ **Sync is idempotent and change-gated.** {§membership-change-gated-sync} Per turn, membership materializes every member's disk content into its entry — but the *work* is gated on a cheap per-member change-detect: a member unchanged on disk since its last sync is not re-read, re-tokenized, or rewritten. **Coverage is exhaustive — every member is detected every turn — but work is proportional to change**, so a ninety-repo forest costs detection, not a full re-read. Invariant: after a pass every member's entry equals its disk content; a no-change pass is a no-op.
1026
+
1027
+ **EMI divergence signal.** {§membership-emi-divergence-signal} The detector that gates the work *is* the one that fires this — one mechanism, not a second full read. When the change-detect finds a member moved out-of-band, the delta detector (§env-delta) surfaces it as a system `EDIT` log row naming the file, `source="file"` — the model sees what changed without diffing the manifest against memory. 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.
1028
+
1029
+ **Permission flags.** {§membership-git-flags} `PLURNK_GIT_ALLOWED` is the hard ceiling: `=0` denies all git membership service-wide, un-re-enableable — the sandbox/benchmark lockout. `PLURNK_GIT_AUTO` is the default declaration: `=1` (default) declares an implicit `repo` at `project_root` (no-op if it isn't a git tree); `=0` declares nothing — service/clients `repo`-declare explicitly. `ALLOWED` gates `AUTO`.
1010
1030
 
1011
- **Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory — and *is* the curation, outsourced: git bounds membership by tracking, the client supersedes by constraint, the model curates its view by READ/FOLD. The engine curates nothing. EMI re-reads every member each turn; the full-repo cost is git's to bound (what it tracks) and the client's to bound (`ignore`), never the engine's to bound by relevance.
1031
+ **Rationale.** Session is the right scope unit; membership *is* the curation, outsourced and tiered: git bounds it by tracking, the client supersedes by overlay, the model curates its own render by READ/FOLD the engine curates nothing. The forest falls out of "session = world": one workspace can be many repos, so membership is their union, declared not guessed (the scan and its security are the client's). Exhaustiveness is a property of *coverage*, not *work*: every member is checked every turn so no drift hides, but unchanged members cost only a detect — the full-repo cost is git's to bound (what it tracks) and the client's to bound (`hide`), never the engine's to pay re-reading what hasn't moved.
1012
1032
 
1013
- **Migration path.** Tenancy / cross-session shared workspaces require a `workspaces` table joining sessions to workspace id, with constraints lifted off `session_constraints`. Disk co-location semantics unchanged.
1033
+ **Migration path.** `session_constraints.effect` gains `repo`; the three renames (`add`→`pick`, `ignore`→`hide`, `read-only`→`view`) are wire-surface changes on `session.constrain`. Forest resolution iterates declared repos (was one `ls-files` at root). The change-detect adds a per-member stored signal — mtime+size or content hash, and *that choice is the EMI reliability bound* — gating the existing materialize. `PLURNK_GIT_ALLOWED` replaces `PLURNK_GIT_ENABLED`; `PLURNK_GIT_AUTO` is new. Tenancy / cross-session shared workspaces still require a `workspaces` table lifting constraints off `session_constraints`.
1014
1034
 
1015
1035
  ### §grinder Budget enforcement: the grinder
1016
1036
 
@@ -1025,7 +1045,7 @@ Each entry: question, answer, rationale, migration path.
1025
1045
 
1026
1046
  **What the model sees.** A `budget_overflow` telemetry event (§telemetry), in the model's own terms: which of its entries left the window, by scheme. No mechanism vocabulary — no "layer," no "grinder," no "reclaim" — and no advice. The engine reports *what happened to the model's world*; the budget readout (§tokenomics) — its turn and entry weights — is the diagnostic surface, and the model — which can see what changed in its repo, its reads, its turn — diagnoses the cause the engine can't attribute. {§grinder-event-model-terms} Per the gamification policy (§telemetry), the *strike* the overflow triggers stays engine-internal; the model sees the hidden entries, never the accounting.
1027
1047
 
1028
- **Rationale.** The model owns curation (§tokenomics); the grinder is the exceptional backstop. It only *hides* — reversibly — the prior turn's render; nothing is deleted, so the model can OPEN it back and log history stays intact. Rummy's §1316 spec described clearing log *bodies*, but its code instead hid the prior turn whole — because body-clearing is destructive (it deletes the read result) and bespoke. The code was the lesson; plurnk follows it.
1048
+ **Rationale.** The model owns curation (§tokenomics); the grinder is the exceptional backstop. It only *folds* — reversibly — the prior turn's render; nothing is deleted, so the model can OPEN it back and log history stays intact. Rummy's §1316 spec described clearing log *bodies*, but its code instead folded the prior turn whole — because body-clearing is destructive (it deletes the read result) and bespoke. The code was the lesson; plurnk follows it.
1029
1049
 
1030
1050
  **Migration path.** None on mechanism. Speculative or non-overflow trimming is a different feature, deliberately excluded — the grinder fires only in response to actual overflow.
1031
1051
 
@@ -1099,9 +1119,9 @@ type Packet = {
1099
1119
  };
1100
1120
  ```
1101
1121
 
1102
- **Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `plurnk://prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's prompt body materializes into `packet.user.prompt`; the entry itself stays READ/FOLD-able like any other.
1122
+ **Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `plurnk:///prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's prompt body materializes into `packet.user.prompt`; the entry itself stays READ/FOLD-able like any other.
1103
1123
 
1104
- **The entry catalog.** `plurnk://manifest.json` is a real session entry the model READs to discover what's available — rewritten every turn as a live view of the full entry set. Built in the schemes layer (`_entry-manifest`) and materialized like any entry (the engine only orchestrates the per-turn write — the same pattern as git membership), so it's READable and queryable. Body is `application/json`: a flat, **complete, unranked** array — one item per entry across all schemes, every entry listed in no relevance order, each `{ path, channels: { <name>: { mimetype, tokens, lines } } }`. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's write-time count (budget depth), `lines` the content extent from `Mimetypes.process().totalLines`. The engine counts neither. It does not list itself. {§packet-manifest-catalog}
1124
+ **The entry catalog.** `plurnk:///manifest.json` is a real session entry the model READs to discover what's available — rewritten every turn as a live view of the full entry set. Built in the schemes layer (`_entry-manifest`) and materialized like any entry (the engine only orchestrates the per-turn write — the same pattern as git membership), so it's READable and queryable. Body is `application/json`: a flat, **complete, unranked** array — one item per entry across all schemes, every entry listed in no relevance order, each `{ path, channels: { <name>: { mimetype, tokens, lines } } }`. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's write-time count (budget depth), `lines` the content extent from `Mimetypes.process().totalLines`. The engine counts neither. It does not list itself. {§packet-manifest-catalog}
1105
1125
 
1106
1126
  ### §telemetry user.telemetry — model-facing runtime telemetry
1107
1127
 
@@ -1129,7 +1149,7 @@ Slot for telemetry the model MUST react to immediately. Rendered at the bottom o
1129
1149
  | `max_commands_exceeded` | Single emission exceeded `PLURNK_MAX_COMMANDS` cap; overflow ops dropped without dispatch | `source: "engine:rail"`, `kind`, `emitted`, `dropped` |
1130
1150
  | `budget_overflow` | Assembled packet exceeded the budget ceiling; entries moved out of the window to fit | `source: "engine:rail"`, `kind`, `hidden` (per-scheme `[{scheme, count}]` — entries removed from the window) |
1131
1151
 
1132
- Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeeping are all engine-internal — they drive abandonment silently per the gamification policy above. Action-bound failures (handler returned 4xx/5xx or threw) mirror as `action_failure` kind on the next packet. Full detail queryable via `log://`. {§telemetry-no-error-scheme}
1152
+ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeeping are all engine-internal — they drive abandonment silently per the gamification policy above. Action-bound failures (handler returned 4xx/5xx or threw) mirror as `action_failure` kind on the next packet. Full detail queryable via `log:///`. {§telemetry-no-error-scheme}
1133
1153
 
1134
1154
  **No `error://` scheme.** Actionless failures route to telemetry, not a queryable scheme namespace.
1135
1155
 
@@ -1224,7 +1244,7 @@ Body: one match per line as `<line>:\t<value>` — the same `N:\t` form READ emi
1224
1244
  - `<0>` / `<-1>` → `[]` for READ
1225
1245
  - Out-of-range → 416; malformed JSON → 400
1226
1246
 
1227
- **Killer composition.** `<<READ(log://N/M/K)<P>::READ` picks the P-th match from a prior matcher result — matcher rx is `application/json`, structural `<L>` selects the P-th element. {§slice-semantics-compose-pattern}
1247
+ **Killer composition.** `<<READ(log:///N/M/K)<P>::READ` picks the P-th match from a prior matcher result — matcher rx is `application/json`, structural `<L>` selects the P-th element. {§slice-semantics-compose-pattern}
1228
1248
 
1229
1249
  ### §json-edit Structural EDIT on JSON
1230
1250
 
@@ -1259,10 +1279,10 @@ When effective mimetype is `application/json`, EDIT dispatches through `applyJso
1259
1279
 
1260
1280
  `resolveEntryMimetype` (exported from `@plurnk/plurnk-schemes`): pathname extension → `Mimetypes.detect({ ext })` (with `text/plain` normalized to `text/markdown` per the text-primitive rule §markdown-primitive); falls back to scheme manifest channel default when no extension.
1261
1281
 
1262
- - `known://users.json` → `application/json` (extension wins)
1263
- - `known://notes.md` → `text/markdown` (extension; matches default)
1264
- - `known://config.yaml` → `application/yaml`
1265
- - `known://users` (no suffix) → `text/markdown` (Known manifest default)
1282
+ - `known:///users.json` → `application/json` (extension wins)
1283
+ - `known:///notes.md` → `text/markdown` (extension; matches default)
1284
+ - `known:///config.yaml` → `application/yaml`
1285
+ - `known:///users` (no suffix) → `text/markdown` (Known manifest default)
1266
1286
 
1267
1287
  Same rule applies across Known, Unknown, Skill, Plurnk, File. Effective mimetype is stored in `entry_channels.mimetype` on write and drives `<L>` and matcher dispatch on read. {§ext-mimetype-extension-mimetype}
1268
1288
 
@@ -1295,7 +1315,7 @@ Carried from the contract walk; durable.
1295
1315
  - **COPY `<L>`** → source range, symmetric with READ `<L>`.
1296
1316
  - **READ rx** prefixes each line with `N:\t` per §render-rule. `sliceLinesRaw` (used by COPY) returns the lines without prefix.
1297
1317
  - **FIND body matcher** applies to entry content (all dialects), per-candidate via `Matcher.matchAgainstContent` → `Mimetypes.query` (status 200 = content hit → entry selected). Scope + tags select candidates in SQL; the path-glob is the (target).
1298
- - **OPEN/FOLD** operate on the **log** (`log://`), not entries (§open-fold) — FOLD collapses a log row to its path, OPEN restores its body. Aimed at an entry scheme they return 501.
1318
+ - **OPEN/FOLD** operate on the **log** (`log:///`), not entries (§open-fold) — FOLD collapses a log row to its path, OPEN restores its body. Aimed at an entry scheme they return 501.
1299
1319
  - **SEND[410]** deletes as a side-effect (not the model idiom; §move): with `#fragment`, that channel only; without, the whole entry. **SEND[499]** is owned by the streaming scheme that holds the subscription.
1300
1320
  - **File scheme** reads disk content with mimetype detected via `Mimetypes.detect({ path })` (plumbed through `PlurnkSchemeContext.mimetypes`). Binary mimetypes → 415 on READ and EDIT.
1301
1321
 
package/dist/Paths.d.ts CHANGED
@@ -2,7 +2,6 @@ export default class Paths {
2
2
  #private;
3
3
  static migrations: string;
4
4
  static instructionsSystem: string;
5
- static grammarGbnf: string;
6
5
  static defaultPersona: string;
7
6
  static defaultRequirements: string;
8
7
  static docs(): Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"Paths.d.ts","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;IAKtE,MAAM,CAAC,WAAW,SAAoD;IAKtE,MAAM,CAAC,cAAc,SAAkC;IAIvD,MAAM,CAAC,mBAAmB,SAAuC;IA8BjE,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAW5D"}
1
+ {"version":3,"file":"Paths.d.ts","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;IAOtE,MAAM,CAAC,cAAc,SAAkC;IAIvD,MAAM,CAAC,mBAAmB,SAAuC;IA8BjE,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAW5D"}
package/dist/Paths.js CHANGED
@@ -15,11 +15,8 @@ export default class Paths {
15
15
  static #GRAMMAR_ROOT = dirname(fileURLToPath(import.meta.resolve("@plurnk/plurnk-grammar/package.json")));
16
16
  static migrations = resolve(Paths.#PACKAGE_ROOT, "migrations");
17
17
  static instructionsSystem = resolve(Paths.#GRAMMAR_ROOT, "plurnk.md");
18
- // The GBNF artifact (the full multi-op root) for grammar-constrained
19
- // sampling, resolved from the grammar package like the sysprompt above.
20
- // Plumbed to the provider per generate() when PLURNK_PROVIDERS_GBNF is
21
- // enabled; the service holds no opinion about its content or root.
22
- static grammarGbnf = resolve(Paths.#GRAMMAR_ROOT, "dist/plurnk.gbnf");
18
+ // (GBNF artifact resolution moved to Engine.#grammarConstraint the env value
19
+ // SELECTS the variant from @plurnk/plurnk-grammar; no hardcoded default here, #225.)
23
20
  // packet.system.persona DEFAULT. Cascade at packet-build time is
24
21
  // loops.persona > runs.persona > sessions.persona > this file
25
22
  // RPC overrides on loop.run / session.attach / session.create populate
@@ -51,7 +48,7 @@ export default class Paths {
51
48
  }
52
49
  // Operator reference docs auto-READ into every model run at turn 0.
53
50
  // `PLURNK_MD_<ALIAS>=<path>` materializes <path>'s markdown as a
54
- // `plurnk://<ALIAS>.md` entry the model READs — an idiomatic, userland way
51
+ // `plurnk:///<ALIAS>.md` entry the model READs — an idiomatic, userland way
55
52
  // to inject standing context (an ordinary entry + READ op, not a bespoke
56
53
  // packet section like persona). `~` expands to home; relative paths resolve
57
54
  // against the package root. Resolved fresh each call so it tracks the env.
package/dist/Paths.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Paths.js","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Paths.js","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,8 @@
4
4
  //
5
5
  // A pathname's extension drives the effective mimetype (via the sibling
6
6
  // Mimetypes detect service); the scheme manifest's channel default is the
7
- // fallback when no extension is present. `known://users.json` →
8
- // application/json; `known://notes` → scheme default.
7
+ // fallback when no extension is present. `known:///users.json` →
8
+ // application/json; `known:///notes` → scheme default.
9
9
  import { PathMimetype as _PathMimetype } from "@plurnk/plurnk-schemes";
10
10
  export default class PathMimetype {
11
11
  static resolveEntryMimetype(pathname, schemeDefault, mimetypes) {
@@ -1 +1 @@
1
- {"version":3,"file":"path-mimetype.js","sourceRoot":"","sources":["../../src/content/path-mimetype.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,iFAAiF;AACjF,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,gEAAgE;AAChE,sDAAsD;AACtD,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvE,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,MAAM,CAAC,oBAAoB,CAAC,QAAgB,EAAE,aAAqB,EAAE,SAAgC;QACjG,OAAO,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;CACJ"}
1
+ {"version":3,"file":"path-mimetype.js","sourceRoot":"","sources":["../../src/content/path-mimetype.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,iFAAiF;AACjF,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,iEAAiE;AACjE,uDAAuD;AACvD,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvE,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,MAAM,CAAC,oBAAoB,CAAC,QAAgB,EAAE,aAAqB,EAAE,SAAgC;QACjG,OAAO,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;CACJ"}
@@ -1,11 +1,20 @@
1
1
  import type { Db } from "./Db.ts";
2
2
  export type ChannelState = "static" | "active" | "closed" | "errored";
3
+ export interface StreamCoordinate {
4
+ loop_seq: number;
5
+ turn_seq: number;
6
+ sequence: number;
7
+ }
3
8
  export interface StreamEventPayload {
4
9
  entryId: number;
5
10
  target: string;
6
11
  channel: string;
7
12
  state: ChannelState;
8
13
  contentLength: number;
14
+ loop_seq?: number;
15
+ turn_seq?: number;
16
+ sequence?: number;
17
+ mimetype?: string;
9
18
  }
10
19
  export type StreamEventNotify = (sessionId: number, event: StreamEventPayload) => void;
11
20
  export interface WakeRunPayload {
@@ -17,6 +26,9 @@ export interface WakeRunPayload {
17
26
  closeStatus: number;
18
27
  scheme: string;
19
28
  summary: string;
29
+ loop_seq?: number;
30
+ turn_seq?: number;
31
+ sequence?: number;
20
32
  }
21
33
  export type WakeRunNotify = (payload: WakeRunPayload) => void;
22
34
  export interface TelemetryEventPayload {
@@ -26,17 +38,20 @@ export interface TelemetryEventPayload {
26
38
  export type TelemetryEventNotify = (sessionId: number, payload: TelemetryEventPayload) => void;
27
39
  export default class ChannelWrite {
28
40
  #private;
29
- static appendToChannel(db: Db, { entryId, channel, chunk, notify }: {
41
+ static appendToChannel(db: Db, { entryId, channel, chunk, notify, coordinate, mimetype }: {
30
42
  entryId: number;
31
43
  channel: string;
32
44
  chunk: string;
33
45
  notify?: StreamEventNotify;
46
+ coordinate?: StreamCoordinate;
47
+ mimetype?: string;
34
48
  }): Promise<void>;
35
- static setChannelState(db: Db, { entryId, channel, state, notify }: {
49
+ static setChannelState(db: Db, { entryId, channel, state, notify, coordinate }: {
36
50
  entryId: number;
37
51
  channel: string;
38
52
  state: ChannelState;
39
53
  notify?: StreamEventNotify;
54
+ coordinate?: StreamCoordinate;
40
55
  }): Promise<void>;
41
56
  static openSubscription(db: Db, { runId, entryId, scheme, handle }: {
42
57
  runId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
@@ -9,28 +9,34 @@ export default class ChannelWrite {
9
9
  static #channelMeta(db) { return db.channel_meta; }
10
10
  static #appendStmt(db) { return db.append_to_channel; }
11
11
  static #stateStmt(db) { return db.set_channel_state; }
12
+ static #mimetypeStmt(db) { return db.set_channel_mimetype; }
12
13
  static #openSubStmt(db) { return db.open_subscription; }
13
14
  static #closeSubStmt(db) { return db.close_subscription; }
14
15
  static #findActiveStmt(db) { return db.find_active_subscription; }
15
16
  static #execTerminalStmt(db) { return db.find_exec_close_status; }
16
17
  // The entry's target URI for stream notifications (#179). A NULL scheme is
17
18
  // a filesystem entry (the file scheme stores scheme=NULL), so it decodes to
18
- // file://.
19
+ // file:///.
19
20
  static #targetUri(scheme, pathname) {
20
21
  return `${scheme === null ? "file" : scheme}://${pathname}`;
21
22
  }
22
- static async appendToChannel(db, { entryId, channel, chunk, notify }) {
23
+ static async appendToChannel(db, { entryId, channel, chunk, notify, coordinate, mimetype }) {
23
24
  const result = await ChannelWrite.#appendStmt(db).run({ chunk, entry_id: entryId, channel });
24
25
  if (result.changes === 0)
25
26
  return;
27
+ // #226 — a streaming scheme labels the channel with the body's per-call
28
+ // type (http's Content-Type varies per fetch). Conditional UPDATE: only
29
+ // writes when it changed, so labelling every chunk is a steady-state no-op.
30
+ if (mimetype !== undefined)
31
+ await ChannelWrite.#mimetypeStmt(db).run({ mimetype, entry_id: entryId, channel });
26
32
  if (notify === undefined)
27
33
  return;
28
34
  const meta = await ChannelWrite.#channelMeta(db).get({ entry_id: entryId, channel });
29
35
  if (meta === undefined)
30
36
  return;
31
- notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength });
37
+ notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength, mimetype: meta.mimetype, ...coordinate });
32
38
  }
33
- static async setChannelState(db, { entryId, channel, state, notify }) {
39
+ static async setChannelState(db, { entryId, channel, state, notify, coordinate }) {
34
40
  const result = await ChannelWrite.#stateStmt(db).run({ state, entry_id: entryId, channel });
35
41
  if (result.changes === 0)
36
42
  return;
@@ -39,7 +45,7 @@ export default class ChannelWrite {
39
45
  const meta = await ChannelWrite.#channelMeta(db).get({ entry_id: entryId, channel });
40
46
  if (meta === undefined)
41
47
  return;
42
- notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength });
48
+ notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength, mimetype: meta.mimetype, ...coordinate });
43
49
  }
44
50
  static async openSubscription(db, { runId, entryId, scheme, handle }) {
45
51
  const row = await ChannelWrite.#openSubStmt(db).get({ run_id: runId, entry_id: entryId, scheme, handle });
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,9 @@ import type { PlurnkStatement } from "@plurnk/plurnk-grammar";
2
2
  import type SchemeRegistry from "./SchemeRegistry.ts";
3
3
  import { Mimetypes } from "@plurnk/plurnk-mimetypes";
4
4
  import type { Db } from "./Db.ts";
5
- import type { LoopFlags } from "./scheme-types.ts";
5
+ import type { WriterTier, LoopFlags } from "./scheme-types.ts";
6
6
  import type ExecutorRegistry from "./ExecutorRegistry.ts";
7
7
  import type { StreamEventNotify, TelemetryEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
8
- type Origin = "model" | "client" | "plurnk" | "plugin";
9
8
  type ChatMessage = {
10
9
  role: "system" | "user" | "assistant";
11
10
  content: string;
@@ -18,7 +17,7 @@ type DispatchContext = {
18
17
  loopId: number;
19
18
  turnId: number;
20
19
  sequence: number;
21
- origin: Origin;
20
+ origin: WriterTier;
22
21
  onDispatch?: (logEntryId: number) => void;
23
22
  };
24
23
  type DispatchResult = {
@@ -85,7 +84,7 @@ export default class Engine {
85
84
  maxStrikes?: number;
86
85
  minCycles?: number;
87
86
  maxCyclePeriod?: number;
88
- origin?: Origin;
87
+ origin?: WriterTier;
89
88
  signal?: AbortSignal;
90
89
  onDispatch?: (logEntryId: number) => void;
91
90
  }): Promise<{
@@ -102,7 +101,7 @@ export default class Engine {
102
101
  sessionId: number;
103
102
  runId: number;
104
103
  loopId: number;
105
- origin?: Origin;
104
+ origin?: WriterTier;
106
105
  signal?: AbortSignal;
107
106
  onDispatch?: (logEntryId: number) => void;
108
107
  turnNumber?: number;