@plurnk/plurnk-service 1.0.8 → 1.1.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/README.md +4 -0
- package/SPEC.md +43 -13
- package/dist/core/ChannelWrite.d.ts +2 -1
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +2 -2
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +2 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +50 -11
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +1 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +105 -39
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +24 -13
- package/dist/core/Owner.d.ts +9 -0
- package/dist/core/Owner.d.ts.map +1 -0
- package/dist/core/Owner.js +49 -0
- package/dist/core/Owner.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +50 -31
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +21 -2
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +6 -6
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/caps/CapsResolve.d.ts.map +1 -1
- package/dist/core/caps/CapsResolve.js +2 -1
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +5 -7
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +3 -3
- package/dist/core/git-membership.d.ts +1 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +25 -11
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/namespace.d.ts +6 -0
- package/dist/core/namespace.d.ts.map +1 -0
- package/dist/core/namespace.js +84 -0
- package/dist/core/namespace.js.map +1 -0
- package/dist/core/owner.sql +14 -0
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +1 -2
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +2 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +12 -13
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/world-state.d.ts +10 -0
- package/dist/core/world-state.d.ts.map +1 -0
- package/dist/core/world-state.js +35 -0
- package/dist/core/world-state.js.map +1 -0
- package/dist/core/world-state.sql +35 -0
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +6 -1
- package/dist/digest/Digest.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/schemes/Exec.d.ts +7 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +36 -10
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +15 -4
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +93 -68
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.js +1 -1
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Prompt.d.ts +13 -0
- package/dist/schemes/Prompt.d.ts.map +1 -0
- package/dist/schemes/Prompt.js +40 -0
- package/dist/schemes/Prompt.js.map +1 -0
- package/dist/schemes/Worker.d.ts +6 -4
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +116 -72
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +7 -3
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +18 -7
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +17 -25
- package/dist/schemes/_entry-find.d.ts +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +32 -24
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +3 -25
- package/dist/schemes/_entry-graph.sql +2 -2
- package/dist/schemes/_entry-manifest.d.ts +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +16 -20
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +4 -3
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +31 -29
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +4 -15
- package/dist/schemes/_entry-semantic.sql +4 -4
- package/dist/schemes/_entry-send.d.ts +1 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +5 -4
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/server/Daemon.js +2 -2
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/drain.sql +35 -5
- package/dist/server/envelope.d.ts +1 -1
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +15 -7
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +6 -0
- package/dist/server/loopDocs.js +2 -2
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/seam-entry-read.sql +3 -2
- package/migrations/0000-00-00.01_schema.sql +40 -10
- package/package.json +17 -16
- package/dist/schemes/Known.d.ts +0 -17
- package/dist/schemes/Known.d.ts.map +0 -1
- package/dist/schemes/Known.js +0 -40
- package/dist/schemes/Known.js.map +0 -1
- package/dist/schemes/Plurnk.d.ts +0 -17
- package/dist/schemes/Plurnk.d.ts.map +0 -1
- package/dist/schemes/Plurnk.js +0 -46
- package/dist/schemes/Plurnk.js.map +0 -1
- package/dist/schemes/Unknown.d.ts +0 -17
- package/dist/schemes/Unknown.d.ts.map +0 -1
- package/dist/schemes/Unknown.js +0 -39
- package/dist/schemes/Unknown.js.map +0 -1
package/README.md
CHANGED
|
@@ -41,6 +41,10 @@ Config + state live in `~/.plurnk/` (created on first run): put your config in `
|
|
|
41
41
|
|
|
42
42
|
`FIND`'s `~query` ranks semantically via an optional embedder peer, `@plurnk/plurnk-mimetypes-embeddings` (heavy native deps; not installed by default). Absent → `~query` falls back to FTS keyword ranking and `start` prints an `embedder inactive` notice. Enable vector search: `npm i @plurnk/plurnk-mimetypes-embeddings`.
|
|
43
43
|
|
|
44
|
+
## The file sandbox
|
|
45
|
+
|
|
46
|
+
Plurnk sees what git sees. The model's file surface is defined by three external grantors — your explicit client adds, your git's own inclusion rules (tracked plus untracked-not-ignored, minus ignored), and the AGENTS.md policy knob — and by nothing else: a file inside the project that none of these admit does not exist for the model, and your gitignored secrets can be neither read nor overwritten. Writes are narrower still: create-only inside the project (and only where the result will be visible), proposal-gated edits on members, read-only beyond the root unless you explicitly grant otherwise. The shell (EXEC) deliberately reaches beyond this sandbox — gate it accordingly; it is your machine's perimeter, not the grammar's. Details: SPEC §scheme-address ({§fs-namespace} through {§fs-world-state}).
|
|
47
|
+
|
|
44
48
|
## Tests
|
|
45
49
|
|
|
46
50
|
`test:lint`, `test:unit`, `test:intg`, `test:live`, `test:demo`; off-hot-path `test:installation`.
|
package/SPEC.md
CHANGED
|
@@ -164,6 +164,8 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
|
|
|
164
164
|
|
|
165
165
|
**A worker carries its actor.** Each worker records its `origin` — `model` (the conversation), `client` (a connection's own worker), or `plurnk` (the runtime's self-hosting worker) — set once at creation and inherited by a fork. `workspace.workers` returns it, so a conversation client identifies the model worker by its actor, not by parsing the name — which is set at instantiation and immutable, never renamed (a worker is permanent history, §machine-processes-worker-is-its-log). {§machine-processes-worker-origin}
|
|
166
166
|
|
|
167
|
+
**The primary worker is the lineage root.** {§worker-primary} The PRIMARY worker of a turn's lineage is the no-parent root reached by walking `parent_worker_id` up; a no-parent worker is its own primary. Core supplies it on the first-party metadata channel alongside `Worker-Id` (same gate, computed per turn), stamped on EVERY turn including the primary's own (where it equals `Worker-Id`) — absent-with-a-Worker-Id is a contract violation, never a silent "assume primary." An unresolvable root (a corrupt/cyclic parent chain the `parent != id` CHECK forbids) fails hard. Providers emits it as `Plurnk-Worker-Primary`; a consumer routes primary-vs-spawned by equality (`Worker-Primary == Worker-Id` ⇒ the primary; `!=` ⇒ any-depth spawn, no depth math) and groups the worker tree by the shared root (#522).
|
|
168
|
+
|
|
167
169
|
**Fork — copy the log, share the world.** A fork is a new worker in the *same* workspace (`workers.parent_worker_id`, §lifecycle-terms). It copies the **log** — the rows, their fold-state riding along — so the branch inherits everything the parent observed (§env-delta makes a worker's timeline self-contained for exactly this) and diverges freely after. {§machine-processes-fork-copies-the-log} It shares the **world** — the one filesystem, the one overlay — live and uncopied, because the worker never owned it. {§machine-processes-fork-shares-the-world}
|
|
168
170
|
|
|
169
171
|
**A workspace cannot be forked.** There is nothing to branch — a workspace *is* the shared ground. `workers` carries `parent_worker_id`; `workspaces` carries no parent. Parallel histories over one workspace are forks of its workers; a divergent workspace is a new workspace. {§machine-processes-no-fork-workspace}
|
|
@@ -172,7 +174,13 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
|
|
|
172
174
|
|
|
173
175
|
**Migration path.** Mostly stating what the schema already carries: `workers.parent_worker_id` and the parentless `workspaces` exist (§lifecycle-terms); `workspace_constraints` is workspace-level (§membership); §env-delta already makes a worker's timeline self-contained, so a fork's log copy suffices. Additive: `worker.fork` over the wire (the engine fork is built). Two repatriations: §actor-boundary's "read-only overlay scopes a worker's writable surface" becomes a *workspace* policy bounding every worker uniformly; and the §env-delta environment door has shed its per-worker snapshot — a worker's only memory is its log, so drift is pulled from the shared log (other actors' edits since the worker's last turn) and the filesystem narrates its own through the `plurnk` worker, both already log entries, never a per-worker shadow.
|
|
174
176
|
|
|
175
|
-
### §worker-scheme The worker:// scheme — control (spawn, irc, fork, terminate, cap, collect)
|
|
177
|
+
### §worker-scheme The worker:// scheme — the knowledgebase (commons, own space, named spaces, the kernel surface) and worker control (spawn, irc, fork, terminate, cap, collect)
|
|
178
|
+
|
|
179
|
+
**The authority names the OWNER** (#527, the (c) carving): `worker:///plan.md` is the COMMONS — the shared blackboard, the encouraged default (owner ruling: sharing is the low-energy path; clobber is accepted, the log is the recovery surface); `worker://~/draft.md` is the calling worker's own private space; `worker://<name>/result.md` is a named worker's space; `worker://plurnk/docs/x.md` is the kernel's published surface, world-readable. Storage keys the owner on the entries.owner_id column (§entry-owner) — the pathname is always the bare entry path, and a FIND's result paths re-apply the queried authority so the model sees the address it typed. `~` and `plurnk` are resolver-interpreted reserved authorities; `self`/`commons` are reserved names no worker may take. {§worker-authority-carving}
|
|
180
|
+
|
|
181
|
+
**Named spaces are ancestry-gated reads**: the reader is the owner or an ANCESTOR (the recursive parent_worker_id walk) — oversight flows down the tree, a parent reads `worker://child/result` across generations, a child cannot snoop upward, and an unknown name or unpermitted reader resolves 404 with no existence leak. The kernel surface is the one world-readable named space. {§worker-read-scope}
|
|
182
|
+
|
|
183
|
+
**Writes are self-and-commons only**: a model writes `worker://~/` and `worker:///` — every named authority is read-only to it (403), and owner_id is engine-stamped from the dispatch context, never model-set. Nothing worker-authored can land under another principal — `worker://plurnk/` included, which is what makes the kernel surface the trust boundary with no guard to forget (only the kernel, dispatching as itself, authors it). The entry-copy seam (COPY/MOVE) is pathname-keyed and addresses the commons; a space's content moves via READ + EDIT. {§worker-write-scoping}
|
|
176
184
|
|
|
177
185
|
The worker:// scheme makes §machine-processes addressable: a `worker://` target is a sister worker in the workspace — `worker://self` the current worker, `worker://<name>` a workspace-scoped sibling (`workers.name`). `self` is the reserved current-worker sentinel; empty authority (`worker:///`) is invalid (400). Same-workspace only; a worker never addresses another workspace's workers (§actor-boundary). The path discriminates the two faces: **path-absent is control** on the worker-as-actor — the NAME is the authority (`worker://<name>`, two slashes, no path) — while **path-present is worker-scope storage**: `worker://<owner>/<path>` (`worker://self/<path>` for self) addresses the owner's private scratch (Scratch + Perspective, below). The control ops are three, fire-and-forget — the child workers independently, lineage in `workers.parent_worker_id`:
|
|
178
186
|
|
|
@@ -186,12 +194,10 @@ All three ride one engine seam — the daemon's inject (active→fold, idle→en
|
|
|
186
194
|
|
|
187
195
|
Beyond the three creation ops:
|
|
188
196
|
|
|
189
|
-
- **
|
|
190
|
-
- **Scratch KILL (delete)** — `KILL(worker://<owner>/<path>)` with an entry **path present** deletes that scratch entry (200; 404 if absent), self-only like EDIT (a cross-worker delete is **403**) — the model's curation lever over its own workspace, distinct from the path-ABSENT `KILL(worker://<name>)` which terminates the worker (§worker-scheme-terminate). The discriminator is the entry path, never the op. {§worker-scheme-scratch-kill}
|
|
191
|
-
- **Perspective** — a worker's own scratch is catalogued in **its** manifest alone — `Manifest(worker) = workspace-scope ∪ this-worker's-worker-scope`, foisted as `FIND(worker://self/**)` at turn 0 — so a sibling reaches it only by explicit `FIND(worker://<name>/**)` and never sees it in its own perspective; isolation is structural (`scope='worker'` is excluded from every workspace query, the owner opted back in only on its own read paths). {§worker-scheme-find-perspective}
|
|
197
|
+
- **Entries (storage)** — entry addressing rides the authority carving above (§worker-authority-carving): `worker:///` the commons, `worker://~/` the own space, a name an ancestry-gated read (§worker-read-scope), writes self-and-commons only (§worker-write-scoping). An entry-path `KILL` deletes under the same write-scoping (200; 404 absent; 403 named) — distinct from the path-ABSENT `KILL(worker://<name>)` which terminates the worker (§worker-scheme-terminate); the discriminator is the entry path, never the op. A worker's own space is catalogued in ITS perspective alone (`FIND(worker://~/**)`, foisted at turn 0 when non-empty); isolation is the owner column, structural.
|
|
192
198
|
- **Terminate** — `KILL(worker://<name>)` aborts a worker by address (self is `worker://self`): its active loop closes 499 and its subscriptions tear down; a name with no worker is 404. The override to the fire-and-forget default — not a parent-power, whoever holds the address may end it; a worker left alone simply ends at its own `SEND[200]`. {§worker-scheme-terminate}
|
|
193
199
|
- **Cap** — `PLURNK_SERVICE_WORKSPACE_WORKERS_MAX_ACTIVE` ceilings the *concurrent* active workers per workspace (a worker with a non-terminal loop); a spawn or fork past it fails hard (508 — no queue, no retry), irc exempt; `-1` disables it. The fork-bomb brake, sized for workspaces that live for months. {§worker-scheme-cap}
|
|
194
|
-
- **Collect** — a worker's loop reaching a terminal status surfaces to its sisters as an ambient delta (§env-delta): a `SEND` from `worker://<name>` carrying the loop's deliverable — the `SEND[200]` body, or for an abandonment the reason. A **2xx deliverable is born OPEN** (its body materialized into the parent's packet, not hidden behind a fold): a child's success must reach the parent open and awakening, never a bodyless row. An abandonment (non-2xx) surfaces folded. Every death-path is stamped uniformly, so no termination is silent; collection is the shared world moving, never a verb. **Child orientation.** Beyond the conclusion delta, every turn the
|
|
200
|
+
- **Collect** — a worker's loop reaching a terminal status surfaces to its sisters as an ambient delta (§env-delta): a `SEND` from `worker://<name>` carrying the loop's deliverable — the `SEND[200]` body, or for an abandonment the reason. A **2xx deliverable is born OPEN** (its body materialized into the parent's packet, not hidden behind a fold): a child's success must reach the parent open and awakening, never a bodyless row. An abandonment (non-2xx) surfaces folded. Every death-path is stamped uniformly, so no termination is silent; collection is the shared world moving, never a verb. **Child orientation.** Beyond the conclusion delta, every turn the packet's status clump surfaces the live things THIS worker *currently holds* — open streams (`## Child Streams`) and unconcluded child workers (`## Active Child Workers`) — as terse `* <status> <path>` pointers (the same shape as the errors section), just above it. A worker is otherwise marked only at spawn and at conclusion; in between it goes silent, so a model loses track of what it holds and premature-terminates. This is ORIENTING STATE, never advice: the model SEES its live subtree (`* 102 worker://worker-x`, `* active sh:///1/2/3`) and reasons for itself — READ/OPEN/KILL via the path — the error stays terse. Empty → omitted, like errors. {§child-orientation} The **pull** side mirrors the push: a path-absent `READ(worker://<name>)` collects the same deliverable on demand — the latest loop's terminal message (the result, or the abandonment reason) for a concluded worker; a worker **still running** has not delivered, so the READ returns **425** (Too Early) and the turn's `SEND[202]` **blocks the loop on the join** ({§join-blocking-collect}) until the worker delivers — the engine holds the join, the model never drives a park. A missing name is 404. So the model never needs to guess a scratch path to "check on" a worker — reading the worker itself yields its outcome or a wait. {§worker-scheme-collect}
|
|
195
201
|
|
|
196
202
|
### §run-lifecycle Run lifecycle: the drain, the reap, the passive wake
|
|
197
203
|
|
|
@@ -258,6 +264,8 @@ Model alias parsing (`parseAliasesFromEnv` / `resolveActiveAlias`) lives in [`@p
|
|
|
258
264
|
|
|
259
265
|
**Grammar enforcement is verified at boot.** When the operator sets `PLURNK_PROVIDERS_GBNF`, `loadActiveProvider` forces a trivial grammar (`root ::= "PLURNK-RAILS-LIVE"`) and confirms the backend returned exactly that — a live end-to-end proof the rails engaged. Anything else **fails hard at boot**: the openai provider only transports the grammar when its probe detects llama-server (grammarStyle `llamacpp`), and any probe hiccup silently falls back to `none` — unconstrained generation with no signal, the whole grammar contract dark, model rambles that read as reasoning failure. The Provider interface exposes no capability to introspect this, so the contract is *verified* rather than trusted; a legible boot refusal beats silent garbage. No-op when GBNF is unset/`0` (unconstrained is then a deliberate mode). **Resolved PER ALIAS** {§gbnf-per-alias}: `PLURNK_PROVIDERS_GBNF_<alias>` over the bare fallback (scopeEnvToAlias, #353), because only sampling-constraining backends benefit — a cloud model that merely IGNORES an in-band grammar earns a filter-mode divergence event every turn for nothing. So the bare default ships OFF and the GBNF-capable aliases opt IN (in the operator's own .env, never the template); the boot verify still gates each enabled alias on its provider's `constrainsOutput` claim. {§grammar-enforcement-verified-at-boot}
|
|
260
266
|
|
|
267
|
+
**Rail truth is the engine's verdict, on every path.** {§rail-truth-engine-verdict} On every completed model turn whose alias configures a grammar, the engine grades the raw emission against that grammar (`@plurnk/gbnf` `validateGbnf`) — delegated enforcement changes who *constrains*, never who *verifies*, and verification that shares a failure domain with the enforcer is not verification (#534: an enforcing relay's own telemetry attests green even when it enforces a stale grammar). The turn's `meta` carries `railsAttached` — `"client"` when the provider claims `constrainsOutput` (transported), else `"delegated"` — and `railsVerdict` (`accept`/`incomplete`/`reject`, or `unverifiable` when the grammar itself fails to parse in the validator, warned once), engine-stamped over the provider passthrough: the one service-authored carve-out in the #252 meta contract. A non-accept verdict mints the `grammar_unenforced` telemetry event engine-side when the provider did not already observe it, so the error channel is path-independent. On a hosted alias the GBNF knob thereby configures the *contract the engine verifies*, independent of transport (settles the #532 knob question: it stays).
|
|
268
|
+
|
|
261
269
|
```
|
|
262
270
|
PLURNK_MODEL_gemma=openai/macher.gguf
|
|
263
271
|
PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
|
|
@@ -278,13 +286,27 @@ Author-facing contract: [plurnk-schemes#1](https://github.com/plurnk/plurnk-sche
|
|
|
278
286
|
|
|
279
287
|
### §scheme-address Address resolution (RFC 3986)
|
|
280
288
|
|
|
281
|
-
Every op targets a URI; the entry key is `(
|
|
289
|
+
Every op targets a URI; the entry key is `(workspace, owner, scheme, pathname)` ({§entry-identity-no-null}). The URI parses per RFC 3986 (`scheme://[authority]/path`) and maps to that key by one rule — no per-scheme carve-out:
|
|
282
290
|
|
|
283
291
|
- A **registered** scheme is a plurnk namespace: its authority is a leading path segment, folded into the pathname (`Dispatcher.#extractTarget` → `foldAuthorityIntoPath`). So `known://x`, `known:///x`, and pathname `/x` are the same entry — the authority is never a host, and the two-slash and three-slash forms are not distinct resources. {§scheme-address-namespace-fold}
|
|
284
292
|
- A **foreign** scheme (unregistered — `http`/`https`) is a real web host: its authority stays in `hostname`, never folded.
|
|
285
|
-
-
|
|
293
|
+
- The **`file` class is the workspace filesystem** — a mount namespace with its own resolution and naming law, specified below.
|
|
294
|
+
|
|
295
|
+
**The workspace is a mount namespace; `project_root` is the model's `/`.** {§fs-namespace} Chroot semantics: host paths do not exist inside the jail, and no engine surface ever folds a host-absolute spelling onto a member (the convenience fold was run59's biography — #545). The root is **fixed immutably at workspace creation** (headless is forever); the namespace's mount table changes only through the declared membership overlay (§membership), never by re-rooting. At `project_root = /` the jail is the whole filesystem and every rule below degenerates to identity — the design's proof case, and the common benchmark topology.
|
|
296
|
+
|
|
297
|
+
**Resolution is namei over the mount table.** {§fs-namei} The model's CWD is permanently `/`, so `src/x.md` and `/src/x.md` are the same name — the slash rule is a corollary, never a legislated equivalence. Resolution is lexical: `.` and `..` resolve before anything touches storage (`..` is legal *during* traversal); the final name lands in the root subtree (a bare key), on a declared outside-root mount (a `../`-prefixed key — the git-style overlay), or names nothing (404 carrying the resolved form). Containment is the resolution semantics — there is no separate traversal check to forget.
|
|
298
|
+
|
|
299
|
+
**One canonical name, storage ≡ wire: the git pathspec.** {§fs-canonical-name} In-tree member keys follow gitformat-index(5) verbatim (reference edition: git 2.47.3): *relative to the top level directory, without leading slash, `/`-separated, no `.`/`..`/`.git` components, no trailing slash, no NUL*. Directories are never entries and the root needs no name — files only, as git. The `../`-prefixed keys for declared outside-root mounts are ALSO git's convention, not an extension: git addresses beyond any single position constantly (CWD-relative pathspecs; `git ls-files`' default output prints content above your directory with `../` prefixes — `--full-name` is the override, not the norm; `git diff --no-index` takes arbitrary outside paths; worktrees and submodules span trees under one repository). The mapping: our `project_root` plays git's *current directory*, and the declared forest (§membership-forest) plays the worktree — a `../`-key is exactly the CWD-relative name git itself prints for content above you, so the leading-run-only shape of `..` is derived (that is what relative paths above the position look like), never bespoke. The ONE true divergence from git, stated with its reason: git stores per-tree indexes top-relative and derives the CWD-relative view at output; plurnk stores the root-anchored key directly in the one entries table, because workspace identity is rooted at the access point ({§entry-identity-no-null} — one identity tuple, one workspace). Every spelling the model types canonicalizes to this form BEFORE storage or comparison — the database never receives a model spelling; the identity law is the identity half of that discipline, this is the name half.
|
|
286
300
|
|
|
287
|
-
|
|
301
|
+
**Visibility has three grantors; plurnk is never one of them.** {§fs-visibility-grantors} A file is visible to the model only when admitted by (1) the client's explicit grant (the constraint overlay's pick/repo verbs), (2) git's inclusion semantics (ls-files ∪ untracked-not-ignored − ignored, per declared repo), or (3) the AGENTS.md knob (§policy-sections — auto-pulled as POLICY, deliberately not a member; a git-tracked AGENTS.md may separately be an ordinary member via grantor 2). Model creation is a WRITE permission, never a visibility source — the created file's visibility rides git's rules, so a gitignored creation would be invisible even to its creator (which is why the write gate refuses it — the blind-write closure in {§fs-write-surface}). **Counterintuitive on purpose**: a file physically inside the root that no grantor admits DOES NOT EXIST for the model — this is the sandbox narrative's center, and it is what makes the story auditable: every visible byte traces to a line in the client's constraint table or a rule in the operator's own gitignore, never to a plurnk decision. The `.env`-protection is this law's theorem, not a special case: gitignored → never a member → never readable, never clobberable. In practice the whole contract compresses to three clauses: *plurnk expects a git environment; the shell (EXEC, operator-gated) reaches beyond the grammar's sandbox deliberately; and the client override expresses any bespoke security arrangement* — the grammar surface is structurally sandboxed, and the machine's security perimeter is wherever exec config puts it.
|
|
302
|
+
|
|
303
|
+
**The write surface — mount semantics, grantor-keyed.** {§fs-write-surface} The project root is the model's ONE read-write mount; writability elsewhere tracks the GRANTOR (owner ruling): *write permission = inside the root, or client-granted outside it — git's grants confer rw only within the project.* The matrix: (1) root + empty path → exclusive CREATE (`open(O_CREAT|O_EXCL)` semantics), and only where the RESULT will be a member — the **blind-write closure**: a create that git would ignore and no client pick covers is refused, because plurnk never writes bytes its own sandbox cannot see afterward; the blame stays external (git's ignore rule, or the absent client grant), and a non-git root grants nothing by itself. (2) root + existing member → the proposal-gated EDIT, any grantor. (3) root + existing NON-member → refused, revealing occupancy only (the POSIX `O_EXCL` precedent: namespace existence is not secret, content is — the hidden file stays protected and dark). (4) declared mount + client-granted member → read-write, proposal-gated (a per-file rw bind mount — explicit intent carries write permission). (5) declared mount + git-included member → read-only. (6) any mount + create → refused, always: only the root mints.
|
|
304
|
+
|
|
305
|
+
**The engine answers in canon.** {§fs-answer-in-canon} Every engine-authored address — log-row pathname columns, rx spans and error facts, FIND results, the catalog, the foists — renders the one canonical form: exactly what `git ls-files --full-name` prints, byte-for-byte on the git-membership subset. A miss names the RESOLVED form, never an echo of the model's spelling. The single verbatim survivor is the model's own emission text — history is never rewritten. There is no shadow universe of model-preferred addressing.
|
|
306
|
+
|
|
307
|
+
**errno discipline — one error, one meaning, each with its fact.** {§fs-errno} Error facts speak wire canon and state the fact, never a tutorial. ENOENT: `no entry at <resolved-name>` on any exact-path miss — READ and FIND alike; a FIND over an unresolvable **exact** path is ENOENT, never a certified empty (the run59 green lie taught a model a real function did not exist), while a glob or folder scope with zero matches remains the blessed orienting empty survey. EEXIST-class: `a file exists at <key>` on exclusive-create against an occupied path (the POSIX precedent: occupancy leaks, content stays dark). EROFS-class: the read-only facts on mount writes and mint refusals ({§fs-write-surface}). ERANGE: `range not satisfiable — entry has N lines` — the house exemplar. The jail adaptation of git's own fatal: the fact names the KEY, never the host root — the reader lives inside the namespace. A model distinguishes wrong-address from wrong-range from occupancy by the strings alone.
|
|
308
|
+
|
|
309
|
+
**The world-state harness — coverage that closes the class.** {§fs-world-state} Op-outcome tests check what an op RETURNED; the harness checks what the world looks like AFTER — the blindness that let run59 ship (74k rows for 530 identities was invisible to every op-outcome assertion). `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, orphan-freedom (channels/tags without parents), the closed admission set (a file row's grantor is git or a client act — or the create-accepted transient NULL the next reconcile stamps), and sig-coherence. It runs as a lifecycle-test epilogue, at every soak turn boundary (where the delta half applies: an idle turn grows the entries table by ZERO), and by bench over campaign specimens (#536 evidence contract). A violation names its law and its row.
|
|
288
310
|
|
|
289
311
|
### §scheme-manifest Manifest
|
|
290
312
|
|
|
@@ -643,6 +665,12 @@ Engine routes unconditionally to `exec` scheme (the `(target)` slot is a local p
|
|
|
643
665
|
|
|
644
666
|
**Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose** (lifecycle §proposal): the worker waits for a human gate, then spawns and writes stdout/stderr to channels of a `<runtime>:///<loop>/<turn>/<seq>` entry (the runtime tag is the URI scheme, §exec/#240; the coordinate matches the op's log-row coordinate, e.g. `sh:///1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive what the model sees at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
|
|
645
667
|
|
|
668
|
+
**Every entry is owned by a worker** (#527 beachhead). `entries.owner_id` is a real worker row, part of the identity key — the workspace's reserved `commons` worker for shared content, the spawning worker for capability streams. Never NULL (NULLs are distinct under UNIQUE — a nullable owner would let the shared-content identity fragment into duplicate rows), never rendered into a URI or packet: the model addresses owners by NAME in the authority slot. `plurnk` (the kernel) and `commons` are the two reserved rows; no spawn or client may take their names (nor `~`, the #527 self-sigil). {§entry-owner}
|
|
669
|
+
|
|
670
|
+
**Capability streams are owner-scoped** (#526). Concurrent workers' stream coordinates are loop-relative and IDENTICAL (every worker's first loop is sequence 1), so the entry identity keys on the owner and identical coordinates across workers are distinct rows. The address's authority names the owner: **empty = the calling worker** — your own streams need no qualifier, so a fan-out sibling's output can never surface under your READ — and a **named authority** reaches that worker's streams gated by ancestry (the reader is the owner or an ancestor; oversight flows down the tree, unknown-or-unpermitted resolves 404 with no existence leak). KILL stays self-only — a parent controls a child through the worker lifecycle, never by reaching into its streams. The storage pathname stays the bare loop coordinate; the owner rides the column, so nothing model-facing carries a worker id. {§stream-owner-scoped}
|
|
671
|
+
|
|
672
|
+
**Auto-names are id-free ordinals** — worker names are the addressable authority, so an auto-name is `<prefix>-<N>` (per-workspace monotonic count, the fork `<parent>-fork-<N>` pattern), never a timestamp-hash that would leak machine identity through the hostname. Unique among live workers per the reclaim doctrine (§machine-processes-worker-origin); reserved names refused at every naming door. {§worker-auto-name}
|
|
673
|
+
|
|
646
674
|
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**: no proposal, no human gate, no notification. It skips the gate a host command faces, but it does NOT resolve in-band — like every exec it backgrounds and streams, its output reaching the model through the environment-observation injector (a foisted READ of the stream's new bytes each turn, §exec-stream), never a same-turn receipt. {§exec-readpure-ungated}
|
|
647
675
|
|
|
648
676
|
**Stream surfacing.** An exec's output is *observed, not fetched*. Each turn the environment-observation injector — the same machine §env-delta rides — reads each of the worker's open channels from a per-channel byte cursor and foists the new bytes as an `origin=plurnk` READ at `<runtime>:///<coord>#<channel>`, then advances the cursor — each delta carries the `startLine` that cursor implies, so a stream spanning turns numbers into one continuous sequence (lines 1–k, then k+1–m), not a fresh `1:` each turn. The delta is **folded** while the channel streams and auto-**opened** on the terminal one (the channel closed): a model ignores a chatty background stream but always SEES a finished one. It never types these READs — it consumes them. The EXEC row itself renders the *command* it ran, `:::`-fenced and line-numbered per §render-rule so the model can line-reference its own code — the input, distinct from the stream above (the output). This is exec as an instance of one ambient machine, env-delta as another (sibling edits, timestamp cursor, always folded). {§exec-stream}
|
|
@@ -735,6 +763,8 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
|
|
|
735
763
|
- **Forward — apply-once evolution.** The idempotent-`INIT` model creates a fresh schema but does not *evolve* a populated one. When persisted state outlives a "just nuke it" reset, the date-prefixed ordering is the foundation a real migration policy layers onto: ordered `ALTER` files applied once and recorded in a marker table, not recreated idempotently. The current scheme is chosen so that transition is **additive** — a marker table plus apply-once dispatch — never a restructuring of how DDL is authored or ordered.
|
|
736
764
|
- **Schema-alignment test**: loads `@plurnk/plurnk-grammar/schema/*.json`, parses DDL via `node:sqlite` introspection, asserts every required schema field has a corresponding `NOT NULL` column. Grammar drift fails CI.
|
|
737
765
|
- DDL = storage truth; JSON Schemas = wire truth. Tested-aligned, allowed to differ where ergonomics demand.
|
|
766
|
+
- **Schema-version stamp.** {§db-schema-version-stamp} Every plurnk DB carries `PRAGMA user_version` (current: `2`), set as an idempotent `INIT`. Any change to the schema's *shape* — tables, columns, identity keys — bumps the integer in the same commit. The stamp is the cross-repo drift gate (#536): external consumers (bench's digest) read it with zero table dependency and fail hard on mismatch — "schema v2 required, found v1" — instead of rotting silently against a moved schema (the pre-rename specimen class: "no such table").
|
|
767
|
+
- **Identity components are never NULL.** {§entry-identity-no-null} The entries identity tuple — (workspace, owner, scheme, pathname) — admits no NULL component, because NULLs are distinct under SQL UNIQUE and a nullable component voids the identity index entirely: the #526 disease, re-run on the scheme axis as run59/#545 (the "idempotent" per-turn membership upsert never conflicted — one phantom row per member per turn, 74k rows over 530 identities, null-keyed lookups landing on arbitrary rows, the sig-gate misfiring, EDIT anchors resolving against stale bytes). File members persist under the reserved **`file`** scheme (`storedScheme: "file"`; they still render as bare paths); `entries.scheme` is `NOT NULL`; a manifest declaring `storedScheme: null` is refused at dispatch; the v1→v2 heal INIT folds legacy NULL rows onto `file` in place — a v1 db already fragmented fails the heal on the identity index, loudly, and recovers via a fresh db.
|
|
738
768
|
|
|
739
769
|
### §sql-ts-boundary SQL/TS responsibility boundary
|
|
740
770
|
|
|
@@ -1116,7 +1146,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1116
1146
|
|
|
1117
1147
|
**Question.** Rummy uses priority-ordered filter chains for packet assembly. Plurnk builds a default ordered section list directly in `PacketBuilder.buildRequestPacket`, then lets trusted plugins rewrite it.
|
|
1118
1148
|
|
|
1119
|
-
**Decision.** Two stages. (1) The engine builds the default section list
|
|
1149
|
+
**Decision.** Two stages. (1) The engine builds the default section list, **monotone in volatility end to end** {§packet-cache-monotone}: every prompt-cache scheme in production (prefix, paged, radix, breakpoint) reuses up to the first changed byte, so the wire orders immutable → append-mostly → per-turn-volatile and the cache prefix survives through the log's frozen head instead of dying at the first gauge digit. The system slot is the **timeless** — `definition`, `tools`, `schemes`, the policy sections `system-policy`/`project-policy` — a byte-stable system message (itself a perfect message-granular cache breakpoint). The user slot is the **situated**: `log` (READ results, exec output, the model's own mirror: data at the action point, never a privileged rule), then the status clump — `child-streams`/`child-workers` (pointer lists), `errors` (uri+status pointers; the error item+body live in the log), `git` (counts), `budget` (budget is law — a hard ceiling), the `prompt` pointer list (§prompt-auto-read) — nearest the generation point, where the dashboard reads freshest; then the `requirements` footer. Trust is the one-way **admission rule**, not the slot: the system slot admits only framework-authored, non-injectable content — engine-authored status may ride the user slot, and nothing that can carry attacker-reachable text ever rides system. (2) `SchemeRegistry.transformSections` pipes that list through every registered scheme that implements `transformSections(sections) → sections`, in registration order, before the engine measures. A plugin returns whatever list it wants — add, remove, reorder. {§packet-plugin-transform}
|
|
1120
1150
|
|
|
1121
1151
|
**Why a whole-list transform, not a per-section hook.** It is the legible, fork-avoiding seam: a plugin that can reshape the packet to its needs never has a reason to fork the engine (§ecosystem). And it is **strictly in-process and trusted** (behind `PLURNK_PLUGINS_TRUSTED_ONLY`) — the client/RPC wire never reaches the packet, because handing an untrusted connection the model's entire context is exactly the actor-boundary violation the engine exists to prevent. Pure list-in/list-out; no context is handed to plugins.
|
|
1122
1152
|
|
|
@@ -1138,7 +1168,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1138
1168
|
- **Per-turn weight.** A markdown table groups render-weight by turn — the `loop/turn` coordinate prefix — listed chronologically (oldest first). The turn is the grinder's fold unit, and the rail folds only the **newest** (§grinder); the model sees which turns are fat and can FOLD ahead of the rail. {§tokenomics-turn-totals}
|
|
1139
1169
|
- **Heaviest entries.** A second table lists the five heaviest log entries by their **body weight — the same number the row's own `tokens` shows** (what a FOLD saves; #466: the budget and the log never disagree about one row), each by its `log:///<coord>/<op>` handle — the FOLD targets behind the turn weight. A bodyless row is no FOLD target and never ranks. The handle carries the turn, so the two tables interlock. {§tokenomics-largest-entries}
|
|
1140
1170
|
- **Context-window percent.** The headline carries usage as a percent of the ceiling — `usage Y (P%)` — a fullness gauge beside the absolutes. Reads the ceiling already in hand; no extra provider call. {§tokenomics-context-percent}
|
|
1141
|
-
- **The window is a partition, never a fraction.** the window and both reserves are **provider-owned** (#507, owner-ruled): `provider.contextWindow` (probed/declared in the provider tier), `provider.reasoningReserve` and `provider.completionReserve` (ingested or `PLURNK_PROVIDERS_*`-pinned there, percent or absolute). A null window or null reserves is genuinely-unknown — nobody chose an envelope — and is treated as **NO-CAP** — the prompt is unbounded, the budget and ceiling resolve to null and the gauge omits its headline — rather than let numbers nobody chose silently stand in for unknown physics; a probe blip must degrade to no-cap, never crash the loop {§tokenomics-window-unpollable-deliberate}) splits **without remainder**: `promptBudget = provider.contextWindow − reasoningReserve − completionReserve − PLURNK_SERVICE_SAFETY` is what the service may send, `max_tokens = reasoningReserve + completionReserve` is the generation envelope passed on every `generate({maxTokens})` when the reserves are known, and `SAFETY` — core's ONE remaining partition knob, the ruler's own packing margin — covers chat-template overhead no content counter sees. The retired `PLURNK_SERVICE_{CONTEXT_WINDOW,REASONING,COMPLETION}` names fail hard at boot naming their provider-tier successors. The prompt ceiling is **derived, never set**: a settable ceiling lets policy contradict physics, and a fractional one budgets the prompt against the window while *forgetting the response lives there too* (at 0.9 × 49152, any emission past ~4.9k overflowed gemma with a perfectly honest ruler). Reserves exceeding the window (`promptBudget ≤ 0`) are a configuration contradiction and fail hard — post-migration its one cause is pinned absolute reserves over a detected window; percent reserves derive and cannot contradict. When native reasoning is on, llama-server ignores per-request numeric budgets — the serving box's `--reasoning-budget` launch flag must equal the alias's resolved reasoning reserve, and the service warns at boot when it cannot verify that coupling. The LOCAL example partition (turboderp suffix, 78848 − 4096 − 8192 − 1024) yields exactly 65536 prompt tokens; the bare cloud defaults yield a large decode envelope the backend self-clamps. {§tokenomics-window-partition} **The partition is PER ALIAS** (#352): each knob resolves `PLURNK_SERVICE_<KNOB>_<alias>` over the bare fallback (providers' `scopeEnvToAlias`), scoped by the alias that produced the turn's provider (the service's own provider→alias side-table, falling back to the active boot alias). BARE defaults are cloud-generous — the generation envelope is a large `max_tokens` the backend self-clamps to its true output limit (cloud APIs accept and silently clamp; none expose a max-output field, so generous-and-clamp is the honest posture, and a cloud reasoner burning ~16k on thought finishes its DSL instead of dying at `finish=length`). LOCAL (llama-server) aliases MUST keep a measured envelope — `n_predict` is honored to the context wall, so an unbounded decode wall-runs (providers#10, the cap doctrine's origin); a local alias whose partition contradicts its real window fails HARD at build time, naming the per-alias knobs to set.
|
|
1171
|
+
- **The window is a partition, never a fraction.** the window and both reserves are **provider-owned** (#507, owner-ruled): `provider.contextWindow` (probed/declared in the provider tier), `provider.reasoningReserve` and `provider.completionReserve` (ingested or `PLURNK_PROVIDERS_*`-pinned there, percent or absolute). A null window or null reserves is genuinely-unknown — nobody chose an envelope — and is treated as **NO-CAP** — the prompt is unbounded, the budget and ceiling resolve to null and the gauge omits its headline — rather than let numbers nobody chose silently stand in for unknown physics; a probe blip must degrade to no-cap, never crash the loop {§tokenomics-window-unpollable-deliberate}) splits **without remainder**: `promptBudget = min(operatorCap, provider.contextWindow) − reasoningReserve − completionReserve − PLURNK_SERVICE_SAFETY` is what the service may send — the operator's `PLURNK_PROVIDERS_CONTEXT_WINDOW` pin (alias-scoped else bare) is CORE's log-budget cap (#528): construction strips it so the provider reports its NATURAL window, reserves derive from that nature (a tighter log never throttles how much the model may think or say), and the cap tightens the PROMPT alone; a pin over an UNPOLLABLE window (#419) declares the window itself — no separate nature exists, reserves derive from the declaration — `max_tokens = reasoningReserve + completionReserve` is the generation envelope passed on every `generate({maxTokens})` when the reserves are known, and `SAFETY` — core's ONE remaining partition knob, the ruler's own packing margin — covers chat-template overhead no content counter sees. The retired `PLURNK_SERVICE_{CONTEXT_WINDOW,REASONING,COMPLETION}` names fail hard at boot naming their provider-tier successors. The prompt ceiling is **derived, never set**: a settable ceiling lets policy contradict physics, and a fractional one budgets the prompt against the window while *forgetting the response lives there too* (at 0.9 × 49152, any emission past ~4.9k overflowed gemma with a perfectly honest ruler). Reserves exceeding the window (`promptBudget ≤ 0`) are a configuration contradiction and fail hard — post-migration its one cause is pinned absolute reserves over a detected window; percent reserves derive and cannot contradict. When native reasoning is on, llama-server ignores per-request numeric budgets — the serving box's `--reasoning-budget` launch flag must equal the alias's resolved reasoning reserve, and the service warns at boot when it cannot verify that coupling. The LOCAL example partition (turboderp suffix, 78848 − 4096 − 8192 − 1024) yields exactly 65536 prompt tokens; the bare cloud defaults yield a large decode envelope the backend self-clamps. {§tokenomics-window-partition} **The partition is PER ALIAS** (#352): each knob resolves `PLURNK_SERVICE_<KNOB>_<alias>` over the bare fallback (providers' `scopeEnvToAlias`), scoped by the alias that produced the turn's provider (the service's own provider→alias side-table, falling back to the active boot alias). BARE defaults are cloud-generous — the generation envelope is a large `max_tokens` the backend self-clamps to its true output limit (cloud APIs accept and silently clamp; none expose a max-output field, so generous-and-clamp is the honest posture, and a cloud reasoner burning ~16k on thought finishes its DSL instead of dying at `finish=length`). LOCAL (llama-server) aliases MUST keep a measured envelope — `n_predict` is honored to the context wall, so an unbounded decode wall-runs (providers#10, the cap doctrine's origin); a local alias whose partition contradicts its real window fails HARD at build time, naming the per-alias knobs to set.
|
|
1142
1172
|
- **Derivation is off the hot path; search is never degraded by it.** The per-turn derivation pump and the workspace warm run on a background chain (serialized, drained at daemon stop, failures logged) — **a turn never waits on an embedding** (a 2-CPU container CPU-embedding a 335-entry ingest starved every loop ~28min). Full fidelity survives the move on both fusion halves: the **keyword half indexes at the write** (plain string→FTS, no handler invoked — a cold workspace's first query narrows over everything ever written) {§semantic-fts-at-write}, and a `~` query **derives its own FTS-narrowed candidate slice inline** at dispatch (bounded, cap-telemetered) — ranking only ever scores the narrowed set, so warming exactly that slice is bit-identical to a fully-warm corpus, from turn 1, on any hardware {§semantic-cold-query-full-fidelity}. {§derivation-off-hot-path}
|
|
1143
1173
|
- **Binary truth beats the label; no entry dominates the corpus.** A tracked member whose HEAD bytes contain NUL is materialized as a binary marker (empty body, `application/octet-stream`, READ-415) **regardless of what extension-based detection claims** — the markdown default for unmapped extensions once shipped a 3.3MB `.wasm` blob into the semantic corpus as prose, three copies, ~10M tokens (#320) {§membership-binary-sniff}. And the per-entry chunk cap is a **latency stage, never a coverage bound**: the inline (dispatch-time) slice embeds head-first up to its budget so a cold `~` answers in bounded seconds — and a capped pass does NOT stamp the deep hash, so the background pump completes the entry to full depth (a 300-page book is entirely searchable at steady state; rank cannot be dominated regardless — `semantic_rank` takes one best chunk per entry). A flat cap would silently foreclose legitimate large texts: head-only vectors under a whole-file keyword narrow return head-biased spans, permanently. {§semantic-entry-chunk-cap}
|
|
1144
1174
|
- **A turn is never blank; the provider never adjudicates.** A completed exchange ALWAYS returns from the provider — the model's bytes flow in `assistant` no matter what, with any grammar-conformance verdict riding `response.telemetry` as an **observation** (providers 0.32; the provider transports and observes — the engine's own parse is the judge). Every emission takes the one pipeline: complete statements dispatch, malformed text mints position-carrying parse-error rows the model reads next turn, nothing-parsed is the ordinary no-ops 422, and the record keeps the emission verbatim with its real usage billed. A `ProviderError` reaching the engine means NO completed exchange exists (auth, network beyond retries, rate limit) — an infrastructure failure, and the loop dies 500 carrying the cause, first occurrence: tolerating an infrastructure violation is how a bug accretes concentric layers of policy instead of a fix (the retired empty-turn fallback laundered provider adjudications into model-behavior 422s, and forensics chased the wrong suspect for days). {§turn-never-blank}
|
|
@@ -1306,7 +1336,7 @@ The wire projection (`PacketWire.renderSlot`) groups sections by slot into the s
|
|
|
1306
1336
|
|
|
1307
1337
|
**The arrival law.** {§arrival-law} Every body lands as an entry and appears in the packet as a weighted meta line; how much body rides OPEN on arrival is set by INVITATION. **Self-invited** content — what this worker addressed by its own op (READ/FIND/OPEN results, its own exec streams) — rides open within free headroom; over, it arrives folded (the grinder + budget_overflow contract). **Pushed** content — text another actor put in front of this worker (the user prompt, a child's deliverable, a sibling irc) — rides the PREVIEW: whole when under the bound, the head + the cut statement + the pull address when over. The bound is `PLURNK_SERVICE_ARRIVAL_PREVIEW_LINES` (default 16) lines AND 80×N chars — the char cap guards single-line bombs. The trust line is authorship: a worker's own ask is its curation, weighed in advance; another actor's push is unratified by the recipient at any size (run111: a child's legally-terminal 19,363-token deliverable landed whole in its parent, was relayed into a 40k child prompt, and cascaded the pipeline — #499). A child whose loop ends non-2xx already lands FOLDED (the termination-delta insert); the preview bounds the ratified-giant case. New lanes inherit the law at the arrival boundary — a future scheme or module cannot reopen the hole by adding a feature. The preview is enforced at RENDER for every pushed lane — the prompt foist's line-slice bounds lines, and the render's char cap cuts what a line-slice cannot (a single-line char-bomb prompt renders as its 80×N-char head + the cut statement + its address).
|
|
1308
1338
|
|
|
1309
|
-
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `
|
|
1339
|
+
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `prompt:///<loop>/<N>` (indexable, body channel, text/markdown); injected mid-loop prompts land the same way at their turn's slot. The **User Prompts** section closes the status clump (user slot, just above the recap — {§packet-cache-monotone}) as a PATHS-ONLY list (the errors shape — `* prompt:///<loop>/<N>`, no bodies): every prompt the loop holds stays listed and READable by address, so prior prompts are never silently lost and recovering them is never a curation imposition. {§prompt-auto-read} **The frame is SELF-ONLY and owner-keyed** (#527, closing #382 with zero exceptions): the entry's owner_id is the worker, the address carries only the loop coordinate — no worker identity ever rides a pathname or a packet. Concurrent workers' identical coordinates (every worker's first loop is sequence 1) are DISTINCT rows on the owner column, so a WORK-spawned sister's turn-1 foist can never clobber the parent's task. The scheme takes no authority slot: packets are per-worker and every cross-worker prompt flow (parent→child inject, the drain's orphan promotion) is engine-mediated, so a worker only ever addresses its own frames; every writer and query builds through one boundary (`plurnk-uri.promptPathname`/`promptLoopPrefix` + the query's owner param). The model never constructs the address — it READs what the section lists. {§prompt-self-only} **A loop CONTAINS its prompts — plural, ordinal-keyed** (owner ruling): a loop is created by its originating prompt (frame 1) and holds EVERY prompt that arrives before it concludes — each lands at the loop's next ordinal (`prompt:///<loop>/<N>`, the Nth prompt of the loop), so rapid arrivals are frames N and N+1, both kept, nothing superseded, and the key structurally cannot collide regardless of turn timing. A new prompt raises a NEW loop only when no active loop exists to contain it. Delivery is the next turn boundary's auto-READ of every not-yet-delivered frame (oldest first — the frame's own foisted READ in the loop is the delivery record); a frame still undelivered when the loop concludes is the orphan the drain promotes into a fresh loop, so no instruction is ever silently lost. {§prompt-loop-containment} The prompt's CONTENT reaches the model through a foisted auto-`READ` of its own entry — `<1,16>` (the §arrival-law preview bound, `PLURNK_SERVICE_ARRIVAL_PREVIEW_LINES`), or `<1,-1>` when the prompt runs under it (the whole-read form doubles as teaching) — dispatched on the first turn for the loop prompt and on the landing turn for each injected prompt, so an arriving prompt always opens in the log like any retrieval. The foisted `EDIT`'s **log row is folded by default** (`expanded=0`): the body arrives via the auto-READ, so the log keeps the write for forensics while collapsing the duplicate, re-OPENable like any fold (§open-fold). {§prompt-fold} **A model `FOLD` targeting a prompt row is ILLEGAL** (#382 — a weak model in a housekeeping turn folded its own task auto-READ and lost the plot): the fold is refused (*"Illegal attempt to FOLD a user prompt. Use KILL if you want it removed."*), so the frame can't be accidentally hidden — while `KILL` still deletes it deliberately, preserving the model's curation of a prompt it genuinely wants gone. A glob sweep silently spares the prompt and folds the rest. {§prompt-fold-illegal}
|
|
1310
1340
|
|
|
1311
1341
|
**The entry catalog.** The catalog is the **complete, unranked directory** of what a workspace holds, served by `FIND(scheme:///**)` — one per-scheme array, queried on demand, not a single materialized entry (there is no `plurnk:///manifest.json`; the per-scheme arrays replaced it). Built in the schemes layer (`_entry-manifest.catalogRowsFor`); a per-turn derivation pump (`maintainDerivations`) refreshes the deep channels the rows report. A scheme's array is **every entry it holds, in no relevance order**, each `{ path, seconds?, tags?, channels: { <uri>: { mimetype, tokens, lines } } }` — every channel keyed by the URI the model READs (the default channel by the bare path, a non-default by `path#channel`), so it reaches a channel without guessing. `tags` is present only when the entry carries `entry_tags` — its own categorization, surfaced so the model can `FIND` by tag. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's live count recounted at render, `lines` the content extent from `Mimetypes.process().totalLines`. The catalog never lists itself. {§packet-catalog}
|
|
1312
1342
|
|
|
@@ -1369,11 +1399,11 @@ A `## Schemes` section renders in the system slot **after the definition (plurnk
|
|
|
1369
1399
|
|
|
1370
1400
|
### §inject system.inject — the operator injection
|
|
1371
1401
|
|
|
1372
|
-
When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as a `## Plurnk Operator Notes` section in the system slot **right after the teaching** (definition → tools → schemes → inject), ahead of the policy sections
|
|
1402
|
+
When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as a `## Plurnk Operator Notes` section in the system slot **right after the teaching** (definition → tools → schemes → inject), ahead of the policy sections — part of the cached prefix. Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard (a deliberate setting with a broken path is a misconfig, surfaced not hidden). `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section. {§packet-inject}
|
|
1373
1403
|
|
|
1374
1404
|
### §policy system.policy — the client's policy injection
|
|
1375
1405
|
|
|
1376
|
-
Two sections ride the system slot **below the operator notes,
|
|
1406
|
+
Two sections ride the system slot **below the operator notes, at the slot's bottom**: `## Policy` from `PLURNK_SERVICE_POLICY` (default `~/.plurnk/AGENTS.md`) and `## Project Policy` from `PLURNK_SERVICE_PROJECT` (default `<projectRoot>/AGENTS.md`, resolved relative to the workspace root). AGENTS.md is **policy** — the client's authoritative rules promoted into the privileged zone — NOT a curatable, foldable, READ-able entry; the model cannot FOLD it away. A default-absent path is silent (the section is omitted); an explicit override (env set) that fails to read fails the turn hard — a deliberate setting with a broken path is a misconfig, surfaced not hidden. Read per-turn so edits take effect live. Reference/scratch docs are NOT policy — they ride `PLURNK_SERVICE_MD_*` (materialized as READ-able entries, §operator-config), which is where the dev-notes AGENTS.md used to hold belong. {§policy-sections}
|
|
1377
1407
|
|
|
1378
1408
|
**The scheme self-doc contract.** `example` is the hot-path one-liner; `documentation` is the deep doc — the exact shape execs already use (`example` + `documentation`). `SchemeRegistry.teach()` renders the directory; `docEntries()` materializes the docs (per loop.run, alongside the operator docs). `documentation` rides a service-side `SchemeManifest` extension until plurnk-schemes#25 lands it in the contract.
|
|
1379
1409
|
|
|
@@ -76,8 +76,9 @@ export default class ChannelWrite {
|
|
|
76
76
|
subscriptionId: number;
|
|
77
77
|
status: number;
|
|
78
78
|
}): Promise<void>;
|
|
79
|
-
static execTerminalStatus(db: Db, { workspaceId, pathname }: {
|
|
79
|
+
static execTerminalStatus(db: Db, { workspaceId, workerId, pathname }: {
|
|
80
80
|
workspaceId: number;
|
|
81
|
+
workerId: number;
|
|
81
82
|
pathname: string;
|
|
82
83
|
}): Promise<number | null>;
|
|
83
84
|
static findActiveSubscription(db: Db, { workerId, entryId }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
|
|
@@ -86,8 +86,8 @@ export default class ChannelWrite {
|
|
|
86
86
|
// Terminal close_status of a finished exec stream, by coordinate pathname —
|
|
87
87
|
// the KILL-on-a-non-running-exec lookup (Exec.kill). null = no closed
|
|
88
88
|
// subscription for that coordinate (unknown exec).
|
|
89
|
-
static async execTerminalStatus(db, { workspaceId, pathname }) {
|
|
90
|
-
const row = await ChannelWrite.#execTerminalStmt(db).get({ workspace_id: workspaceId, pathname });
|
|
89
|
+
static async execTerminalStatus(db, { workspaceId, workerId, pathname }) {
|
|
90
|
+
const row = await ChannelWrite.#execTerminalStmt(db).get({ workspace_id: workspaceId, worker_id: workerId, pathname });
|
|
91
91
|
return row?.close_status ?? null;
|
|
92
92
|
}
|
|
93
93
|
static async findActiveSubscription(db, { workerId, entryId }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,7 +72,8 @@ WHERE worker_id = $worker_id AND closed_at IS NULL AND turn_scoped = 1;
|
|
|
72
72
|
SELECT s.close_status
|
|
73
73
|
FROM entries e
|
|
74
74
|
JOIN subscriptions s ON s.entry_id = e.id
|
|
75
|
-
WHERE e.
|
|
75
|
+
WHERE e.workspace_id = $workspace_id
|
|
76
|
+
AND s.worker_id = $worker_id
|
|
76
77
|
AND e.scheme = 'exec' AND e.pathname = $pathname
|
|
77
78
|
AND s.closed_at IS NOT NULL
|
|
78
79
|
ORDER BY s.closed_at DESC
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/Dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,wBAAwB,CAAC;AAC/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/Dispatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Dispatcher.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { parsePath } from "@plurnk/plurnk-grammar";
|
|
3
|
+
import Owner from "./Owner.js";
|
|
3
4
|
import { foldAuthorityIntoPath, schemeNameOf } from "./plurnk-uri.js";
|
|
4
5
|
import Fork from "./fork.js";
|
|
5
6
|
import WorkerCap from "./worker-cap.js";
|
|
6
7
|
import { decodePathParens } from "./path-decode.js";
|
|
8
|
+
import Namespace from "./namespace.js";
|
|
7
9
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
8
10
|
import { LineMarkerOps, MimetypeBinary } from "../content/index.js";
|
|
9
11
|
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
@@ -59,6 +61,28 @@ class Dispatcher {
|
|
|
59
61
|
this.#searchGate = searchGate;
|
|
60
62
|
this.#joinTargets = joinTargets ?? new Set();
|
|
61
63
|
}
|
|
64
|
+
// workspace → project_root, memoized: {§fs-namespace} fixes the root immutably at
|
|
65
|
+
// workspace creation, so a process-lifetime cache can never go stale.
|
|
66
|
+
#rootCache = new Map();
|
|
67
|
+
async #workspaceRoot(workspaceId) {
|
|
68
|
+
if (this.#rootCache.has(workspaceId))
|
|
69
|
+
return this.#rootCache.get(workspaceId) ?? null;
|
|
70
|
+
const row = await this.#db.envelope_get_workspace.get({ id: workspaceId });
|
|
71
|
+
const root = row?.project_root ?? null;
|
|
72
|
+
this.#rootCache.set(workspaceId, root);
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
// {§fs-answer-in-canon} — a file-class target's engine-authored address COLUMNS carry
|
|
76
|
+
// the canonical key; the tx JSON keeps the model's verbatim statement (history is never
|
|
77
|
+
// rewritten — the one surviving spelling). An un-canonicalizable spelling keeps its raw
|
|
78
|
+
// form: the row must still faithfully exist for the op that happened.
|
|
79
|
+
async #canonColumns(target, workspaceId) {
|
|
80
|
+
if (target.scheme !== null || target.pathname === null)
|
|
81
|
+
return;
|
|
82
|
+
const key = Namespace.canonicalizeSpelling(target.pathname, await this.#workspaceRoot(workspaceId));
|
|
83
|
+
if (key !== null)
|
|
84
|
+
target.pathname = key;
|
|
85
|
+
}
|
|
62
86
|
async dispatch(context) {
|
|
63
87
|
const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, turnParseErrors } = context;
|
|
64
88
|
const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
|
|
@@ -126,7 +150,7 @@ class Dispatcher {
|
|
|
126
150
|
// arm the join so THIS turn's bare SEND[102] parks (the blocking collect) instead of spinning.
|
|
127
151
|
if (typeof result.awaitWorker === "string")
|
|
128
152
|
this.#joinTargets.add(loopId);
|
|
129
|
-
const logEntryId = await this.#writeLog({ statement, result, workerId, loopId, turnId, sequence, origin });
|
|
153
|
+
const logEntryId = await this.#writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin });
|
|
130
154
|
// §search-gate — register successful searches AFTER #writeLog stamps the runtime
|
|
131
155
|
// entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
|
|
132
156
|
if (statement.op === "EXEC" && result.status < 400) {
|
|
@@ -162,6 +186,7 @@ class Dispatcher {
|
|
|
162
186
|
// YOLO listener auto-resolves) BEFORE awaiting — they may
|
|
163
187
|
// resolve synchronously inside their handlers.
|
|
164
188
|
const target = this.#extractTarget(statement.target);
|
|
189
|
+
await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon} — compare in the same canon the rows store
|
|
165
190
|
const flags = await this.#loadLoopFlags(loopId); // the loop/proposal notification carries flags (yolo) — §dual-yolo-proposal-carries-flags
|
|
166
191
|
// #note10 — if the target diverged on disk this turn, the model's EDIT is based
|
|
167
192
|
// on a stale read; flag it so a YOLO auto-accept rejects instead of clobbering.
|
|
@@ -305,7 +330,8 @@ class Dispatcher {
|
|
|
305
330
|
return { status: 429, error: `Per-turn search limit reached (${verdict.cap}).` };
|
|
306
331
|
}
|
|
307
332
|
if (verdict.verdict === "duplicate") {
|
|
308
|
-
|
|
333
|
+
// {§stream-owner-scoped} — the prior survivor digest is the CALLER's own stream entry.
|
|
334
|
+
const prior = await EntryCrud.readEntry(verdict.priorPathname, ctx, runtime, ctx.workerId);
|
|
309
335
|
const raw = prior.entry?.channels["#results"]?.content ?? "";
|
|
310
336
|
let results = raw;
|
|
311
337
|
try {
|
|
@@ -413,6 +439,9 @@ class Dispatcher {
|
|
|
413
439
|
return { status: 400, error: `${statement.op} requires a worker name (worker://<name>)` };
|
|
414
440
|
if (name === "self")
|
|
415
441
|
return { status: 400, error: `'self' is the current run — ${statement.op} names a NEW run (worker://<name>)` };
|
|
442
|
+
// {§entry-owner} — 'commons'/'plurnk' are the reserved owner rows, '~' the self-sigil; no spawn takes them.
|
|
443
|
+
if (Owner.RESERVED.has(name))
|
|
444
|
+
return { status: 400, error: `'${name}' is a reserved worker name` };
|
|
416
445
|
if (ctx.injectWorker === undefined)
|
|
417
446
|
throw new Error("run control: injectWorker capability absent");
|
|
418
447
|
const denied = await WorkerCap.deny(this.#db, ctx.workspaceId);
|
|
@@ -516,7 +545,9 @@ class Dispatcher {
|
|
|
516
545
|
// addressed stream (sh:///l/t/s) routes here, not to deleteEntry. §exec
|
|
517
546
|
const killable = this.#schemes.get(schemeName);
|
|
518
547
|
if (killable !== undefined && typeof killable.kill === "function") {
|
|
519
|
-
|
|
548
|
+
// Pass the model's OWN scheme so a stream-KILL error answers in the runtime tag the
|
|
549
|
+
// model addressed (sh:///…), not the internal `exec` ({§fs-answer-in-canon}).
|
|
550
|
+
return await killable.kill(pathnameFromPath(path), statement.signal, ctx, schemeName);
|
|
520
551
|
}
|
|
521
552
|
if (schemeName === "worker") {
|
|
522
553
|
// Entry-path present → KILL a worker-scope scratch ENTRY (delete it), self-only —
|
|
@@ -525,16 +556,16 @@ class Dispatcher {
|
|
|
525
556
|
const entryPath = path.kind === "url" ? (path.pathname ?? "") : "";
|
|
526
557
|
if (entryPath !== "" && entryPath !== "/") {
|
|
527
558
|
const workerHandler = this.#schemes.get("worker");
|
|
528
|
-
return await workerHandler.
|
|
559
|
+
return await workerHandler.killEntry(statement, ctx);
|
|
529
560
|
}
|
|
530
561
|
// terminate — abort any worker by address; whoever holds it may end it.
|
|
531
562
|
// `worker://self` = self. cancelWorker (→ Daemon.cancelDrain) aborts the worker's signal
|
|
532
563
|
// (its loop closes 499); an idle run is a no-op-200, a missing run 404.
|
|
533
|
-
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §worker-scheme —
|
|
564
|
+
const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §worker-scheme — the worker is the AUTHORITY
|
|
534
565
|
if (name === "")
|
|
535
|
-
return { status: 400, error: "worker:// kill requires a worker name or
|
|
566
|
+
return { status: 400, error: "worker:// kill requires a worker name or ~ (worker://<name>)" };
|
|
536
567
|
let workerId = ctx.workerId;
|
|
537
|
-
if (name !== "
|
|
568
|
+
if (name !== "~") {
|
|
538
569
|
const row = await this.#db.worker_resolve_by_name.get({ workspace_id: ctx.workspaceId, name });
|
|
539
570
|
if (row === undefined)
|
|
540
571
|
return { status: 404, error: `worker://${name} not found in this workspace` };
|
|
@@ -597,7 +628,7 @@ class Dispatcher {
|
|
|
597
628
|
// status, exactly like a non-fanned READ. The model sees the empty/failed result, not silence.
|
|
598
629
|
if (found.status !== 200 || matches.length === 0) {
|
|
599
630
|
const result = { status: found.status === 200 ? 204 : found.status };
|
|
600
|
-
const id = await this.#writeLog({ statement, result, workerId, loopId, turnId, sequence, origin });
|
|
631
|
+
const id = await this.#writeLog({ statement, result, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence, origin });
|
|
601
632
|
onDispatch?.(id);
|
|
602
633
|
return { ...result, rowsWritten: 1 };
|
|
603
634
|
}
|
|
@@ -611,7 +642,7 @@ class Dispatcher {
|
|
|
611
642
|
// had FINDed then READ. On a degenerate single-line document the N delivery rows are
|
|
612
643
|
// identical whole-file lines; the summary row is what tells the model its query hit N
|
|
613
644
|
// times and WHERE (run30: two hits indistinguishable from failure; 17 retries, 508).
|
|
614
|
-
const findRowId = await this.#writeLog({ statement: { ...statement, op: "FIND" }, result: found, workerId, loopId, turnId, sequence, origin });
|
|
645
|
+
const findRowId = await this.#writeLog({ statement: { ...statement, op: "FIND" }, result: found, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence, origin });
|
|
615
646
|
onDispatch?.(findRowId);
|
|
616
647
|
const wholeByPath = new Map();
|
|
617
648
|
const fannedStatuses = [];
|
|
@@ -623,7 +654,7 @@ class Dispatcher {
|
|
|
623
654
|
wholeByPath.set(m.pathname, whole);
|
|
624
655
|
}
|
|
625
656
|
const result = _a.#sliceMatch(whole, m.span);
|
|
626
|
-
const id = await this.#writeLog({ statement: _a.#retargetRead(statement, m.pathname, m.span), result, workerId, loopId, turnId, sequence: sequence + written, origin });
|
|
657
|
+
const id = await this.#writeLog({ statement: _a.#retargetRead(statement, m.pathname, m.span), result, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence: sequence + written, origin });
|
|
627
658
|
onDispatch?.(id);
|
|
628
659
|
fannedStatuses.push(result.status);
|
|
629
660
|
written++;
|
|
@@ -677,6 +708,13 @@ class Dispatcher {
|
|
|
677
708
|
const dstSchemeName = schemeNameOf(dstPath);
|
|
678
709
|
if (srcSchemeName === null || dstSchemeName === null)
|
|
679
710
|
return { status: 400, error: "COPY/MOVE require URL paths with schemes" };
|
|
711
|
+
// {§worker-authority-carving} — the entry-copy seam is pathname-keyed; on worker:// it
|
|
712
|
+
// addresses the COMMONS. A space's content moves via READ + EDIT, not COPY.
|
|
713
|
+
for (const p of [srcPath, dstPath]) {
|
|
714
|
+
if (p.kind === "url" && p.scheme === "worker" && (p.hostname ?? "") !== "") {
|
|
715
|
+
return { status: 400, error: "COPY/MOVE address the commons (worker:///…); a space's content moves via READ + EDIT" };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
680
718
|
const srcHandler = this.#schemes.get(srcSchemeName);
|
|
681
719
|
const dstHandler = this.#schemes.get(dstSchemeName);
|
|
682
720
|
if (srcHandler === undefined || dstHandler === undefined)
|
|
@@ -943,8 +981,9 @@ class Dispatcher {
|
|
|
943
981
|
return true;
|
|
944
982
|
return !(statement.op === "SEND" && statement.target === null);
|
|
945
983
|
}
|
|
946
|
-
async #writeLog({ statement, result, workerId, loopId, turnId, sequence, origin, }) {
|
|
984
|
+
async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, }) {
|
|
947
985
|
const target = this.#extractTarget(statement.target);
|
|
986
|
+
await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon} — columns speak canon; tx below stays verbatim
|
|
948
987
|
const lineMarkerJson = "lineMarker" in statement && statement.lineMarker !== null
|
|
949
988
|
? JSON.stringify(statement.lineMarker)
|
|
950
989
|
: null;
|