@plurnk/plurnk-service 0.7.0 → 0.9.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.
- package/SPEC.md +116 -83
- package/bin/plurnk-service.ts +132 -0
- package/dist/Paths.d.ts +8 -0
- package/dist/Paths.d.ts.map +1 -0
- package/dist/Paths.js +47 -0
- package/dist/Paths.js.map +1 -0
- package/dist/content/index.d.ts +9 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/line-marker.d.ts +26 -0
- package/dist/content/line-marker.d.ts.map +1 -0
- package/dist/content/line-marker.js +323 -0
- package/dist/content/line-marker.js.map +1 -0
- package/dist/content/matcher.d.ts +15 -0
- package/dist/content/matcher.d.ts.map +1 -0
- package/dist/content/matcher.js +112 -0
- package/dist/content/matcher.js.map +1 -0
- package/dist/content/mimetype-binary.d.ts +9 -0
- package/dist/content/mimetype-binary.d.ts.map +1 -0
- package/dist/content/mimetype-binary.js +86 -0
- package/dist/content/mimetype-binary.js.map +1 -0
- package/dist/content/path-mimetype.d.ts +6 -0
- package/dist/content/path-mimetype.d.ts.map +1 -0
- package/dist/content/path-mimetype.js +49 -0
- package/dist/content/path-mimetype.js.map +1 -0
- package/dist/content/read-resolve.d.ts +20 -0
- package/dist/content/read-resolve.d.ts.map +1 -0
- package/dist/content/read-resolve.js +60 -0
- package/dist/content/read-resolve.js.map +1 -0
- package/dist/core/ChannelWrite.d.ts +35 -30
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +49 -41
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +16 -10
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +309 -115
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/EnvFlags.d.ts +6 -3
- package/dist/core/EnvFlags.d.ts.map +1 -1
- package/dist/core/EnvFlags.js +62 -60
- package/dist/core/EnvFlags.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +26 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -0
- package/dist/core/ExecutorRegistry.js +99 -0
- package/dist/core/ExecutorRegistry.js.map +1 -0
- package/dist/core/PluginLoader.d.ts +6 -3
- package/dist/core/PluginLoader.d.ts.map +1 -1
- package/dist/core/PluginLoader.js +77 -73
- package/dist/core/PluginLoader.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -2
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +23 -22
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +1 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +3 -3
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/git-membership.d.ts +8 -0
- package/dist/core/git-membership.d.ts.map +1 -0
- package/dist/core/git-membership.js +125 -0
- package/dist/core/git-membership.js.map +1 -0
- package/dist/core/packet-wire.d.ts +47 -6
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +376 -312
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/resolveForLoop.d.ts +16 -0
- package/dist/core/resolveForLoop.d.ts.map +1 -0
- package/dist/core/resolveForLoop.js +34 -0
- package/dist/core/resolveForLoop.js.map +1 -0
- package/dist/core/results.d.ts +40 -0
- package/dist/core/results.d.ts.map +1 -0
- package/dist/core/results.js +52 -0
- package/dist/core/results.js.map +1 -0
- package/dist/core/scheme-types.d.ts +7 -4
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js +4 -4
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/types.d.ts +26 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +17 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -43
- package/dist/index.js.map +1 -1
- package/dist/schemes/EffectPolicy.d.ts +6 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -0
- package/dist/schemes/EffectPolicy.js +18 -0
- package/dist/schemes/EffectPolicy.js.map +1 -0
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +86 -45
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +0 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +32 -69
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.d.ts.map +1 -1
- package/dist/schemes/Known.js +13 -13
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +8 -52
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.d.ts.map +1 -1
- package/dist/schemes/Plurnk.js +13 -13
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +13 -13
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.d.ts.map +1 -1
- package/dist/schemes/Unknown.js +13 -13
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +5 -3
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +55 -50
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +10 -3
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +99 -77
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts +6 -0
- package/dist/schemes/_entry-manifest.d.ts.map +1 -0
- package/dist/schemes/_entry-manifest.js +45 -0
- package/dist/schemes/_entry-manifest.js.map +1 -0
- package/dist/schemes/_entry-ops.d.ts +7 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +198 -316
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-send.d.ts +4 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +57 -55
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/server/ClientConnection.js +3 -3
- package/dist/server/ClientConnection.js.map +1 -1
- package/dist/server/Daemon.d.ts +5 -5
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +234 -176
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.d.ts +4 -1
- package/dist/server/clientTurn.d.ts.map +1 -1
- package/dist/server/clientTurn.js +19 -17
- package/dist/server/clientTurn.js.map +1 -1
- package/dist/server/dsl.d.ts +19 -16
- package/dist/server/dsl.d.ts.map +1 -1
- package/dist/server/dsl.js +127 -105
- package/dist/server/dsl.js.map +1 -1
- package/dist/server/envelope.d.ts +22 -19
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +116 -102
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/logEntry.d.ts +4 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +41 -39
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/methods/_dispatchAsClient.d.ts +3 -1
- package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
- package/dist/server/methods/_dispatchAsClient.js +31 -29
- package/dist/server/methods/_dispatchAsClient.js.map +1 -1
- package/dist/server/methods/discover.d.ts +3 -1
- package/dist/server/methods/discover.d.ts.map +1 -1
- package/dist/server/methods/discover.js +8 -6
- package/dist/server/methods/discover.js.map +1 -1
- package/dist/server/methods/entry_read.d.ts +4 -1
- package/dist/server/methods/entry_read.d.ts.map +1 -1
- package/dist/server/methods/entry_read.js +54 -52
- package/dist/server/methods/entry_read.js.map +1 -1
- package/dist/server/methods/log_read.d.ts +4 -1
- package/dist/server/methods/log_read.d.ts.map +1 -1
- package/dist/server/methods/log_read.js +38 -36
- package/dist/server/methods/log_read.js.map +1 -1
- package/dist/server/methods/loop_cancel.d.ts +3 -1
- package/dist/server/methods/loop_cancel.d.ts.map +1 -1
- package/dist/server/methods/loop_cancel.js +19 -17
- package/dist/server/methods/loop_cancel.js.map +1 -1
- package/dist/server/methods/loop_resolve.d.ts +3 -1
- package/dist/server/methods/loop_resolve.d.ts.map +1 -1
- package/dist/server/methods/loop_resolve.js +42 -40
- package/dist/server/methods/loop_resolve.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts +3 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +104 -102
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/op_copy.d.ts +3 -1
- package/dist/server/methods/op_copy.d.ts.map +1 -1
- package/dist/server/methods/op_copy.js +25 -23
- package/dist/server/methods/op_copy.js.map +1 -1
- package/dist/server/methods/op_dispatch.d.ts +3 -1
- package/dist/server/methods/op_dispatch.d.ts.map +1 -1
- package/dist/server/methods/op_dispatch.js +18 -16
- package/dist/server/methods/op_dispatch.js.map +1 -1
- package/dist/server/methods/op_edit.d.ts +3 -1
- package/dist/server/methods/op_edit.d.ts.map +1 -1
- package/dist/server/methods/op_edit.js +23 -21
- package/dist/server/methods/op_edit.js.map +1 -1
- package/dist/server/methods/op_exec.d.ts +3 -1
- package/dist/server/methods/op_exec.d.ts.map +1 -1
- package/dist/server/methods/op_exec.js +20 -18
- package/dist/server/methods/op_exec.js.map +1 -1
- package/dist/server/methods/op_find.d.ts +3 -1
- package/dist/server/methods/op_find.d.ts.map +1 -1
- package/dist/server/methods/op_find.js +23 -21
- package/dist/server/methods/op_find.js.map +1 -1
- package/dist/server/methods/op_hide.d.ts +3 -1
- package/dist/server/methods/op_hide.d.ts.map +1 -1
- package/dist/server/methods/op_hide.js +23 -21
- package/dist/server/methods/op_hide.js.map +1 -1
- package/dist/server/methods/op_move.d.ts +3 -1
- package/dist/server/methods/op_move.d.ts.map +1 -1
- package/dist/server/methods/op_move.js +23 -21
- package/dist/server/methods/op_move.js.map +1 -1
- package/dist/server/methods/op_parse.d.ts +3 -1
- package/dist/server/methods/op_parse.d.ts.map +1 -1
- package/dist/server/methods/op_parse.js +24 -22
- package/dist/server/methods/op_parse.js.map +1 -1
- package/dist/server/methods/op_read.d.ts +3 -1
- package/dist/server/methods/op_read.d.ts.map +1 -1
- package/dist/server/methods/op_read.js +23 -21
- package/dist/server/methods/op_read.js.map +1 -1
- package/dist/server/methods/op_send.d.ts +3 -1
- package/dist/server/methods/op_send.d.ts.map +1 -1
- package/dist/server/methods/op_send.js +22 -20
- package/dist/server/methods/op_send.js.map +1 -1
- package/dist/server/methods/op_show.d.ts +3 -1
- package/dist/server/methods/op_show.d.ts.map +1 -1
- package/dist/server/methods/op_show.js +23 -21
- package/dist/server/methods/op_show.js.map +1 -1
- package/dist/server/methods/ping.d.ts +3 -1
- package/dist/server/methods/ping.d.ts.map +1 -1
- package/dist/server/methods/ping.js +8 -6
- package/dist/server/methods/ping.js.map +1 -1
- package/dist/server/methods/providers_list.d.ts +3 -1
- package/dist/server/methods/providers_list.d.ts.map +1 -1
- package/dist/server/methods/providers_list.js +19 -17
- package/dist/server/methods/providers_list.js.map +1 -1
- package/dist/server/methods/session_attach.d.ts +3 -1
- package/dist/server/methods/session_attach.d.ts.map +1 -1
- package/dist/server/methods/session_attach.js +43 -41
- package/dist/server/methods/session_attach.js.map +1 -1
- package/dist/server/methods/session_create.d.ts +3 -1
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +51 -49
- package/dist/server/methods/session_create.js.map +1 -1
- package/dist/server/methods/session_list.d.ts +3 -1
- package/dist/server/methods/session_list.d.ts.map +1 -1
- package/dist/server/methods/session_list.js +9 -7
- package/dist/server/methods/session_list.js.map +1 -1
- package/dist/server/methods/session_runs.d.ts +3 -1
- package/dist/server/methods/session_runs.d.ts.map +1 -1
- package/dist/server/methods/session_runs.js +19 -17
- package/dist/server/methods/session_runs.js.map +1 -1
- package/dist/server/methods/session_set_persona.d.ts +3 -1
- package/dist/server/methods/session_set_persona.d.ts.map +1 -1
- package/dist/server/methods/session_set_persona.js +28 -26
- package/dist/server/methods/session_set_persona.js.map +1 -1
- package/dist/server/methods/session_set_root.d.ts +3 -1
- package/dist/server/methods/session_set_root.d.ts.map +1 -1
- package/dist/server/methods/session_set_root.js +31 -29
- package/dist/server/methods/session_set_root.js.map +1 -1
- package/dist/server/noProposals.d.ts +6 -0
- package/dist/server/noProposals.d.ts.map +1 -0
- package/dist/server/noProposals.js +37 -0
- package/dist/server/noProposals.js.map +1 -0
- package/dist/server/yolo.d.ts +3 -1
- package/dist/server/yolo.d.ts.map +1 -1
- package/dist/server/yolo.js +15 -13
- package/dist/server/yolo.js.map +1 -1
- package/package.json +71 -32
- package/bin/plurnk-service.js +0 -112
- /package/migrations/{001_schema.sql → 0000-00-00.01_schema.sql} +0 -0
package/SPEC.md
CHANGED
|
@@ -102,7 +102,7 @@ Dependency direction (from root to leaf):
|
|
|
102
102
|
- `plurnk-mimetypes` — handler base classes, discovery, fitting algorithm, matcher dispatch. Handler children are per-mimetype: `plurnk-mimetypes-text-{python,typescript,markdown,html,csv,plain}`, `plurnk-mimetypes-application-{json,yaml,toml,pdf}`, …
|
|
103
103
|
- `plurnk-schemes` — scheme-author types (`SchemeManifest`, `WriterTier`, `LoopFlags`), result-shape contracts (`EntryResult` / `ProposalResult` / `PassthroughResult`), slicing primitives, matcher helpers, `schemeError(...)` constructor. Future scheme children: `plurnk-schemes-http`, `plurnk-schemes-git`, …
|
|
104
104
|
- `plurnk-execs` — `BaseExecutor`, `SubprocessExecutor`, runtime resolver, discovery. Children declare runtimes: `plurnk-execs-sh`, future `plurnk-execs-search`, `plurnk-execs-node`, …
|
|
105
|
-
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), discovers installed mimetype handlers + provider vendors + executor siblings at boot, hosts the daemon (`bin/plurnk-service.
|
|
105
|
+
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), discovers installed mimetype handlers + provider vendors + executor siblings at boot, hosts the daemon (`bin/plurnk-service.ts` over WebSocket + JSON-RPC), and projects packets to the wire per `Packet.json`. Most of the substantive runtime work lives here.
|
|
106
106
|
- **`plurnk`** (client) — terminal UI consuming the daemon's RPC surface. Renders `telemetry/event` notifications, subscribes to log/stream/proposal events. No engine logic of its own.
|
|
107
107
|
|
|
108
108
|
The grammar is the contract. The frameworks consume the contract and add author-facing surfaces. The service consumes the frameworks and runs the engine. The client consumes the service and renders to humans. Each tier is its own published package; each tier's evolution happens in its own repo.
|
|
@@ -134,23 +134,23 @@ Author-facing contract: [plurnk-providers#1](https://github.com/plurnk/plurnk-pr
|
|
|
134
134
|
|
|
135
135
|
Three entry points:
|
|
136
136
|
|
|
137
|
-
- `provider.generate({messages, signal})` — once per turn; returns `{ assistant: { content, reasoning, usage, finishReason, model }, assistantRaw }`. **Engine parses `assistant.content`** into `PlurnkStatement[]` via `@plurnk/plurnk-grammar`.
|
|
138
|
-
- `provider.countTokens(text)` — synchronous, called at write-time (§14.2) and render-time. Non-negative integer.
|
|
139
|
-
- `provider.costFor(usage)` — once per completed turn; pico-USD. Engine writes to `turns.usage_cost_pico`; triggers cascade to `runs.cost_pico` / `sessions.cost_pico`.
|
|
137
|
+
- `provider.generate({messages, signal})` — once per turn; returns `{ assistant: { content, reasoning, usage, finishReason, model }, assistantRaw }`. **Engine parses `assistant.content`** into `PlurnkStatement[]` via `@plurnk/plurnk-grammar`. {§2.1-generate}
|
|
138
|
+
- `provider.countTokens(text)` — synchronous, called at write-time (§14.2) and render-time. Non-negative integer. {§2.1-counttokens}
|
|
139
|
+
- `provider.costFor(usage)` — once per completed turn; pico-USD. Engine writes to `turns.usage_cost_pico`; triggers cascade to `runs.cost_pico` / `sessions.cost_pico`. {§2.1-costfor}
|
|
140
140
|
|
|
141
|
-
Plus immutable identity: `provider.contextSize` (token total, or `null` → "no budget info") and `provider.model
|
|
141
|
+
Plus immutable identity: `provider.contextSize` (token total, or `null` → "no budget info"), read by the budget {§2.1-identity}; and `provider.model` — the instance identity the deferred model-switch recompute compares (§14.2-hot-switch-recompute), exposed but not yet consumed here.
|
|
142
142
|
|
|
143
143
|
### §2.2 Engine → provider guarantees
|
|
144
144
|
|
|
145
145
|
- `messages` is a complete prompt (`system_definition`, `persona`, `index`, `log`, `prompt`, `telemetry`, `system_requirements` pre-assembled). Provider does not reorder.
|
|
146
|
-
- `signal` is wired to the run's AbortController.
|
|
147
|
-
- `generate` is single-call per turn. No parallel calls on the same instance.
|
|
148
|
-
- `assistantRaw` is opaque to the engine (forensics-only).
|
|
146
|
+
- `signal` is wired to the run's AbortController. {§2.2-signal-wired}
|
|
147
|
+
- `generate` is single-call per turn. No parallel calls on the same instance. {§2.2-single-call}
|
|
148
|
+
- `assistantRaw` is opaque to the engine (forensics-only). {§2.2-assistantraw-opaque}
|
|
149
149
|
- `countTokens` is cheap by contract; engine calls frequently.
|
|
150
150
|
|
|
151
151
|
### §2.3 Provider instantiation
|
|
152
152
|
|
|
153
|
-
Model alias parsing (`parseAliasesFromEnv` / `resolveActiveAlias`) lives in [`@plurnk/plurnk-providers`](https://github.com/plurnk/plurnk-providers). Dynamic provider instantiation (`instantiateProvider` / `loadActiveProvider`) lives in `src/core/ProviderInstantiate.ts` here — `import()` resolves package specifiers relative to the calling module, so the dynamic-import path stays in the consumer where the `@plurnk/plurnk-providers-<vendor>` packages are installed.
|
|
153
|
+
Model alias parsing (`parseAliasesFromEnv` / `resolveActiveAlias`) lives in [`@plurnk/plurnk-providers`](https://github.com/plurnk/plurnk-providers). {§2.3-alias-resolution} Dynamic provider instantiation (`instantiateProvider` / `loadActiveProvider`) lives in `src/core/ProviderInstantiate.ts` here — `import()` resolves package specifiers relative to the calling module, so the dynamic-import path stays in the consumer where the `@plurnk/plurnk-providers-<vendor>` packages are installed.
|
|
154
154
|
|
|
155
155
|
```
|
|
156
156
|
PLURNK_MODEL_gemma=openai/macher.gguf
|
|
@@ -160,9 +160,9 @@ PLURNK_MODEL=gemma
|
|
|
160
160
|
|
|
161
161
|
First path segment = provider plugin; rest = provider's own model id.
|
|
162
162
|
|
|
163
|
-
### §2.4
|
|
163
|
+
### §2.4 Mock provider (sibling fixture)
|
|
164
164
|
|
|
165
|
-
`Mock` (exported from `@plurnk/plurnk-providers`) — intg fixture + reference implementation. `{ contextSize, responses }` constructor; `generate` shifts from the queue. `MockResponse.assistant.ops?: PlurnkStatement[]` is a pre-parsed escape hatch the engine consumes directly when present; production providers don't expose this.
|
|
165
|
+
`Mock` (exported from `@plurnk/plurnk-providers`) — intg fixture + reference implementation. `{ contextSize, responses }` constructor; `generate` shifts from the queue. `MockResponse.assistant.ops?: PlurnkStatement[]` is a pre-parsed escape hatch the engine consumes directly when present; production providers don't expose this. {§2.4-mock-fixture}
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
@@ -172,15 +172,15 @@ Author-facing contract: [plurnk-schemes#1](https://github.com/plurnk/plurnk-sche
|
|
|
172
172
|
|
|
173
173
|
### §3.1 Manifest
|
|
174
174
|
|
|
175
|
-
Per author contract. Each scheme declares a `static manifest: SchemeManifest` with `name`, `channels`, `defaultChannel`, `category`, `scope`, `writableBy`, `volatile`, `modelVisible`, optional `flags`. Identity match enforced at plugin load: `manifest.name` must equal `package.json#plurnk.name`.
|
|
175
|
+
Per author contract. Each scheme declares a `static manifest: SchemeManifest` with `name`, `channels`, `defaultChannel`, `category`, `scope`, `writableBy`, `volatile`, `modelVisible`, optional `flags`. {§3.1-manifest} Identity match enforced at plugin load: `manifest.name` must equal `package.json#plurnk.name`.
|
|
176
176
|
|
|
177
177
|
### §3.2 CRUD primitives
|
|
178
178
|
|
|
179
|
-
Per author contract (`readEntry` / `writeEntry` / `deleteEntry`). Engine drives cross-scheme COPY/MOVE/SEND[410] through these. Each method is one SQL transaction; engine owns the outer transaction for orchestrations.
|
|
179
|
+
Per author contract (`readEntry` / `writeEntry` / `deleteEntry`). Engine drives cross-scheme COPY/MOVE/SEND[410] through these — the orchestration and its 404/409/415 semantics are anchored under §6.4/§6.5. Each method is one SQL transaction; engine owns the outer transaction for orchestrations.
|
|
180
180
|
|
|
181
181
|
### §3.3 Op methods
|
|
182
182
|
|
|
183
|
-
Per author contract (`edit`/`read`/`show`/`hide`/`find`/`send`/`exec?`). Engine dispatches by `PlurnkStatement.op`. COPY and MOVE are NOT scheme methods — engine orchestrates over CRUD primitives.
|
|
183
|
+
Per author contract (`edit`/`read`/`show`/`hide`/`find`/`send`/`exec?`). Engine dispatches by `PlurnkStatement.op`. {§3.3-op-dispatch} COPY and MOVE are NOT scheme methods — engine orchestrates over CRUD primitives (§6.4/§6.5).
|
|
184
184
|
|
|
185
185
|
### §3.4 Cross-scheme orchestration
|
|
186
186
|
|
|
@@ -200,17 +200,17 @@ move(source_path, dest_path, signal_tags, ctx):
|
|
|
200
200
|
src_scheme.deleteEntry(source_pathname, ctx)
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
Same- and cross-scheme operations share the orchestrator. Same-scheme COPY is not a special case.
|
|
203
|
+
Same- and cross-scheme operations share the orchestrator. Same-scheme COPY is not a special case. Orchestration behavior — 404/409/415, `move` = `copy` + `deleteEntry` — is anchored under §6.4/§6.5.
|
|
204
204
|
|
|
205
205
|
### §3.5 SEND dispatch (status-code-as-verb)
|
|
206
206
|
|
|
207
207
|
Directed SEND (non-null path) routes to scheme's `send`. Status = intent:
|
|
208
208
|
|
|
209
209
|
- `SEND[200](path)` — write body into resource (WS message, exec stdin).
|
|
210
|
-
- `SEND[410](path)` — delete the resource. {§3.5-410-deletes-resource}
|
|
211
|
-
- `SEND[410](path#fragment)` — delete the named channel only. {§3.5-410-fragment-channel-delete}
|
|
212
210
|
- `SEND[499](path)` — cancel active subscription (§7).
|
|
213
211
|
|
|
212
|
+
`SEND[410](path[#fragment])` also deletes the target entry/channel — an implemented side-effect, NOT taught to the model and with no live/demo surface. The model-facing delete idiom is MOVE to `/dev/null` (§6.5).
|
|
213
|
+
|
|
214
214
|
Other status codes return 501 from entry-bearing schemes by default. {§3.5-entry-schemes-501-on-non-410}
|
|
215
215
|
|
|
216
216
|
Null-path SEND is broadcast (§6.7), engine-handled.
|
|
@@ -240,11 +240,11 @@ Engine → scheme guarantees:
|
|
|
240
240
|
|
|
241
241
|
- `ctx` is fresh per call. No mutation across calls.
|
|
242
242
|
- `ctx.writer` reflects the actual writer at this dispatch.
|
|
243
|
-
- `manifest.writableBy` checked BEFORE invocation; engine returns 403 directly on exclusion.
|
|
244
|
-
- `ctx.signal` is wired to the run's AbortController.
|
|
245
|
-
- Scheme exceptions become the action-entry's outcome (status 500); summary surfaces in next turn's `packet.user.telemetry.errors[]` (§15.1).
|
|
243
|
+
- `manifest.writableBy` checked BEFORE invocation; engine returns 403 directly on exclusion. {§3.6-writableby-403}
|
|
244
|
+
- `ctx.signal` is wired to the run's AbortController (§2.2-signal-wired).
|
|
245
|
+
- Scheme exceptions become the action-entry's outcome (status 500); summary surfaces in next turn's `packet.user.telemetry.errors[]` (§15.1). {§3.6-exception-500}
|
|
246
246
|
|
|
247
|
-
**Tokenization participation.** Schemes route writes through the shared `_entry-crud.ts` write helper (in plurnk-service today; migrates to plurnk-schemes). Helper populates `entry_channels.tokens` at write time via `ctx.provider.countTokens
|
|
247
|
+
**Tokenization participation.** Schemes route writes through the shared `_entry-crud.ts` write helper (in plurnk-service today; migrates to plurnk-schemes). Helper populates `entry_channels.tokens` at write time via `ctx.provider.countTokens` (§14.2-tokens-stored-at-write). Raw DB writes bypass tokenization — out of API scope.
|
|
248
248
|
|
|
249
249
|
---
|
|
250
250
|
|
|
@@ -413,16 +413,17 @@ Per-op semantics. AST shapes from `@plurnk/plurnk-grammar`'s `PlurnkStatement`.
|
|
|
413
413
|
AST: `{ op: "EDIT", target, body: string | null, signal: tags | null, lineMarker? }`.
|
|
414
414
|
|
|
415
415
|
- Resolves target channel from fragment (§5.5); unknown channel → 400; undeclared in manifest → engine crash (§5.3).
|
|
416
|
-
- Writes body; `body: null` clears.
|
|
417
|
-
- Sets `indexed=1` for written channel in current run.
|
|
418
|
-
- Returns `{ status: 201, entryId }` for new entries; `{ status: 200, entryId }` for updates.
|
|
419
|
-
-
|
|
416
|
+
- Writes body; `body: null` clears. {§6.1-null-clears}
|
|
417
|
+
- Sets `indexed=1` for written channel in current run. {§6.1-indexed}
|
|
418
|
+
- Returns `{ status: 201, entryId }` for new entries; `{ status: 200, entryId }` for content updates. {§6.1-status-201-200}
|
|
419
|
+
- A write that changes nothing — identical content and no new tag — returns `{ status: 304, entryId }`, mirroring SHOW/HIDE's no-op (§6.3). {§6.1-noop-304}
|
|
420
|
+
- Tags from `signal[]` apply additively via `entry_tags` (scheme may vary). {§6.1-tags-additive}
|
|
420
421
|
|
|
421
422
|
### §6.2 READ
|
|
422
423
|
|
|
423
424
|
AST: `{ op: "READ", target, body: MatcherBody | null, signal: tags | null, lineMarker? }`.
|
|
424
425
|
|
|
425
|
-
- Returns channel content + mimetype, or 404.
|
|
426
|
+
- Returns channel content + mimetype {§6.2-read-content}, or 404 {§6.2-read-404}.
|
|
426
427
|
- `lineMarker` slices per §16.3.
|
|
427
428
|
- `body` matcher dispatches through `Mimetypes.query` per §16.1 (all four dialects wired).
|
|
428
429
|
|
|
@@ -431,7 +432,7 @@ AST: `{ op: "READ", target, body: MatcherBody | null, signal: tags | null, lineM
|
|
|
431
432
|
AST: `{ op: "SHOW"|"HIDE", target, body: MatcherBody | null, signal: tags | null, lineMarker? }`.
|
|
432
433
|
|
|
433
434
|
- Flips `visibility.indexed` for the targeted channel(s) per §5.5 rules.
|
|
434
|
-
- Returns 200 on transition, 304 on no-op, 404 if entry absent.
|
|
435
|
+
- Returns 200 on transition {§6.3-flip-200}, 304 on no-op {§6.3-noop-304}, 404 if entry absent {§6.3-absent-404}.
|
|
435
436
|
|
|
436
437
|
### §6.4 COPY (engine-orchestrated)
|
|
437
438
|
|
|
@@ -440,7 +441,7 @@ AST: `{ op: "COPY", target (source), body (destination), signal: tags | null, li
|
|
|
440
441
|
Engine orchestrates over CRUD primitives (§3.2, §3.4):
|
|
441
442
|
|
|
442
443
|
1. `src_scheme.readEntry` → 404 if missing. {§6.4-missing-source-404}
|
|
443
|
-
2. `dst_scheme.readEntry` →
|
|
444
|
+
2. `dst_scheme.readEntry` → conflict verdict, deferred until the written content is known (step 5): exists with identical content + tags → 304 (no-op, mirrors EDIT §6.1) {§6.4-noop-304}; exists with different content → 409 (no overwrite) {§6.4-conflict-409}; absent → proceed.
|
|
444
445
|
3. Mimetype compat — channels' mimetypes must be accepted by `dst_scheme.manifest.channels`. Mismatch → 415.
|
|
445
446
|
4. Tags: `signal` non-null replaces source tags {§6.4-signal-replaces-source-tags}; null/empty carries source tags {§6.4-no-signal-carries-source-tags}.
|
|
446
447
|
5. `dst_scheme.writeEntry({channels, tags})`.
|
|
@@ -453,7 +454,7 @@ Returns 201 on success. Same- and cross-scheme COPY share the orchestrator. {§6
|
|
|
453
454
|
AST: `{ op: "MOVE", target (source), body: dest | null, signal: tags | null, lineMarker? }`.
|
|
454
455
|
|
|
455
456
|
- **Relocation** (`body` non-null): COPY (§6.4) + `src_scheme.deleteEntry` in one transaction. 201 on success. {§6.5-relocation-deletes-source} Cross-scheme same as same-scheme. {§6.5-cross-scheme-move}
|
|
456
|
-
- **Deletion** (`body: null`): `src_scheme.deleteEntry` directly. {§6.5-null-body-deletes} 200 on success, 404 if absent. {§6.5-missing-source-404}
|
|
457
|
+
- **Deletion** (`body: null`, or `body` = `/dev/null`): `src_scheme.deleteEntry` directly. {§6.5-null-body-deletes} 200 on success, 404 if absent. {§6.5-missing-source-404} `/dev/null` is the model-facing delete idiom the grammar teaches; a null body is its programmatic equivalent. {§6.5-dev-null-deletes}
|
|
457
458
|
|
|
458
459
|
Log history preserved — `log_entries` stores path tuple as text, not FK to `entries.id`.
|
|
459
460
|
|
|
@@ -462,7 +463,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
462
463
|
AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal: tags | null, lineMarker? }`.
|
|
463
464
|
|
|
464
465
|
- Filters entries within scope (scheme + pathname prefix). {§6.6-scope-prefix-filter}
|
|
465
|
-
- `body` matcher operates on
|
|
466
|
+
- `body` matcher operates on entry content (glob/regex/jsonpath/xpath), per grammar plurnk.md §"Body matcher dispatch"; the path-glob lives in the (target), not the body. {§6.6-glob-filter-on-content}
|
|
466
467
|
- `signal` is a tag filter; entries match if they have ALL listed tags. {§6.6-tag-filter-and-semantics}
|
|
467
468
|
- Session + scheme scoped — no cross-session/cross-scheme leakage. {§6.6-scoped-isolation}
|
|
468
469
|
- Returns `{ status: 200, results: string }` (newline-separated matching paths, `text/plain`).
|
|
@@ -478,7 +479,11 @@ AST: `{ op: "SEND", target: ParsedPath | null, body: SendBody | null, signal: nu
|
|
|
478
479
|
|
|
479
480
|
AST: `{ op: "EXEC", target (cwd), body: string | null (command), signal: string | null (runtime tag) }`.
|
|
480
481
|
|
|
481
|
-
Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI).
|
|
482
|
+
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`. {§6.8-registry-resolves}
|
|
483
|
+
|
|
484
|
+
**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**: the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec://r-<hash>` entry, returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive the model's view at subsequent turn boundaries (§5.6). {§6.8-host-proposes}
|
|
485
|
+
|
|
486
|
+
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. {§6.8-readpure-inline}
|
|
482
487
|
|
|
483
488
|
`SEND[499](exec://r-<hash>)` cancels in-flight subprocess via subscription registry's stored AbortController (§7.7).
|
|
484
489
|
|
|
@@ -631,7 +636,7 @@ Plugin discovery (§9) registers whatever's in `node_modules/@plurnk/*`.
|
|
|
631
636
|
- Render budget per channel — `PLURNK_ENTRY_SIZE_DEFAULT_TOKENS` (§12); tokenization per §14.2.
|
|
632
637
|
- Backpressure caps — none (§7.8).
|
|
633
638
|
- Stream cancel — `SEND[499]` (§7.7).
|
|
634
|
-
- Delete — `SEND[410]` (§3.5
|
|
639
|
+
- Delete — MOVE to `/dev/null` (§6.5); `SEND[410]` also deletes as a side-effect (§3.5).
|
|
635
640
|
- Per-loop flags — `loops.flags` JSON column; `yolo` end-to-end today, others scheduled.
|
|
636
641
|
- Default-channel wire rendering — §5.5.
|
|
637
642
|
|
|
@@ -639,7 +644,7 @@ Plugin discovery (§9) registers whatever's in `node_modules/@plurnk/*`.
|
|
|
639
644
|
|
|
640
645
|
## §12 Operator Configuration
|
|
641
646
|
|
|
642
|
-
Env-var cascade: `.env.example` < `.env` < `.env.<config>` (via `--config=`) < shell < CLI flags. `bin/plurnk-service.
|
|
647
|
+
Env-var cascade: `.env.example` < `.env` < `.env.<config>` (via `--config=`) < shell < CLI flags. `bin/plurnk-service.ts` auto-loads `.env.example`; zero-setup boot.
|
|
643
648
|
|
|
644
649
|
Model selection: separate alias cascade in `ProviderRegistry` (§2.3). `PLURNK_MODEL_<alias>=<provider>/<model-id>` declares; `PLURNK_MODEL=<alias>` selects. Aliases live in `.env`, not `.env.example` (operator-specific).
|
|
645
650
|
|
|
@@ -669,7 +674,7 @@ Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
|
|
|
669
674
|
|
|
670
675
|
External plugins declare their own env vars in their own `.env.example`; service merges at boot via the cascade.
|
|
671
676
|
|
|
672
|
-
**Admin CLI flag derivation.** `bin/plurnk-service.
|
|
677
|
+
**Admin CLI flag derivation.** `bin/plurnk-service.ts` auto-derives flags from `.env.example`: every `PLURNK_*` becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). Comment immediately above (no blank line) becomes `-h` description. Non-`PLURNK_*` vars in `.env.example` are bugs — vendor config belongs in the vendor's package namespace.
|
|
673
678
|
|
|
674
679
|
---
|
|
675
680
|
|
|
@@ -711,7 +716,7 @@ registry.register("loop.run", {
|
|
|
711
716
|
- `description`: one-liner surfaced by `discover`.
|
|
712
717
|
- `params`: `"type — meaning"` per param; `?` suffix = optional. Self-documenting, not enforced.
|
|
713
718
|
- `requiresInit`: rejects until a session is attached.
|
|
714
|
-
- `longRunning`: exempt from `PLURNK_RPC_TIMEOUT`.
|
|
719
|
+
- `longRunning`: exempt from `PLURNK_RPC_TIMEOUT`. {§13.3-register}
|
|
715
720
|
|
|
716
721
|
### §13.4 Discovery
|
|
717
722
|
|
|
@@ -737,7 +742,7 @@ registry.register("loop.run", {
|
|
|
737
742
|
}
|
|
738
743
|
```
|
|
739
744
|
|
|
740
|
-
`capabilities` lists registered plug-ins by `(kind, name)`. Cold clients call `discover` first. No hardcoded method names or capability lists in any client.
|
|
745
|
+
`capabilities` lists registered plug-ins by `(kind, name)`. Cold clients call `discover` first. No hardcoded method names or capability lists in any client. {§13.4-discover}
|
|
741
746
|
|
|
742
747
|
### §13.5 Core method set
|
|
743
748
|
|
|
@@ -754,18 +759,18 @@ registry.register("loop.run", {
|
|
|
754
759
|
|------------------------|---------------------|-------------------|-------|
|
|
755
760
|
| `session.create` | `name?: string`, `projectRoot?: string`, `persona?: string` | `{ id, name, projectRoot, persona }` | Creates new session; auto-name if unprovided. Optional `projectRoot` pins the workspace (null/omitted = headless). Optional `persona` sets the session-level persona override. |
|
|
756
761
|
| `session.list` | none | `{ sessions: Session[] }` | Lists all sessions. |
|
|
757
|
-
| `session.attach` | `id: number`, `runId?: number`, `runName?: string`, `persona?: string` | `{ id, name, runId, runName }` | Binds this connection to an existing session. Optional `runId` resumes that specific run (must belong to the session). Optional `runName` reuses-or-creates by name within the session. Both omitted → new auto-named run. Optional `persona` sets run-level persona only when a NEW run is created. |
|
|
762
|
+
| `session.attach` | `id: number`, `runId?: number`, `runName?: string`, `persona?: string` | `{ id, name, runId, runName }` | Binds this connection to an existing session. Optional `runId` resumes that specific run (must belong to the session). Optional `runName` reuses-or-creates by name within the session. Both omitted → new auto-named run. Optional `persona` sets run-level persona only when a NEW run is created. {§13.5-session-attach} |
|
|
758
763
|
| `session.runs` | `id?: number` | `{ runs: Run[] }` | Lists runs in a session (defaults to attached session); most-recent first. |
|
|
759
764
|
| `session.set_root` | `projectRoot: string \| null` | `{ projectRoot }` | Update the workspace pointer on the attached session. Null reverts to headless. |
|
|
760
765
|
| `session.set_persona` | `persona: string \| null` | `{ persona }` | Update the session-level persona. Null clears the override (falls through to PLURNK_PERSONA file). |
|
|
761
766
|
|
|
762
|
-
**Auto-envelope.** Clients calling a `requiresInit: true` method without first attaching get auto-created session → run → client loop. Records persist normally; auto-created ≠ auto-deleted. Cleanup is a future `session.delete` / `session.archive` endpoint.
|
|
767
|
+
**Auto-envelope.** Clients calling a `requiresInit: true` method without first attaching get auto-created session → run → client loop. Records persist normally; auto-created ≠ auto-deleted. Cleanup is a future `session.delete` / `session.archive` endpoint. {§13.5-auto-envelope}
|
|
763
768
|
|
|
764
769
|
**Loops (model-driven)**
|
|
765
770
|
|
|
766
771
|
| Method | Params | Result | Notes |
|
|
767
772
|
|-------------------|-------------------------------------|------------------------|-------|
|
|
768
|
-
| `loop.run` | `prompt: string`, `maxTurns?: number`, `alias?: string`, `flags?: LoopFlags`, `persona?: string` | `{ loopId, turnIds, finalStatus, hitMaxTurns, reason }` | Model-driven loop. Optional `alias` overrides the boot-time `PLURNK_MODEL`. Optional `flags` carries per-loop flags (currently `{yolo?: boolean}`; more arrive as wired — see §0.5). Optional `persona` sets the loop-level persona (highest precedence in the cascade). Streams `log/entry` and `loop/proposal` notifications during. `longRunning: true`. |
|
|
773
|
+
| `loop.run` | `prompt: string`, `maxTurns?: number`, `alias?: string`, `flags?: LoopFlags`, `persona?: string` | `{ loopId, turnIds, finalStatus, hitMaxTurns, reason }` | Model-driven loop. Optional `alias` overrides the boot-time `PLURNK_MODEL`. Optional `flags` carries per-loop flags (currently `{yolo?: boolean}`; more arrive as wired — see §0.5). Optional `persona` sets the loop-level persona (highest precedence in the cascade). Streams `log/entry` and `loop/proposal` notifications during. `longRunning: true`. {§13.5-loop-run} |
|
|
769
774
|
| `loop.resolve` | `logEntryId: number`, `decision: "accept" \| "reject" \| "cancel"`, `body?: string`, `outcome?: string` | `{ status, logEntryId }` | Resolve a pending proposal (status=202 log entry). Engine.dispatch unpauses on resolution. |
|
|
770
775
|
| `providers.list` | none | `{ aliases: ProviderAlias[] }` | Lists configured `PLURNK_MODEL_<alias>` entries with `{alias, provider, model, active}`. Clients use to populate model-selection UI. |
|
|
771
776
|
|
|
@@ -773,12 +778,12 @@ registry.register("loop.run", {
|
|
|
773
778
|
|
|
774
779
|
| Method | Params | Result | Notes |
|
|
775
780
|
|---------------|-------------------------------------|------------------------|-------|
|
|
776
|
-
| `entry.read` | `target: string` | `{ status, entry }` | Read the full entry shape (channels + tags + metadata) at the given URI. |
|
|
777
|
-
| `log.read` | `loopId?: number`, … | `{ entries: LogEntry[] }` | Read recent log entries from the attached session, optionally filtered by loop. |
|
|
781
|
+
| `entry.read` | `target: string` | `{ status, entry }` | Read the full entry shape (channels + tags + metadata) at the given URI. {§13.5-entry-read} |
|
|
782
|
+
| `log.read` | `loopId?: number`, … | `{ entries: LogEntry[] }` | Read recent log entries from the attached session, optionally filtered by loop. {§13.5-log-read} |
|
|
778
783
|
|
|
779
784
|
**DSL operations (client-driven, mirror grammar)**
|
|
780
785
|
|
|
781
|
-
Per the **Speak in DSL, not plumbing** rule (AGENTS.md): `op.*` methods construct DSL statements internally and dispatch through `Engine.dispatch`. Param shapes are ergonomic (semantic names, not HEREDOC slots); semantics are the DSL's.
|
|
786
|
+
Per the **Speak in DSL, not plumbing** rule (AGENTS.md): `op.*` methods construct DSL statements internally and dispatch through `Engine.dispatch`. {§13.5-op-mirror} Param shapes are ergonomic (semantic names, not HEREDOC slots); semantics are the DSL's.
|
|
782
787
|
|
|
783
788
|
Each `op.*` call creates a turn in the connection's client loop (§13.7), dispatches, fires `log/entry`, returns the dispatch result.
|
|
784
789
|
|
|
@@ -808,7 +813,7 @@ Server-initiated events on the same WebSocket.
|
|
|
808
813
|
|
|
809
814
|
| Notification | Params | When fired |
|
|
810
815
|
|--------------------|-------------------------------------|------------|
|
|
811
|
-
| `log/entry` | `{ entry: LogEntry }` | Every `log_entries` write. |
|
|
816
|
+
| `log/entry` | `{ entry: LogEntry }` | Every `log_entries` write. {§13.6-log-entry-notify} |
|
|
812
817
|
| `loop/terminated` | `{ loopId, finalStatus, hitMaxTurns }` | Loop reaches terminal status. |
|
|
813
818
|
| `loop/proposal` | `{ logEntryId, sessionId, runId, loopId, turnId, op, target, body, attrs, flags }` | Dispatch pauses on status=202. Carries `flags` so server-YOLO clients can suppress review UI. Client responds with `loop.resolve` (or `PLURNK_PROPOSAL_TIMEOUT_MS` fires). |
|
|
814
819
|
| `session/created` | `{ id, name, projectRoot, persona }` | Any client creates a session. |
|
|
@@ -874,7 +879,7 @@ Plurnk-specific (`-32000` to `-32099`):
|
|
|
874
879
|
| -32006 | Mimetype unavailable |
|
|
875
880
|
| -32007 | Timeout |
|
|
876
881
|
|
|
877
|
-
Error responses MAY include `data: {…}` with structured context (404'd path, timed-out method, etc.).
|
|
882
|
+
Error responses MAY include `data: {…}` with structured context (404'd path, timed-out method, etc.). {§13.8-error-codes}
|
|
878
883
|
|
|
879
884
|
### §13.9 Versioning
|
|
880
885
|
|
|
@@ -896,43 +901,73 @@ Each entry: question, answer, rationale, migration path.
|
|
|
896
901
|
|
|
897
902
|
**Migration path.** If a plugin needs to inject a packet section, grow a single `packet.augment` hook called after `#buildIndex`; plugins return system/user augmentation objects merged into the packet. Additive — engine-direct base stays.
|
|
898
903
|
|
|
899
|
-
### §14.2 Tokenomics: real provider tokens,
|
|
904
|
+
### §14.2 Tokenomics: real provider tokens, render-weight budget, per-scheme balance
|
|
905
|
+
|
|
906
|
+
**Question.** How does plurnk track token costs accurately enough to ground the model's SHOW/HIDE/compose decisions? Accuracy is the whole game — a budget that smells wrong is one the model stops trusting and curating against.
|
|
907
|
+
|
|
908
|
+
**Two measures, never conflated:**
|
|
900
909
|
|
|
901
|
-
**
|
|
910
|
+
- **render-weight** — the tokens the model actually processes this turn (rendered previews + meta + fences). The budget is about this.
|
|
911
|
+
- **content-depth** — an entry's full content size (`entry_channels.tokens`). The manifest's `tokens` is this.
|
|
902
912
|
|
|
903
|
-
**
|
|
913
|
+
**Built.**
|
|
904
914
|
|
|
905
|
-
|
|
915
|
+
- **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. {§14.2-tokens-stored-at-write}
|
|
916
|
+
- **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 previews; render-weight is the accurate measure. {§14.2-render-weight-budget}
|
|
917
|
+
- **Per-scheme balance.** A markdown table groups the model's context by scheme — `indexed`/`archived` counts and render-weight `tokens` — anchored `repo, known, unknown, log`, tail sorted by tokens. The model sees at a glance what's eating its window. {§14.2-per-scheme-balance}
|
|
918
|
+
- **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. {§14.2-context-percent}
|
|
919
|
+
- **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. {§14.2-depth-render-fresh}
|
|
920
|
+
- **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. {§14.2-over-budget-floor}
|
|
906
921
|
|
|
907
|
-
|
|
908
|
-
- **Stored at write time.** `entry_channels.tokens` populated by `_entry-crud.ts` on INSERT/UPDATE. `log_entries.tokens` populated by `Engine.#writeLog`. Write helper IS the contract sister modules see.
|
|
909
|
-
- **Render-time SUM.** `Engine.#buildIndex` adds per-scheme `SUM(tokens)`. Only wrapper strings re-tokenize each render — bounded.
|
|
910
|
-
- **Hot model switch is a feature.** Session model change walks `entry_channels` + `log_entries` and recomputes against new tokenizer. One-time cost at switch boundary.
|
|
911
|
-
- **Budget table self-reference via placeholder substitution.** Render with `{{tokenUsage}}`/`{{tokensFree}}`, measure, substitute. ±1–2 token drift accepted.
|
|
912
|
-
- **Telemetry shape.** Per-scheme breakdown table in `packet.user.telemetry.budget` with `tokenCeiling`/`tokenUsage`/`tokensFree` headline; markdown table groups by scheme with indexed-count, archived-count, tokens.
|
|
922
|
+
**Rejected / obviated.**
|
|
913
923
|
|
|
914
|
-
**
|
|
924
|
+
- **Hot model-switch recompute** — *obviated* by render-fresh depth (above). There's no cross-loop cache to recompute: the manifest re-tokenizes at build, the budget always did. A model change between loops can't stale a number nothing caches.
|
|
925
|
+
- **Reasoning-token surfacing** — *rejected* for the model-facing budget: reasoning is *output*, not window-context, and the model can't HIDE it. The thinking-vs-output distinction is cost-forensics (the usage breakdown is stored on every packet), not a curation signal.
|
|
915
926
|
|
|
916
|
-
**
|
|
927
|
+
**Rationale.** Rummy used chars/DIVISOR + compute-at-SELECT only because its sync-only SQL couldn't call a tokenizer. plurnk has real `countTokens`: store content tokens once at write (the depth), measure the small rendered output for the budget (the weight). Approximation can't ground curation — the model only curates against numbers it trusts.
|
|
928
|
+
|
|
929
|
+
**Migration path.** None on cost — SQLite, JS, and a local tokenizer are negligible against the model's token budget, the only thing worth economizing. The fallback divisor is a correctness tripwire (no provider tokenizer wired), not a performance retreat. Schema unchanged.
|
|
917
930
|
|
|
918
931
|
### §14.3 Workspace identity, membership, disk co-location
|
|
919
932
|
|
|
920
933
|
**Question.** How does plurnk represent the project a session works on? Where does file membership come from? Does writing an entry imply writing to disk?
|
|
921
934
|
|
|
922
|
-
**
|
|
935
|
+
**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).
|
|
936
|
+
|
|
937
|
+
**Built — git-substrate membership.** {§14.3-git-membership}
|
|
938
|
+
|
|
939
|
+
- **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.
|
|
940
|
+
- **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).
|
|
941
|
+
- **EMI eager + relevance-bounded.** Materializes only the run's indexed members (`visibility.indexed=1`) at prompt-composition, re-reading disk so a divergent member reflects current content.
|
|
942
|
+
- **Disk-read-first edits.** Disk writes route through `File.edit`, which reads disk before diffing — an untouched member's baseline is its real content, never empty. Silent overwrite is structurally prevented.
|
|
923
943
|
|
|
924
|
-
|
|
925
|
-
- **D2. No new `entries.scope` value.** Stays `∈ {'agent', 'session'}`.
|
|
926
|
-
- **D3. Disk co-location optional.** `sessions.project_root` set → disk side-effects on accept; null → canonical entries land, no disk write (client owns materialization).
|
|
927
|
-
- **D4. Membership without git: no fs-walk.** git present → ls-files + constraint overlay. git absent → `effect='add'` constraints only.
|
|
928
|
-
- **D5. EMI is eager + relevance-bounded.** Fires at prompt-composition time; checks only entries the current run will include (`visibility.indexed=1`). Divergent → re-read disk + emit synthetic log entry.
|
|
929
|
-
- **D6. Edit-on-untouched-file: disk-read-first.** EDIT targeting a member with no existing entry reads disk first to populate canonical baseline, then diffs. Prevents silent overwrite.
|
|
930
|
-
- **D7. Constraint pattern matching via `node:path.matchesGlob`.** No dep. Helper wrapper for swap-out optionality.
|
|
944
|
+
**Deferred — promised, not yet built.** Each carries an anchor with a deliberately-red test so the deferral is tracked, never silently covered.
|
|
931
945
|
|
|
932
|
-
**
|
|
946
|
+
- **Constraint overlay — the client supersede.** {§14.3-constraint-overlay} A `session_constraints` table layering *add* (members git misses), *ignore* (drop ones it tracks), and *read-only* (member for read, writes rejected) over the git substrate; glob matching via `node:path.matchesGlob`. git-absent, these `effect='add'` constraints are the *sole* membership source — so this overlay is not merely the override knob, it is the entire membership mechanism without git.
|
|
947
|
+
- **EMI divergence signal.** {§14.3-emi-divergence-signal} When EMI re-reads a member whose disk content changed out-of-band, emit a synthetic log entry so the model sees the change (the re-read is built; only the signal is deferred).
|
|
948
|
+
|
|
949
|
+
**Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory. EMI's eager-relevance-bounded firing prevents stale previews without per-turn full-repo cost.
|
|
933
950
|
|
|
934
951
|
**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.
|
|
935
952
|
|
|
953
|
+
### §14.4 Budget enforcement: the grinder
|
|
954
|
+
|
|
955
|
+
**Question.** §14.2 surfaces the budget honestly and the model curates against `tokensFree` — almost always enough. Three states defeat self-regulation, none of them the model's doing: a jumbo prompt, a jumbo repo (the index overflows before the model acts), an unexpectedly large read. What enforces the ceiling when the signal isn't enough?
|
|
956
|
+
|
|
957
|
+
**Decision — a pre-LLM grinder, fired only on actual overflow.** In `Engine.runTurn`, after the packet is assembled (`#buildRequestPacket`) and before `provider.generate`, the assembled render-weight (§14.2) is measured against the ceiling. At or under → the packet ships untouched; the grinder never trims speculatively or "helpfully." {§14.4-overflow-only} On overflow it reclaims window in two passes, re-measuring between:
|
|
958
|
+
|
|
959
|
+
- **Prior-turn rollback.** The immediately-prior turn's log entries — the latest emissions, the ones that pushed the packet over — are hidden (`indexed=0`, the same flag the model's own HIDE uses); the prior turn fit by induction, so reverting it usually lands back under. Hidden, not deleted: rows and bodies persist and are re-SHOWable, so log *history* is preserved while the render shrinks. {§14.4-layer1-rollback}
|
|
960
|
+
- **Index collapse.** If clearing replays isn't enough, every catalog entry except `plurnk://manifest.json` is hidden (`indexed=0`); the manifest stays as the lifeline the model reads to pull anything back by path. The index is engine-built scaffolding — a derived, previews-only view regenerated each turn — never the model's context, so collapsing it removes no capability. {§14.4-layer2-index-collapse}
|
|
961
|
+
- **Hard stop.** If the packet still overflows with only the manifest left, the loop abandons at 499 (`engine_loop_cancel`) — the path `maxTurns` and the strike threshold already use. No third pass. {§14.4-hard-413-abort}
|
|
962
|
+
|
|
963
|
+
**Strike coupling.** A grinder fire bumps the engine's `turnErrors` — the same internal counter cycle detection feeds — so an overflow counts toward the strike streak that ends a runaway loop at 499. This is the pressure that keeps self-curation the path of least resistance. {§14.4-strike-coupling} **Turn 0/1 is exempt:** the first turn's overflow precedes any model action — it's the environment, not the model — so it never strikes. {§14.4-soft-turn-0-1}
|
|
964
|
+
|
|
965
|
+
**What the model sees.** A `budget_overflow` telemetry event (§15.1), 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 per-scheme budget table (§14.2) 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. {§14.4-event-model-terms} Per the gamification policy (§15.1), the *strike* the overflow triggers stays engine-internal; the model sees the hidden entries, never the accounting.
|
|
966
|
+
|
|
967
|
+
**Rationale.** The model owns curation (§14.2); the grinder is the exceptional backstop. It only *hides* — reversibly — the prior turn's render, then the index scaffolding; nothing is deleted, so the model can SHOW any of 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 (and the §1316 index-collapse pass that doc never caught up to).
|
|
968
|
+
|
|
969
|
+
**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.
|
|
970
|
+
|
|
936
971
|
---
|
|
937
972
|
|
|
938
973
|
## §15 Packet shape
|
|
@@ -962,6 +997,8 @@ type Packet = {
|
|
|
962
997
|
|
|
963
998
|
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a system-origin `EDIT` against `plurnk://prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's entry is foisted out of `packet.system.index` into `packet.user.prompt` — no duplicate rendering. Previous loops' prompts remain in the index, addressable for READ/HIDE. {§15-current-prompt-foist}
|
|
964
999
|
|
|
1000
|
+
**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 indexed, READable, and queryable. Body is `application/json`: a flat array, one item per entry across all schemes (hidden ones included — the model sees what it could pull in), each `{ path, shown, channels: { <name>: { mimetype, tokens, lines } } }`. `shown` is whether the entry is in this turn's index — `[?(@.shown==false)]` gives the hidden inventory to SHOW; `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. {§15-manifest-catalog}
|
|
1001
|
+
|
|
965
1002
|
### §15.1 user.telemetry — model-facing runtime telemetry
|
|
966
1003
|
|
|
967
1004
|
Slot for telemetry the model MUST react to immediately. Rendered at the bottom of the user section. Errors are transient — appear on the turn AFTER the failure, clear once seen. `packet.system.log[]` is the durable audit; `telemetry.errors[]` is the **alert**.
|
|
@@ -986,6 +1023,7 @@ Slot for telemetry the model MUST react to immediately. Rendered at the bottom o
|
|
|
986
1023
|
| `parse_error` | Grammar parser failed mid-statement | `source: "grammar"`, `kind`, `message`, `position` (content-offset), `snippet` (model's offending line, N:\t-prefixed), `parserSource` (`lexer`/`parser`/`visitor`) |
|
|
987
1024
|
| `action_failure` | Log entry with `status_rx ≥ 400` from previous turn | `kind`, `coordinate` (`<L>/<T>/<S>`), `op`, `status`, `target` (URI or null). May carry scheme-emitted `error` (a terse fact, not guidance). |
|
|
988
1025
|
| `max_commands_exceeded` | Single emission exceeded `PLURNK_MAX_COMMANDS` cap; overflow ops dropped without dispatch | `source: "engine:rail"`, `kind`, `emitted`, `dropped` |
|
|
1026
|
+
| `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) |
|
|
989
1027
|
|
|
990
1028
|
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://`. {§15.1-no-error-scheme}
|
|
991
1029
|
|
|
@@ -997,7 +1035,7 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
|
|
|
997
1035
|
|
|
998
1036
|
### §15.2 user.system_requirements — static per-turn rules
|
|
999
1037
|
|
|
1000
|
-
Rendered at the END of the user packet under `# Plurnk System Requirements` — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`").
|
|
1038
|
+
Rendered at the END of the user packet under `# Plurnk System Requirements` {§15.2-requirements-render-last} — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. The header is omitted entirely when the requirements string is empty. {§15.2-requirements-omitted-when-empty} Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`").
|
|
1001
1039
|
|
|
1002
1040
|
**Sourcing:** caller supplies the string via `runLoop({ requirements })` / `runTurn({ requirements })`. Plurnk-service exposes `PATHS.defaultRequirements` (resolves `PLURNK_REQUIREMENTS` env → in-package `requirements.md`). No DB cascade — same string every turn.
|
|
1003
1041
|
|
|
@@ -1027,10 +1065,10 @@ Glob anchoring (`TODO*` starts-with, `*TODO*` contains, `*.log` ends-with, `[Tt]
|
|
|
1027
1065
|
|
|
1028
1066
|
### §16.2 Matcher result shape (uniform across dialects)
|
|
1029
1067
|
|
|
1030
|
-
Body:
|
|
1068
|
+
Body: one match per line as `<line>:\t<value>` — the same `N:\t` form READ emits, so `<L>` can page the result set. Empty → 204. Mimetype = `text/markdown` regardless of source dialect.
|
|
1031
1069
|
|
|
1032
|
-
-
|
|
1033
|
-
-
|
|
1070
|
+
- `<line>` — 1-indexed source line, shifted back to source coordinates when matching inside an `<L>` slice.
|
|
1071
|
+
- `<value>` — the extracted match, rendered bare when it is a single-line string, else JSON-encoded (preserving the one-match-per-line invariant). Polymorphic per dialect:
|
|
1034
1072
|
- **bare regex** → string (full match)
|
|
1035
1073
|
- **anon captures** → array `[c1, c2, …]`
|
|
1036
1074
|
- **named captures** → object `{name: v, …}`. Mixed anon+named uses positional keys `"1"`, `"2"` alongside names.
|
|
@@ -1038,7 +1076,6 @@ Body: JSON array of `{line, matched, matching?}`. Empty → 204. Mimetype = `app
|
|
|
1038
1076
|
- **jsonpath** → JSON value at the path
|
|
1039
1077
|
- **xpath text/attr** → string
|
|
1040
1078
|
- **xpath node** → serialized XML
|
|
1041
|
-
- `matching` — per-instance discriminator. Present for jsonpath wildcards (bracket form like `$['users'][0]['name']`) and xpath multi-match (`(//user)[1]`). Omitted otherwise.
|
|
1042
1079
|
|
|
1043
1080
|
| Dialect | Extracts | Natural use |
|
|
1044
1081
|
|---|---|---|
|
|
@@ -1073,12 +1110,12 @@ Body: JSON array of `{line, matched, matching?}`. Empty → 204. Mimetype = `app
|
|
|
1073
1110
|
|
|
1074
1111
|
### §16.4 Structural EDIT on JSON
|
|
1075
1112
|
|
|
1076
|
-
When effective mimetype is `application/json`, EDIT dispatches through `applyJsonItemEdit`. Body shape rule (parse-then-discriminate):
|
|
1113
|
+
When effective mimetype is `application/json`, EDIT dispatches through `applyJsonItemEdit`. {§16.4-structural-json-edit} Body shape rule (parse-then-discriminate):
|
|
1077
1114
|
|
|
1078
1115
|
- Body parses as JSON array → items to splice
|
|
1079
1116
|
- Body parses as non-array JSON → single item to splice
|
|
1080
1117
|
- Empty body → delete the selection
|
|
1081
|
-
- Body fails JSON parse → 400 (path-extension declares intent; honor strictly)
|
|
1118
|
+
- Body fails JSON parse → 400 (path-extension declares intent; honor strictly) {§16.4-json-parse-fail-400}
|
|
1082
1119
|
|
|
1083
1120
|
**Array source marker × body:**
|
|
1084
1121
|
|
|
@@ -1109,14 +1146,14 @@ When effective mimetype is `application/json`, EDIT dispatches through `applyJso
|
|
|
1109
1146
|
- `known://config.yaml` → `application/yaml`
|
|
1110
1147
|
- `known://users` (no suffix) → `text/markdown` (Known manifest default)
|
|
1111
1148
|
|
|
1112
|
-
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.
|
|
1149
|
+
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. {§16.5-extension-mimetype}
|
|
1113
1150
|
|
|
1114
1151
|
### §16.6 Render rule (mimetype-driven)
|
|
1115
1152
|
|
|
1116
1153
|
`packet-wire` log render branches on `isLineNavigableMimetype`:
|
|
1117
1154
|
|
|
1118
|
-
- **Line-navigable** (text/markdown, text/plain, csv, source code, yaml, toml) → `N:\t` line-number prefix per line
|
|
1119
|
-
- **Tree-navigable** (application/json, application/xml, text/html, +json/+xml suffixes) → verbatim body (no `N:\t` — outer line numbers would collide with structural navigation like jsonpath/xpath)
|
|
1155
|
+
- **Line-navigable** (text/markdown, text/plain, csv, source code, yaml, toml) → `N:\t` line-number prefix per line {§16.6-line-navigable-prefix}
|
|
1156
|
+
- **Tree-navigable** (application/json, application/xml, text/html, +json/+xml suffixes) → verbatim body (no `N:\t` — outer line numbers would collide with structural navigation like jsonpath/xpath) {§16.6-tree-navigable-verbatim}
|
|
1120
1157
|
|
|
1121
1158
|
The `N:\t` prefix is presentation/reference per plurnk.md ("not part of the source"); stripped before any matcher operation on the log entry.
|
|
1122
1159
|
|
|
@@ -1124,16 +1161,12 @@ The `N:\t` prefix is presentation/reference per plurnk.md ("not part of the sour
|
|
|
1124
1161
|
|
|
1125
1162
|
Auto-derived text mimetypes anywhere in plurnk-service normalize to `text/markdown`:
|
|
1126
1163
|
|
|
1127
|
-
- `<L>` slice on line-navigable source → `text/markdown`
|
|
1164
|
+
- `<L>` slice on line-navigable source → `text/markdown` {§16.7-text-markdown-normalize}
|
|
1128
1165
|
- File scheme extension fallback → `text/markdown`
|
|
1129
1166
|
- `Mimetypes.detect()` returning `text/plain` → normalized via `normalizeAutoTextMimetype`
|
|
1130
1167
|
|
|
1131
1168
|
`text/plain` survives only where a scheme explicitly declares it (exec stdout/stderr — subprocess byte-streams aren't markdown). The model never auto-encounters `text/plain` from defaults.
|
|
1132
1169
|
|
|
1133
|
-
### §16.8 EDIT response surfaces unified diff
|
|
1134
|
-
|
|
1135
|
-
Every EDIT@200/201 carries `diff` (unified diff format) on the result. Both `_entry-ops.editSessionEntry` and `File.edit` produce it. `packet-wire` log render emits the diff under the entry's fence so the model sees what changed without a follow-up READ — wrong-marker mistakes (e.g., `<1>` when `<-1>` was meant) become visible on the next turn. Omitted on no-op edits (content unchanged). {§16.8-edit-diff}
|
|
1136
|
-
|
|
1137
1170
|
### §16.9 Op-level invariants and resolved ambiguities
|
|
1138
1171
|
|
|
1139
1172
|
Carried from the contract walk; durable.
|
|
@@ -1143,9 +1176,9 @@ Carried from the contract walk; durable.
|
|
|
1143
1176
|
- **EDIT `<L>` on non-existent entry** → body becomes content; `<L>` is positional-only on existing content.
|
|
1144
1177
|
- **COPY `<L>`** → source range, symmetric with READ `<L>`.
|
|
1145
1178
|
- **READ rx** prefixes each line with `N:\t` per §16.6. `sliceLinesRaw` (used by COPY) returns the lines without prefix.
|
|
1146
|
-
- **FIND body matcher** applies to
|
|
1147
|
-
- **SHOW/HIDE** has a single-entry path (exact pathname, no slots) and a multi-entry path (any of body/signal/`<L>` present). Multi-entry path treats target as pathname glob scope, applies body matcher to
|
|
1148
|
-
- **SEND[410]** with `#fragment
|
|
1179
|
+
- **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).
|
|
1180
|
+
- **SHOW/HIDE** has a single-entry path (exact pathname, no slots) and a multi-entry path (any of body/signal/`<L>` present). Multi-entry path treats target as pathname glob scope, applies the body matcher to entry content (shared with FIND via `matchPathnames`), filters by `signal` tags, paginates with `<L>`, and flips visibility on the resulting set.
|
|
1181
|
+
- **SEND[410]** deletes as a side-effect (not the model idiom; §6.5): with `#fragment`, that channel only; without, the whole entry. **SEND[499]** is owned by the streaming scheme that holds the subscription.
|
|
1149
1182
|
- **File scheme** reads disk content with mimetype detected via `Mimetypes.detect({ path })` (plumbed through `PlurnkSchemeContext.mimetypes`). Binary mimetypes → 415 on READ and EDIT.
|
|
1150
1183
|
|
|
1151
1184
|
### §16.10 Directed-SEND status code policy
|