@plurnk/plurnk-schemes 3.3.0 → 3.3.2

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 (73) hide show
  1. package/README.md +6 -6
  2. package/SPEC.md +119 -745
  3. package/dist/ByteSource.d.ts +13 -0
  4. package/dist/ByteSource.d.ts.map +1 -1
  5. package/dist/ByteSource.js +62 -1
  6. package/dist/ByteSource.js.map +1 -1
  7. package/dist/MessageScheme.js +1 -1
  8. package/dist/MessageScheme.js.map +1 -1
  9. package/dist/MetadataOptions.d.ts +2 -7
  10. package/dist/MetadataOptions.d.ts.map +1 -1
  11. package/dist/MetadataOptions.js +5 -8
  12. package/dist/MetadataOptions.js.map +1 -1
  13. package/dist/MimetypeClassifier.d.ts +5 -0
  14. package/dist/MimetypeClassifier.d.ts.map +1 -1
  15. package/dist/MimetypeClassifier.js +33 -5
  16. package/dist/MimetypeClassifier.js.map +1 -1
  17. package/dist/OutputScheme.js +2 -2
  18. package/dist/OutputScheme.js.map +1 -1
  19. package/dist/PacketSections.d.ts +9 -1
  20. package/dist/PacketSections.d.ts.map +1 -1
  21. package/dist/PacketSections.js +1 -1
  22. package/dist/PacketSections.js.map +1 -1
  23. package/dist/ResourceNames.js +1 -1
  24. package/dist/ResourceNames.js.map +1 -1
  25. package/dist/Results.d.ts.map +1 -1
  26. package/dist/Results.js +4 -1
  27. package/dist/Results.js.map +1 -1
  28. package/dist/SchemeDiscovery.d.ts.map +1 -1
  29. package/dist/SchemeDiscovery.js +9 -48
  30. package/dist/SchemeDiscovery.js.map +1 -1
  31. package/dist/Slicer.js +11 -11
  32. package/dist/Slicer.js.map +1 -1
  33. package/dist/Summarize.js +2 -2
  34. package/dist/Summarize.js.map +1 -1
  35. package/dist/ctx.d.ts +2 -3
  36. package/dist/ctx.d.ts.map +1 -1
  37. package/dist/edit-receipt.d.ts +4 -0
  38. package/dist/edit-receipt.d.ts.map +1 -1
  39. package/dist/handler.d.ts +26 -2
  40. package/dist/handler.d.ts.map +1 -1
  41. package/dist/index.d.ts +7 -15
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -11
  44. package/dist/index.js.map +1 -1
  45. package/package.json +5 -5
  46. package/dist/FileByteSource.d.ts +0 -9
  47. package/dist/FileByteSource.d.ts.map +0 -1
  48. package/dist/FileByteSource.js +0 -43
  49. package/dist/FileByteSource.js.map +0 -1
  50. package/dist/GeneratedByteSource.d.ts +0 -8
  51. package/dist/GeneratedByteSource.d.ts.map +0 -1
  52. package/dist/GeneratedByteSource.js +0 -21
  53. package/dist/GeneratedByteSource.js.map +0 -1
  54. package/dist/PathMimetype.d.ts +0 -6
  55. package/dist/PathMimetype.d.ts.map +0 -1
  56. package/dist/PathMimetype.js +0 -30
  57. package/dist/PathMimetype.js.map +0 -1
  58. package/dist/edit-statement.d.ts +0 -5
  59. package/dist/edit-statement.d.ts.map +0 -1
  60. package/dist/edit-statement.js +0 -2
  61. package/dist/edit-statement.js.map +0 -1
  62. package/dist/module-slice.d.ts +0 -22
  63. package/dist/module-slice.d.ts.map +0 -1
  64. package/dist/module-slice.js +0 -2
  65. package/dist/module-slice.js.map +0 -1
  66. package/dist/packet.d.ts +0 -10
  67. package/dist/packet.d.ts.map +0 -1
  68. package/dist/packet.js +0 -4
  69. package/dist/packet.js.map +0 -1
  70. package/dist/resource-tree.d.ts +0 -15
  71. package/dist/resource-tree.d.ts.map +0 -1
  72. package/dist/resource-tree.js +0 -2
  73. package/dist/resource-tree.js.map +0 -1
package/SPEC.md CHANGED
@@ -1,179 +1,41 @@
1
- # plurnk-schemes — Specification
2
-
3
- Contract for `@plurnk/plurnk-schemes-*` sibling packages. Audience: implementer of a URI scheme handler. Consumer: [plurnk-service](https://github.com/plurnk/plurnk-service) (SPEC.md §3).
4
-
5
- ## §manifest §1 Manifest
6
-
7
- <!-- map -->
8
- | Tag | Promise |
9
- | --- | --- |
10
- | {§manifest-authority} | `authority?` — URI-authority disposition: `"namespace"` folds authored authority into pathname; `"resource"`… |
11
- | {§manifest-metadata-modifier} | `metadataModifier?` — `true` declares `[metadata]` on resource operations. |
12
- | {§manifest-capability-traits} | `traits?` — Unique lowercase capability facts (for example `web` or `interaction`). |
13
- | {§manifest-client-display} | `glyph?` — Non-empty opaque client presentation glyph. |
14
- | {§service-metadata-keys} | Two keys are the service's, withheld from every owner. |
15
- | {§manifest-self-doc} | Discoverable scheme references. |
16
- <!-- /map -->
17
-
18
- ```json
19
- {
20
- "name": "@plurnk/plurnk-schemes-<name>",
21
- "plurnk": { "kind": "scheme", "name": "<scheme name>" }
22
- }
23
- ```
1
+ # `@plurnk/plurnk-schemes` Specification
24
2
 
25
- Class-level manifest (static field on the default export):
3
+ Contract for `@plurnk/plurnk-schemes-*` sibling packages, entry/channel handler dispatch, text/window slicing, and third-party scheme discovery.
26
4
 
27
- ```ts
28
- import type { SchemeManifest } from "@plurnk/plurnk-schemes";
29
-
30
- class Notes {
31
- static manifest: SchemeManifest = {
32
- name: "notes",
33
- authority: "namespace",
34
- channels: { body: "text/markdown", preview: "text/markdown" },
35
- defaultChannel: "body",
36
- category: "data",
37
- writableBy: ["model", "client"],
38
- volatile: false,
39
- modelVisible: true,
40
- textEditScopes: true,
41
- traits: ["web"],
42
- };
43
- }
44
- ```
5
+ ## §manifest Scheme manifest
6
+
7
+ Every scheme package declares `plurnk: { "kind": "scheme", "name": "<scheme>" }` (or `plurnk.schemes: [{ name, export }]`) in `package.json` and exposes a static or instance `manifest: SchemeManifest` validated by `Manifest.of`:
45
8
 
46
9
  | Field | Constraint |
47
10
  |---|---|
48
- | `name` | Matches `package.json#plurnk.name`. Addressing/routing identity (the URI prefix). |
49
- | §manifest-authority `authority?` | URI-authority disposition: `"namespace"` folds authored authority into pathname; `"resource"` preserves it as the entry authority. Absent means `"namespace"`. |
50
- | `channels` | `Record<channelName, mimetype>` for declared channel types. Channel names lowercase; an untyped default may coexist with typed siblings. |
51
- | `defaultChannel` | Lowercase channel targeted when path has no `#fragment`. Its omission from `channels` means its type is determined per resource; empty means no default. |
52
- | `category` | `"data"` (entry-bearing) \| `"logging"` (`log://` rows) \| `"control"` (owns no entries). |
53
- | `writableBy` | Subset of `["model", "client", "_plurnk"]`; empty declares a scheme no operation writes, such as a runtime's output, which its execution writes through its own stream. Consumer returns 403 for outside-set writes. |
11
+ | `name` | Matches `package.json#plurnk.name` (the addressing/routing URI prefix). |
12
+ | `authority?` | `"namespace"` (default — folds authored authority into pathname) or `"resource"` (preserves it as the entry authority). |
13
+ | `channels` | `Record<channelName, mimetype>` for declared channel types. Channel names are lowercase; an untyped default may coexist with typed siblings. |
14
+ | `defaultChannel` | Lowercase channel targeted when a path has no `#fragment`. Omission from `channels` means its type is determined per resource; empty string means no default. |
15
+ | `category` | `"data"` (entry-bearing), `"logging"` (`log://` rows), or `"control"` (owns no entries). |
16
+ | `writableBy` | Subset of `["model", "client", "_plurnk"]`; empty declares a scheme no operation writes (such as runtime output). Consumer returns `403` for outside-set writes. |
54
17
  | `volatile` | Boolean. |
55
18
  | `modelVisible` | Boolean. |
56
- | `folderScopes?` | `true` declares that a trailing slash on FIND is a collection scope. Absent/false means `/` is ordinary resource syntax. |
57
- | `lineAnchors?` | `true` publishes and accepts shared line anchors for stable textual representations without declaring EDIT support. |
58
- | `textEditScopes?` | `true` declares the shared textual EDIT coordinate and collision contract. It implies published anchors only on resources authorized for model writes by {§entry-address-resolution}; handlers receive only numeric coordinates and route standard entry mutation through `ctx.entries.operations.editBatch`. |
59
- | §manifest-metadata-modifier `metadataModifier?` | `true` declares `[metadata]` on resource operations. Absent/false rejects it at dispatch; admitted turns first apply {§metadata-ignored}. SEND recipients own their message metadata independently; accepting SEND options does not enable resource-read options. |
60
- | §manifest-capability-traits `traits?` | Unique lowercase capability facts (for example `web` or `interaction`). The scheme declares facts only; the consumer's general capability-policy cascade decides admission. |
61
- | `documentation?` | The **deep doc** (semantics / channels / edge cases), with an exact H2 `Summary` for discovery. Consumer materializes it as a pull-able `worker:///_plurnk/plurnk/<name>.md` entry READ on demand; never hits the hot path. Analogous to executor supplemental `details`. |
62
- | §manifest-client-display `glyph?` | Non-empty opaque client presentation glyph. It is projected through {§client-display-capabilities}; omission delegates identity fallback to the client. It never enters model teaching. |
63
- | `storedScheme?` | Value persisted to `entries.scheme`, which may differ from the addressing `name`. Absent defaults to `name`. It must be a non-null string because every persisted identity component is non-null. |
64
-
65
- The manifest is closed: unknown top-level fields fail admission. Every entry
66
- belongs to the operation's workspace. Resource coordinates and intrinsic
67
- mutability are resolved through {§entry-address-resolution}.
68
-
69
- {§manifest-metadata-modifier} is the scheme's explicit opt-in to
70
- {§scheme-metadata-modifier}. Core never parses or moves metadata into the
71
- target. Direct operation methods receive it on `statement.metadata`; exact
72
- resource preparation receives the same ordered raw blocks on
73
- `request.metadata`. A present modifier on any scheme that did not opt in is a
74
- non-retryable `400 scheme-metadata-unsupported`, and the handler is not called.
75
-
76
- The invoked handler owns this admission, not an address it consumes internally.
77
- An execution delegates its blocks to the selected executor under {§executor-metadata};
78
- its source acquisition is a separate READ without inherited execution metadata.
79
-
80
- §service-metadata-keys **Two keys are the service's, withheld from every owner.**
81
- `MetadataOptions.SERVICE_KEYS` names `env` and `lifetime`: the reader strips them from
82
- every owner's `options` and surfaces them raw beside it, so no scheme or executor
83
- interprets or refuses them and the service alone owns their shapes — the environment a
84
- scope opens, and how long its live work may run. `pattern` is the language's, lifted
85
- before the block reaches the reader ({§matcher-option}).
86
-
87
- {§manifest-capability-traits} `traits` are closed manifest data, not a policy
88
- channel. Names match `^[a-z][a-z0-9-]*$`, are unique, and carry through a
89
- synthesized runtime-output scheme. A handler never interprets policy or a named
90
- client mode. The consumer combines operation, access class, scheme, runtime,
91
- tool, and these declared facts into its general capability descriptor; the
92
- same decision governs dispatch and teaching visibility.
93
-
94
- Proposal behavior is orthogonal to capabilities. A handler proposes by
95
- returning 202; the consumer's proposal lifecycle routes it to the worker
96
- owner's review unless the server disposition `PLURNK_SERVICE_PROPOSALS` accepts
97
- or rejects it, with timeout as a lifecycle bound.
98
-
99
- §manifest-self-doc **Discoverable scheme references.** `documentation` contains the scheme's operation contracts and examples. The consumer materializes it as **`worker:///_plurnk/plurnk/<name>.md`**: FIND projects its exact H2 `Summary`, and READ retrieves its body on demand. Reference availability follows the registered capabilities, not illustrative operations. `glyph` is client display metadata under {§manifest-client-display}, not self-documentation.
100
-
101
- **Authoring convention — `docs/<name>.md`.** The contract field stays a plain `string`, but a sibling SHOULD keep the deep doc in a **`docs/<name>.md`** file at the package root rather than inline, and load it into the manifest at module init — e.g. `documentation: await readFile(new URL("../docs/<name>.md", import.meta.url), "utf-8")` (top-level await; `../` resolves identically from `src/` in test and `dist/` once built). Ship it by adding `docs/**/*` to `files`. This keeps prose out of the handler source and gives editors real Markdown; the consumer materializes it at `worker:///_plurnk/plurnk/<name>.md`. A missing file fails-hard at import (no silent empty doc).
102
-
103
- ## §2 Interface
104
-
105
- <!-- map -->
106
- | Tag | Promise |
107
- | --- | --- |
108
- | {§resource-publication-names} | Published resources have names, not invented ordinals. |
109
- | {§scheme-source-bytes} | Original bytes retain the full resource identity. |
110
- | {§web-materialization-contract} | Web materialization is a handler capability, not a package name. |
111
- | {§scheme-operation-dispatch} | Sister scheme handlers implement op methods consumed by plurnk-service via dispatch. |
112
- | {§resolved-edit-statement} | `ResolvedEditStatement` is the scheme-facing EDIT shape: its `lineMarker` is `LineMarker \| null` and… |
113
- | {§read-preparation} | `prepareRepresentation?` is the optional, operation-neutral acquisition seam for one exact resource. |
114
- | {§entry-address-resolution} | `resolveEntryAddress?` defines canonical addresses for client and model operations. |
115
- | {§handler-lifecycle} | A registered handler object is a process-lived shared instance and may receive overlapping operation calls. |
116
- | {§scheme-ctx-entries} | The entry CRUD primitives (`readEntry`/`writeEntry`/`deleteEntry`) are not handler operations; schemes use… |
117
- | {§scheme-edit-batch-receipt} | Regional mutation receipts. |
118
- <!-- /map -->
119
-
120
- §resource-publication-names **Published resources have names, not invented ordinals.**
121
- `ResourceNames` allocates one URI-encoded component per resource: a supplied name
122
- is preserved; no name produces eight hexadecimal characters. A repeated name
123
- gets an eight-character hash suffix, never overwrites a sibling. Passing stable
124
- identities in collection order reconstructs the same names. Allocation neither
125
- creates entries nor changes their ownership or publication policy.
126
-
127
- §scheme-source-bytes **Original bytes retain the full resource identity.**
128
- `byteSource({authority, pathname}, ctx)` supplies a `ByteSource`: `size()` returns
129
- the byte count or `null` for an absent source; `read(first, last)` returns the
130
- one-based inclusive byte window. The context carries the invoking actor and
131
- workspace. READ, FIND, transfers, and native model attachments
132
- use this same source contract. Public `EntryData` also accepts `bytes` beside
133
- an empty `content`; the consumer owns its storage encoding. Binary channels retain
134
- those bytes exactly. Textual channels accept exact UTF-8 (including BOM and line
135
- endings); invalid text bytes fail before replacing the stored resource.
136
-
137
- An optional `nativePath()` identifies the original regular file, or `null` when
138
- absent. It never creates a copy. The resource owner resolves it under the same
139
- identity and admission as the byte source; only an admitted consumer may use
140
- it. `FileByteSource` implements this for filesystem-backed schemes. Consumers
141
- must not substitute the file for a selected derived channel. Native execution
142
- preserves the file's name and surrounding filesystem; it grants no new
143
- capability, changes no working directory, and performs no automatic copying.
144
-
145
- §web-materialization-contract **Web materialization is a handler capability, not a package name.**
146
- A handler may publish `webMaterializer`: `fetch(url, {signal})` acquires a checked
147
- `WebFetchResult` (`null` for a dead source), `materialize(fetched, projection)` produces
148
- the entry's `WebMaterializedResult`, and `materializedChannels(result, request?)` lays it
149
- out as entry channels. The result and error shapes (`WebFetchResult`,
150
- `WebMaterializedResult`, `WebMaterializationError`) are this framework's. The `https`
151
- handler publishes it; the service's entry sink resolves it through the scheme registry and
152
- names no extension package, so a compatible extension replaces it through the ordinary installation
153
- and discovery path.
154
-
155
- §scheme-operation-dispatch Sister scheme handlers implement op methods consumed by plurnk-service via
156
- dispatch. The consumer owns READ for every `category: "data"` scheme and owns
157
- exact-resource FIND over its canonical representation; no handler method can
158
- replace either projection. Other absent operation methods return **501**. The
159
- exported **`SchemeHandler`** interface gives operation methods their grammar
160
- statement and `ctx`, including KILL's target, scope, metadata and aside. Bundled
161
- adapters use the same interface; no positional pathname/scope overload exists.
162
- Representation preparation receives a deliberately narrower request.
163
- `editBatch` returns the typed `EditBatchResult`
164
- specialization:
19
+ | `folderScopes?` | `true` declares that a trailing slash on `FIND` is a collection scope; absent/`false` treats `/` as ordinary resource syntax. |
20
+ | `lineAnchors?` | `true` publishes and accepts shared line anchors for stable textual representations without declaring `EDIT` support. |
21
+ | `textEditScopes?` | `true` declares the shared textual `EDIT` coordinate and collision contract ({§line-anchors}). Handlers receive numeric coordinates only and route standard entry mutation through `ctx.entries.operations.editBatch`. |
22
+ | `metadataModifier?` | `true` opts into `[metadata]` on resource operations ({§slot-order}). Absent/`false` rejects `[metadata]` at dispatch with non-retryable `400 scheme-metadata-unsupported`. |
23
+ | `traits?` | Unique lowercase capability facts matching `^[a-z][a-z0-9-]*$` (e.g. `web`, `interaction`) consumed by {§capability-admission}. |
24
+ | `documentation?` | Deep reference Markdown (loaded from `docs/<name>.md`) with an exact H2 `Summary`, materialized on demand at `plurnk:///<name>.md` (`FIND` projects H2 `Summary`; `READ` retrieves the body). |
25
+ | `glyph?` | Non-empty opaque client presentation glyph projected through {§mcp-server-definition}; never enters model teaching. |
26
+ | `storedScheme?` | Non-null string persisted to `entries.scheme` when different from `name` (defaults to `name`). |
165
27
 
166
- ```ts
167
- import type { EditBatchResult, ResolvedEditStatement, SchemeAddressCtx, SchemeHandler } from "@plurnk/plurnk-schemes";
28
+ - The manifest is closed: unknown top-level fields fail admission.
29
+ - **Reserved service metadata keys:** `MetadataOptions.SERVICE_KEYS` (`["lifetime"]`) are stripped from every owner's `options` and surfaced raw beside `options` so no scheme or executor interprets or refuses `lifetime` ({§exec-lifetime}).
30
+
31
+ ## §scheme-operation-dispatch Handler interface and operation dispatch
168
32
 
33
+ ```ts
169
34
  export interface SchemeHandler {
170
35
  ready?(): Promise<void>;
171
36
  close?(): Promise<void>;
172
37
  resolveEntryAddress?(target: ParsedPath, ctx: SchemeAddressCtx, access?: "read" | "write"): Promise<EntryAddress | SchemeResult | null>;
173
- prepareRepresentation?(
174
- request: RepresentationPreparationRequest,
175
- ctx: SchemeCtx,
176
- ): Promise<RepresentationPreparationResult>;
38
+ prepareRepresentation?(request: RepresentationPreparationRequest, ctx: SchemeCtx): Promise<RepresentationPreparationResult>;
177
39
  prepareFind?(statement: FindStatement, ctx: SchemeCtx): Promise<SchemeResult>;
178
40
  find?(statement: FindStatement, ctx: SchemeCtx): Promise<SchemeResult>;
179
41
  editBatch?(statements: readonly ResolvedEditStatement[], ctx: SchemeCtx): Promise<EditBatchResult>;
@@ -184,612 +46,124 @@ export interface SchemeHandler {
184
46
  }
185
47
  ```
186
48
 
187
- §resolved-edit-statement `ResolvedEditStatement` is the scheme-facing EDIT
188
- shape: its `lineMarker` is `LineMarker | null` and therefore contains numbers
189
- only. Core resolves the model-facing {§text-line-anchor-syntax} before invoking
190
- `SchemeHandler.editBatch` or `EntryOperationCaps.editBatch`; an unresolved
191
- anchor crossing that boundary is an internal contract violation. Core also
192
- prepares authored body syntax before this boundary ({§zero-width-column-one-insert});
193
- handlers apply the supplied replacement literally, including at zero-width spans.
194
- The anchor precondition remains core-private. A public handler declaring
195
- `textEditScopes: true` MUST route its standard textual mutation through
196
- `ctx.entries.operations.editBatch`, which rechecks that precondition at the
197
- snapshot owner and lands with the consumer's compare-and-swap. Applying the
198
- numeric coordinates independently would discard the declared collision
199
- contract. Core-owned special resources enforce the equivalent invariant inside
200
- their mutation adapter.
201
-
202
- §read-preparation `prepareRepresentation?` is the optional, operation-neutral
203
- acquisition seam for one exact resource. Core first resolves its canonical
204
- authority and pathname, binds `ctx.entries`/channels/subscriptions to
205
- that exact workspace coordinate, and removes the channel fragment. The request
206
- is exactly `{ target, metadata, authority, pathname }`: the target remains pure
207
- resource identity while `metadata` is the exact opaque modifier or `null`.
208
- READ coordinates, FIND matchers, the selected channel, and operation intent are
209
- structurally unavailable.
210
-
211
- A stored scheme omits the hook. An acquired scheme writes its complete channel
212
- topology through `ctx.entries` and returns `200` readiness. A live scheme first
213
- seeds those same canonical channels, opens a subscription, and may return `102`
214
- only while production remains live. Other statuses are exact terminal results.
215
- Per-channel producer status belongs durably on that channel as
216
- `producerResult`; no transient preparation result may carry content, channel
217
- selection, or channel outcomes. Neither preparation nor producer results supply
218
- the READ's `terminal` flag: core derives it from channel state under
219
- {§stream-observation-result}. After `200`, core alone selects the authored
220
- channel, applies tag/binary/text rules (including {§markerless-first-page}), and
221
- composes that channel's producer evidence. Cold and warm reads therefore have
222
- identical semantics.
223
-
224
- FIND classifies the target after canonical address resolution; a resolved
225
- collection remains broad when `folderScopes` is declared, even when the authored
226
- path omitted its trailing slash. Exact FIND uses the same `prepareRepresentation?`
227
- seam and then the standard entry query; the queried default channel's durable producer result composes
228
- with that exact query just as it does with exact READ. `prepareFind?` is only
229
- the optional broad-scope discovery seam
230
- used before standard entry selection when no custom broad `find()` exists. A
231
- custom `find()` may own genuinely different candidate enumeration, but never
232
- exact-resource materialization or READ coordinates. Every standard query uses
233
- the same canonical identity as `ctx.entries`; URL authority cannot disappear
234
- between preparation and lookup.
235
-
236
- An exact COPY/MOVE source also resolves and prepares through this seam before
237
- core selects its canonical stored channel. COPY/MOVE retain their own source
238
- scope algebra: an absent source scope means the complete channel and an explicit
239
- scope preserves raw source separators. Neither distinction is exposed to the
240
- producer. A live `102` leaves the composition unapplied; a ready representation
241
- is selected from the same channels used by READ and exact FIND. A selected
242
- channel's producer failure aborts before destination mutation; successful
243
- non-`200` content remains eligible for transfer.
244
-
245
- §entry-address-resolution `resolveEntryAddress?` defines canonical addresses for client and model operations. Core removes the channel fragment and target-slot escapes {§path-parentheses} before invocation; other identity components remain exact. The hook receives capability-free `SchemeAddressCtx` and returns only the resource coordinate. The optional access argument defaults to `read`; `write` checks intrinsic mutability before a mutation or proposal. Resolution is observational and has no effects.
49
+ | Seam / helper | Contract |
50
+ |---|---|
51
+ | Operation dispatch | Core owns `READ` for every `category: "data"` scheme and exact-resource `FIND` over its canonical representation; unimplemented optional methods return `501`. Log-targeted `KILL`, `WORK`, `FORK`, and native dispositions (`NOTE`, `WAIT`) are core-owned and never dispatch to scheme handlers. |
52
+ | `prepareRepresentation?(request, ctx)` | Operation-neutral acquisition seam for one exact resource (`{ target, metadata, authority, pathname }`, with no scope, matcher, or channel fragment). Writes canonical channels via `ctx.entries` and returns `200`, `102` while a live `StreamSubscription` remains active ({§exec-stream}), or an exact terminal result. Shared by exact `READ`, exact `FIND`, and exact `COPY`/`MOVE` sources. |
53
+ | `prepareFind?(statement, ctx)` | Broad-scope discovery seam invoked before standard entry selection when no custom `find()` is implemented. |
54
+ | `ResourceNames` | Allocates one URI-encoded component per published resource: preserves supplied names, generates 8-hex characters when unnamed, and appends an 8-hex hash suffix on collision without overwriting siblings. |
55
+ | `ByteSource` (`FileByteSource`, `GeneratedByteSource`) | Supplies `size()` (byte count or `null` when absent), 1-based inclusive `read(first, last)`, and optional `nativePath()` (original regular file path or `null`, never creating a copy). Binary channels preserve exact bytes; textual channels require valid UTF-8. |
56
+ | `webMaterializer` | Optional handler capability (`{ fetch, materialize, materializedChannels }` returning `WebFetchResult`, `WebMaterializedResult`, or `WebMaterializationError`) published by `https` and resolved through the scheme registry without hardcoding an extension package name. |
57
+
58
+ `resolveEntryAddress?(target, ctx, access)` receives capability-free `SchemeAddressCtx` and `access` (`"read"` default, or `"write"`). Core strips `#fragment` and {§path-syntax} target parentheses before invocation:
246
59
 
247
60
  | Return | Meaning |
248
61
  |---|---|
249
- | Hook absent | Use the standard coordinate from {§manifest-authority}. |
62
+ | Hook absent | Use the standard coordinate from `manifest.authority` ({§manifest}). |
250
63
  | `{ authority, pathname }` | Bind this exact resource in the operation's workspace. |
251
64
  | Non-success `SchemeResult` | Preserve the expected address or mutability refusal exactly. |
252
65
  | `null` | No resource at this selector. |
253
66
 
254
- Entry, channel, notification, subscription, mutation, proposal, client-read, and cancellation capabilities use that same workspace coordinate. Caller identity supplies provenance, not resource ownership or an alternate namespace. READ checks model write eligibility only to publish EDIT anchors ({§line-anchor-write-authority}); COPY checks the destination and MOVE also checks source mutability before any destination effect.
67
+ Handler lifecycle invariants for process-lived `SchemeHandler` instances:
255
68
 
256
- A sibling does `export default class X implements SchemeHandler` (with `static manifest: SchemeManifest`) and gets compile-time signature checking. Every registered handler exposes either that static manifest or an instance `manifest` for dynamically derived identities; `Manifest.of` validates the complete resolved declaration and its registration name before the handler becomes dispatchable. The interface is the handler-delegable subset of grammar's operation union. Log-targeted KILL is core-owned curation, and WORK/FORK and the native dispositions are core-owned worker/program operations; none is dispatchable to an extension scheme. **The statement + path types (`ReadStatement`, `SendStatement`, `UrlPath`, …) are re-exported from this barrel**, so a sibling depends on and peers (`^1`) ONLY `@plurnk/plurnk-schemes` — grammar rides underneath as the framework's transitive dep (§3).
69
+ | Concern | Contract |
70
+ |---|---|
71
+ | Readiness (`ready?`) | Runs after registration and before capability advertisement; establishes that advertised resources are usable. |
72
+ | Hook cardinality | Invoked once per unique handler object identity, even when multiple registered names share that object. |
73
+ | Operation context | `SchemeCtx` and capabilities extracted from it are operation-owned; only `StreamSubscription` from `subscriptions.open` is retainable. |
74
+ | Retained state & live ownership | Tenant- or address-selectable state keys every isolation coordinate; conflicting live acquisition fails explicitly without overwriting the existing owner. |
75
+ | Shutdown (`close?`) | Runs after in-flight scheme work drains and before backing stores close; the consumer awaits every unique handler close and aggregates failures. |
257
76
 
258
- §handler-lifecycle A registered handler object is a process-lived shared
259
- instance and may receive overlapping operation calls. Retained handler state is
260
- ordinary; handlers must make concurrent access and semantic isolation explicit.
77
+ Regional mutation receipts (`ResolvedEditStatement`, `EditBatchResult`): Core resolves {§matcher-prefix-claims} line anchors and {§zero-width-column-one-insert} bodies before calling `editBatch`, so `ResolvedEditStatement.lineMarker` contains numbers only. Regional `editBatch` returns `EditBatchResult` carrying `EditBatchReceipt` (`revision`, `unit: "lines" | "codePoints"`, `before`, `after`, optional advisory `parseIssues: { before, after }`, {§op-shapes}):
261
78
 
262
- | Concern | Contract |
263
- | ------------------ | -------- |
264
- | Readiness | `ready?()` runs after registration and before capability advertisement. It establishes that advertised resources are usable. |
265
- | Hook cardinality | The consumer invokes each hook once per unique handler object identity, even when multiple registered names share that object. |
266
- | Operation context | `SchemeCtx` and capabilities extracted from it remain operation-owned under {§scheme-ctx-lifetime}; only the `StreamSubscription` returned by `subscriptions.open` is retainable. |
267
- | Retained state | Tenant- or address-selectable state keys every semantic isolation coordinate. A global pool is valid only when it carries no cross-tenant session state. |
268
- | Live ownership | An address-selectable live resource has one unambiguous owner. Conflicting acquisition fails explicitly; it never overwrites the existing owner. |
269
- | Shutdown | After in-flight scheme work drains and before backing stores close, `close?()` releases partially or fully initialized handler resources. The consumer attempts and awaits every unique handler close, then aggregates every failure. |
79
+ | Batch outcome | Authored correlation | Landed effects |
80
+ |---|---|---|
81
+ | Applied as proposed (`AppliedEditBatchReceipt`) | `effects[N]` describes authored `EDIT` `N` (`requested`, `source`, `result`, `removed`, `inserted`, source-numbered `context`) | One truthful effect per authored `EDIT` |
82
+ | Resolver replaced proposed body (`ReviewerReplacementEditBatchReceipt`) | `disposition: "reviewer-replaced"`; `superseded[N]` retains authored marker `N` | One `replacement` effect describes the bytes that actually landed |
270
83
 
271
- Handlers without initialization or retained resources omit the corresponding
272
- hook.
84
+ ## §network-address Network address and media helpers
273
85
 
274
- §scheme-ctx-entries The entry CRUD primitives (`readEntry`/`writeEntry`/`deleteEntry`) are not handler operations; schemes use the manifest-bound `ctx.entries` (`read`, `write`, and `delete`), and the engine drives that same capability for COPY/MOVE/KILL orchestration when a `data` scheme omits resource-specific storage hooks. Proposal application is the optional `applyResolution` handler hook described in §3.bis.
86
+ `NetworkAddress.from(target)` normalizes `http`, `https`, `ws`, and `wss` targets into `{ scheme, authority, pathname, url, hasCredentials }`, and `NetworkAddress.render({ scheme, authority, pathname })` reconstructs the model-facing address with {§path-syntax} target escaping:
275
87
 
276
- A log-targeted KILL is not a handler method. Core owns that active-projection
277
- curation; an entry scheme has no log visibility state and never receives it.
88
+ | Component | Entry identity (`authority` / `pathname`) | Transport `url` | Plurnk channel |
89
+ |---|---|---|---|
90
+ | Scheme (`http`, `https`, `ws`, `wss`) | Exact addressed scheme | Yes | No |
91
+ | Host & non-default port | `<host>[:<port>]` in `authority` (WHATWG canonical host) | Yes | No |
92
+ | Path (`%28`/`%29` $\to$ `(` / `)`) & query | `<path>[?<query>]` in `pathname` (preserving order, duplicates, and empty `?`) | Yes | No |
93
+ | `#fragment` | No | No | Yes |
94
+ | Userinfo | Rejected (`hasCredentials: true`) | No | No |
278
95
 
279
- §scheme-edit-batch-receipt **Regional mutation receipts.** COPY and MOVE are not
280
- handler methods. The engine composes their source and
281
- destination resource selections over `ctx.entries` and uses `editBatch` for a
282
- scoped destination or source mutation. This keeps channel selection, snapshot
283
- ordering, cross-scheme failures, and proposal sequencing uniform for every data
284
- scheme. Scheme hooks return their ordinary mutation result and, for a regional
285
- `editBatch`, an `EditBatchResult` carrying its aggregate `EditBatchReceipt`:
96
+ Additional framework helpers:
286
97
 
287
- ```ts
288
- type EditReceiptUnit = "lines" | "codePoints";
289
-
290
- interface EditEffectReceipt {
291
- readonly requested: string;
292
- readonly source: string;
293
- readonly result: string;
294
- readonly removed: number;
295
- readonly inserted: number;
296
- readonly context: string;
297
- }
98
+ - **`MimetypeClassifier` & `PathMimetype`:** `isBinary(mimetype)` (delegates to `@plurnk/plurnk-mimetypes`), `isHtml(mimetype)` (`text/html`, `application/xhtml+xml`), `isJson(mimetype)` (`application/json` and `+json`, excluding NDJSON), `normalizeAutoText(mimetype)` (`text/plain` / `null` / `undefined` $\to$ `TEXT_PRIMITIVE_MIMETYPE` `"text/markdown"`), and `isTransferCompatible(source, destination, sourceClassification?)` (permits verbatim `COPY`/`MOVE` between equal mimetypes or from any textual source into `text/plain` or `text/markdown`, {§mimetype-content}). `PathMimetype.resolve(pathname, defaultMimetype, mimetypes)` detects by extension and auto-normalizes `text/plain` to `text/markdown`.
99
+ - **Packet transformation (`PacketSections`):** A scheme implementing `transformSections(sections: PacketSectionDraft[])` may add, remove, or reorder `{ name, slot: "system" | "user" | "assistant", header: string | null, content }` drafts before core weight measurement ({§packet-assembly}), validated before and after by `PacketSections.assertDrafts` (unique non-empty names).
100
+ - **Executor output schemes (`OutputScheme`, `Summarize`):** `OutputScheme.manifestFromRuntime(decl)` derives a read-only volatile data `SchemeManifest` from an executor `RuntimeDecl` (preserving `glyph` and `traits`); `Summarize.summarize(content, mimetype) -> OrientIndex` produces structural-only receipt indices.
101
+ - **Resource capture (`ctx.resources.capture`, `MessageAttachments`):** Acquires ordered snapshots (`{ name, mediaType, contentHash, target, bytes }`) without performing `SEND` or transferring ownership ({§op-methods-op-dispatch}).
298
102
 
299
- interface ParseIssueTransition {
300
- readonly before: number;
301
- readonly after: number;
302
- }
103
+ `ctx.projection` delegates model-facing projection to the configured mimetype framework (`ProjectionInputLimitError` exports its bounded-input failure):
303
104
 
304
- interface AppliedEditBatchReceipt {
305
- readonly revision: string;
306
- readonly unit: EditReceiptUnit;
307
- readonly before: number;
308
- readonly after: number;
309
- readonly parseIssues?: ParseIssueTransition;
310
- readonly effects: readonly EditEffectReceipt[];
311
- }
105
+ | Surface | Contract |
106
+ |---|---|
107
+ | `readable(content, mimetype)` | Project an acquired Unicode representation (`""` is present; only `null` denotes absence). |
108
+ | `binary(chunks, mimetype)` | Return bounded source `bytes`, nullable `readable: ProjectedText`, and `projectionIdentity`, consuming one async byte source under {§mimetype-binary-input}. |
109
+ | `identity(mimetype)` | Return opaque identity of configured projection behavior for cache and materialization freshness. |
110
+ | `isBinary(mimetype)` | Classify through the configured registry so installed handler declarations remain authoritative. |
111
+ | `parseIssues(content, mimetype)` | Return nonnegative parser-recovery count, or `undefined` when unavailable ({§mimetype-channel-selection}). |
112
+ | `ProjectedText` | Derived Unicode plus output mimetype, source mimetype, and opaque projection identity. |
312
113
 
313
- interface ReviewerReplacementEditBatchReceipt {
314
- readonly revision: string;
315
- readonly unit: EditReceiptUnit;
316
- readonly before: number;
317
- readonly after: number;
318
- readonly parseIssues?: ParseIssueTransition;
319
- readonly disposition: "reviewer-replaced";
320
- readonly superseded: readonly string[];
321
- readonly replacement: EditEffectReceipt;
322
- }
114
+ ## §slicer-text-algebra Text, window, and pagination slicing
323
115
 
324
- type EditBatchReceipt =
325
- | AppliedEditBatchReceipt
326
- | ReviewerReplacementEditBatchReceipt;
116
+ `Slicer` owns one coordinate algebra across textual lines, code-point regions, positional byte windows (`Slicer.window`), and ordered result pagination (`Slicer.page`):
327
117
 
328
- interface EditBatchResult extends SchemeResult {
329
- readonly editReceipt?: EditBatchReceipt | null;
330
- }
331
- ```
118
+ | Scope | Meaning |
119
+ |---|---|
120
+ | `<N>` | Whole 1-based physical line, byte (`unit: "byte"`), or result item `N` |
121
+ | `<N,M>` | Inclusive 1-based range `N..M` (`M = -1` selects through the end) |
122
+ | `<SL,SC,EL,EC>` | Exact exclusive-end text region using 1-based Unicode code-point columns ({§text-region}) |
123
+ | `<0>` / `<-1>` | Single-position mutation anchors before the first / after the final line (`start: null` on `window()`) |
124
+
125
+ - **One-based ranges (`range-starts-at-one`):** A two-coordinate range starts at `1`. `<0,M>` is refused `416 range-not-satisfiable` by `lines()`, `linesRaw()`, `textReplacement()`, `lineMarkerEdit[Batch]()`, `window()`, and `page()` (even on empty content/results) and never clamped to `<1,M>`. As a shared `READ` projection tolerance, `<0,M>` (for positive safe integer `M` or `-1`) selects `<1,M>`, retains `[0,M]` as requested, and emits scope-normalization evidence.
126
+ - **3-coordinate lowering and end clamping:** `<startLine,startColumn,endLine>` lowers to `<startLine,startColumn,endLine,endCol>` with boundary-validated normalization evidence. Harmless end-bound overshoots clamp to EOF / final code point; start-bound overshoots and inverted ranges fail `416`.
127
+ - **`Slicer.wholeLineBody(content, marker, body)`:** Appends the content's line separator only for a non-empty body without a trailing newline at a zero-width column-1 region ({§zero-width-column-one-insert}).
128
+ - **Batch edits (`lineMarkerEditBatch`):** Validates all replacements against one snapshot; resolves identical twins, co-located insertions, and evidenced 1-line shared endpoints (recorded in `merges`); rejects any other overlap with `409 overlapping-edits` (`conflicts`, `cleanRegions`, `conflictingRegions`, `editCount`, `applied: 0`) and applies all or none.
129
+ - **Results and Matcher (`Results`, `Matcher`):** `Results.isErrorStatus(status)` is `status >= 400`; `Results.isEntry`, `isProposal`, and `isPassthrough` guard optional result shapes; `Results.assert`, `assertReadResult`, and `assertMatchEvidenceList` enforce RFC 9457 `ProblemDetails` on `400..599` and `{ locator?, region?, enclosingRegion?, matched? }` on matches. `Matcher.matchAgainstContent(body, content, mimetype, mimetypes, diagnostic?)` adapts `Mimetypes.query` (`200` matches, `204` empty, `400 invalid-expression` with bounded `diagnostic`, `415 unsupported-dialect`, `203` raw-text fallback with `reason`, {§matcher-dispatch}).
130
+
131
+ | Problem code | Status | Contract & pinned wording |
132
+ |---|---:|---|
133
+ | `range-not-satisfiable` | `416` | Range lies outside available extent (`Lines and columns count from 1.`, `An exact region names its earlier position first: <SL,SC,EL,EC>.`, `-1 is the only position counted from the end; lines run 1..N.`, `Choose positions within 1..N.`, or ``Range <0,end> starts at 0, which is not a line; lines are numbered from 1 and 0 is only the `<0>` prepend position. Recovery: `<L,M>` selects lines L through M; `<0>` prepends and `<-1>` appends without replacing anything.`` / `Range <0,end> starts at 0; <unit> positions are numbered from 1.`). Unsupported scope arity: `A scope requires one position or an inclusive two-position range.` |
134
+ | `overlapping-edits` | `409` | Two `EDIT` regions overlap, or a whole-resource replacement coexists with another `EDIT`. `Recovery: Submit the whole-resource replacement by itself.` |
135
+ | `invalid-expression` | `400` | `The <dialect> matcher expression is invalid. Recovery: Revise the matcher expression.` |
136
+ | `unsupported-dialect` | `415` | `The <dialect> matcher is not supported for <mimetype>. Recovery: Use a matcher supported by the resource mimetype.` |
137
+ | `metadata-invalid` | `400` | `[metadata] must be a JSON array.` or `[metadata] must be a JSON array of option objects; every element is an object.` |
138
+ | `metadata-unsupported` | `400` | `Message metadata accepts only attachments.` |
139
+ | `attachments-invalid` | `400` | `attachments must be an array of non-empty resource addresses.` |
332
140
 
333
- | Batch outcome | Authored correlation | Landed effects |
334
- | ------------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------- |
335
- | Applied as proposed | `effects[N]` describes authored EDIT `N` | One truthful effect per authored EDIT |
336
- | Resolver replaced the proposed body | `superseded[N]` retains authored marker `N` | One `replacement` effect describes the bytes that actually landed |
337
-
338
- `requested` retains the admitted scope. `source` and `result` are resolved
339
- scope strings using {§text-scope-semantics}: `<line>`, `<first,last>`, or
340
- `<startLine,startColumn,endLine,endColumn>`. Empty selections use an exact
341
- zero-width region at the source insertion point or landed deletion join;
342
- they never invent a line beyond an unterminated EOF. Producers resolve these
343
- coordinates from the respective revision, not from another formatted receipt.
344
-
345
- The replacement form is not a heuristic attribution. An arbitrary resolver
346
- body supersedes every authored EDIT because its correspondence to those edits
347
- cannot be known. The consumer projects the replacement once on the durable
348
- proposal-owning row and projects each authored marker's superseded disposition
349
- on its own row.
350
-
351
- `context` is an ordered, source-numbered projection of the landed result. For a
352
- consumer-selected count `C`, it contains up to `C` surrounding lines and the
353
- first and last `C` landed lines at each result boundary. Overlapping windows
354
- coalesce and coordinate jumps expose an omitted middle. A deletion instead
355
- contains up to `C` lines on each side of its join.
356
-
357
- The engine validates that receipt and owns the ordered COPY/MOVE resource
358
- effects shown to consumers; extensions do not invent a second effect envelope.
359
- `parseIssues`, when present, carries nonnegative parser-recovery counts for the
360
- complete source and resulting revisions. It is omitted when both are clean or
361
- either inspection is unavailable. The hint is advisory: inspection failure
362
- does not reverse a landed mutation.
363
-
364
- ## §3 Helpers exported by this repo
365
-
366
- <!-- map -->
367
- | Tag | Promise |
368
- | --- | --- |
369
- | {§scheme-packet-transform} | Packet-section transformation. |
370
- | {§executor-scheme-output} | Executor-scheme ("an executor is a scheme"): `OutputScheme.manifestFromRuntime(decl)` derives a… |
371
- | {§scheme-catalog-parse-issues} | An `EntryCatalogChannel` may carry a positive `parseIssues` count when its exact content projection reported… |
372
- | {§network-address} | Network address identity |
373
- | {§mimetype-classifier} | Mimetype classification — `MimetypeClassifier` |
374
- | {§mimetype-verbatim-transfer} | `MimetypeClassifier.isTransferCompatible(source, destination, sourceClassification?)` permits verbatim… |
375
- | {§scheme-projection} | Projection capability |
376
- | {§scheme-resource-capture} | `ctx.resources.capture(addresses)` acquires ordered exact resource/channel snapshots through Core's shared… |
377
- | {§slicer-window} | Positional windows without content - `Slicer.window` |
378
- | {§slicer-text-algebra} | Text-region slicing and replacement - `Slicer` |
379
- | {§range-starts-at-one} | A two-coordinate range starts at 1. |
380
- | {§read-zero-start} | The shared READ projection tolerates a two-coordinate `<0,M>` when `M` is a positive safe integer or `-1`: it… |
381
- | {§path-extension-mimetype} | Path-extension mimetype — `PathMimetype` |
382
- | {§result-shape-guards} | `Results.isEntry` / `Results.isProposal` / `Results.isPassthrough` — guards for the optional conventional… |
383
- | {§result-error-status} | `Results.isErrorStatus(status)` — `status >= 400`, and only those. |
384
- | {§capability-ctx} | §3.bis Capability ctx — the stable trusted-extension surface |
385
- | {§scheme-ctx-workspace-environment} | `workspaceId` selects shared Functionality and admission policy; `workerId` attributes the operation and… |
386
- | {§scheme-interactions} | `interactions` — `request(ClientInteractionRequest, signal?)` awaits the contracts-owned interaction and… |
387
- | {§scheme-subscriptions} | `subscriptions` — one streaming lifecycle: |
388
- | {§scheme-ctx-lifetime} | `SchemeCtx` belongs to one operation call. |
389
- | {§scheme-edit-proposal-receipt} | An accepted EDIT proposal may return its final aggregate `editReceipt` alongside its per-row model-facing… |
390
- <!-- /map -->
391
-
392
- ### Types
393
-
394
- - Manifest: `SchemeManifest`, `SchemeAuthority`, `EntryCoordinate`, and `WriterTier`.
395
- - Mutation receipts: `EditBatchResult`, `EditBatchReceipt`, `EditReceipt`, `EditEffectReceipt`, `EditReceiptUnit`, and `ParseIssueTransition`.
396
- - §scheme-packet-transform **Packet-section transformation.** A scheme may implement `transformSections(sections: PacketSectionDraft[]) → PacketSectionDraft[] | Promise<…>` to add, remove, or reorder sections before core measures the packet. The exact draft shape is `{ name; slot: "system"|"user"|"assistant"; header: string|null; content }`; no token measurement exists at this boundary. Core invokes implementations in registration order and applies `PacketSections.assertDrafts` to the initial list and every returned list. Names are non-empty and unique within the packet. Slots are message roles; their order determines the envelope ({§packet-wire-envelope}).
397
- - Behavior contract: `SchemeHandler` (§2). Scheme-facing grammar types re-exported here so siblings pin only this package: `PlurnkStatement` + the per-op statement types (`ReadStatement`, `FindStatement`, `CopyStatement`, `MoveStatement`, `SendStatement`, `ExecStatement`, `WorkStatement`, `ForkStatement`, `KillStatement`, `ContinuationStatement`) and path types (`ParsedPath` = `LocalPath` | `UrlPath`). EDIT uses `ResolvedEditStatement` under {§resolved-edit-statement}.
398
- - Target syntax: contracts-owned `PathSyntax` is re-exported for the shared exact-versus-path-glob classifier {§path-glob}.
399
- - Discovery: `SchemeDiscovery` (behavior class) with `SchemeInfo` / `SchemeDiscoveryResult` / `DiscoverOptions` (§6).
400
- - §executor-scheme-output Executor-scheme ("an executor is a scheme"): `OutputScheme.manifestFromRuntime(decl)` derives a read-only-output `SchemeManifest` from an executor's `RuntimeDecl` (zero scheme-authoring). Executor output is a canonical entry and therefore inherits core's exact READ projection under {§read-preparation}; no executor-specific READ helper exists. `Summarize.summarize(content, mimetype)` -> `OrientIndex` is the structural-only execution-receipt index (no content - universal-receipt containment). A per-tag executor-scheme supplies its manifest via instance `get manifest()` (§2 `SchemeHandler.manifest?`).
401
- - Results: `SchemeResult` is the universal operation-result contract. Statuses below 400 carry no `problem`; statuses 400–599 require RFC 9457 `ProblemDetails`. `EntryResult`, `ProposalResult`, and `PassthroughResult` are optional conventional shapes, not engine routing discriminators. Guards inspect those optional shapes; proposal routing itself is engine-owned and follows status plus operation semantics.
402
- - Standard `EntryFindResult` exposes only its paged `results`, complete `matchingPathCount` / `matchLocationCount`, `itemsWeightTotal` / `returnedItemsWeightTotal`, and typed range. `EntryCatalogChannel.weight` and `EntryCatalogScope.weight` are model-independent curation weights; model-facing JSON may project them under its own vocabulary. Each resource-mode `EntryCatalogItem` is a nonempty, default-first array of flat `EntryCatalogChannel` objects; a scope is the one-element `EntryCatalogScopeGroup`. Exact matcher mode returns flat `MatchEvidence` locations. Pagination is the materialization bound; no path-owning channel wrapper, hidden `matches`, `pathnames`, or overflow-only result collection exists.
403
- - §scheme-catalog-parse-issues An `EntryCatalogChannel` may carry a positive `parseIssues` count when its exact content projection reported parser recovery sites. Zero and unavailable evidence are omitted. This is advisory metadata and never a validity gate or operation failure.
404
- - Capability ctx (see §3.bis): `SchemeCtx`, `StreamSubscription`, and the domain capabilities. Entry authors additionally receive `EntryOperationCaps`, `EntryAddress`, and typed standard-operation results. `editBatch` receives the numeric splices of one operation for one canonical resource and channel; it validates against one snapshot and commits one revision or none ({§edit-batch}). Core calls it separately for each authored EDIT, in execution order.
405
-
406
- Behavior ships as `export default class` (one class per file, static methods) — the ecosystem class paradigm. Type-only modules and the barrel are the only non-class files.
407
-
408
- ### §network-address Network address identity
409
-
410
- `NetworkAddress.from(target)` is the single non-secret normalization path for
411
- HTTP and WebSocket targets. It returns the exact addressed `scheme`, canonical
412
- entry `authority` and `pathname`, a fragmentless and credential-free transport
413
- `url`, and whether userinfo was present so the handler can reject it.
414
-
415
- | Component | Entry identity | Transport URL | Plurnk channel |
416
- |------------------|-----------------------------------------------------|---------------|----------------|
417
- | Scheme | Exact `http`, `https`, `ws`, or `wss` | Yes | No |
418
- | Host | Canonical WHATWG hostname | Yes | No |
419
- | Port | Non-default port | Yes | No |
420
- | Path | Canonical pathname | Yes | No |
421
- | Query | Serialized order, duplicates, and explicit empty `?` | Yes | No |
422
- | Fragment | No | No | Yes |
423
- | Userinfo | No | No | No |
424
-
425
- The canonical storage coordinate is authority `<host>[:<port>]` plus pathname
426
- `<path>[?<query>]`, keyed together with workspace and the exact
427
- addressed scheme. `NetworkAddress.render({ scheme, authority, pathname })` is
428
- its model-facing inverse: it applies the Plurnk lexical target spelling
429
- {§path-parentheses} after reconstructing the exact address. Transport URLs and
430
- storage identity never carry that syntax layer. Routing aliases select a
431
- handler; they do not collapse resource identity.
432
- Scheme metadata is statement-level input under {§scheme-metadata-modifier}; it
433
- is never a `NetworkAddress` component.
434
-
435
- ### §mimetype-classifier Mimetype classification — `MimetypeClassifier`
436
-
437
- - `MimetypeClassifier.isBinary(mimetype)` — pure taxonomy for a registry-free boundary. Delegates to `classifyMimetype` from `@plurnk/plurnk-mimetypes`; a consumer with a configured `Mimetypes` service uses `Mimetypes.classify()` so installed handler declarations remain authoritative ({§mimetype-classification}).
438
- - `MimetypeClassifier.isHtml(mimetype)` — recognizes the normalized web-projection family: `text/html` and `application/xhtml+xml`.
439
- - `MimetypeClassifier.isJson(mimetype)` - `application/json` plus `+json` variants, used only by result summarization.
440
- - `MimetypeClassifier.normalizeAutoText(mimetype)` — `text/plain` / null / undefined → `TEXT_PRIMITIVE_MIMETYPE` (`text/markdown`).
441
- - §mimetype-verbatim-transfer `MimetypeClassifier.isTransferCompatible(source, destination, sourceClassification?)` permits verbatim COPY/MOVE between equal mimetypes, or from any textual source into `text/plain` or `text/markdown`. Unequal structured or binary destination types remain incompatible. Configured consumers supply the source's resolved `{ binary }` classification; registry-free callers use the shared taxonomy. Compatibility never converts content or changes either channel's declared type.
442
- - `TEXT_PRIMITIVE_MIMETYPE` — `"text/markdown"` (named export from the same module).
443
-
444
- ### §scheme-projection Projection capability
445
-
446
- §scheme-resource-capture `ctx.resources.capture(addresses)` acquires ordered
447
- exact resource/channel snapshots through Core's shared source-selection path.
448
- It returns bytes plus immutable receipt descriptors, or the acquisition failure
449
- without delivering anything. It neither performs SEND nor transfers resource
450
- ownership. `MessageAttachments` opts a message recipient into the common
451
- metadata shape under {§send-resource-attachments}; other recipients retain
452
- their own metadata vocabulary.
453
-
454
- Acquisition schemes delegate model-facing projection to the consumer's one
455
- configured mimetype framework. They neither instantiate readers nor discard source
456
- bytes merely because no readable projection exists.
457
-
458
- | Surface | Contract |
459
- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
460
- | `readable(content, mimetype)` | Project an acquired Unicode representation. |
461
- | `binary(chunks, mimetype)` | Return bounded source `bytes`, nullable `readable: ProjectedText`, and the configured `projectionIdentity`, even without a reader; consume one async byte source under {§mimetype-binary-input}. |
462
- | `identity(mimetype)` | Return the opaque identity of configured projection behavior for cache and materialization freshness. |
463
- | `isBinary(mimetype)` | Classify through the configured registry; installed handler declarations remain authoritative. |
464
- | `parseIssues(content, mimetype)` | Return a nonnegative parser-recovery count, or omit unavailable evidence, under {§mimetype-parse-issues}. |
465
- | `ProjectedText` | Derived Unicode plus its output mimetype, source mimetype, and opaque projection identity. |
466
-
467
- A readable projection is present even when its `content` is `""`; only `null`
468
- denotes absence. Consumers must not infer projection presence from content
469
- length. Thrown readable, identity, and classification calls are execution
470
- failures whose causes propagate; they must never be converted to `null`.
471
- Parser-recovery inspection is deliberately non-gating: successful clean
472
- inspection returns `0`; tooling failure or structural degradation surfaces a
473
- Notice where available and returns `undefined`.
474
- `ProjectionInputLimitError` is the projection boundary's exported name for the
475
- configured mimetype framework's typed bounded-input failure.
476
-
477
- ### §slicer-window Positional windows without content - `Slicer.window`
478
-
479
- `Slicer.window(marker, total, unit)` resolves a one- or two-coordinate marker against a
480
- total alone, with the exact normalization, sentinels, extents, and 416 facts of `lines()`, for
481
- a resource whose positions are not text lines in memory: the byte view of a binary
482
- (`unit: "byte"`, {§read-bytes} in core). A sentinel selects nothing (`start: null`); a
483
- region of four coordinates has no meaning over a window and is refused.
484
-
485
- ### §slicer-text-algebra Text-region slicing and replacement - `Slicer`
486
-
487
- `Slicer` owns one text algebra for every textual mimetype:
141
+ ## §capability-ctx Capability context and subscriptions
488
142
 
489
- | Scope | Meaning |
143
+ `SchemeCtx` is the operation-scoped capability surface (`{ workspaceId, sessionId, loopId, turnId, writer, signal, logEntryId? }`, {§execution-output-identity}) injected by the consumer:
144
+
145
+ | Capability | Contract |
490
146
  |---|---|
491
- | `<N>` | whole physical line `N` |
492
- | `<N,M>` | inclusive whole physical lines `N..M` |
493
- | `<SL,SC,EL,EC>` | exact exclusive-end region using 1-based Unicode code-point columns |
494
- | `<0>` / `<-1>` | mutation anchors before the first / after the final line |
495
-
496
- Every replacement inserts its body verbatim. `Slicer.wholeLineBody(content, marker, body)` is
497
- the pure helper core applies where a fenced EDIT body becomes inserted content
498
- ({§zero-width-column-one-insert}): it appends the content's separator only for a non-empty body
499
- without a trailing newline at a zero-width column-1 region, and returns every other body unchanged.
500
-
501
- §range-starts-at-one A two-coordinate range starts at 1. `<0>` is a single-position
502
- prepend anchor, never a range start: `<0,M>` is refused 416 by `lines()`,
503
- `linesRaw()`, `textReplacement()`, `lineMarkerEdit[Batch]()`, `window()` and
504
- `page()`, on empty content and empty result sets too, and never clamped to
505
- `<1,M>`. The refusal's detail states the zero start in the range's unit; its
506
- `recovery` never rebuilds the refused range ({§diagnostic-observation}):
507
-
508
- | Unit | `recovery` |
147
+ | `entries` & `channels` | Direct storage (`address`, `read`, `write`, `delete`, optional scheme-private `attributes`) and standard `operations` (`editBatch`, `find`, `send`), plus `channels` (`append`, `replace`, `setState`), bound by Core to the resolved workspace coordinate ({§scheme-surface}). |
148
+ | `notify`, `projection`, `resources`, `messages` | `notify.streamEvent` (between-turn client metadata signal), `projection`, `resources.capture`, and `messages`. |
149
+ | `interactions` | `request(ClientInteractionRequest, signal?) -> Promise<ClientInteractionResolution>` ({§provider-request-accounting}). |
150
+ | `subscriptions` | `open(pathname, handle)` returns the sole capability retainable after the handler call returns: `StreamSubscription` (`AbortSignal` + `notifyChunk(channel, chunk, mimetype?)` + `close(result, summary?, channelResults?)` that validates `ChannelProducerResult` overrides, settles channel states, unregisters the live handle, and wakes the session). |
151
+ | Proposals (`applyResolution`) | A side-effecting scheme proposes by returning `status: 202`; on acceptance, Core calls `applyResolution({ attrs, metadata, body }, ctx)` ({§log-wire-format}). Accepted `EDIT` proposals may return an aggregate `editReceipt` (using `reviewer-replaced` when the resolver replaced the body). |
152
+
153
+ ## §resource-tree-scheme Module slice and resource trees
154
+
155
+ | Type / concern | Contract |
509
156
  |---|---|
510
- | line | the line forms `<L,M>`, `<0>` and `<-1>` |
511
- | byte, result, resource | the fixed `range-not-satisfiable` recovery ({§problems-schemes}) |
512
-
513
- §read-zero-start The shared READ projection tolerates a two-coordinate `<0,M>`
514
- when `M` is a positive safe integer or `-1`: it selects `<1,M>`, retains `[0,M]`
515
- as the receipt's requested range, and returns scope-normalization evidence for
516
- the ordinary non-striking warning. Text and byte views, including empty resources,
517
- use the same rule. The strict algebra above remains unchanged for mutation,
518
- removal, and transfer operands. Single-position sentinels, character regions,
519
- and other invalid endpoints are not reinterpreted.
520
-
521
- As an unadvertised ingestion tolerance, `Slicer` accepts
522
- `<startLine,startColumn,endLine>` and immediately lowers it to the complete
523
- four-coordinate region ending after the final code point of `endLine`.
524
- Successful results carry schemes-owned, boundary-validated normalization
525
- evidence containing both the authored and canonical coordinates. Producers do
526
- not emit or proactively teach the tolerated form.
527
-
528
- A harmless end-bound overshoot clamps rather than erroring: an oversized line
529
- range ends at the final line, an exact end line beyond the content ends at EOF,
530
- and an oversized end column ends at its line's final code point. Start-line and
531
- start-column overshoots remain 416 errors.
532
-
533
- - `Slicer.lines(content, marker)` returns the selected text, source
534
- `startLine`, and complete resolved `region`; `Slicer.linesRaw` preserves
535
- original newline separators for COPY/MOVE source transfer.
536
- - `Slicer.textReplacement(content, marker, body)` lowers a line shorthand or
537
- exact region to one `{start,end,body}` replacement against the source
538
- snapshot.
539
- - `Slicer.lineMarkerEdit` applies one replacement.
540
- - `Slicer.lineMarkerEditBatch` validates all replacements against one snapshot,
541
- resolves an identical twin, two insertions at one point, and an evidenced one-line shared
542
- endpoint (reported in `merges`, receipts from `applied`), rejects every other overlap, and
543
- applies all or none. An overlap Problem states the
544
- failure once for the whole batch: every conflicting pair with its relation in
545
- `conflicts`, the regions that conflict with nothing in `cleanRegions`, the
546
- first pair in `conflictingRegions`, plus `editCount` and `applied: 0`. The
547
- prose states counts and, for a shared endpoint, the line both regions claim,
548
- its text and the two regions; it adds no second line-only representation.
549
- - `Slicer.page(items, marker, options?)` is the separate ordered-result
550
- pagination helper; it accepts only one or two integer positions and can name
551
- an operation-owned range unit. An empty result set satisfies any well-formed
552
- request as an empty page; only an inverted range fails.
553
- - `Slicer.coversAvailable(range)` derives whether a successful range selected
554
- the complete available extent; that fact is never serialized separately.
555
-
556
- A successful line or pagination selection carries the contracts-owned
557
- `RangeExtent` {§range-extent}; a 416 carries the same extent without
558
- `returned`. Exact regions use `region` instead. An exact-region failure,
559
- including a failed tolerated three-coordinate scope, carries
560
- `requestedCoordinates` exactly as authored; an invalid-arity text scope does
561
- likewise. Both carry `columnKind: "unicodeCodePoints"`. A pagination request of
562
- neither one nor two positions carries `requestedPositions` exactly as authored
563
- and no extent.
564
-
565
- ### §path-extension-mimetype Path-extension mimetype — `PathMimetype`
566
-
567
- - `PathMimetype.resolve(pathname, defaultMimetype, mimetypes)` — pathname extension → `Mimetypes.detect({ ext })`; falls back to `defaultMimetype` when no extension. text/plain auto-normalizes to text/markdown.
568
-
569
- ### Result families — `Results`
570
-
571
- - §result-shape-guards `Results.isEntry` / `Results.isProposal` / `Results.isPassthrough` — guards for the optional conventional result shapes; at most one matches a result, none matches a bare one, and extension-owned fields may ride beside `status` without adopting a shape.
572
- - §result-error-status `Results.isErrorStatus(status)` — `status >= 400`, and only those.
573
- - `Results.problem(owner, code, status, detail, extensions?)` — build and validate RFC 9457 Problem Details with a stable `https://problems.plurnk.xyz/<owner>/<code>` type.
574
- - `Results.failure(owner, code, status, detail, fields?, extensions?)` — build and validate a failed operation result; its type carries the fields it was given.
575
- - `Results.assert(result)` — validate the complete success/failure discrimination and reject malformed extension output.
576
- - `Results.assertMatchEvidence(evidence)` / `assertMatchEvidenceList(evidence)` - enforce the exact `{ locator?, region?, enclosingRegion?, matched? }` shape and shared `TextRegion` contract.
577
- - `Results.assertReadResult(result)` - validate the universal operation result plus any `region` and `matches` it exposes.
578
- - `Results.attachInstance(result, uri)` — attach the durable occurrence URI to a failed result.
579
-
580
- A handler owns its failure classification and explanation. A retained failure's
581
- `instance` continues to identify its original occurrence; reading it does not create
582
- a different failure. When absent, the daemon adds the committed log coordinate. A
583
- malformed handler result is an extension contract violation and fails hard; the
584
- consumer does not invent a fallback error or reinterpret arbitrary fields.
585
- The same discrimination applies to every `SchemeCtx` capability result:
586
- entries and channels never return a bare failure status.
587
-
588
- ### Matcher dispatch - `Matcher` {§matcher-dispatch}
589
-
590
- - `Matcher.matchAgainstContent(body, content, mimetype, mimetypes, diagnostic?)` is the body-matcher adapter over `Mimetypes.query` (glob/regex/jsonpath/xpath). The optional projector supplies one bounded caught-parser `diagnostic` extension without transferring error-formatting policy into this package or splicing implementation text into Problem prose.
591
- - A match returns status 200 and `matches: MatchEvidence[]`.
592
- `MatchEvidence` is `{locator?, region?, enclosingRegion?, matched?}`. `locator` preserves a structural locator;
593
- `matched` is the matched text itself, present for the regex and glob dialects so a match row
594
- reads without a READ;
595
- `region` is the exact selected source span; `enclosingRegion` is context when
596
- the handler can locate only the containing text. Both are complete four-coordinate
597
- `TextRegion` values. Context does not authorize exact-source mutation.
598
- Each item must contain at least one of `locator`, `region`, or `enclosingRegion`; other fields violate
599
- the shared evidence contract.
600
- - The matcher is a boolean resource selector. It does not replace content with matched values or choose a retrieval window. FIND owns selection and pagination; exact READ owns text projection.
601
- - Empty results return 204 with `matches: []`; `UnsupportedDialectError` maps to 415; `InvalidExpressionError` maps to 400 with the bounded native parser cause when one was projected; `QueryParseFailureError` maps to 203 with raw content, text/markdown, and `reason`.
602
- - A multi-resource matcher omits candidates that return 415 when at least one candidate supports the dialect. If no candidate supports it, the matcher returns the first exact 415 Problem. An unreadable binary marker therefore cannot poison a repository-wide text search or masquerade as a match.
603
-
604
- ### §capability-ctx §3.bis Capability ctx — the stable trusted-extension surface
605
-
606
- Scheme extensions are trusted in-process Node.js code. `SchemeCtx` is not a
607
- sandbox or a security boundary; an installed extension already has the process's
608
- authority. It is the stable semantic API that keeps extensions independent of
609
- database schemas, prepared-statement names, and private service modules.
610
- **Interfaces only**: this repo exports the contract and the consumer injects
611
- its implementation.
612
-
613
- `SchemeCtx` carries per-dispatch identity (`workspaceId`/`workerId`/`loopId`/`turnId`/`writer`/`signal`, and during proposal application `logEntryId`, the operation row being settled, so a scheme can keep what it did on that row, {§log-mutation-patch}) plus capability namespaces:
614
-
615
- §scheme-ctx-workspace-environment `workspaceId` selects shared Functionality and admission policy; `workerId` attributes the operation and selects its journal. Core binds the addressed resource before supplying this context. Handlers must use that binding rather than reinterpret caller identity as resource ownership ({§runtime-resource-binding}).
616
-
617
- - `entries` — direct storage over the scheme and authority already bound by core
618
- (`address`/`read`/`write`/`delete`) plus `operations`, the standard PLURNK
619
- `EDIT`/`FIND`/`SEND` implementations for entry-bearing schemes. READ selection
620
- and model-facing projection belong to the consumer's universal dispatch
621
- ({§universal-read-composition}); `entries.read` returns stored channels, not
622
- a projected READ receipt.
623
- `address(pathname)` returns the bound resource's URI without fetching or
624
- creating it. Links preserve the bound resource authority, never a
625
- caller-relative namespace.
626
- A write may omit channel state to select the `static` default; a successful
627
- storage read always returns each channel's persisted lifecycle state.
628
- Optional `attributes` are scheme-private durable metadata. They are scoped to
629
- that entry, replaced only when explicitly written, and never projected into
630
- model content, catalogs, or client entry data.
631
- Standard operations are bound to the canonical workspace coordinate selected
632
- before the handler receives this context. A handler may implement its own op method
633
- instead. In particular, a handler with `find()` owns FIND; one without it
634
- receives the standard stored-entry behavior.
635
- - `channels` — content writes + state (`append`/`replace`/`setState`).
636
- - `notify` — between-turn client signal (`streamEvent`, metadata-only); not model-facing. Wake delivery belongs to stream settlement, not notification.
637
- - `projection` — the text and bounded-byte projection capability in {§scheme-projection}. Acquisition schemes own source representations; they do not instantiate or second-guess the readers. `null` means no readable projection.
638
- - §scheme-interactions `interactions` — `request(ClientInteractionRequest, signal?)`
639
- awaits the contracts-owned interaction and returns its
640
- `ClientInteractionResolution` ({§client-interaction-wire}). Core binds the
641
- current operation identity, persistence, client projection, and cancellation;
642
- the scheme retains no callback, interaction identity, or private lifecycle.
643
- An optional signal narrows, never replaces, the operation's cancellation scope.
644
- - §scheme-subscriptions `subscriptions` — one streaming lifecycle:
645
-
646
- | Surface | Lifetime | Contract |
647
- | --------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
648
- | `open(pathname, handle)` | Operation | Bind durable and live identity, return the exact object, and route cancellation through its signal/handle. |
649
- | Returned `StreamSubscription` | Retainable | `AbortSignal` plus fused chunk publication and terminal settlement; the only retainable capability. |
650
-
651
- `notifyChunk` appends and emits one stream event. Its optional stateless
652
- `mimetype` retypes the channel only when the stored type differs.
653
- `close(result, summary?, channelResults?)` validates and persists the exact
654
- universal `ChannelProducerResult`, settles every channel with an exact named
655
- result override or that universal default, unregisters the live handle, and
656
- wakes the worker. Channel state is derived from its settled result.
657
- `summary` is presentation only; it never replaces or reconstructs the result.
658
- An override names an existing channel and is itself an exact
659
- `ChannelProducerResult`; invalid overrides fail before any settlement changes.
660
- This permits one multi-channel producer to preserve successful evidence beside
661
- an independently failed representation without inventing another settlement
662
- path or reducing a result to a state label.
663
-
664
- `EntryCoordinates.resolve(target, authority)` supplies the same canonical
665
- coordinate Core binds ({§scheme-address}); handlers do not invent another
666
- interpretation of namespace authorities.
667
-
668
- The consumer passes exactly this public context to every handler. Bundled
669
- adapters that also implement daemon-owned lifecycle behavior receive those
670
- collaborators separately at construction or binding time; consumers must not
671
- decorate `SchemeCtx` with database handles, registries, tokenizers, notifier
672
- callbacks, or other private service state.
673
-
674
- §scheme-ctx-lifetime `SchemeCtx` belongs to one operation call. A handler may
675
- use it for the duration of that call, including asynchronous work awaited by
676
- the call, but must not retain the context or any other extracted capability
677
- after the handler returns. A streaming handler may retain only the exact
678
- `StreamSubscription` returned by `subscriptions.open`; its bound methods carry
679
- that subscription's identity without retaining the general context. Other
680
- retained handler-owned resources follow {§handler-lifecycle}.
681
-
682
- **Proposals are not a capability.** A side-effecting scheme proposes by *returning* a `ProposalResult` (status 202); the engine owns the resolution lifecycle. On acceptance it calls the handler's optional `applyResolution({ attrs, metadata, body }, ctx)` hook. `attrs` is the payload returned by the proposing operation, `metadata` is the exact statement-level scheme metadata retained only across the live proposal pause, and `body` is the resolver-approved body, when present. Metadata is never added to proposal attrs or client projection. The hook returns a `ProposalApplyResult`: a status below 400 completes the accept; a failure preserves the applying scheme's Problem Details as the proposal's final result.
683
-
684
- §scheme-edit-proposal-receipt An accepted EDIT proposal may return its final
685
- aggregate `editReceipt` alongside its per-row model-facing `result`. A resolver
686
- replacement must use the reviewer-replaced form from
687
- {§scheme-edit-batch-receipt}. Core consumes that aggregate as transient batch
688
- coordination and COPY/MOVE composition input; it persists only per-row receipts
689
- or ordered resource effects.
690
-
691
- ## §4 What's NOT in this repo
692
-
693
- DB-coupled entry/channel machinery — CRUD + write-time tokenization, the
694
- standard entry operations, channel writes, and subscription registry — lives
695
- in the consumer. This package defines their stable interfaces and pure helpers
696
- ({§scheme-ctx-entries}).
697
-
698
- ## §5 Trusted extension contract
699
-
700
- Installed schemes may legitimately own network connections, subprocesses,
701
- caches, pools, or other host resources under {§handler-lifecycle}. These powers
702
- are why installation is a trust decision and why contained interoperability
703
- belongs in MCP rather than an in-process extension.
704
-
705
- The supported compatibility boundary is `@plurnk/plurnk-schemes`. Extensions
706
- should not import private service modules, depend on database layout, or call
707
- prepared statements directly: those are unstable implementation details, not
708
- additional extension capabilities. Use `SchemeCtx` or propose a new semantic
709
- capability when the public surface cannot express a coherent extension.
710
-
711
- ## Module slice
712
-
713
- §scheme-module-slice **A daemon module contributes to the scheme kind through types this package
714
- owns.** They complete the module contract's slices ({§module-seam-slices}).
715
-
716
- | Type | Contract |
717
- | --- | --- |
718
- | `SchemeRegistrationSeam` | `registerScheme(name, handler)` adds one process-wide addressable `SchemeHandler`; scheme readiness and model-facing capability publication remain the host's. |
719
- | `ResourceTreeRegistrationSeam` | `registerResourceTreeScheme(name, source)` exposes named read-only resource trees through {§resource-tree-scheme}, without giving the module a storage interface. |
720
- | `RuntimeSchemeFacet` | A scheme face over a module's own resources. It claims only its own path subtree and there is the scheme's whole live half: READ and FIND preparation, FIND, EDIT, SEND and KILL are its own wherever it implements them, and its `manifest` states what the executor's output contract does not — the resource authority, channels, default channel and `writableBy` that govern every claimed coordinate. Unclaimed coordinates keep the standard executor-output behavior. A runtime carries one on its registration ({§executor-module-slice}), and a Functionality family's manager may expose one ({§functionality-adapter}). |
721
-
722
- §resource-tree-scheme **A tree supplies resources; the host supplies their entry projections.**
723
- `ResourceTreeSource.trees(workspaceId)` returns the currently available trees keyed by URI
724
- authority. Each tree lists relative resource paths and supplies a {§scheme-source-bytes}
725
- `ByteSource` for a relative path. Listing is authoritative; a source's null size means absent.
726
- The source owns containment and acquisition, including optional native-file identity.
157
+ | `SchemeRegistrationSeam` | `registerScheme(name, handler)` registers a process-wide `SchemeHandler` ({§module-seam-slices}). |
158
+ | `RuntimeSchemeFacet` | Claims a path subtree under an executor or Functionality scheme ({§executor-role}, {§functionality-adapter}) with its own `manifest` (`authority`, `channels`, `defaultChannel`, `writableBy`) and live `prepareRepresentation`, `prepareFind`, `find`, `editBatch`, `send`, and `kill` hooks while unclaimed coordinates keep standard executor-output behavior. |
159
+ | `ResourceTreeRegistrationSeam` | `registerResourceTreeScheme(name, source)` exposes read-only `ResourceTreeSource.trees(workspaceId)` keyed by URI authority (`name://authority/path`, resource authority, folder scopes, dynamic MIME `body` channel). |
160
+ | Tree publication & projection | Demand-loaded entries on `READ` and `COPY`; model/client writes are forbidden (`403`). The host detects MIME, applies the input bound, and runs the installed MIME projection while keeping original `ByteSource` bytes for byte views, transfers, and attachments. |
161
+ | Tree `FIND`, withdrawal & failures | `FIND` refreshes resources in the requested authority/path scope and evicts withdrawn authorities or absent paths (an exact missing `READ` never returns cached content). Optional `refusal(cause, address)` translates recognized source errors into failure results (`status >= 400`). |
727
162
 
728
- | Concern | Contract |
729
- | --- | --- |
730
- | Addressing | `name://authority/path`; resource authority, ordinary folder scopes, a dynamic MIME `body` channel. READ and source transfers share exact-resource preparation. |
731
- | Publication | Demand-loaded entries, not eagerly injected bodies. Model and client writes are forbidden; only the host writes projections. |
732
- | Projection | The host detects MIME, applies the configured input bound, and uses the installed MIME projection. Original bytes remain the source of byte views, transfers and attachments. Refresh replaces obsolete projection metadata too. |
733
- | FIND | The host refreshes resources in the requested path/authority scope, including descendants needed for shallow folder summaries, then performs ordinary pattern selection. |
734
- | Withdrawal | FIND removes cached projections for withdrawn authorities and absent resources in its refreshed scope. An exact missing READ never returns cached content. Historical log receipts are unchanged. |
735
- | Failures | Optional `refusal(cause, address)` translates recognized source failures into ordinary failure results (status ≥400); null preserves the host's missing-file result or propagates the original unexpected exception. Projection and persistence failures must not be recast as source failures. |
736
-
737
- The module does not receive scopes, matchers, database handles, or entry lifecycle controls through
738
- this source interface. A tree registration uses the ordinary scheme registry, with the same unique
739
- name, readiness, capability and lifecycle rules as other schemes.
740
-
741
- ## §scheme-discovery §6 Discovery & registration (third-party)
742
-
743
- A scheme handler is discovered and registered with **zero first-party involvement** — install it, it lights up. The contract:
744
-
745
- - **Declare** the exact string `plurnk.kind: "scheme"` in `package.json` ({§extension-kind}). Then name the scheme(s) it owns in one of two forms: `plurnk.schemes: [{ name, export }, …]` (canonical — one entry per scheme, `export` naming the handler-class export) or `plurnk.name: "<scheme>"` (one-scheme shorthand for the `default` export). One package may own several names inside this kind; each name has exactly one owner.
746
- - **`SchemeDiscovery` owns the scan (this package).** `SchemeDiscovery.discover({ cwd? })` walks *all* of `node_modules` — scoped (`@acme/foo`) and unscoped — and returns `{ schemes: {name, packageName, exportName?}[], packageAttributions, skipped }` for every package declaring `plurnk.kind === "scheme"`. Scope-agnostic, so a third party under their own scope is found with no first-party allow-list; two names claiming one prefix fail-hard (across packages or within one), as does a malformed `plurnk.schemes` (locality of error, not a silent skip). It returns **descriptors, not handlers** — contract-only, it never imports a scheme package; the consumer imports each `packageName` and registers `new mod[exportName ?? "default"]()`, applying in-tree precedence. The scan primitives — package enumeration, the `PLURNK_EXTENSIONS_TRUSTED_ONLY` trust gate, the deployment-root `node_modules` walk — are one implementation in `@plurnk/plurnk-meta`, shared by every kind's scanner; `SchemeDiscovery` adds only the scheme-descriptor shape on top.
747
- - **Attribution is package-authored.** `packageAttributions` carries one canonical validated static tag list per admitted package; a loaded handler may additionally implement the shared runtime `attributions` function ({§extension-attribution}). The consumer owns no other attribution policy.
748
- - **The framework stays contract-only.** `@plurnk/plurnk-schemes` does not depend on scheme extensions. The daemon declares its bundled extensions as direct dependencies, and additional scheme extensions are installed at the application root. Each declares the framework as a peer dependency using the repository's normal same-minor compatibility range; the framework itself is ignored by discovery because it has no `plurnk.kind`.
749
- - **The default bundle is the daemon's own `dependencies`**, not an aggregator package. Installing `plurnk-core` surfaces the first-party schemes; any other leaf — first-party or third-party — is added by installing it, and scope-agnostic discovery lights it up identically. No bundle is ever a gate.
750
- - **Trust.** The scanner enforces the shared predicate before attribution or scheme-field validation and returns withheld package names in `skipped`; the host owns presentation ({§extension-trust-boundary}).
751
-
752
- ## Message sources
753
-
754
- <!-- map -->
755
- | Tag | Promise |
756
- | --- | --- |
757
- | {§message-source-scheme} | Protocol modules register `MessageScheme` or compose its hooks into their existing scheme. |
758
- | {§message-immutable} | An EDIT, COPY or MOVE onto, or KILL of, a message address is refused 405 `message-immutable` with no… |
759
- | {§problems-schemes} | Selection and metadata Problems. |
760
- | {§pinned-wording-schemes} | Pinned wording. |
761
- <!-- /map -->
762
-
763
- §message-source-scheme Protocol modules register `MessageScheme` or compose its hooks into
764
- their existing scheme. Core's `SchemeCtx.messages` materializes retained messages in that
765
- scheme for ordinary READ/FIND/COPY and delivers exact-address SEND replies under
766
- {§send-response-receipt}. The materialization is a derived representation, never the source
767
- of message body or reply state. The protocol module owns URI identity; Core owns durable
768
- admission and correlation. Message representations reject mutation while remaining SEND
769
- recipients. Scratch entries and protocol artifacts retain their own existing write contracts.
770
-
771
- §message-immutable An EDIT, COPY or MOVE onto, or KILL of, a message address is
772
- refused 405 `message-immutable` with no `recovery` ({§diagnostic-observation}); its detail
773
- names the address as an immutable received message:
774
- `` `message://tester/bde40185` is a received message; it cannot be edited, moved or killed. ``
775
-
776
- §problems-schemes **Selection and metadata Problems.** Every code minted here, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
777
-
778
- | code | status | contract |
779
- |---|---:|---|
780
- | `range-not-satisfiable` | 416 | The range lies outside the available extent (a line before 1, past the end, or a column outside its line; a result page names the unit it pages). Recovery: Choose a range within the available extent. Some refusals state the extent or show the working form of the construct instead; none rebuilds the input into another scope ({§diagnostic-observation}): a result position outside its page the page's bound (`Choose positions within 1..3.`), a region holding a zero line or column the counting rule of {§text-scope-semantics} (`Lines and columns count from 1.`), an inverted region the form's order (`An exact region names its earlier position first: <SL,SC,EL,EC>.`), a `<-N,-1>` range the line extent (`-1 is the only position counted from the end; lines run 1..92.`), and a line range from 0 the line forms ({§range-starts-at-one}). |
781
- | `overlapping-edits` | 409 | Two EDIT regions overlap, or a whole-resource replacement cannot coexist with another EDIT. Recovery: Submit the whole-resource replacement by itself. |
782
- | `invalid-expression` | 400 | The *dialect* matcher expression is invalid. Recovery: Revise the matcher expression. |
783
- | `unsupported-dialect` | 415 | The *dialect* matcher is not supported for *mimetype*. Recovery: Use a matcher supported by the resource mimetype. |
784
- | `metadata-unsupported` | 400 | Message metadata accepts only attachments. |
785
- | `attachments-invalid` | 400 | attachments must be an array of non-empty resource addresses. |
786
- | `metadata-invalid` | 400 | [metadata] must be a JSON array of option objects; every element is an object. |
163
+ ## §scheme-discovery Third-party discovery
787
164
 
788
- §pinned-wording-schemes **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.
165
+ `SchemeDiscovery.discover({ cwd? })` scans `node_modules` via `@plurnk/plurnk-meta` ({§extension-discovery}) for packages declaring `plurnk.kind === "scheme"` (`plurnk.schemes: [{ name, export }]` or `plurnk.name`), failing hard on duplicate scheme prefixes or malformed declarations and returning `{ schemes, packageAttributions, skipped }` descriptors without importing handler modules.
789
166
 
790
- | sentence | arises when |
791
- |---|---|
792
- | `Range <0,end>` starts at 0, which is not a line; lines are numbered from 1 and 0 is only the `<0>` prepend position. Recovery: `<L,M>` selects lines L through M; `<0>` prepends and `<-1>` appends without replacing anything. / `Range <0,end>` starts at 0; *unit* positions are numbered from 1. | a range whose start is 0 ({§range-starts-at-one}) |
793
- | [metadata] must be a JSON array. | a malformed JSON metadata modifier |
794
- | [metadata] must be a JSON array of option objects; every element is an object. | a non-object element supplied to an object-only metadata owner |
795
- | A scope requires one position or an inclusive two-position range. | a scope of unsupported arity |
167
+ ## §message-source-scheme Message sources
168
+
169
+ Protocol modules register `MessageScheme` or compose its hooks so `SchemeCtx.messages` materializes retained inbound messages for `READ`, `FIND`, and `COPY` and routes exact-address `SEND` replies under {§message-completion}. Any `EDIT`, `COPY`/`MOVE` onto, or `KILL` of a message address is refused `405 message-immutable` with no `recovery` ({§diagnostic-observation}): `` `<address>` is a received message; it cannot be edited, moved or killed. ``