@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.
Files changed (102) hide show
  1. package/INSTALL.md +3 -3
  2. package/README.md +1 -1
  3. package/SPEC.md +90 -44
  4. package/dist/build-info.json +1 -1
  5. package/dist/content/edit-collision.d.ts +1 -1
  6. package/dist/content/edit-collision.d.ts.map +1 -1
  7. package/dist/content/edit-collision.js +2 -1
  8. package/dist/content/edit-collision.js.map +1 -1
  9. package/dist/content/edit-receipt.d.ts +2 -2
  10. package/dist/content/edit-receipt.d.ts.map +1 -1
  11. package/dist/content/edit-receipt.js +15 -6
  12. package/dist/content/edit-receipt.js.map +1 -1
  13. package/dist/content/line-anchors.d.ts.map +1 -1
  14. package/dist/content/line-anchors.js +13 -6
  15. package/dist/content/line-anchors.js.map +1 -1
  16. package/dist/content/line-marker.d.ts +0 -1
  17. package/dist/content/line-marker.d.ts.map +1 -1
  18. package/dist/content/line-marker.js.map +1 -1
  19. package/dist/core/CoreSchemeServices.d.ts +1 -1
  20. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  21. package/dist/core/CoreSchemeServices.js +1 -1
  22. package/dist/core/CoreSchemeServices.js.map +1 -1
  23. package/dist/core/Dispatcher.d.ts +2 -1
  24. package/dist/core/Dispatcher.d.ts.map +1 -1
  25. package/dist/core/Dispatcher.js +9 -5
  26. package/dist/core/Dispatcher.js.map +1 -1
  27. package/dist/core/EmbeddingCall.d.ts +23 -0
  28. package/dist/core/EmbeddingCall.d.ts.map +1 -0
  29. package/dist/core/EmbeddingCall.js +149 -0
  30. package/dist/core/EmbeddingCall.js.map +1 -0
  31. package/dist/core/Engine.js +2 -2
  32. package/dist/core/Engine.js.map +1 -1
  33. package/dist/core/Engine.sql +89 -31
  34. package/dist/core/InferenceCall.d.ts +18 -0
  35. package/dist/core/InferenceCall.d.ts.map +1 -0
  36. package/dist/core/InferenceCall.js +86 -0
  37. package/dist/core/InferenceCall.js.map +1 -0
  38. package/dist/core/ModelCall.d.ts +4 -13
  39. package/dist/core/ModelCall.d.ts.map +1 -1
  40. package/dist/core/ModelCall.js +10 -77
  41. package/dist/core/ModelCall.js.map +1 -1
  42. package/dist/core/PacketBuilder.d.ts.map +1 -1
  43. package/dist/core/PacketBuilder.js +3 -1
  44. package/dist/core/PacketBuilder.js.map +1 -1
  45. package/dist/core/ResourceMutations.d.ts +1 -0
  46. package/dist/core/ResourceMutations.d.ts.map +1 -1
  47. package/dist/core/ResourceMutations.js +140 -17
  48. package/dist/core/ResourceMutations.js.map +1 -1
  49. package/dist/core/StrikeRail.d.ts +1 -0
  50. package/dist/core/StrikeRail.d.ts.map +1 -1
  51. package/dist/core/StrikeRail.js +11 -2
  52. package/dist/core/StrikeRail.js.map +1 -1
  53. package/dist/core/Turn.sql +6 -3
  54. package/dist/core/TurnRunner.d.ts +0 -1
  55. package/dist/core/TurnRunner.d.ts.map +1 -1
  56. package/dist/core/TurnRunner.js +30 -12
  57. package/dist/core/TurnRunner.js.map +1 -1
  58. package/dist/core/packet-wire.d.ts +1 -0
  59. package/dist/core/packet-wire.d.ts.map +1 -1
  60. package/dist/core/packet-wire.js +27 -12
  61. package/dist/core/packet-wire.js.map +1 -1
  62. package/dist/digest/Digest.d.ts.map +1 -1
  63. package/dist/digest/Digest.js +58 -16
  64. package/dist/digest/Digest.js.map +1 -1
  65. package/dist/digest/digest.sql +35 -17
  66. package/dist/schemes/Exec.d.ts +1 -0
  67. package/dist/schemes/Exec.d.ts.map +1 -1
  68. package/dist/schemes/Exec.js +15 -2
  69. package/dist/schemes/Exec.js.map +1 -1
  70. package/dist/schemes/File.d.ts.map +1 -1
  71. package/dist/schemes/File.js +10 -4
  72. package/dist/schemes/File.js.map +1 -1
  73. package/dist/schemes/Log.d.ts.map +1 -1
  74. package/dist/schemes/Log.js +1 -1
  75. package/dist/schemes/Log.js.map +1 -1
  76. package/dist/schemes/_entry-chunk.d.ts.map +1 -1
  77. package/dist/schemes/_entry-chunk.js +5 -1
  78. package/dist/schemes/_entry-chunk.js.map +1 -1
  79. package/dist/schemes/_entry-find.d.ts.map +1 -1
  80. package/dist/schemes/_entry-find.js +2 -5
  81. package/dist/schemes/_entry-find.js.map +1 -1
  82. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  83. package/dist/schemes/_entry-ops.js +12 -2
  84. package/dist/schemes/_entry-ops.js.map +1 -1
  85. package/dist/schemes/_entry-semantic.d.ts +5 -3
  86. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  87. package/dist/schemes/_entry-semantic.js +20 -8
  88. package/dist/schemes/_entry-semantic.js.map +1 -1
  89. package/dist/schemes/_path-scope.d.ts.map +1 -1
  90. package/dist/schemes/_path-scope.js +6 -1
  91. package/dist/schemes/_path-scope.js.map +1 -1
  92. package/dist/schemes/_search-index.d.ts.map +1 -1
  93. package/dist/schemes/_search-index.js +14 -2
  94. package/dist/schemes/_search-index.js.map +1 -1
  95. package/dist/server/Daemon.d.ts.map +1 -1
  96. package/dist/server/Daemon.js +1 -0
  97. package/dist/server/Daemon.js.map +1 -1
  98. package/dist/server/lifecycle-recovery.sql +14 -20
  99. package/dist/service.js +2 -2
  100. package/dist/service.js.map +1 -1
  101. package/migrations/001_schema.sql +260 -67
  102. 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 embeddings. An explicitly disabled or unavailable embedder makes `~query` fall back to FTS keyword ranking. Set `PLURNK_MIMETYPES_EMBED_BASE_URL` to use a remote OpenAI-compatible embedding endpoint instead.
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 point derivation at a real embedder: `PLURNK_MIMETYPES_EMBED_BASE_URL` (any OpenAI-compatible `/v1/embeddings` a host GPU turns a CPU-hours corpus grind into seconds). 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.
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), `PLURNK_MIMETYPES_EMBED_WORKERS` (the embedder's pool — mimetypes-owned).
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 remote OpenAI-compatible embedder can replace the included local path; see [`INSTALL.md`](./INSTALL.md).
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 `model_calls`, emission-admission rows, and physical provider requests are not: one issued call or request has one owning worker. Parent and fork accounting therefore includes only work issued in that branch, while workspace accounting never double-counts copied history. |
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 beneath that loop first settles as an error with absent usage and explicitly unknown cost, then its logical model call closes; 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.
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 `model_calls` row and its 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 model 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.
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 pre-I/O `model_calls` row carries that call's exact set, including response-less failures. |
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`, `embedBatch`, `tokenizer` | Plan and derive semantic-search chunks without reaching into artifact packages. |
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, embedding,
1381
- and tokenizer leaves through the service manifest. The lean framework also
1382
- supports direct consumers that assemble a different set. Tree-sitter grammar
1383
- WASM leaves and third-party handlers remain independently installable and
1384
- resolve from the same consumer-visible package graph under trust-gated
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.embedBatch`
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`, one-based line ordinal `L`, configured non-negative
1524
- neighbor count `C`, and ordered content array `W` containing that line and up to
1525
- `C` complete lines on either side (all excluding separators), core hashes the
1526
- JSON tuple `["plurnk-line-anchor-v1",R,L,C,W]` with SHA-256, interprets the
1527
- digest as a big-endian integer modulo `62^5`, and encodes five fixed-width
1528
- characters with alphabet `0-9A-Za-z`. The universal READ projector derives
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`. Transparent
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** but **suppressed from the packet render**: 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 while the render still costs it nothing, so FOLD stays genuinely free (the original rowless design's concern, met by hide-not-drop). 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.
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
- §kill-log-receipt-suppressed **A successful KILL of an active log item is suppressed from the render too.** It retires the selected rows from the worker's active projection under {§log-history-projection}; it does not delete their execution history. The KILL event, exact active/folded transition for every target, and authored `turnOps` remain durable, while none creates replacement packet weight. The suppression 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}. Failed KILL renders like every operation error.
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. The started receipt always
1926
- names the working directory. The EXEC `(path)` is one of cwd, script, or tool
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 discovery without changing the service manifest. The
2367
- default local embedding artifact owns the exact counter for its own model;
2368
- remote embedding deployments install `tokenizers` when their provider does not
2369
- supply an exact counter.
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 logical response/failure evidence, `turn_attempts` specialize emission admission, and `provider_requests` are the sole durable accounting representation. Emissions, BARE calls, 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. The derived aggregate usage sums every reported quantity the same way. 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.
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 logical model calls beneath turns,
3422
- emission admission as their specialization, and cardinal physical requests
3423
- beneath each call. Its constraints distinguish pending calls, response
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. Packetless initialization and recovery turns
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 their classification in `turn_attempts`.
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 model call, emission attempt, physical request, and log row to that one worker. |
3867
- | `workspaceId` | Narrows workers and dependent evidence to one workspace; when both selectors are present they intersect. |
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
 
@@ -1 +1 @@
1
- {"package":"@plurnk/plurnk-service","version":"1.11.0","revision":"47a27b26432adf28be2d3200514bfc9e87ea8c3c","dirty":false}
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,GAC/C,YAAY,CAad;CACJ"}
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;QAE9C,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;SACnB,CACJ,CAAC;IACN,CAAC;CACJ"}
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":"AAEA,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;AA+MF,eAAO,MAAM,WAAW,aACV,MAAM,WACP,MAAM,SACR,SAAS,WAAW,EAAE,gBACf,oBAAoB,KACnC,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,KACnC,mCA2BF,CAAC"}
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
- const addContext = (effects, updated) => {
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}:${lines[line - 1]}`);
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.");