@plurnk/plurnk-service 1.11.0 → 1.12.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/INSTALL.md +3 -3
- package/README.md +1 -1
- package/SPEC.md +90 -44
- package/dist/build-info.json +1 -1
- package/dist/content/edit-collision.d.ts +1 -1
- package/dist/content/edit-collision.d.ts.map +1 -1
- package/dist/content/edit-collision.js +2 -1
- package/dist/content/edit-collision.js.map +1 -1
- package/dist/content/edit-receipt.d.ts +2 -2
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +15 -6
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/line-anchors.d.ts.map +1 -1
- package/dist/content/line-anchors.js +13 -6
- package/dist/content/line-anchors.js.map +1 -1
- package/dist/content/line-marker.d.ts +0 -1
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js.map +1 -1
- package/dist/core/CoreSchemeServices.d.ts +1 -1
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +1 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +2 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +9 -5
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/EmbeddingCall.d.ts +23 -0
- package/dist/core/EmbeddingCall.d.ts.map +1 -0
- package/dist/core/EmbeddingCall.js +149 -0
- package/dist/core/EmbeddingCall.js.map +1 -0
- package/dist/core/Engine.js +2 -2
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +89 -31
- package/dist/core/InferenceCall.d.ts +18 -0
- package/dist/core/InferenceCall.d.ts.map +1 -0
- package/dist/core/InferenceCall.js +86 -0
- package/dist/core/InferenceCall.js.map +1 -0
- package/dist/core/ModelCall.d.ts +4 -13
- package/dist/core/ModelCall.d.ts.map +1 -1
- package/dist/core/ModelCall.js +10 -77
- package/dist/core/ModelCall.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +3 -1
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +1 -0
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +140 -17
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +1 -0
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +11 -2
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/Turn.sql +6 -3
- package/dist/core/TurnRunner.d.ts +0 -1
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +30 -12
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/packet-wire.d.ts +1 -0
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +27 -12
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +58 -16
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +35 -17
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +15 -2
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +10 -4
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +1 -1
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/_entry-chunk.d.ts.map +1 -1
- package/dist/schemes/_entry-chunk.js +5 -1
- package/dist/schemes/_entry-chunk.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +2 -5
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +12 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-semantic.d.ts +5 -3
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +20 -8
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_path-scope.d.ts.map +1 -1
- package/dist/schemes/_path-scope.js +6 -1
- package/dist/schemes/_path-scope.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +14 -2
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +1 -0
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/lifecycle-recovery.sql +14 -20
- package/dist/service.js +2 -2
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +260 -67
- package/package.json +31 -31
package/INSTALL.md
CHANGED
|
@@ -23,7 +23,7 @@ edit`, `config defaults`, and `config check` to inspect and maintain the one
|
|
|
23
23
|
environment cascade. A pre-XDG `~/.plurnk` is never read implicitly; stop the
|
|
24
24
|
old daemon and run `plurnk-service paths migrate` once.
|
|
25
25
|
|
|
26
|
-
The default service includes local
|
|
26
|
+
The default service embedding artifact includes its local model and the exact counters required by supported hosted profiles. An explicitly disabled or unavailable embedder makes `~query` fall back to FTS keyword ranking. Set `PLURNK_EMBEDDING_MODEL` to a standard provider/model route or declared model alias to use a hosted or operator-run embedding endpoint instead; provider endpoints and credentials use the same provider configuration as generation. A custom profile whose counter is not built in may add the optional `@plurnk/plurnk-mimetypes-tokenizers` catalog.
|
|
27
27
|
|
|
28
28
|
## The cascade (where a value actually comes from)
|
|
29
29
|
|
|
@@ -107,7 +107,7 @@ These are relationships *between* flags. Set them as a unit.
|
|
|
107
107
|
- **Local GPU (llama-server).** `PLURNK_MODEL_local="openai/<name>"`, `OPENAI_BASE_URL=http://127.0.0.1:<port>`, `PLURNK_MODEL=local`, `PLURNK_PROVIDERS_GBNF_local=plurnk.qwen.gbnf` for Qwen's template-prefilled `<think>` protocol (or `plurnk.gemma.gbnf` when Gemma generates a complete Harmony channel). The provider detects llama-server, verifies its GBNF transport, and derives input capacity from the detected window and configured output envelope. Add `PLURNK_PROVIDERS_LLAMA_SERVER_local=1` only when the endpoint cannot be fingerprinted reliably.
|
|
108
108
|
- **Cloud, bring-your-own-key.** `PLURNK_MODEL_cloud="openrouter/<model>"`, `OPENROUTER_API_KEY=…`, `PLURNK_MODEL=cloud`. No local GBNF or `LLAMA_SERVER` pin.
|
|
109
109
|
- **plurnk.ai endpoint.** `PLURNK_MODEL_plurnk="plurnk/plurnk"`, `PLURNK_API_KEY=…`, `PLURNK_MODEL=plurnk`.
|
|
110
|
-
- **Headless / CI / constrained container.** A CPU-only box should NOT disable semantic search — it should
|
|
110
|
+
- **Headless / CI / constrained container.** A CPU-only box should NOT disable semantic search — it should select a hosted embedder with `PLURNK_EMBEDDING_MODEL=<provider>/<model>` or an embedding alias. The route inherits the standard provider endpoint and credential contract; a local OpenAI-compatible GPU endpoint uses the same alias and `PLURNK_BASEURL_<alias>` convention as generation. Weak hardware is the target workload, not a reason to shed capability; `PLURNK_SERVICE_EMBED_DISABLE=1` exists for test lanes that deterministically assert non-semantic behavior, nothing else. Consider `PLURNK_SERVICE_MAX_TURNS=<n>` as a cost cap, `PLURNK_SERVICE_GIT_ALLOWED=0` to lock out git in a sandbox.
|
|
111
111
|
|
|
112
112
|
## Flag sections (breakdown of the service's `.env.defaults`)
|
|
113
113
|
|
|
@@ -128,7 +128,7 @@ Each mirrors a `# --- section ---` in the floor; consult the floor for exact def
|
|
|
128
128
|
`PLURNK_SERVICE_PROMPT_PROJECTION` assigns the automatic prompt-body share
|
|
129
129
|
of that provider-derived curation gauge.
|
|
130
130
|
- **Plugins** — bare `PLURNK_PLUGINS_TRUSTED_ONLY` (0/unset = load all installed; a value = `@plurnk/*` plus an allowlist).
|
|
131
|
-
- **Semantic search** — `PLURNK_SERVICE_SEMANTIC_CHUNK_TOKENS`/`_CHUNK_OVERLAP` (service-side chunking), `PLURNK_SERVICE_EMBED_DISABLE` (FTS-only), `
|
|
131
|
+
- **Semantic search** — `PLURNK_SERVICE_SEMANTIC_CHUNK_TOKENS`/`_CHUNK_OVERLAP` (service-side chunking), `PLURNK_SERVICE_EMBED_DISABLE` (FTS-only), `PLURNK_EMBEDDING_MODEL` (optional provider route/alias), and `PLURNK_EMBEDDING_WORKERS` (the local embedder pool — mimetypes-owned).
|
|
132
132
|
- **Schemes: http** — `PLURNK_SCHEMES_HTTP_FETCH_TIMEOUT`, `_TTL_MS`, optional `# PLURNK_SCHEMES_HTTP_MATERIALIZER=` (a materializer plugin id).
|
|
133
133
|
- **Execs** — `PLURNK_EXECS_<runtime>=0` disables a runtime; web discovery is an MCP attachment (`PLURNK_MCP_<server>`), not an executor.
|
|
134
134
|
- **Hooks** — `PLURNK_HOOKS_COMMAND` plus JSON `PLURNK_HOOKS_ARGS` invokes one exact no-shell command for the core events selected by `PLURNK_HOOKS_EVENTS`; see `@plurnk/plurnk-hooks/README.md`.
|
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ The daemon can emit OpenTelemetry traces and low-cardinality metrics through the
|
|
|
65
65
|
|
|
66
66
|
## Semantic search
|
|
67
67
|
|
|
68
|
-
The default service installation includes `@plurnk/plurnk-mimetypes-embeddings`, so `FIND`'s `~query` uses embedding cosine ranking without a separate package install. `PLURNK_SERVICE_EMBED_DISABLE=1` explicitly selects FTS keyword ranking; a missing required artifact is a broken installation and refuses startup. A
|
|
68
|
+
The default service installation includes `@plurnk/plurnk-mimetypes-embeddings`, whose local model and built-in hosted profiles carry their own exact counters, so `FIND`'s `~query` uses embedding cosine ranking without a separate package install. `PLURNK_SERVICE_EMBED_DISABLE=1` explicitly selects FTS keyword ranking; a missing required embedding artifact is a broken installation and refuses startup. A standard provider route can replace the included local model through the same provider configuration used for generation; custom profiles may add the optional general tokenizer catalog. See [`INSTALL.md`](./INSTALL.md).
|
|
69
69
|
|
|
70
70
|
## The file sandbox
|
|
71
71
|
|
package/SPEC.md
CHANGED
|
@@ -504,7 +504,7 @@ terminal history.**
|
|
|
504
504
|
| Shared worker entries (`worker:///...`) | Workspace commons | Shared live. |
|
|
505
505
|
| Membership overlay ({§machine-processes-one-overlay}) | Workspace | Shared unchanged; divergent membership requires another workspace. |
|
|
506
506
|
| Log items ({§machine-processes-fork-copies-the-log}) | Worker | Durable events, curation effects, tags, current active/folded projection, and the matching observation cursor are copied as terminal history. Parent-audience occurrences still pending at the fork boundary belong to the snapshot; later sibling activity does not. |
|
|
507
|
-
| §machine-processes-fork-cost **Provider evidence and accounting** | Worker | Turns and their model-facing log history are copied, but
|
|
507
|
+
| §machine-processes-fork-cost **Provider evidence and accounting** | Worker | Turns and their model-facing log history are copied, but turn-attached inference calls, their specializations, admission rows, and physical provider requests are not: one issued call or request has one causal branch. Parent and fork accounting therefore includes only work issued in that branch, while workspace accounting never double-counts copied history. |
|
|
508
508
|
| §machine-processes-entry-inheritance **Worker-owned entries** | Worker | The scheme's mandatory `{§manifest-entry-inheritance}` decides: `snapshot` copies only entries whose channels are all quiescent and remaps ownership; `rederive` copies no bytes and lets the child materializer rebuild them from inherited Functionality; `none` carries nothing. Within a `snapshot` scheme the Worker scheme's generated subtree is always rederived ({§worker-generated-subtree}). Parent and child then diverge. |
|
|
509
509
|
| Active loops, turns, and cancellation | Worker | Never copied as live work; inherited structure is terminal history, then a new loop starts. |
|
|
510
510
|
|
|
@@ -863,7 +863,7 @@ boundary.
|
|
|
863
863
|
- §worker-lifecycle-idle-is-concluded **An idle worker concludes; it does not park.** A loop is idle only when it has neither live obligations nor completed results awaiting their first packet. A live child or stream blocks a SEND signal `202` join; a completed stream, child result, or same-turn retrieval continues directly to the next packet where it is observed. Only after those sets are drained does signal `202` resolve like signal `200`. There is no held-open idle loop and no `loop/quiesced` soft signal. A concluded worker is durable working history and an addressed arrival reawakens it as a new loop.
|
|
864
864
|
- §worker-lifecycle-no-lost-loop **A loop is never stranded by a drain's exit.** A drain relinquishes its registry slot only after a lock-held re-claim confirms the queue is empty; a loop enqueued during that teardown is either re-claimed by the exiting drain or claimed by a fresh drain that a later inject starts. The relinquish and the start are serialized, so neither the lost-loop hang nor a transient double-drain can occur.
|
|
865
865
|
- §worker-lifecycle-durable-disposition **Durable disposition wins cancellation races.** At a turn boundary, the engine reads the loop's durable status before interpreting a process-local abort. A committed `202` park survives a later daemon-shutdown signal; only a loop still durably running at `102` can be terminalized by that cancellation.
|
|
866
|
-
- §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request
|
|
866
|
+
- §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request first settles as an error with absent usage and explicitly unknown cost; then its logical generation or embedding call closes, including a workspace-only embedding with no turn to invent. Recovery never fabricates zero evidence. Every durable proposed operation likewise lost its process-local resolution waiter and settles as a visible `500 owner_vanished` occurrence rather than an unresolvable interrupt ({§proposal-list}). A pending client interaction also lost its exact awaiting operation, so boot removes the orphan instead of replaying work or inventing a response ({§client-interactions}). Every durable-open subscription belonged to a vanished callable: active channels become errored and its row closes `500`. A `202` continuation survives only while a live child obligation remains; after reconciliation, an unblocked park requeues `202→100` and resumes in place. Child terminalization wakes its parked parent on every outcome, including provider exceptions, cancellation, and restart interruption, recursively through the durable parent edges. These operations are idempotent, so an interrupted recovery safely repeats.
|
|
867
867
|
|
|
868
868
|
---
|
|
869
869
|
|
|
@@ -881,6 +881,24 @@ Three current entry points:
|
|
|
881
881
|
|
|
882
882
|
§provider-surface-identity Provider capacity and identity are immutable for one instance. `contextWindow`, `maxInputTokens`, and `maxOutputTokens` carry known model limits; `outputBudget` is the total generation envelope, optional `reasoningBudget` is its strict subset, and `inputCapacity` is the stable intersection of known input constraints ({§tokenomics}). Unknown facts remain `null`. `model` identifies persisted turn/provider evidence. Local GBNF admission also consumes `constrainsOutput` ({§grammar-configuration-admission}).
|
|
883
883
|
|
|
884
|
+
§inference-ledger **Logical inference is provider-neutral and physical requests
|
|
885
|
+
have one ledger.** Every inference opens one `inference_calls` identity with a
|
|
886
|
+
mandatory workspace, optional real causal turn, ordered kind, request model,
|
|
887
|
+
and forward-only lifecycle. Its specialization owns only domain evidence:
|
|
888
|
+
|
|
889
|
+
| Kind | Specialization | Causal scope |
|
|
890
|
+
|---|---|---|
|
|
891
|
+
| `emission`, `bare` | `model_calls`: normalized response/failure and capacity | A model/inference turn is required; only emission has `turn_attempts` admission evidence. |
|
|
892
|
+
| `embedding_query`, `embedding_documents` | `embedding_calls`: input/output cardinality, artifact metadata, or failure | Workspace is required; a turn is attached only when the operation has one. |
|
|
893
|
+
|
|
894
|
+
Every physical request is an ordered `provider_requests` child opened before
|
|
895
|
+
I/O and settled once. Hosted generation and embeddings use that same observer;
|
|
896
|
+
local embeddings retain their logical call and create no fictitious physical
|
|
897
|
+
request. Turn-attached embeddings contribute to turn, loop, worker, and
|
|
898
|
+
workspace accounting; workspace-only embeddings contribute only to workspace
|
|
899
|
+
accounting. Neither is packet occupancy, so only an emission may supply the
|
|
900
|
+
latest context gauge.
|
|
901
|
+
|
|
884
902
|
§meta-passthrough **Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag. The service stores it unenforced per turn (`turns.meta`, `json_valid` only — no schema) and forwards the latest turn's blob in `loop/terminated.usage` ({§notifications}). The service never reads a field within it. Providers own their metadata shapes; monetary values carry an explicit amount and currency rather than an implied unit. Absent → `{}`. The mirror direction (client → provider, the self-identified `client` id) rides `generate({client})` ({§attribution}).
|
|
885
903
|
|
|
886
904
|
### §provider-guarantees Engine → provider guarantees
|
|
@@ -899,7 +917,7 @@ A completed provider exchange is an **emission attempt**, not necessarily an eng
|
|
|
899
917
|
|
|
900
918
|
§safe-uri-target-groups After source and authored-command admission, Core tolerates one target group on READ, FOLD, OPEN, or KILL only when splitting its raw target at top-level comma or whitespace separators produces at least two members and every member independently parses as an explicit `scheme://` URI. Request-metadata blocks are opaque to this split. Each member becomes one ordinary statement with an independent dispatch outcome and log row, in authored member order; scheduling may still move the complete operation class under {§op-mode-phases}. Otherwise the target remains exactly singular, including local filenames containing spaces or commas. The stored `turnOps` and authored command count remain unexpanded, and no other operation admits target groups.
|
|
901
919
|
|
|
902
|
-
Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `
|
|
920
|
+
Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `inference_calls` row with its `model_calls` specialization and emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the logical call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone extends `turns.packet` with response evidence; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `packetNNN.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
|
|
903
921
|
|
|
904
922
|
The first exhaustion in a consecutive sequence closes that unadmitted turn as a continue and opens exactly one ordinary recovery turn. Its packet projects the latest rejected response OPEN from a durably FOLDED emission-attempt item under {§rejected-emission-entry} and carries one transient `invalid_emission` Notice: `Response rejected before dispatch; no operations were performed.` followed by `Parser: <the latest attempt's first diagnostic>` with its `content-offset` position — the model sees why, at which line, against its own projected text. The Notice states only observed admission facts; it does not classify the response as unrecoverable, infer why generation ended, or prescribe intent beyond the parser-owned diagnostic. Attempt count and rail state never become model-facing. The recovery turn has its own honestly stored packet and its configured private same-packet attempts. The packet-local projection never changes the row's curation state, so no later packet repeats the malformed body unless the model explicitly OPENs it. Admission clears the recovery state; exhausting the informed turn terminates instead of opening another.
|
|
905
923
|
|
|
@@ -943,7 +961,7 @@ shared contract {§plugin-attribution}:
|
|
|
943
961
|
| Collection | Immediately before each emission attempt, Core pulls the admitted scheme, executor, loaded mimetype-handler, and selected provider sources. A BARE call pulls only its selected provider source because it admits no other plugin capability. |
|
|
944
962
|
| Composition | Core flattens, deduplicates, and sorts the tags. The resulting non-empty array rides `generate({ attributions })`; an empty set omits that provider field. |
|
|
945
963
|
| Meaning | Core neither verifies nor infers contribution. Tags are plugin-authored folksonomy for telemetry, optimization, attribution, or downstream rules. The `@plurnk/` reservation is the only namespace policy ({§plugin-attribution}). |
|
|
946
|
-
| Request evidence | The stored request packet carries the exact set most recently forwarded for that turn. Every
|
|
964
|
+
| Request evidence | The stored request packet carries the exact set most recently forwarded for that turn. Every generation-kind `inference_calls` row carries that call's exact set, including response-less failures. |
|
|
947
965
|
| Derived reporting | Turn, loop, digest, and client views project the recorded sets. `loop/terminated.attributions` is their deduplicated sorted union and remains separate from provider usage and charge evidence. |
|
|
948
966
|
|
|
949
967
|
Runtime hooks are synchronous and receive only the attempt coordinates. A hook
|
|
@@ -1194,6 +1212,8 @@ Registration precedes loop affinity:
|
|
|
1194
1212
|
- §op-synchronous **Decisive operations settle before the next scheduled operation.** The dispatcher `await`s every decisive operation. Work remains in flight only when the operation's contract deliberately creates concurrency: `FORK`, `WORK`, stream-producing `EXEC`, and a streaming `READ` after its scheme-specific acquisition boundary. Such a READ first establishes its durable subscription, returns `102`, and then retains only its `StreamSubscription`; a later scheduled operation may address that live owner. MODE changes scheduling, not completion semantics. This is why a same-turn KILL followed by SEND signal `200` concludes ({§send-premature-terminate}): KILL synchronously flips the worker's live loops terminal (`engine_terminate_worker_live_loops`) before the End phase judges the pending set, while the physical scope reap rides `cancelWorker` asynchronously and invisibly.
|
|
1195
1213
|
|
|
1196
1214
|
- §edit-batch **Same-resource EDITs are one mutation.** Every EDIT targeting the same canonical resource and channel in one turn applies to the resource's one pre-turn snapshot. The scheme validates the complete batch before writing, applies disjoint replacements from the highest original coordinate downward, and commits one resulting revision atomically; reversing the statements cannot change that revision. A failing statement rejects that resource batch without a partial write; independent resource batches remain independent. When validation identifies one malformed statement, that row receives its exact 4xx Problem while every otherwise-valid sibling receives 424 Failed Dependency without borrowing the malformed statement's coordinates. Whole-resource replacement or creation cannot coexist with another EDIT in the same batch, selected regions may not overlap, and a zero-length insertion may occur at most once at each boundary. Prepend (`<0>`), append (`<-1>`), and exact equal-endpoint insertions compose with non-overlapping replacements. Proposal-gated schemes expose one proposal for the resource batch and accept all or none. The public scheme contract is batch-shaped: a scheme must never emulate this guarantee by applying individual EDITs sequentially.
|
|
1215
|
+
- §edit-batch-receipt **A refused batch names everything wrong with it.** Validation of a resource batch resolves every line anchor and checks every region pair before any verdict, so one receipt carries the complete correction. A `line-anchor-collision` lists every anchor that no longer resolves in `staleAnchors` (anchor, kind, and the matching lines when ambiguous). An `overlapping-edits` refusal lists every conflicting pair with its relation (same insertion point, duplicate, one contains the other, overlap) in `conflicts`, the regions that conflict with nothing in `cleanRegions`, and keeps the first pair in `conflictingRegions`. Both carry `editCount` and `applied: 0`, and their recovery prose states those counts, so the model never learns a batch's defects one resubmission at a time and never has to guess whether anything was written. Every row of the refused batch carries the same receipt.
|
|
1216
|
+
- §edit-batch-merges **Four resolutions are certain enough to apply; everything else stays a refusal, and every resolution is reported on its row.** Before the conflict check, core and the Slicer resolve exactly these shapes: (1) an identical twin (same region, same body up to trailing whitespace and trailing newlines — `whitespaceOnly: true` when they differed that way) is applied once and the twin's row carries `merged: {rule: "duplicate-of", of}` with no effect of its own; (2) two insertions at one point land in authored order (`same-insertion-point`); (3) two whole-line regions meeting on exactly one shared line, where that line's text appears verbatim in exactly one of the two bodies, give the line to that body and shrink the other by one (`shared-endpoint`, with the line, its text, the authored and applied coordinates, and `claimedBy`) — the inclusive `<SL,EL>` read as half-open, the commonest overlap on the 2026-08-29 runs; (4) a body whose every non-empty line carries this resource's own rendered `@xxxxx L:` prefix, hash-verified at its ordinal against the current anchors, is the READ rendering pasted back: the prefixes are stripped (`rendered-prefix-stripped`), and a look-alike that does not verify is written as authored with `rendered-prefix-unverified` reported — the hash is the proof, so intentional text of that shape is never altered. (5) one region inside another resolves when the inner region's original lines occur exactly once in the outer body: the inner change is relocated there (`contained-relocated`, with the outer index, the line within the outer body, and the authored coordinates); (6) an outer body that already carries the inner body makes the inner redundant (`contained-already-applied`). A shared endpoint that no body reproduces, and a containment whose inner lines occur zero or several times in the outer body, remain 409 with the line (and its text) or the containing region named. Each applied resolution is also a notice. Receipts are built from the edits as applied; a dropped twin's row carries its merge fact instead of an effect.
|
|
1197
1217
|
|
|
1198
1218
|
### §orchestration Cross-scheme orchestration
|
|
1199
1219
|
|
|
@@ -1326,7 +1346,7 @@ consume these public methods:
|
|
|
1326
1346
|
| `detect`, `process` | Resolve mimetypes, extents, readable content, symbols, and references. |
|
|
1327
1347
|
| `projectionIdentity` | Identify installed reader behavior for derived entries and search artifacts that consume symbols and references. |
|
|
1328
1348
|
| `query` | Execute glob/regex/JSONPath/XPath through `@plurnk/plurnk-schemes/Matcher`, which maps typed outcomes to operation results. |
|
|
1329
|
-
| `embedderInfo`, `
|
|
1349
|
+
| `embedderInfo`, `embedDocuments`, `tokenizer` | Plan and derive semantic-search chunks without reaching into artifact packages. |
|
|
1330
1350
|
|
|
1331
1351
|
`@plurnk/plurnk-contracts` owns model-facing matcher syntax; parsed content
|
|
1332
1352
|
dialects pass to `Mimetypes.query` without reclassification. Mimetype handlers
|
|
@@ -1377,12 +1397,11 @@ internal contract failure, never a reason to substitute the pure heuristic.
|
|
|
1377
1397
|
| READ/EDIT and COPY/MOVE scope | Admit text regions or return 415. |
|
|
1378
1398
|
| Search derivation | Build graph/FTS/vector artifacts or mark nonsemantic. |
|
|
1379
1399
|
|
|
1380
|
-
The default service installation includes its structured, document,
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
discovery ({§mimetype-discovery}).
|
|
1400
|
+
The default service installation includes its structured, document, and
|
|
1401
|
+
embedding leaves through the service manifest. Exact tokenizer vocabularies,
|
|
1402
|
+
tree-sitter grammar WASM leaves, and third-party handlers remain independently
|
|
1403
|
+
installable and resolve from the same consumer-visible package graph under
|
|
1404
|
+
trust-gated discovery ({§mimetype-discovery}).
|
|
1386
1405
|
|
|
1387
1406
|
**Token accounting.** The daemon injects no tokenizer into `Mimetypes`; content
|
|
1388
1407
|
projection is independent of packet budgeting. Core uses the stable
|
|
@@ -1431,7 +1450,7 @@ flowchart LR
|
|
|
1431
1450
|
The graph projection stores only addressable symbol names. A structured-data handler may legitimately emit an empty key into its symbols channel, but the `&graph` matcher cannot name an empty symbol; that one definition is omitted from graph storage without suppressing FTS, vectors, or the remaining definitions. Invalid references and other persistence violations still fail the resource derivation explicitly.
|
|
1432
1451
|
|
|
1433
1452
|
The pass tiles the exact readable text into token-budgeted fragment strings and
|
|
1434
|
-
sends every tile for one resource through one ordered `mimetypes.
|
|
1453
|
+
sends every tile for one resource through one ordered `mimetypes.embedDocuments`
|
|
1435
1454
|
call; it never re-runs a format handler against partial fragments. Workspace
|
|
1436
1455
|
warms coalesce; a request arriving during a pass forces one final rescan.
|
|
1437
1456
|
Progress exposes `preparing`, `indexing`, `complete`, or `failed`. Producer
|
|
@@ -1520,12 +1539,17 @@ Per-op semantics. AST shapes come from `@plurnk/plurnk-contracts`'s `PlurnkState
|
|
|
1520
1539
|
A scheme declaring `lineAnchors: true`, or `textEditScopes: true` with model
|
|
1521
1540
|
write authority, publishes the contracts-owned {§text-line-anchor-syntax}.
|
|
1522
1541
|
Model-writable `textEditScopes` implies anchors; `lineAnchors` alone makes no EDIT claim. For canonical model-facing
|
|
1523
|
-
resource identity `R`,
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1542
|
+
resource identity `R`, configured non-negative neighbor count `C`, ordered
|
|
1543
|
+
content array `W` containing that line and up to `C` complete lines on either
|
|
1544
|
+
side (all excluding separators), and the line's offset `O` within `W`
|
|
1545
|
+
(`min(L-1, C)` for one-based ordinal `L`), core hashes the JSON tuple
|
|
1546
|
+
`["plurnk-line-anchor-v2",R,C,O,W]` with SHA-256, interprets the digest as a
|
|
1547
|
+
big-endian integer modulo `62^5`, and encodes five fixed-width characters with
|
|
1548
|
+
alphabet `0-9A-Za-z`. The ordinal itself is not hashed (#428): a line keeps its
|
|
1549
|
+
anchor wherever it moves while its content and neighborhood are unchanged, so
|
|
1550
|
+
edits above a line — the model's own earlier edits included — never stale the
|
|
1551
|
+
anchors below them; identical neighborhoods share one anchor and resolve as
|
|
1552
|
+
ambiguous with the matching lines, never as a silent landing on a twin. The universal READ projector derives
|
|
1529
1553
|
anchors from the complete canonical selected channel before applying the
|
|
1530
1554
|
authored text slice; its durable result retains the canonical derivation
|
|
1531
1555
|
identity and anchors aligned with returned lines. Packet rendering right-aligns
|
|
@@ -1658,7 +1682,9 @@ The packet projects one actionable owner for each retrieval fact:
|
|
|
1658
1682
|
| exact matcher FIND | compact `matchLocation` range | each row's locator/region; a regex or glob row also carries `matched`, the matched text | none |
|
|
1659
1683
|
|
|
1660
1684
|
The compact range is `{ unit, total, requested: [first,last], returned?:
|
|
1661
|
-
[first,last] }` ({§range-extent}); empty results omit `returned`.
|
|
1685
|
+
[first,last] }` ({§range-extent}); empty results omit `returned`. An empty result
|
|
1686
|
+
set satisfies any well-formed page: zero matches is the answer, a 200 with no items,
|
|
1687
|
+
never a 416 (#425 F9). Transparent
|
|
1662
1688
|
coordinates let the model determine whether more material exists and choose
|
|
1663
1689
|
its own next request, so packet metadata never prescribes `next`, `complete`,
|
|
1664
1690
|
or `all`. FIND range cardinality replaces top-level `items`, `lines`, and
|
|
@@ -1680,9 +1706,9 @@ ordinary bounded bodies expose their displayed and complete chunk extents there.
|
|
|
1680
1706
|
- §log-curation-folder-idiom **Log curation speaks the folder idiom; a zero-match sweep is a no-op success** — OPEN/FOLD/KILL take a concrete coordinate or a path-glob, and a **trailing slash or a partial coordinate means "the contents"** ({§log-coordinate-hierarchy}), like a folder-scoped FIND: `## FOLD0 (log:///1/2)` folds turn 1/2's rows. OPEN and FOLD may instead take only a tag filter ({§log-item-tags}). A **well-formed selection that matches nothing is 204 with `matched: 0`**; a successful sweep's rx carries `matched: N`. A targetless operation without tags or a matcher is 400.
|
|
1681
1707
|
- §log-curation-set-selection **Row selection and body scope are independent** — target/glob, optional body matcher, and optional ALL-tags filter compose by intersection into the affected row set. An optional `<L>` or `<SL,EL>` then intersects each selected canonical body; it never paginates or changes the selected set. Thus `## FOLD0 (log:///**/READ) <17,-1>` may change long READs and no-op on short ones while reporting every selected row in `matched`.
|
|
1682
1708
|
|
|
1683
|
-
§fold-open-meta-operations **OPEN and FOLD are meta-operations — log-curation directives, not world actions.** They change log visibility and classifications, never the underlying resources. A **successful** OPEN/FOLD **is recorded in the log**
|
|
1709
|
+
§fold-open-meta-operations **OPEN and FOLD are meta-operations — log-curation directives, not world actions.** They change log visibility and classifications, never the underlying resources. A **successful** OPEN/FOLD **is recorded in the log** and **renders exactly once** — in the packet after its turn, as its path, target, and status — then dissolves from the projection ({§curation-receipt-dissolves}): the actor sees its `200` or `204` at the one moment it decides whether to conclude or repeat, the row exists for forensics (a curation act with NO trace is how a weak model folding its own task frame stayed invisible until a database dig, and how a model that deferred its confirmation re-issued a KILL into the turn ceiling), and the log never accumulates housekeeping — a permanent receipt row would be a crumb that itself needs sweeping. Its exact selected target set, each target's active/folded state before and after, and the classifications actually added and removed persist with that event; `matched: N` and the authored selector are not the database's sole effect evidence. The operation row, projection changes, tag changes, and landed effects commit in one database statement with each before state as a collision guard. The authored program also survives verbatim in its `turnOps` item. A **failed** OPEN/FOLD (bad target, matcher, or tag signal) renders normally with its status — errors are signals. The idle-turn gate reads the *emitted statements*, so a pure-curation turn is work, never idleness.
|
|
1684
1710
|
|
|
1685
|
-
§
|
|
1711
|
+
§curation-receipt-dissolves **Successful log-curation receipts dissolve.** A model-authored OPEN, FOLD, or KILL of a log item renders in exactly the packet immediately after its turn — path, target, and status, no body — and leaves the active projection once a later model turn has rows; history keeps the row, the exact active/folded transition for every target, and the authored `turnOps`. Nothing is left to curate: a receipt that dissolves is not a log item to sweep. A KILL of a log item retires the selected rows from the worker's active projection under {§log-history-projection}; it does not delete their execution history. The dissolving is **scoped to log targets**: a `KILL` of a `worker://` note, an `sh://` stream, or another stored artifact retains its scheme-owned world or process semantics and stays visible. A killed exact coordinate resolves 404 in ordinary log operations; a well-formed broad selection with no active matches remains the 204 no-op of {§log-curation-folder-idiom}, and that 204 renders once like any dissolving receipt. Failed OPEN/FOLD/KILL render like every operation error and persist.
|
|
1686
1712
|
|
|
1687
1713
|
### §log-sensitive-request-evidence Durable request evidence
|
|
1688
1714
|
|
|
@@ -1761,7 +1787,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
1761
1787
|
|
|
1762
1788
|
AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal: tags | null, lineMarker? }`.
|
|
1763
1789
|
|
|
1764
|
-
- §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does. Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
|
|
1790
|
+
- §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does — in every spelling: a `**` glued to a name (`**.go`, `src/**.ts`) is matched as `**/*.go` / `src/**/*.ts`, never demoted to a one-level `*` the way a native matcher reads it (run67, 2026-08-29: a whole-repository search silently confined to the root). Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
|
|
1765
1791
|
- An exact target resolves to the same canonical `(scheme, authority, pathname)` identity
|
|
1766
1792
|
as READ, entry CRUD, and any preceding `prepareFind()`. URI authorities are
|
|
1767
1793
|
identity-bearing: `https://example.com/page` queries
|
|
@@ -1865,6 +1891,12 @@ the loop continue; repeated offenses terminate through the engine's 500.
|
|
|
1865
1891
|
| Idle turn | An engine-rail error row with the corrective disposition | One strike |
|
|
1866
1892
|
| Refused disposition | The final SEND's 409 row with its exact Problem Detail | One strike |
|
|
1867
1893
|
|
|
1894
|
+
Executor results are evidence, never strikes: a command's nonzero exit — surfaced
|
|
1895
|
+
as its completion READ ({§exec-stream}) or read by the model from the stream —
|
|
1896
|
+
carries an `executor/*` problem identity and does not enter the streak. Structural
|
|
1897
|
+
violations (a missing PLAN or terminal SEND, an operation dropped by a parse
|
|
1898
|
+
failure) do strike: six in a row is a degenerated run.
|
|
1899
|
+
|
|
1868
1900
|
- §send-target-recipient **A SEND target is a recipient.** A model's directed SEND
|
|
1869
1901
|
addresses a worker (`## SEND0 (worker://<name>)`), an outbound agent (`a2a://`),
|
|
1870
1902
|
or a scheme that implements SEND (an `https://` POST); with `[410]` it names a
|
|
@@ -1874,7 +1906,7 @@ the loop continue; repeated offenses terminate through the engine's 500.
|
|
|
1874
1906
|
neutral recovery distinguishes targetless replies from directed SEND without
|
|
1875
1907
|
guessing which one was intended. A scheme that does not implement SEND
|
|
1876
1908
|
answers its ordinary factual 501 without grafting a guessed recovery onto it.
|
|
1877
|
-
- §send-idle-turn **Idle turn** — a continuing turn (102) whose ops are only PLAN/SEND — no work op. The model continued with nothing to do. The steer, verbatim: *"If your work is done, conclude with `## SEND0 [200]`. If you're waiting on a child or stream you spawned, use `## SEND0 [202]` to block on it — a 202 with nothing to wait on simply concludes."* A successful same-turn FOLD is the exception: its `202` continues without a strike so the curated packet can support the next reasoning turn.
|
|
1909
|
+
- §send-idle-turn **Idle turn** — a continuing turn (102) whose ops are only PLAN/SEND — no work op. The model continued with nothing to do. The steer, verbatim: *"If your work is done, conclude with `## SEND0 [200]`. If you're waiting on a child or stream you spawned, use `## SEND0 [202]` to block on it — a 202 with nothing to wait on simply concludes."* A successful same-turn FOLD is the exception: its `202` continues without a strike so the curated packet can support the next reasoning turn. **An empty `[102]` while the worker holds a live stream or child is a mis-spelled wait, not idleness**: the engine parks the turn as `[202]` — the same live-work predicate the `[200]` gate uses, so the shift never disagrees with the orientation the model reads — records the SEND as `202` with the correction in that row's annotation (a park drops transient notices; the row survives the wake); no strike. With nothing in flight the idle-turn 409 stands — it is the deterministic recovery for that case.
|
|
1878
1910
|
- §send-premature-terminate **Premature terminate — the pending set.**
|
|
1879
1911
|
A model's completion claim is gated by one rule: *nothing pending may be silently
|
|
1880
1912
|
discarded*. Pending work has two states: **live obligations** (open
|
|
@@ -1922,8 +1954,15 @@ script; anything else is refused `400 target-not-found`, naming that directory
|
|
|
1922
1954
|
and giving the applicable accepted form without inferring what the model meant.
|
|
1923
1955
|
When the target is a registered tool of another runtime, recovery gives that
|
|
1924
1956
|
tool's exact runtime-qualified invocation; otherwise it distinguishes an existing
|
|
1925
|
-
directory/script target from a targetless shell-command body.
|
|
1926
|
-
|
|
1957
|
+
directory/script target from a targetless shell-command body. A non-file resource
|
|
1958
|
+
target that cannot be read keeps the owning READ's failure identity (#163) and states
|
|
1959
|
+
the slot contract in its recovery — the resource is the program and the body its stdin;
|
|
1960
|
+
a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
|
|
1961
|
+
names the working directory only when it is not the project root, and then in the
|
|
1962
|
+
model's own project-relative form ({§fs-namespace}: the root is the model's `/`, so it
|
|
1963
|
+
is never rendered, and no receipt or Problem carries a host-absolute path — the
|
|
1964
|
+
batch of 2026-08-29 showed the absolute `cwd` copied back into the target slot as
|
|
1965
|
+
`(cwd: /host/path)`). The EXEC `(path)` is one of cwd, script, or tool
|
|
1927
1966
|
name — the runtime's declaration decides which (interpreters: cwd or script;
|
|
1928
1967
|
tool families: tool name) — and a command is never a target. The default shell
|
|
1929
1968
|
is taught as targetless bare `EXEC`; `[sh]` remains the explicit form, and an
|
|
@@ -2084,7 +2123,7 @@ two states and no others:
|
|
|
2084
2123
|
| state | what the model receives |
|
|
2085
2124
|
|---|---|
|
|
2086
2125
|
| active | nothing in the Log. The `## Child Streams` pointer names the stream with each channel's size and its growth since the last packet ({§child-orientation}); the model READs any range it wants. |
|
|
2087
|
-
| terminal | ONE `origin=_plurnk` READ at `<runtime>:///<coord>#<channel>`, born OPEN, that is exactly a markerless READ of the channel — its first page ({§read-selection-projection}: lines 1–16, the whole channel when it fits, the channel's own mimetype), the `range` extent, terminal status and Problem, `terminal: true`, any producer-supplied integer `exitCode`, and `source: log:///<coord>/EXEC` linking the causal invocation. |
|
|
2126
|
+
| terminal | ONE `origin=_plurnk` READ at `<runtime>:///<coord>#<channel>`, born OPEN, that is exactly a markerless READ of the channel — its first page ({§read-selection-projection}: lines 1–16, the whole channel when it fits, the channel's own mimetype), the `range` extent, terminal status and Problem, `terminal: true`, any producer-supplied integer `exitCode`, and `source: log:///<coord>/EXEC` linking the causal invocation. The packet renders that address under `stream`, exactly as the invocation row links its output, never under `target`: a stream is observed, not a slot to author. |
|
|
2088
2127
|
|
|
2089
2128
|
§exec-concurrency **Bounded admission per workspace (#389).** At most
|
|
2090
2129
|
`PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (shipped `12`;
|
|
@@ -2358,15 +2397,15 @@ freshness remains the owning family's concern.
|
|
|
2358
2397
|
| Schemes | `@plurnk/plurnk-schemes` | `@plurnk/plurnk-schemes-http` |
|
|
2359
2398
|
| Mimetypes | `@plurnk/plurnk-mimetypes` | `application-ipynb`, `application-json`, `application-jsonl`, and `application-xml` format leaves. |
|
|
2360
2399
|
| | | `text-csv`, `text-diff`, `text-dotenv`, `text-html`, `text-ini`, `text-markdown`, and `text-plain` format leaves. |
|
|
2361
|
-
| | | Fixed `embeddings` artifact. All names use the `@plurnk/plurnk-mimetypes-*` prefix.
|
|
2400
|
+
| | | Fixed `embeddings` artifact, including exact counters for its built-in profiles. All names use the `@plurnk/plurnk-mimetypes-*` prefix. |
|
|
2362
2401
|
| Executors | `@plurnk/plurnk-execs` | `common`, `jq`, and `sqlite` leaves under the `@plurnk/plurnk-execs-*` prefix. |
|
|
2363
2402
|
|
|
2364
|
-
The independently published `application-pdf` handler and `tokenizers`
|
|
2403
|
+
The independently published `application-pdf` handler and general `tokenizers`
|
|
2365
2404
|
artifact are opt-in leaves. Installing either beside the service admits it
|
|
2366
|
-
through ordinary package
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2405
|
+
through ordinary package resolution without changing the service manifest.
|
|
2406
|
+
The service-owned embedding artifact owns exact counters for its built-in local
|
|
2407
|
+
and hosted profiles; a custom profile may resolve another vocabulary through
|
|
2408
|
+
the optional general artifact.
|
|
2370
2409
|
|
|
2371
2410
|
**Providers:** `@plurnk/plurnk-providers` resolves the Models.dev catalog,
|
|
2372
2411
|
operator declarations, local adapters, and finally installed AI SDK provider
|
|
@@ -2513,7 +2552,7 @@ instead of a user's boot, and a dead knob cannot ship.
|
|
|
2513
2552
|
|
|
2514
2553
|
| Owner | Configuration |
|
|
2515
2554
|
|---|---|
|
|
2516
|
-
| `.env.test` | Safe default model selection and universal real-model gate posture; no alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
2555
|
+
| `.env.test` | Safe default model selection and universal real-model gate posture, including the bundled embedder (an ambient operator embedding route never becomes a gate dependency); no alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
2517
2556
|
| Live/demo scripts | The repository policy path and runner topology. |
|
|
2518
2557
|
| Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
|
|
2519
2558
|
| Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
|
|
@@ -3249,7 +3288,7 @@ time of measurement.
|
|
|
3249
3288
|
- §tokenomics-neutral-telemetry **Curation telemetry is state, not response allowance.** The model-facing `Context Token Budget` section begins with exactly two fields on separate lines: `tokensActiveTotal: N (P%)` and `tokensActiveMax: M`. It never presents their difference as free response tokens. The protocol definition directly requires FOLD, KILL, or trimming of irrelevant log items to keep the next packet within the maximum. Per-entry weights remain on log rows where they describe OPEN cost and FOLD savings. Generic packet composition and physical-token speculation are absent.
|
|
3250
3289
|
- §tokenomics-pressure-inventory **Pressure identifies its reclaimable concentration.** When the ordinary two-field packet measurement reaches 80% of `tokensActiveMax`, the budget section may append `YOU MUST FOLD, KILL, or trim superseded, stale, or irrelevant log content.` followed by `Largest Log Items`: at most five currently OPEN, addressed log bodies, ordered by `tokensActive` descending and then `log:///` path. Each item repeats only that row's `tokensBody` and `tokensActive`. Folded and bodyless rows cannot enter the list because FOLD would reclaim no body from them. The largest prefix that fits may be shown; this conditional block never pushes an otherwise admissible packet over its maximum. Its own weight participates in the final fixed-point `tokensActiveTotal`.
|
|
3251
3290
|
- §tokenomics-content-hash-identity **Content identity, not per-tokenizer counts.** Static channel writes stamp `content_hash` (SHA-256) as stable content identity. `weight` is stored beside that content and is never keyed or recomputed by model.
|
|
3252
|
-
- §tokenomics-provider-usage **Provider accounting is physical-request evidence, not curation state.** Every issued physical request has one durable pre-I/O `provider_requests` identity and settles once as response or error. Each record preserves conventional {§provider-usage} quantities and required {§provider-cost} evidence; an unreported quantity remains absent, including on response-less failures, and is never replaced by zero. `model_calls` own
|
|
3291
|
+
- §tokenomics-provider-usage **Provider accounting is physical-request evidence, not curation state.** Every issued physical request has one durable pre-I/O `provider_requests` identity beneath the normalized {§inference-ledger} and settles once as response or error. Each record preserves conventional {§provider-usage} quantities and required {§provider-cost} evidence; an unreported quantity remains absent, including on response-less failures, and is never replaced by zero. `model_calls` and `embedding_calls` own domain response/failure evidence, `turn_attempts` specialize emission admission, and `provider_requests` are the sole durable accounting representation. Emissions, BARE calls, embeddings, rejected responses, retries, failovers, and errors therefore remain cardinal and ordered. Turn, loop, worker, workspace, digest, and protocol accounting are derived from those records through the shared {§provider-accounting} projection; only emission calls contribute the latest-packet context gauge. The baseline stores no floating-point money, denormalized totals, or rollup triggers. A documented direct charge becomes `charged`; otherwise the provider may compute an exact-decimal USD `estimated` amount from complete usage and the exact model's Models.dev rates; insufficient evidence becomes `unknown`. Derived `costUsd` sums every USD-expressible request and is `null` only when no request is expressible; a response-less failure or an uncataloged model is skipped, never allowed to erase the expressible evidence. Each derived aggregate usage field independently sums its reported quantity, so heterogeneous detail coverage remains partial rather than becoming fictitiously complete. This is operational request accounting, not invoice reconciliation. Output and reasoning are quantities the model cannot FOLD, so they never alter the model-facing Budget ledger.
|
|
3253
3292
|
- §tokenomics-negative-pressure **Negative curation pressure is honest but never submitted.** The provisional readout may report `tokensActiveTotal` and its percentage above `tokensActiveMax`. Crossing the maximum diverts that would-be model turn into {§overflow-turn}; no over-ceiling packet reaches `provider.generate`. Automatic recovery does not create a strike or consume a model-turn allowance.
|
|
3254
3293
|
|
|
3255
3294
|
### §membership Workspace identity, membership, disk co-location
|
|
@@ -3377,7 +3416,7 @@ reconsidered when the operator changes the policy and otherwise remains a stat-o
|
|
|
3377
3416
|
no-op. The file write gate independently stats the source against the same ceiling,
|
|
3378
3417
|
so safety does not depend on a background warm winning a client-operation race.
|
|
3379
3418
|
|
|
3380
|
-
§derivation-dedup-parallel **The index dedups then parallelizes.** The derivation identity hashes the exact READ channel representation, mimetype, reader behavior, embedding configuration, and applicable search exclusion. A channel or log projection attaches the immutable artifact only after it is complete; identical projections therefore share one FTS row, one symbol graph, and one vector set without copying. Distinct artifacts run with bounded producer concurrency (`PLURNK_SERVICE_DERIVE_CONCURRENCY`). Pending artifacts sort by readable content length before entering that pool, so small resources start first while every outlier still derives fully. Unset uses a host-relative square-root fan-out; a positive integer is an exact operator budget and `-1` claims every core. Token-count and embedding batches retain only a pool-sized promise window; graph persistence writes at most `PLURNK_SERVICE_DERIVE_STORE_BATCH` definitions or references per SQLite statement. Every representation completed by a successful pass attaches a terminal classified artifact, identically at concurrency 1 and N. A changed pass emits one immediate `preparing` state, intermediate `indexing` heartbeats at `PLURNK_SERVICE_DERIVE_PROGRESS_HEARTBEAT_MS`, and one immediate `complete` or `failed` state. A no-op pass emits no lifecycle, an indexing heartbeat never claims 100%, and the model-facing Notice buffer retains only the current derivation state while live clients observe each heartbeat.
|
|
3419
|
+
§derivation-dedup-parallel **The index dedups then parallelizes.** The derivation identity hashes the exact READ channel representation, mimetype, reader behavior, embedding configuration, and applicable search exclusion. A channel or log projection attaches the immutable artifact only after it is complete; identical projections therefore share one FTS row, one symbol graph, and one vector set without copying. Distinct artifacts run with bounded producer concurrency (`PLURNK_SERVICE_DERIVE_CONCURRENCY`). Pending artifacts sort by readable content length before entering that pool, so small resources start first while every outlier still derives fully. Unset uses a host-relative square-root fan-out; a positive integer is an exact operator budget and `-1` claims every core. Token-count and embedding batches retain only a pool-sized promise window; graph persistence writes at most `PLURNK_SERVICE_DERIVE_STORE_BATCH` definitions or references per SQLite statement. Every launched worker settles before the maintenance pass reports success or failure, so one failed artifact cannot orphan sibling inference or its accounting. Every representation completed by a successful pass attaches a terminal classified artifact, identically at concurrency 1 and N. A changed pass emits one immediate `preparing` state, intermediate `indexing` heartbeats at `PLURNK_SERVICE_DERIVE_PROGRESS_HEARTBEAT_MS`, and one immediate `complete` or `failed` state. A no-op pass emits no lifecycle, an indexing heartbeat never claims 100%, and the model-facing Notice buffer retains only the current derivation state while live clients observe each heartbeat.
|
|
3381
3420
|
|
|
3382
3421
|
The artifact also retains a positive `{§mimetype-parse-issues}` count and the
|
|
3383
3422
|
full normalized `{§mimetype-summary}` when the exact parsed channel reported
|
|
@@ -3398,7 +3437,7 @@ every non-vector attachment with its disposition and reason. Successful
|
|
|
3398
3437
|
optional projection degradations continue indexing and surface their framework
|
|
3399
3438
|
Notice once per identical observation in a maintenance pass.
|
|
3400
3439
|
|
|
3401
|
-
§semantic-embed-dedup **Identical content embeds once.** The metaproject's repeated `tokenizer.json` channels - and any log result exposing the same exact readable text - attach one content-addressed derivation artifact. Graph, FTS, and chunk vectors exist once; addresses join through the artifact hash. One pass-wide semantic plan binds the selected chunk counter to this identity: the embedder's own counter is covered by model-space identity, while a separately resolved fallback counter contributes its `tokenizerId` and exactness. Model, vocabulary, vector-wire encoding ({§mimetype-embedding-wire}), or configuration changes therefore produce a different identity, so incompatible vector spaces, encodings, or chunk boundaries never share.
|
|
3440
|
+
§semantic-embed-dedup **Identical content embeds once.** The metaproject's repeated `tokenizer.json` channels - and any log result exposing the same exact readable text - attach one content-addressed derivation artifact. Graph, FTS, and chunk vectors exist once; addresses join through the artifact hash. One pass-wide semantic plan binds the selected chunk counter and deterministic tiling revision to this identity: the embedder's own counter is covered by model-space identity, while a separately resolved fallback counter contributes its `tokenizerId` and exactness. Model, vocabulary, tiling behavior, vector-wire encoding ({§mimetype-embedding-wire}), or configuration changes therefore produce a different identity, so incompatible vector spaces, encodings, or chunk boundaries never share.
|
|
3402
3441
|
|
|
3403
3442
|
Lossless chunk admission requires either the embedder's own counter or an exact fallback tokenizer. An empirical estimate never proves that content fits the declared token window. When pending readable content would require vectors and only an estimate is available, maintenance surfaces its degradation Notice and fails before embedding or attaching a derivation; no/disabled embedding and the established empty, binary, excluded, and maximum-size dispositions remain non-vector outcomes.
|
|
3404
3443
|
|
|
@@ -3418,9 +3457,9 @@ The CAS is the **hard backstop**, at the moment of writing, on every accept path
|
|
|
3418
3457
|
|
|
3419
3458
|
**Rationale.** Workspace is the right scope unit and the containing Git repository is its ordinary development boundary. Membership curation is tiered: Git bounds it by tracking, the client supersedes by overlay, and the model curates its render by READ/FOLD. Supporting several independent repositories as one world would require Plurnk-owned topology, synchronization, and model teaching that Git already solves cleanly by treating them as separate workspaces.
|
|
3420
3459
|
|
|
3421
|
-
**Schema.** The version-1 baseline stores
|
|
3422
|
-
|
|
3423
|
-
|
|
3460
|
+
**Schema.** The version-1 baseline stores the normalized {§inference-ledger},
|
|
3461
|
+
its generation or embedding specialization, emission admission, and cardinal
|
|
3462
|
+
physical requests. Its constraints distinguish pending calls, response
|
|
3424
3463
|
evidence, and response-less errors while monetary classification remains
|
|
3425
3464
|
explicit.
|
|
3426
3465
|
|
|
@@ -3454,7 +3493,11 @@ provider I/O.** After packet assembly, Core compares render weight
|
|
|
3454
3493
|
({§tokenomics}) with the provider-derived curation ceiling. An admitted packet
|
|
3455
3494
|
ships untouched. An over-ceiling candidate is never stored as a model request
|
|
3456
3495
|
and never reaches `provider.generate`; its already-created database turn instead
|
|
3457
|
-
becomes a packetless `_plurnk` turn.
|
|
3496
|
+
becomes a packetless `_plurnk` turn. Engine-side inference already booked to that
|
|
3497
|
+
turn (embedding work from semantic attachment) never blocks the transition; only
|
|
3498
|
+
a model emission or BARE call does, because those are model history and the
|
|
3499
|
+
producer cannot change beneath them (run67, 2026-08-29: a 90k-window model died
|
|
3500
|
+
at its first overflow because four embedding calls were counted as history). Packetless initialization and recovery turns
|
|
3458
3501
|
remain ordinary turn chronology but do not consume `maxTurns`, model-call,
|
|
3459
3502
|
emission-attempt, usage, or cost accounting.
|
|
3460
3503
|
|
|
@@ -3572,6 +3615,8 @@ flowchart LR
|
|
|
3572
3615
|
body --> recall["READ log:///…<br/>recalls canonical body"]
|
|
3573
3616
|
```
|
|
3574
3617
|
|
|
3618
|
+
§edit-receipt-anchored-context **An applied EDIT's resulting context carries anchors.** The bounded resulting context each effect renders (`PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES` around and inside the landed region) is rendered exactly as a READ renders — `@xxxxx L:text`, hashed with the resource's READ identity ({§line-anchors}) — so the next batch cites the landed lines by anchor without a READ; both requiems of 2026-08-29 asked for this. A scheme that supplies no identity keeps the line-numbered form.
|
|
3619
|
+
|
|
3575
3620
|
§edit-result-receipt-projection **EDIT projects the scheme-owned batch
|
|
3576
3621
|
receipt.** The scheme framework owns the exact aggregate shape
|
|
3577
3622
|
({§scheme-edit-batch-receipt}). Core validates it and projects the result
|
|
@@ -3675,8 +3720,9 @@ only after authority crosses the client boundary.
|
|
|
3675
3720
|
when an emission is admitted, its response.** Core assembles and measures the
|
|
3676
3721
|
request under {§packet-assembly}. An admitted response extends that same record
|
|
3677
3722
|
before the turn closes; a failed provider call or exhausted invalid emission
|
|
3678
|
-
leaves the request-only record, while rejected exchanges remain in
|
|
3679
|
-
`model_calls` with
|
|
3723
|
+
leaves the request-only record, while rejected exchanges remain in their
|
|
3724
|
+
`inference_calls`/`model_calls` evidence with classification in
|
|
3725
|
+
`turn_attempts`.
|
|
3680
3726
|
|
|
3681
3727
|
| Turn state | `turns.packet` |
|
|
3682
3728
|
| ----------------------------- | ----------------------------------------------- |
|
|
@@ -3863,8 +3909,8 @@ retain distinct contracts and lifetimes.
|
|
|
3863
3909
|
| Import `@plurnk/plurnk-service/digest` | Ships `Digest` and its package-owned SqlRite statements; importing performs no I/O or process action. The CLI wrapper alone invokes it. |
|
|
3864
3910
|
| `run({ dbPath })` | Reads the required database and writes a complete digest to `./test/digest` relative to the caller's working directory. |
|
|
3865
3911
|
| `digestDir` | Selects the output directory. `run` removes and recreates it so stale packet artifacts cannot survive; concurrent callers use distinct directories. |
|
|
3866
|
-
| `workerId` | Narrows workers and every dependent loop, turn, logical
|
|
3867
|
-
| `workspaceId` | Narrows workers and dependent evidence
|
|
3912
|
+
| `workerId` | Narrows workers and every dependent loop, turn, turn-attached logical inference, specialization, physical request, and log row to that one worker. Workspace-only embeddings are excluded. |
|
|
3913
|
+
| `workspaceId` | Narrows workers plus every logical inference and dependent evidence owned by one workspace, including workspace-only embeddings; when both selectors are present they intersect. |
|
|
3868
3914
|
|
|
3869
3915
|
§digest-forensic-fidelity **Forensic fidelity and cardinality.** The digest's machine-readable JSON preserves every log event with its initial and current projection, causal `source`, tags, and structured `attrs`; every exact OPEN/FOLD/log-KILL target effect; the exact Problem on every failed row; each loop's exact terminal result; and every ordered physical provider request. KILLed `turnOps` still produce their chronological `assistant.md` artifacts because curation cannot rewrite what a producer submitted. Each stored packet validates independently: one malformed historical packet remains exact raw evidence with its complete validation error chain and never prevents healthy turns from being projected. Accounting on broader rows is the shared exact derivation from that ledger, never a second stored fact. The reasoning chronology distinguishes readable reasoning content from provider-reported reasoning usage: when tokens were reported but no readable content was returned, it states both facts instead of implying that no reasoning occurred. The human Markdown waterfall shows a present causal source and may preview only the Problem detail because it remains a triage projection, not the machine record. Targets reconstruct the model-visible address, including hostname, port, serialized query, and fragment; an authority-bearing URL must never degrade from `https://host/path` to `https:///path`, and durable resource coordinates render back to their authority form. Its human Markdown waterfall groups identical per-turn op outcomes and typed `entry_materialized` narrations, reporting the exact count and sequence span (`xN (seq A-B)`). Grouping keys include source and the complete target, so distinct causes, authorities, or channels never collapse. Thus amplification is conspicuous without making the diagnostic artifact itself pathological; valid packet files remain byte-identical records of what the model saw.
|
|
3870
3916
|
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"package":"@plurnk/plurnk-service","version":"1.
|
|
1
|
+
{"package":"@plurnk/plurnk-service","version":"1.12.0","revision":"ae514a434aa14b87a6d13b4bbce34347370c6d3d","dirty":false}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SchemeResult } from "@plurnk/plurnk-schemes";
|
|
2
2
|
export default class EditCollision {
|
|
3
|
-
static result(target: string, fields?: Readonly<Record<string, unknown>>): SchemeResult;
|
|
3
|
+
static result(target: string, fields?: Readonly<Record<string, unknown>>, extensions?: Readonly<Record<string, unknown>>): SchemeResult;
|
|
4
4
|
}
|
|
5
5
|
//# sourceMappingURL=edit-collision.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-collision.d.ts","sourceRoot":"","sources":["../../src/content/edit-collision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAM3D,MAAM,CAAC,OAAO,OAAO,aAAa;IAC9B,MAAM,CAAC,MAAM,CACT,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,
|
|
1
|
+
{"version":3,"file":"edit-collision.d.ts","sourceRoot":"","sources":["../../src/content/edit-collision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAM3D,MAAM,CAAC,OAAO,OAAO,aAAa;IAC9B,MAAM,CAAC,MAAM,CACT,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,EAC9C,UAAU,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GACnD,YAAY,CAcd;CACJ"}
|
|
@@ -3,11 +3,12 @@ import Results from "../core/results.js";
|
|
|
3
3
|
// claim or whose observed representation changes before landing. The detection
|
|
4
4
|
// layer is absent from the diagnosis: concurrent correct workers are ordinary.
|
|
5
5
|
export default class EditCollision {
|
|
6
|
-
static result(target, fields = {}) {
|
|
6
|
+
static result(target, fields = {}, extensions = {}) {
|
|
7
7
|
return Results.failure("engine:edit", "edit-collision", 409, "EDIT coordinates collided with another change.", fields, {
|
|
8
8
|
target,
|
|
9
9
|
recovery: "READ the target again before selecting current coordinates.",
|
|
10
10
|
retryable: false,
|
|
11
|
+
...extensions,
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-collision.js","sourceRoot":"","sources":["../../src/content/edit-collision.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,oBAAoB,CAAC;AAEzC,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,CAAC,OAAO,OAAO,aAAa;IAC9B,MAAM,CAAC,MAAM,CACT,MAAc,EACd,MAAM,GAAsC,EAAE;
|
|
1
|
+
{"version":3,"file":"edit-collision.js","sourceRoot":"","sources":["../../src/content/edit-collision.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,oBAAoB,CAAC;AAEzC,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,CAAC,OAAO,OAAO,aAAa;IAC9B,MAAM,CAAC,MAAM,CACT,MAAc,EACd,MAAM,GAAsC,EAAE,EAC9C,UAAU,GAAsC,EAAE;QAElD,OAAO,OAAO,CAAC,OAAO,CAClB,aAAa,EACb,gBAAgB,EAChB,GAAG,EACH,gDAAgD,EAChD,MAAM,EACN;YACI,MAAM;YACN,QAAQ,EAAE,6DAA6D;YACvE,SAAS,EAAE,KAAK;YAChB,GAAG,UAAU;SAChB,CACJ,CAAC;IACN,CAAC;CACJ"}
|
|
@@ -16,7 +16,7 @@ export declare const assertEditReceipt: (value: unknown) => EditReceipt;
|
|
|
16
16
|
export declare const assertEditBatchReceipt: (value: unknown) => EditBatchReceipt;
|
|
17
17
|
export declare const assertResourceEffects: (value: unknown) => readonly ResourceEffect[];
|
|
18
18
|
export declare const projectEditReceipt: (receipt: EditBatchReceipt, index: number) => EditReceipt;
|
|
19
|
-
export declare const editReceipt: (original: string, updated: string, edits: readonly ReceiptEdit[], parseIssues?: ParseIssueTransition) => AppliedEditBatchReceipt;
|
|
19
|
+
export declare const editReceipt: (original: string, updated: string, edits: readonly ReceiptEdit[], parseIssues?: ParseIssueTransition, identity?: string) => AppliedEditBatchReceipt;
|
|
20
20
|
export declare const withEditReceiptParseIssues: (receipt: EditBatchReceipt, parseIssues: ParseIssueTransition | undefined) => EditBatchReceipt;
|
|
21
|
-
export declare const reviewerReplacementReceipt: (original: string, updated: string, authored: EditBatchReceipt, parseIssues?: ParseIssueTransition) => ReviewerReplacementEditBatchReceipt;
|
|
21
|
+
export declare const reviewerReplacementReceipt: (original: string, updated: string, authored: EditBatchReceipt, parseIssues?: ParseIssueTransition, identity?: string) => ReviewerReplacementEditBatchReceipt;
|
|
22
22
|
//# sourceMappingURL=edit-receipt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-receipt.d.ts","sourceRoot":"","sources":["../../src/content/edit-receipt.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"edit-receipt.d.ts","sourceRoot":"","sources":["../../src/content/edit-receipt.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACR,uBAAuB,EACvB,gBAAgB,EAEhB,WAAW,EAEX,oBAAoB,EACpB,mCAAmC,EACtC,MAAM,wBAAwB,CAAC;AAGhC,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,YAAY,EACR,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,mCAAmC,GACtC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAClC;AA+DD,eAAO,MAAM,gBAAgB,8FAA8F,CAAC;AAgD5H,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,WAuBlD,CAAC;AAEF,eAAO,MAAM,sBAAsB,UAAW,OAAO,KAAG,gBA4BvD,CAAC;AAEF,eAAO,MAAM,qBAAqB,UAAW,OAAO,KAAG,SAAS,cAAc,EAqC7E,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAAa,gBAAgB,SAAS,MAAM,KAAG,WA0B7E,CAAC;AAwNF,eAAO,MAAM,WAAW,aACV,MAAM,WACP,MAAM,SACR,SAAS,WAAW,EAAE,gBACf,oBAAoB,aACvB,MAAM,KAClB,uBAoBF,CAAC;AAEF,eAAO,MAAM,0BAA0B,YAC1B,gBAAgB,eACZ,oBAAoB,GAAG,SAAS,KAC9C,gBAQF,CAAC;AAEF,eAAO,MAAM,0BAA0B,aACzB,MAAM,WACP,MAAM,YACL,gBAAgB,gBACZ,oBAAoB,aACvB,MAAM,KAClB,mCA4BF,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import LineAnchors from "./line-anchors.js";
|
|
2
3
|
import { InvalidOperationResultError } from "@plurnk/plurnk-contracts";
|
|
3
4
|
import LineMarkerOps from "./line-marker.js";
|
|
4
5
|
const receiptRecord = (value, label) => {
|
|
@@ -308,9 +309,17 @@ const contextRadius = () => {
|
|
|
308
309
|
}
|
|
309
310
|
return value;
|
|
310
311
|
};
|
|
311
|
-
|
|
312
|
+
// {§edit-receipt-anchored-context} — with the resource's anchor identity, the resulting context
|
|
313
|
+
// renders exactly as a READ does (`@xxxxx L:text`), so the next batch can cite the landed
|
|
314
|
+
// lines by anchor without a READ. Without an identity it stays line-numbered.
|
|
315
|
+
const addContext = (effects, updated, identity) => {
|
|
312
316
|
const lines = splitLines(updated);
|
|
313
317
|
const radius = contextRadius();
|
|
318
|
+
const anchors = identity === undefined ? null : LineAnchors.tokens(identity, updated);
|
|
319
|
+
const width = LineAnchors.lineNumberWidth(updated);
|
|
320
|
+
const prefix = (line) => anchors === null
|
|
321
|
+
? `${line}:`
|
|
322
|
+
: `${anchors[line - 1]} ${String(line).padStart(width)}:`;
|
|
314
323
|
return effects.map((effect) => {
|
|
315
324
|
const selected = new Set();
|
|
316
325
|
const addRange = (first, last) => {
|
|
@@ -331,7 +340,7 @@ const addContext = (effects, updated) => {
|
|
|
331
340
|
}
|
|
332
341
|
const context = [...selected]
|
|
333
342
|
.sort((left, right) => left - right)
|
|
334
|
-
.map((line) => `${line}
|
|
343
|
+
.map((line) => `${prefix(line)}${lines[line - 1]}`);
|
|
335
344
|
const { resultStartLine: _resultStartLine, resultEndLine: _resultEndLine, ...receipt } = effect;
|
|
336
345
|
return {
|
|
337
346
|
...receipt,
|
|
@@ -339,7 +348,7 @@ const addContext = (effects, updated) => {
|
|
|
339
348
|
};
|
|
340
349
|
});
|
|
341
350
|
};
|
|
342
|
-
export const editReceipt = (original, updated, edits, parseIssues) => {
|
|
351
|
+
export const editReceipt = (original, updated, edits, parseIssues, identity) => {
|
|
343
352
|
if (parseIssues !== undefined)
|
|
344
353
|
assertParseIssues(parseIssues);
|
|
345
354
|
const unit = edits.some(({ marker }) => marker.marks.length === 4)
|
|
@@ -358,7 +367,7 @@ export const editReceipt = (original, updated, edits, parseIssues) => {
|
|
|
358
367
|
? codePointCount(updated)
|
|
359
368
|
: splitLines(updated).length,
|
|
360
369
|
...(parseIssues === undefined ? {} : { parseIssues }),
|
|
361
|
-
effects: addContext(effects, updated),
|
|
370
|
+
effects: addContext(effects, updated, identity),
|
|
362
371
|
};
|
|
363
372
|
};
|
|
364
373
|
export const withEditReceiptParseIssues = (receipt, parseIssues) => {
|
|
@@ -371,12 +380,12 @@ export const withEditReceiptParseIssues = (receipt, parseIssues) => {
|
|
|
371
380
|
...(parseIssues === undefined ? {} : { parseIssues }),
|
|
372
381
|
});
|
|
373
382
|
};
|
|
374
|
-
export const reviewerReplacementReceipt = (original, updated, authored, parseIssues) => {
|
|
383
|
+
export const reviewerReplacementReceipt = (original, updated, authored, parseIssues, identity) => {
|
|
375
384
|
const exact = assertEditBatchReceipt(authored);
|
|
376
385
|
if ("disposition" in exact) {
|
|
377
386
|
throw new InvalidOperationResultError("A reviewer replacement cannot supersede an already replaced EDIT batch receipt.");
|
|
378
387
|
}
|
|
379
|
-
const landed = editReceipt(original, updated, [{ marker: { marks: [1, -1] }, body: updated }], parseIssues);
|
|
388
|
+
const landed = editReceipt(original, updated, [{ marker: { marks: [1, -1] }, body: updated }], parseIssues, identity);
|
|
380
389
|
const replacement = landed.effects[0];
|
|
381
390
|
if (replacement === undefined) {
|
|
382
391
|
throw new Error("Reviewer replacement receipt omitted its landed effect.");
|