@plurnk/plurnk-service 1.21.1 → 1.22.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/.env.defaults +12 -3
- package/INSTALL.md +2 -2
- package/README.md +1 -1
- package/SPEC.md +264 -194
- package/dist/build-info.json +1 -1
- package/dist/content/edit-receipt.js +4 -3
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/index.d.ts +2 -7
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +2 -5
- package/dist/content/index.js.map +1 -1
- package/dist/content/line-marker.d.ts +1 -0
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +2 -0
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts +1 -1
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +7 -9
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/pattern-edits.d.ts +6 -0
- package/dist/content/pattern-edits.d.ts.map +1 -1
- package/dist/content/pattern-edits.js +14 -0
- package/dist/content/pattern-edits.js.map +1 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +2 -1
- package/dist/content/read-projector.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +5 -2
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/DataStatementRunner.js +1 -1
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +1 -0
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Dispatcher.sql +1 -1
- package/dist/core/EditMutations.js +4 -4
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/EditSequence.d.ts.map +1 -1
- package/dist/core/EditSequence.js +2 -1
- package/dist/core/EditSequence.js.map +1 -1
- package/dist/core/Engine.d.ts +4 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +7 -2
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +1 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +4 -0
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/FabricatedLog.d.ts +8 -0
- package/dist/core/FabricatedLog.d.ts.map +1 -0
- package/dist/core/FabricatedLog.js +16 -0
- package/dist/core/FabricatedLog.js.map +1 -0
- package/dist/core/KnownToxins.d.ts +0 -1
- package/dist/core/KnownToxins.d.ts.map +1 -1
- package/dist/core/KnownToxins.js +3 -12
- package/dist/core/KnownToxins.js.map +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +3 -2
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogVisibility.d.ts +3 -1
- package/dist/core/LogVisibility.d.ts.map +1 -1
- package/dist/core/LogVisibility.js +30 -16
- package/dist/core/LogVisibility.js.map +1 -1
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +2 -3
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopOutcome.d.ts +0 -1
- package/dist/core/LoopOutcome.d.ts.map +1 -1
- package/dist/core/LoopOutcome.js +1 -1
- package/dist/core/LoopOutcome.js.map +1 -1
- package/dist/core/OutsideEvent.d.ts +10 -0
- package/dist/core/OutsideEvent.d.ts.map +1 -0
- package/dist/core/OutsideEvent.js +5 -0
- package/dist/core/OutsideEvent.js.map +1 -0
- package/dist/core/PacketBuilder.js +2 -2
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PatternSelection.d.ts +1 -1
- package/dist/core/PatternSelection.d.ts.map +1 -1
- package/dist/core/PatternSelection.js +8 -6
- package/dist/core/PatternSelection.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +2 -4
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -4
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +24 -25
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ProviderRecovery.d.ts +1 -1
- package/dist/core/ProviderRecovery.d.ts.map +1 -1
- package/dist/core/ProviderRecovery.js +3 -1
- package/dist/core/ProviderRecovery.js.map +1 -1
- package/dist/core/ResourceSelector.js +1 -1
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/StoredPacket.d.ts +1 -1
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +6 -15
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +2 -0
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +7 -1
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/Turn.d.ts +1 -1
- package/dist/core/Turn.d.ts.map +1 -1
- package/dist/core/Turn.js.map +1 -1
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
- package/dist/core/TurnDispositionHandler.js +1 -4
- package/dist/core/TurnDispositionHandler.js.map +1 -1
- package/dist/core/TurnMaterialization.d.ts.map +1 -1
- package/dist/core/TurnMaterialization.js +5 -7
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +5 -1
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +108 -23
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerControlHandler.d.ts.map +1 -1
- package/dist/core/WorkerControlHandler.js +6 -1
- package/dist/core/WorkerControlHandler.js.map +1 -1
- package/dist/core/WorkerName.sql +2 -2
- package/dist/core/attachments.d.ts +0 -3
- package/dist/core/attachments.d.ts.map +1 -1
- package/dist/core/attachments.js +3 -3
- package/dist/core/attachments.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +1 -2
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/fork.sql +2 -2
- package/dist/core/packet-wire.d.ts +1 -2
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +2 -3
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +0 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +1 -1
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/worker-ops.sql +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +15 -12
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/DigestRender.d.ts.map +1 -1
- package/dist/digest/DigestRender.js +50 -29
- package/dist/digest/DigestRender.js.map +1 -1
- package/dist/digest/DigestRequiem.d.ts.map +1 -1
- package/dist/digest/DigestRequiem.js +8 -4
- package/dist/digest/DigestRequiem.js.map +1 -1
- package/dist/digest/digest-paths.d.ts.map +1 -1
- package/dist/digest/digest-paths.js +5 -14
- package/dist/digest/digest-paths.js.map +1 -1
- package/dist/digest/digest-rows.d.ts +3 -1
- package/dist/digest/digest-rows.d.ts.map +1 -1
- package/dist/digest/digest.sql +2 -1
- package/dist/schemes/EffectPolicy.js +1 -1
- package/dist/schemes/EffectPolicy.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +26 -6
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecScratch.d.ts +10 -0
- package/dist/schemes/ExecScratch.d.ts.map +1 -0
- package/dist/schemes/ExecScratch.js +43 -0
- package/dist/schemes/ExecScratch.js.map +1 -0
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +39 -5
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +5 -4
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/TurnSource.js +2 -2
- package/dist/schemes/TurnSource.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +2 -3
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +2 -1
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-fts.d.ts +1 -0
- package/dist/schemes/_entry-fts.d.ts.map +1 -1
- package/dist/schemes/_entry-fts.js +34 -2
- package/dist/schemes/_entry-fts.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +3 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_path-scope.d.ts.map +1 -1
- package/dist/schemes/_path-scope.js +2 -3
- package/dist/schemes/_path-scope.js.map +1 -1
- package/dist/server/Daemon.d.ts +25 -13
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +89 -58
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +3 -3
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +3 -3
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +0 -1
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +1 -1
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +0 -9
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/WorkerModelResolver.d.ts +8 -8
- package/dist/server/WorkerModelResolver.d.ts.map +1 -1
- package/dist/server/WorkerModelResolver.js +46 -45
- package/dist/server/WorkerModelResolver.js.map +1 -1
- package/dist/server/client-input.d.ts +1 -0
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +7 -0
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/drain.sql +6 -6
- package/dist/server/envelope.sql +6 -6
- package/dist/server/loopDocs.js +1 -1
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/model-catalog.js +2 -2
- package/dist/server/model-catalog.js.map +1 -1
- package/dist/server/model-route.d.ts +2 -2
- package/dist/server/model-route.d.ts.map +1 -1
- package/dist/server/model-route.js +5 -5
- package/dist/server/model-route.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +41 -6
- package/dist/service.js.map +1 -1
- package/dist/share/Share.d.ts +15 -0
- package/dist/share/Share.d.ts.map +1 -0
- package/dist/share/Share.js +106 -0
- package/dist/share/Share.js.map +1 -0
- package/dist/share/share.sql +6 -0
- package/migrations/001_workspaces.sql +2 -2
- package/migrations/002_workers.sql +4 -6
- package/migrations/003_loops.sql +2 -2
- package/migrations/004_inference.sql +2 -2
- package/migrations/005_entries.sql +2 -2
- package/migrations/006_log.sql +2 -2
- package/migrations/007_subscriptions.sql +2 -2
- package/migrations/008_interactions.sql +2 -2
- package/migrations/009_effort.sql +7 -0
- package/migrations/010_outside_text.sql +41 -0
- package/migrations/011_settled.sql +133 -0
- package/package.json +42 -37
package/SPEC.md
CHANGED
|
@@ -92,7 +92,7 @@ Independent axes on entries and channels. Confusion across them is a recurring s
|
|
|
92
92
|
| Term | Meaning |
|
|
93
93
|
|---|---|
|
|
94
94
|
| **writer** | The identity authoring a write. One of `model \| client \| _plurnk \| plugin`. Carried on `ctx.writer` for schemes; engine enforces `manifest.writableBy`. |
|
|
95
|
-
| **origin** | Synonym for writer in log_entries (`log_entries.origin`).
|
|
95
|
+
| **origin** | Synonym for writer in log_entries (`log_entries.origin`). Synonym for writer. |
|
|
96
96
|
| **writable_by** | The set of writers a scheme accepts. Subset of `{model, client, _plurnk, plugin}`. Engine rejects writes outside the set with 403; the rejection is logged as the action-entry ({§subscriptions} action-entry-as-outcome). |
|
|
97
97
|
|
|
98
98
|
### Execution terms
|
|
@@ -496,7 +496,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
496
496
|
| `READ` | existing literal name | Collect the named worker's deliverable. |
|
|
497
497
|
| `KILL` | existing literal name | Terminate the named worker or caller. |
|
|
498
498
|
|
|
499
|
-
- §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
|
|
499
|
+
- §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409 whose receipt names the working forms — `SEND (worker://<name>)` with the task as the body to give the live worker more work, or a name no worker holds for another; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
|
|
500
500
|
- §worker-spawn-prompt-resource **The spawn slot is overloaded by scheme.** A `worker://` path is
|
|
501
501
|
the child's address and keeps the address rules ({§worker-control-addressing}). A path of any
|
|
502
502
|
other scheme is the child's prompt resource: it is read whole (`<1,-1>`) under the caller's read capabilities, composed with
|
|
@@ -506,7 +506,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
506
506
|
resource with no body is `422 spawn-prompt-empty`. Naming the child and giving a resource in one
|
|
507
507
|
statement is not expressible; the body can READ the resource instead. Taught in the deep
|
|
508
508
|
reference only.
|
|
509
|
-
- §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and
|
|
509
|
+
- §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and effort; the sender and daemon default do not re-select it. The caller addresses itself by its literal name; a literal name with no worker in the workspace is 404.
|
|
510
510
|
- §worker-scheme-fork **Fork** — ```` ```FORK (worker://<name>)? ```` with a task body branches the
|
|
511
511
|
current worker into a **named** child: its log is deep-copied
|
|
512
512
|
({§machine-processes-fork-copies-the-log}), which continues with `task`; the
|
|
@@ -541,8 +541,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
541
541
|
missing name is 404. The model therefore reads the worker itself for its
|
|
542
542
|
outcome or a wait rather than guessing a scratch path to "check on" it.
|
|
543
543
|
- §worker-loop-result `ops://<name>/<sequence>` selects one worker-local positive safe-integer
|
|
544
|
-
loop sequence ({§loop-answer};
|
|
545
|
-
both what a loop said and how it ended). It is a read-only resource, not an actor control
|
|
544
|
+
loop sequence ({§loop-answer}; one address serves both what a loop said and how it ended). It is a read-only resource, not an actor control
|
|
546
545
|
address: READ, FIND and COPY use ordinary projections; EDIT, MOVE-source, and KILL cannot change
|
|
547
546
|
it. No query, userinfo, or port is accepted. A coordinate that is not a positive safe integer is
|
|
548
547
|
400; a missing loop 404; a loop that has not answered and has not concluded 425. A concluded
|
|
@@ -580,8 +579,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
580
579
|
and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows; a model never infers the
|
|
581
580
|
present from the last row's coordinate, which may or may not be its own turn. The block
|
|
582
581
|
changes every turn, so nothing of it precedes the log, and the packet carries no date, time
|
|
583
|
-
or zone anywhere
|
|
584
|
-
the log, which is the cached prefix). The coordinate only; the source addresses stay
|
|
582
|
+
or zone anywhere. The coordinate only; the source addresses stay
|
|
585
583
|
documented, not taught.
|
|
586
584
|
|
|
587
585
|
Worker control rides the daemon's inject seam (active→fold, idle→enqueue+drain), so the handler creates/branches the worker and hands off; the daemon owns provider + system prompt. FORK/WORK carry the seed task in the body and are their own ops, dispatched to worker control — never the entry-copy path.
|
|
@@ -642,8 +640,7 @@ and never re-fetch a match.
|
|
|
642
640
|
files by an exact creation record with recorded provenance, and never by `git add`.
|
|
643
641
|
Changing this clause, the register, or the composition is an operator ruling recorded
|
|
644
642
|
on the issue that lands it — never an implementation convenience, never a side effect
|
|
645
|
-
of making a file visible to solve the problem at hand.
|
|
646
|
-
"untracked-but-not-ignored" ambient admission is retired.
|
|
643
|
+
of making a file visible to solve the problem at hand.
|
|
647
644
|
- §membership-model-universe **The exception register — files in the model's universe.**
|
|
648
645
|
Admitted by exact creation records (`source: "create"`, origin `constraint`), never
|
|
649
646
|
staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
|
|
@@ -656,7 +653,7 @@ and never re-fetch a match.
|
|
|
656
653
|
exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
|
|
657
654
|
is not projected. (4) A definition the model proposes through the `members`
|
|
658
655
|
family ({§members-functionality}), admitted only under the operator's ceiling
|
|
659
|
-
`PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (
|
|
656
|
+
`PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}), projected with source
|
|
660
657
|
`model`, and never admitted past the repository's ignore rules or an exclusion.
|
|
661
658
|
Nothing else.
|
|
662
659
|
- §membership-git-membership The workspace owns the Git repository containing
|
|
@@ -713,7 +710,7 @@ identity, and terminal disposition without exposing raw bytes or a base64 lane.
|
|
|
713
710
|
workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
|
|
714
711
|
byte ceiling over one disk source before Core reads it into the canonical file
|
|
715
712
|
snapshot. Its valid range is `1..104857600`, bounded by the channel storage
|
|
716
|
-
contract
|
|
713
|
+
contract. An oversized path remains a real member
|
|
717
714
|
with an empty body channel carrying a durable 413 producer result; no diagnostic
|
|
718
715
|
sentinel impersonates file content. READ therefore names the path, observed bytes,
|
|
719
716
|
ceiling, and recovery through the ordinary result contract, while EDIT returns the
|
|
@@ -769,7 +766,7 @@ The version travels *with the proposal*, never re-read from the entry at accept:
|
|
|
769
766
|
|
|
770
767
|
The CAS is the **hard backstop**, at the moment of writing, on every accept path. It composes with the model-facing {§line-anchors}: an anchor rejects a target whose relevant neighborhood changed before dispatch, while the CAS refuses to write against a snapshot disk left after proposal. An unanchored edit deliberately claims no pre-dispatch stale-view guarantee.
|
|
771
768
|
|
|
772
|
-
§membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1`
|
|
769
|
+
§membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1` includes the repository containing `project_root`; `=0` disables automatic Git membership, leaving member definitions as the only membership source. `ALLOWED` gates `AUTO`.
|
|
773
770
|
|
|
774
771
|
**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/KILL. 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.
|
|
775
772
|
|
|
@@ -859,8 +856,8 @@ waited 7.5 h, #703) is a number rather than a gap.
|
|
|
859
856
|
§digest-storage **The digest states the file's health.** Beside the database path it
|
|
860
857
|
reports the file size, the free pages it holds, its `auto_vacuum` mode, and the six
|
|
861
858
|
largest tables and indexes by allocated bytes (`dbstat`), so growth is a number in
|
|
862
|
-
every digest (#764). The digest reads loops as stored, so
|
|
863
|
-
|
|
859
|
+
every digest (#764). The digest reads loops as stored, so it tolerates databases
|
|
860
|
+
missing later lifecycle columns.
|
|
864
861
|
|
|
865
862
|
§loop-execution-allowance **One task has one execution allowance.** The first
|
|
866
863
|
execution snapshots `PLURNK_SERVICE_LOOP_TIMEOUT` on the loop. Active segments
|
|
@@ -1081,7 +1078,7 @@ These are the complete strike sources:
|
|
|
1081
1078
|
|
|
1082
1079
|
| Strike source | Exact trigger | Model-visible occurrence |
|
|
1083
1080
|
|---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
|
|
1084
|
-
| Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501
|
|
1081
|
+
| Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501`, in a turn where no operation succeeded ({§strike-progress-immunity}). | The originating failure row. |
|
|
1085
1082
|
| Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
|
|
1086
1083
|
| Empty turn | An admitted turn with no authored response operation ({§empty-turn}). | The turn's `422` error row, and its reasoning read back ({§reasoning-empty-turn-read}). |
|
|
1087
1084
|
|
|
@@ -1114,8 +1111,8 @@ effects. Ordinary contract strikes and operator budgets remain independent.
|
|
|
1114
1111
|
call ({§bare-inference}) takes the same recovery as the loop's own inference: each re-issue
|
|
1115
1112
|
is its own model call on the ledger, and a spent window leaves the operation's result as the
|
|
1116
1113
|
provider's exact failure. When a model
|
|
1117
|
-
call fails with a
|
|
1118
|
-
the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
|
|
1114
|
+
call fails with a kind the provider marks `retryable` ({§provider-retryable-truth}: network
|
|
1115
|
+
failure, rate limit, deadline, interrupted resource, dropped output) after the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
|
|
1119
1116
|
notices the client (`engine:provider` / `provider_unavailable`), waits with
|
|
1120
1117
|
exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling up to
|
|
1121
1118
|
`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX`), and re-issues the same call against the exact frozen model
|
|
@@ -1134,18 +1131,28 @@ instead, because parking stops the execution clock and no wake would ever arrive
|
|
|
1134
1131
|
({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
|
|
1135
1132
|
authorization, quota, an invalid response) settles a loop on a provider failure.
|
|
1136
1133
|
|
|
1137
|
-
**Contract Strikes
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1134
|
+
**Contract Strikes**: every turn with one or more contract violations earns a
|
|
1135
|
+
strike; a turn without any contract violations clears the strikes.
|
|
1136
|
+
|
|
1137
|
+
§strike-progress-immunity **A turn in which at least one operation succeeded is
|
|
1138
|
+
immune to the hard-result source** (operator ruling, 2026-09-26, #853): its hard
|
|
1139
|
+
failures keep their exact rows, but the turn counts as progress — it earns no strike
|
|
1140
|
+
and clears the streak. A successful operation is any admitted operation that acts on the
|
|
1141
|
+
task — executions included — whose result status is `< 400`. Operations that steer the
|
|
1142
|
+
loop never qualify: NOTE (it cannot fail; reasoning NOTEs are filed as NOTEs, and outside
|
|
1143
|
+
text is no operation at all, {§outside-text}), WAIT, a parameterless KILL and a targetless SEND; a turn
|
|
1144
|
+
of failures beside a WAIT still strikes. The cycle source is not exempted: a repeating
|
|
1145
|
+
turn's operations succeed by construction, and the backstop exists to catch exactly
|
|
1146
|
+
that ({§engine-cycle-evidence}).
|
|
1147
|
+
The streak counts consecutive violating turns; `PLURNK_SERVICE_MAX_STRIKES` is the
|
|
1148
|
+
threshold, crossed ON the strike that reaches it; the crossing turn terminates at **508
|
|
1142
1149
|
Loop Detected** when cycle-detected, otherwise **500**.
|
|
1143
1150
|
|
|
1144
1151
|
The contracts, and the violation of each that strikes:
|
|
1145
1152
|
|
|
1146
1153
|
| Contract | Violation that strikes |
|
|
1147
1154
|
|---|---|
|
|
1148
|
-
| operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
|
|
1155
|
+
| operation contract | a hard operation failure (status ≥ 400) in an admitted turn where no operation succeeded ({§strike-progress-immunity}) — soft statuses below excluded |
|
|
1149
1156
|
| review contract | none: an eligible final response joins live obligations ({§completion-joins-live-work}) or continues to observe results ({§completion-defers-to-results}) |
|
|
1150
1157
|
| progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
|
|
1151
1158
|
| frame contract | emission attempts exhausted with no admissible turn |
|
|
@@ -1203,10 +1210,10 @@ Author-facing contract: [`@plurnk/plurnk-providers`](../plurnk-providers/SPEC.md
|
|
|
1203
1210
|
Three current entry points:
|
|
1204
1211
|
|
|
1205
1212
|
- §provider-surface-generate `provider.generate(args)` — once per logical model call. An emission attempt supplies the complete packet messages, worker/turn coordinates, generation envelope, optional local grammar, first-party metadata, and `callKind: "emission"`. A BARE inference supplies only one user message containing its resolved prompt plus non-prompt call identity and accounting metadata, including `callKind: "bare"` ({§bare-inference} {§provider-call-kind}). Both receive a durable physical-request observer; provider-owned retry and failover may issue several ordered requests beneath either call. A successful `ProviderResponse` reaches its call-specific consumer; a `ProviderError.attempt` remains failed response evidence under {§provider-interrupted-attempt}. Core persists normalized response evidence separately from physical accounting.
|
|
1206
|
-
- §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — provider
|
|
1207
|
-
- §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with
|
|
1213
|
+
- §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — the provider's verdict under {§provider-capacity-admission}; core acts on it under {§tokenomics-context-envelope-admission}. `generate` performs this assessment for its exact request and preserves the evidence on success and capacity failure.
|
|
1214
|
+
- §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with the provenance of {§provider-prompt-measurement}. Core never substitutes this physical fact for its curation ruler.
|
|
1208
1215
|
|
|
1209
|
-
§provider-surface-identity Provider capacity and identity are immutable for one instance
|
|
1216
|
+
§provider-surface-identity Provider capacity and identity are immutable for one instance ({§provider-interface}); core invents no stand-in for a `null` fact. `inputCapacity` feeds {§tokenomics}; a call's response grant may expand under {§provider-flexed-allowance}; `model` identifies persisted turn/provider evidence; local GBNF admission also consumes `constrainsOutput` ({§grammar-configuration-admission}).
|
|
1210
1217
|
|
|
1211
1218
|
§inference-ledger **Logical inference is provider-neutral and physical requests have one ledger.** Every `inference_calls` identity belongs to a workspace and a model/inference turn, records its ordered kind and request model, and has a forward-only lifecycle. Its `model_calls` specialization owns normalized failure and capacity evidence; the response body is `model_call_responses`, present or retired under {§retention-policy}; one observation view records evidence, body and close together, and a settled call refuses a second observation. Only an `emission` has `turn_attempts` admission evidence; `bare` calls retain independent results. Every physical request is an ordered `provider_requests` child opened before I/O and settled once. Calls contribute to turn, loop, worker, and workspace accounting; only an emission supplies the latest context gauge.
|
|
1212
1219
|
|
|
@@ -1230,9 +1237,13 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
|
|
|
1230
1237
|
| Parsed response | Admission |
|
|
1231
1238
|
|---|---|
|
|
1232
1239
|
| Bounded program, including malformed operations | Admit valid operations and record parser failures; with no authored operation, apply {§empty-turn}. |
|
|
1233
|
-
| Outside response text |
|
|
1240
|
+
| Outside response text | Store it as the turn's `outside` source under {§outside-text}; never a row, never delivered, never completion. |
|
|
1234
1241
|
| Lost boundary after a closed operation | Admit the closed operations and record the boundary diagnostic under {§unparsed-tail-boundary}. |
|
|
1235
1242
|
| Lost boundary before any closed operation | Reject the attempt; neither outside text nor a reasoning NOTE substitutes for a closed response operation. |
|
|
1243
|
+
| Outside text carrying a log-entry heading | Reject the attempt ({§fabricated-log-entry}). |
|
|
1244
|
+
| A response the provider stopped at a repeated line | Reject the attempt with the provider's sentence as its diagnostic ({§repetition-stop}); no provider recovery, notice, or problem row. |
|
|
1245
|
+
|
|
1246
|
+
§fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. Write the operation, then wait for its receipt. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
|
|
1236
1247
|
|
|
1237
1248
|
Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
|
|
1238
1249
|
discloses truncation and precludes completion; it is not independently a rejection.
|
|
@@ -1242,7 +1253,7 @@ optional, with no omission warning or invented operation ({§turn-shape}).
|
|
|
1242
1253
|
|
|
1243
1254
|
§safe-uri-target-groups After source and authored-command admission, Core tolerates one target group on READ 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 at that operation's position under {§op-execution-order}. 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.
|
|
1244
1255
|
|
|
1245
|
-
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
|
|
1256
|
+
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 `<stem>.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
|
|
1246
1257
|
|
|
1247
1258
|
When the loop continues after exhaustion under {§invalid-emission-attempts}, the next ordinary turn's packet projects the latest rejected response visibly from a durably body-suppressed 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 that malformed body unless the model explicitly READs its exact address. Admission clears the recovery projection; another exhaustion replaces it with the latest rejected response if the loop continues.
|
|
1248
1259
|
|
|
@@ -1335,7 +1346,7 @@ whose text is read once per daemon and handed to the provider verbatim
|
|
|
1335
1346
|
name with no path separator is refused with an error that says so, and an
|
|
1336
1347
|
unreadable file fails the constrained generation loudly; neither ever silently
|
|
1337
1348
|
becomes unconstrained. Nothing generates, validates, or grades a grammar, and
|
|
1338
|
-
no
|
|
1349
|
+
no effort is implied by one. The turn records transport as evidence:
|
|
1339
1350
|
`railsAttached: "client"` when the provider reports it sent the grammar, or
|
|
1340
1351
|
`"withheld"` when it reports it did not ({§provider-grammar-evidence}); there
|
|
1341
1352
|
is no verdict key and no notice about conformance, because the parser's
|
|
@@ -1344,7 +1355,7 @@ adds no grammar state at all.
|
|
|
1344
1355
|
|
|
1345
1356
|
```dotenv
|
|
1346
1357
|
PLURNK_MODEL_gemma=openai/macher.gguf
|
|
1347
|
-
|
|
1358
|
+
PLURNK_MODEL_flash=openrouter/deepseek/deepseek-v4.1-flash
|
|
1348
1359
|
PLURNK_MODEL=gemma
|
|
1349
1360
|
```
|
|
1350
1361
|
|
|
@@ -1419,6 +1430,8 @@ historical execution. No address grants ownership or access restrictions.
|
|
|
1419
1430
|
|
|
1420
1431
|
§fs-visibility-grantors **File visibility has two represented grantors; Plurnk never invents a private third one.** A file member is admitted by the active Git substrate or by an ordinary `include` row of the overlay. An `include` is either a projected `members` definition ({§members-projection}) or the exact, inspectable record of an accepted creation ({§fs-create-record}); both resolve to `constraint` membership. AGENTS.md remains auto-pulled as POLICY ({§policy-sections}), deliberately not a file member. A physically existing path that neither Git nor an `include` admits does not exist for the model and cannot be overwritten.
|
|
1421
1432
|
|
|
1433
|
+
### File scheme: creation and misses
|
|
1434
|
+
|
|
1422
1435
|
§fs-write-surface **The write surface — one admission and incorporation path.** Existing writes remain membership-gated. An absent path additionally crosses the effective creation scope and the complete constraint/Git policy before a proposal is issued. EDIT, COPY destinations, and MOVE destinations use this same path regardless of whether the producer is a model, client, plugin, or `_plurnk`. A COPY or MOVE destination scope on an absent channel resolves against its empty pre-mutation value under {§empty-mutation-scope}; a valid scope creates the channel with the selected source as its complete value. A coordinate outside that empty value is 416. Binary scopes remain numeric byte positions or ranges under {§binary-parity}.
|
|
1423
1436
|
|
|
1424
1437
|
| Case | Required admission | Accepted result |
|
|
@@ -1474,9 +1487,9 @@ Every fact names the canonical key, never the host root or an echo of the
|
|
|
1474
1487
|
model's spelling. These classes let a caller distinguish a wrong address, an
|
|
1475
1488
|
invalid range, read-only authority, and occupied hidden state without guessing.
|
|
1476
1489
|
|
|
1477
|
-
§membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'
|
|
1490
|
+
§membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.` Recovery treats path correction with FIND, creation with EDIT, and admission with `members (add)` as alternatives; it must not presume a missing READ requires creation or admission. Admission takes a `{"glob": "<path>"}` body. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo. Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.`, with admission-only recovery. Occupancy may surface there; content never does ({§membership}).
|
|
1478
1491
|
|
|
1479
|
-
§
|
|
1492
|
+
§file-directory-target **A directory is named as a directory.** Inside the root, a READ (or other exact-path read), KILL or EDIT whose target is a directory on disk — with or without a trailing slash — is refused `path-is-directory`, never as a missing or non-member file, since admitting it is not what the model needs: READ and KILL answer 404, EDIT 403. The detail is `'<key>' is a directory, not a file; <OP> reads/removes/writes one file.` and the recovery names the listing that reaches its files, `` List its files with `FIND (<key>/)`, then READ one by its path. `` (KILL: `then KILL each by its path`; EDIT: `` Name a file inside it, as `EDIT (<key>/<file>)`; list its files with `FIND (<key>/)`. ``). Beyond the root the disk stays dark and {§membership-read-refusal} holds unchanged.
|
|
1480
1493
|
|
|
1481
1494
|
### §scheme-manifest Manifest
|
|
1482
1495
|
|
|
@@ -1484,10 +1497,9 @@ invalid range, read-only authority, and occupied hidden state without guessing.
|
|
|
1484
1497
|
|
|
1485
1498
|
### §crud CRUD primitives
|
|
1486
1499
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
own a more specific operation. A stored-entry publication atomically upserts
|
|
1500
|
+
Core implements the `ctx.entries` capability of {§scheme-ctx-entries} and drives
|
|
1501
|
+
it for COPY/MOVE/KILL orchestration when a scheme does not own a more specific
|
|
1502
|
+
operation. A stored-entry publication atomically upserts
|
|
1491
1503
|
one workspace identity, metadata, and its complete channel set. Concurrent
|
|
1492
1504
|
publications expose one complete result, never a mix of channels; a failed
|
|
1493
1505
|
publication leaves the prior entry unchanged. Omitted attributes preserve the
|
|
@@ -1703,12 +1715,11 @@ empty setting excludes nothing, and the first match is the observable reason.
|
|
|
1703
1715
|
|
|
1704
1716
|
§search-size-bound Every search subject, whatever its scheme, is also bounded
|
|
1705
1717
|
by size when the operator sets one: a body longer than
|
|
1706
|
-
`PLURNK_SERVICE_SEARCH_MAX_BYTES` (
|
|
1718
|
+
`PLURNK_SERVICE_SEARCH_MAX_BYTES` (empty = unbounded) is `excluded` with the reason `larger than N bytes`, before
|
|
1707
1719
|
its body is read. It is neither parsed for symbols nor full-text indexed; READ,
|
|
1708
1720
|
FIND by path, and membership are unaffected. The reason joins the derivation
|
|
1709
1721
|
identity, so changing the bound re-derives the affected bodies and retention
|
|
1710
|
-
collects what they leave
|
|
1711
|
-
tokenizer vocabularies (up to 31 MB each) as full text.
|
|
1722
|
+
collects what they leave (#729).
|
|
1712
1723
|
|
|
1713
1724
|
A match produces the `excluded` derivation disposition and suppresses graph
|
|
1714
1725
|
and FTS while leaving the stored channel and direct READ unchanged. The
|
|
@@ -1914,8 +1925,7 @@ range. COPY/MOVE mutation owners retain
|
|
|
1914
1925
|
the resolved endpoint neighborhoods as compare-and-swap preconditions. There is
|
|
1915
1926
|
no revision sidecar or fuzzy relocation. A range authenticates both endpoint
|
|
1916
1927
|
neighborhoods, so every line of a range up to `2C + 2` lines is covered; a
|
|
1917
|
-
longer range retains an unauthenticated interior gap.
|
|
1918
|
-
covers ranges through six lines.
|
|
1928
|
+
longer range retains an unauthenticated interior gap.
|
|
1919
1929
|
|
|
1920
1930
|
### §edit EDIT
|
|
1921
1931
|
|
|
@@ -2012,8 +2022,15 @@ READ is the one fan-out core performs ({§read-fan-out}).
|
|
|
2012
2022
|
result carries `matched`, the count of selected lines inside the scope. Zero
|
|
2013
2023
|
matches is an empty read (204, `matched: 0`), never a failure. A full-text
|
|
2014
2024
|
(`~`) or graph (`&`) pattern selects resources, not lines: 400
|
|
2015
|
-
`pattern-dialect-unsupported
|
|
2025
|
+
`pattern-dialect-unsupported` ({§pattern-dialect-find-only}); a matcher its mimetype cannot run answers the
|
|
2016
2026
|
matcher's own 415/400 ({§matcher-dispatch}).
|
|
2027
|
+
- §pattern-dialect-find-only **A `~` or `&` matcher outside FIND is refused as FIND's alone.** Every
|
|
2028
|
+
400 `pattern-dialect-unsupported` — READ, EDIT, KILL, COPY, MOVE, SEND — names the model's
|
|
2029
|
+
matcher and says only FIND takes it, and its recovery gives both working forms with the
|
|
2030
|
+
model's own target: the FIND carrying that matcher, and the same operation with a text
|
|
2031
|
+
pattern built from the symbol or words it named (regex metacharacters escaped, words joined
|
|
2032
|
+
by `|`): `` Locate it with `FIND (django/urls/resolvers.py) &RoutePattern`, or select lines
|
|
2033
|
+
with a text pattern: `READ (django/urls/resolvers.py) /RoutePattern/`. ``
|
|
2017
2034
|
- §read-fan-out **A READ over a glob reads every matching path.** `READ (pets_*.md)`
|
|
2018
2035
|
and `READ (pets_*.md) /dogs/i` keep their glob ({§read-find-normalization} in the
|
|
2019
2036
|
contracts SPEC) and dispatch fans them out: the ordinary FIND over the same
|
|
@@ -2033,9 +2050,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
|
|
|
2033
2050
|
FIND failure is that failure on the authored glob. The FIND's resource page bounds
|
|
2034
2051
|
the fan-out: when more paths matched than were read, one `read_fanout_bounded`
|
|
2035
2052
|
notice names both counts. A full-text (`~`) or graph (`&`) matcher selects
|
|
2036
|
-
resources, not lines, so that READ dispatches as the FIND survey.
|
|
2037
|
-
2026-09-13: "give it what it asked for" — a model that asked to read the pantry
|
|
2038
|
-
looped five turns on the catalog it was handed instead.
|
|
2053
|
+
resources, not lines, so that READ dispatches as the FIND survey.
|
|
2039
2054
|
- §read-bytes A binary channel, and the `#bytes` view of
|
|
2040
2055
|
any resource whose scheme supplies bytes, reads as the source bytes one hexadecimal
|
|
2041
2056
|
octet per line: coordinate = line = byte, so `<a,b>` selects bytes, the markerless
|
|
@@ -2136,8 +2151,8 @@ turn admitted under {§empty-turn}, one runtime turn of the same loop
|
|
|
2136
2151
|
reasoning source; its receipt renders in the next packet like any other log row.
|
|
2137
2152
|
`PLURNK_REASONING_EMPTY_TURN_LINES` (alias-scoped, default in `.env.defaults`) selects the scope on the same
|
|
2138
2153
|
scale as `PLURNK_REASONING_VIEW_LINES`. No read follows a turn without reasoning, and none follows
|
|
2139
|
-
a turn whose emission or reasoning carries a foreign tool-call grammar
|
|
2140
|
-
the strike and its error row are unchanged.
|
|
2154
|
+
a turn whose emission or reasoning carries a foreign tool-call grammar or leaked template token
|
|
2155
|
+
(`KnownToxins` names them); the strike and its error row are unchanged.
|
|
2141
2156
|
|
|
2142
2157
|
### §log-kill-scope KILL on the log: whole items and scoped bodies
|
|
2143
2158
|
|
|
@@ -2145,6 +2160,8 @@ AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMar
|
|
|
2145
2160
|
|
|
2146
2161
|
KILL deletes context from the **log** (`log:///`, {§packet}). Without a scope it retires the selected rows from the active projection ({§log-history-projection}). With a one-line or inclusive two-line scope it removes only that body's intersecting body-relative physical lines from the readable projection, and the row stays active. An anchor may be one published on that body or one returned by READing its `log:///` coordinate ({§line-anchors}); an anchor absent from the current body selects no line, as with an out-of-bounds numeric line. Scoped KILL is one-way: intervals accumulate, the durable body is untouched, and subsequent access follows {§log-readable-projection}. A scoped KILL on a bodyless row is a friendly 200 no-op with `matched` reported. A KILL that addresses no row is 404 on an exact coordinate and 204 on a sweep ({§log-curation-folder-idiom}). Selection composes target/glob with an optional heading pattern ({§log-curation-set-selection}). Parameterless KILL instead requests completion ({§kill-conclusion}).
|
|
2147
2162
|
|
|
2163
|
+
§log-scope-recovery A log-body scope follows the file slicer's range rule ({§range-starts-at-one} in the schemes SPEC): a range starting at 0 — `<0,-1>` included — is refused 416 `range-not-satisfiable` on every body, empty ones too, and never clamped; its detail is the slicer's own sentence, `Range <0,-1> starts at 0, which is not a line; lines are numbered from 1.`, and its recovery names the forms a log body takes — `Write <1,-1> to trim every line of the body; KILL (log:///1/9/2/READ) with no scope retires the whole row.`, or `To trim lines 1 through M, write <1,M>; …` — never the insert and append positions a body cannot take. Every other scope that names no line is 400 `curation-scope-invalid` and likewise names the model's mistake in its coordinates and the forms that work on that row: `<0>` offers `<1>`; an end below 1 offers `<L,-1>`; a backward `<5,3>` offers `<3,5>`; anything else offers `<L>`, `<L,M>` and the unscoped row KILL.
|
|
2164
|
+
|
|
2148
2165
|
A READ carrying active native media is atomic: any KILL scope is ignored and the entire observation is retired, including its native context contribution ({§packet-attachment-parts}). For a model turn, native activity is the attachment selection in its actual input packet; without a model packet, a native observation is atomic by default. Text-only observations in the same selection retain ordinary scoped behavior. Neither form deletes source data or forensic evidence.
|
|
2149
2166
|
|
|
2150
2167
|
§log-readable-projection Log content has two independent projections:
|
|
@@ -2316,10 +2333,10 @@ single line past the end, a reversed range, empty content, a command's log row
|
|
|
2316
2333
|
|
|
2317
2334
|
| Surface | Contract |
|
|
2318
2335
|
|---|---|
|
|
2319
|
-
| Identity | `ops://<worker>/<loop>/<turn>`, `reasoning://<worker>/<loop>/<turn>`, and `note://<worker>/<loop>/<turn>/<item>` name a worker in the current workspace and its durable coordinates. A note's item is its dispatched NOTE ordinal. The worker authority is required and case-sensitive; userinfo, ports, and queries are invalid. Source identity never depends on the reading worker. `log:///` remains local; READ, FIND and KILL reject log authorities, userinfo, ports and queries with 400, never substitute the caller's log. |
|
|
2336
|
+
| Identity | `ops://<worker>/<loop>/<turn>`, `reasoning://<worker>/<loop>/<turn>`, and `note://<worker>/<loop>/<turn>/<item>` name a worker in the current workspace and its durable coordinates. A note's item is its dispatched NOTE ordinal. The `outside` source ({§outside-text}) has no address: no `outside://` scheme exists, and it is reached only through `outside/event`, FORK and the digest. The worker authority is required and case-sensitive; userinfo, ports, and queries are invalid. Source identity never depends on the reading worker. `log:///` remains local; READ, FIND and KILL reject log authorities, userinfo, ports and queries with 400, never substitute the caller's log. |
|
|
2320
2337
|
| Source | `ops` is exact admitted `text/vnd.plurnk`; `reasoning` is `text/plain` containing the selected original provider reasoning or a non-model producer's authored rationale. Producer identity comes from the owning turn; a harness rationale is not provider evidence. The turn decides existence and the source decides content: a turn that exists but has no source of that kind reads as the ordinary empty resource (204, empty body), never a fabricated one; a worker or turn that does not exist is 404. |
|
|
2321
2338
|
| Notes | Each dispatched NOTE stores its exact literal body as an immutable `text/plain` source and returns its worker-qualified address. There may be multiple notes in a turn, from reasoning, content, or another producer. A missing note is 404, not an empty invented note. Sharing its URI uses ordinary SEND; the receiver deliberately READs it. NOTE itself sends no ambient update. |
|
|
2322
|
-
| Retention | One ops source and one
|
|
2339
|
+
| Retention | One ops source, one reasoning source and one outside source per turn; one note source per NOTE ordinal. An optional inference-call link records provenance. Source removal follows deletion of its owning turn, never log curation. |
|
|
2323
2340
|
| Operations | Ordinary scoped READ, FIND, content search and COPY from any named worker's source within the workspace. FIND accepts authority and path patterns, retaining complete worker-qualified identities in results and folder selectors. READ returns data and never executes it. Sources are read-only for every actor and have no edit hashes. |
|
|
2324
2341
|
| Index | Source text uses the existing derivation, FTS and graph machinery; only its derivation attachment is replaceable. |
|
|
2325
2342
|
| FORK | Sources copy with the inherited turns at identical loop/turn/item coordinates under the fork's own authority. Bytes and embedded source references are preserved verbatim; an explicit reference still names its original worker. Branch receipt curation is independent; neither branch can rewrite source evidence. |
|
|
@@ -2550,9 +2567,10 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
2550
2567
|
|
|
2551
2568
|
- §log-uniform-query **Log speaks the universal query contract** — ```` ```FIND (log://…) ```` works like every scheme's FIND. Candidates are worker rows scoped by the coordinate hierarchy ({§log-coordinate-hierarchy}) and projected exactly as READ shows them. Content dialects use `Matcher.matchCandidates`; `~` full-text and `&graph` use the same persistent derivation artifacts and candidate rankers as entries. Broad results are one-channel catalog groups whose `[0].path` is `log:///loop/turn/seq/OP`; exact matcher results are flat locations ({§find-result-projection}). Log remains the core event ledger rather than duplicating rows into `entries`; its core-private storage adapter supplies one complete channel representation to the same READ projector. That adapter is not a plugin seam and grants no protocol scheme an alternate READ path.
|
|
2552
2569
|
- §find-source-agnostic **The content matcher is source-agnostic** — `Matcher.matchCandidates(body, candidates, mimetypes)` applies a content matcher (regex/jsonpath/xpath/glob) to candidates from ANY source, keyed by the caller's own identity (a pathname for entries, a `loop/turn/seq` coordinate for log). The matcher never cares what table the content came from, so FIND works uniformly across schemes by construction: `EntryFind` and `Log.find` run the one shared primitive rather than re-implementing it per scheme. Log stays its own event stream, but its rows are candidates the shared matcher covers like any entry's content.
|
|
2570
|
+
- §find-line-anchors **A FIND regex anchors each line**, as READ, EDIT and KILL do ({§read-pattern}, {§edit-pattern}): `^` and `$` are a line's ends in every FIND content match — over entries, log rows, turn sources and a binary channel's bytes — so ```` ```FIND (django/urls/resolvers.py) /^from|^import/ ```` locates the same import lines a READ with that pattern shows, never a false 204.
|
|
2553
2571
|
- §find-candidate-containment **One candidate's crash is that candidate's problem** — arbitrary member content can crash a mimetype handler mid-match (an unbalanced template partial crashed Readability and killed a 1,916-file FIND as a blank 500, #449). `Matcher.matchCandidates` contains a per-candidate handler throw: the candidate drops out exactly like unsupported content, the cause goes to daemon stderr, and only a FIND whose every candidate crashed reports a 415 whose Problem names the first crashing member and handler. The operation's other candidates always answer.
|
|
2554
2572
|
|
|
2555
|
-
- §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
|
|
2573
|
+
- §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. 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.
|
|
2556
2574
|
|
|
2557
2575
|
Resource-authority globs select authorities independently of the path scope.
|
|
2558
2576
|
Matching resources retain their full addresses through pattern matching,
|
|
@@ -2567,6 +2585,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
2567
2585
|
matches the selected channel's content or derivation; path globs select
|
|
2568
2586
|
resources through `(target)` ({§path-glob}).
|
|
2569
2587
|
- §find-fulltext-selection Every matcher operates only over the candidate set selected by `(target)`; indexed matchers do not bypass that selection. `~query` passes the native FTS5 expression to SQLite and ranks matching candidates by ascending BM25, with resource identity breaking ties. Native BM25 uses the shared index's term statistics; candidate visibility, owner, channel and target filters determine which resources can be returned. The ordinary FIND pager selects resources for broad targets or match locations for exact targets: markerless search uses {§markerless-first-page}, `<N>` selects position N and `<N,M>` selects an inclusive range. Fractions are invalid result coordinates, not similarity thresholds. Results expose addressable matched text regions; neither cosine scores nor percentage similarity is invented. Native query-syntax failures return 400 with SQLite's diagnostic; database and implementation failures propagate.
|
|
2588
|
+
- §fts-word-phrase **A word with inner punctuation is the phrase of its tokens.** FTS5 barewords hold only letters, digits, `_` and non-ASCII, so before the query reaches SQLite each word outside a quoted string or `NEAR(…)` group that is not a bareword (with optional leading `^` and trailing `*`) is quoted as a phrase: `~inherited-members` searches `"inherited-members"` — the adjacent tokens `inherited members` — instead of failing as `no such column: members`, and `c++`, `x.y`, `a/b` likewise. FTS5's own syntax passes untouched: `AND`/`OR`/`NOT`/`NEAR`, `+`, quoted phrases, parentheses, and column filters (a word containing `:`, `{` or `}`, or opening with `-`). A column-filter failure keeps SQLite's diagnostic and its recovery says what the filter is and gives the bare-word and `NOT` forms: `` `members:` and `-members` are FTS5 column filters, and the index has one column; to search for a word write it bare, as `~members`, and to exclude one write `NOT` between terms, as `~a NOT members`. ``
|
|
2570
2589
|
- §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
|
|
2571
2590
|
- §find-result-projection **The authored target shape determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsWeightTotal, returnedItemsWeightTotal }`:
|
|
2572
2591
|
|
|
@@ -2654,7 +2673,7 @@ same durable liveness.
|
|
|
2654
2673
|
| Fresh operation/parser failure, without an authored WAIT | Continue before any automatic parking. |
|
|
2655
2674
|
| Neither an authored WAIT nor an eligible completion request ({§kill-conclusion}) | Continue, regardless of earlier replies or live work. |
|
|
2656
2675
|
| Live work and either WAIT or an eligible completion request | Park the same loop; message arrival, child or stream settlement, or stream cadence wakes it. No final-answer body is delivered while joining. |
|
|
2657
|
-
| WAIT without live work | Continue; never invent a future wake. The first such WAIT is an honest yield and its row says only `Nothing is in flight. Continuing.`; a second in the same loop is the model waiting on a wake nothing can send, so its own row instead names what WAIT is for and what to reach for — `WAIT doesn't wait unless there's a child worker or stream to wait on. Use schedule for specific timing decisions.` The correction rides the operation's own result, which is the surface the model is certain to read
|
|
2676
|
+
| WAIT without live work | Continue; never invent a future wake. The first such WAIT is an honest yield and its row says only `Nothing is in flight. Continuing.`; a second in the same loop is the model waiting on a wake nothing can send, so its own row instead names what WAIT is for and what to reach for — `WAIT doesn't wait unless there's a child worker or stream to wait on. Use schedule for specific timing decisions.` The correction rides the operation's own result, which is the surface the model is certain to read. |
|
|
2658
2677
|
| Unanswered messages | Continue. |
|
|
2659
2678
|
| Unobserved operation results, failures, child results or stream conclusions | Continue; the next packet presents them. |
|
|
2660
2679
|
| Eligible completion request with no outstanding messages, live work or unobserved results | Conclude successfully. |
|
|
@@ -2709,8 +2728,8 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2709
2728
|
contains exactly one KILL without a target, scope, matcher or metadata, no hard
|
|
2710
2729
|
parse error or lost boundary, and was not cut at the provider's output allowance.
|
|
2711
2730
|
The operation limit must admit the entire program.
|
|
2712
|
-
SEND, NOTE
|
|
2713
|
-
|
|
2731
|
+
SEND, NOTE and log-targeted KILL may accompany it, as may outside text ({§outside-text});
|
|
2732
|
+
every other operation requires continuation. This tolerance is unadvertised:
|
|
2714
2733
|
model teaching requests KILL alone. Reasoning-side NOTEs remain ordinary notes.
|
|
2715
2734
|
An aside is allowed. After the program settles, {§wait-obligation-matrix} admits the
|
|
2716
2735
|
completion or returns a non-striking continuation/parking receipt explaining the
|
|
@@ -2722,23 +2741,23 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2722
2741
|
completion. New arrivals still guard the terminal transition atomically
|
|
2723
2742
|
({§completion-defers-to-messages}); an arrival concurrent with an accepted reply
|
|
2724
2743
|
remains unanswered and keeps the loop running. No implicit successful exit exists.
|
|
2725
|
-
- §
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
the
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2744
|
+
- §outside-text **Text outside the operations is the turn's `outside` source: stored, weighed, never a row.**
|
|
2745
|
+
The spans {§response-text} supplies are stored verbatim as one immutable `outside` turn source
|
|
2746
|
+
per admitted emission, in source order joined by a blank line ({§turn-source-resources}); no
|
|
2747
|
+
operation is minted for them, a repetitive or length-cut response stays one source, and nothing
|
|
2748
|
+
filters what is stored. The model hears only the weight: the next packet's Notices section
|
|
2749
|
+
carries `outside_text: N tokens emitted outside OPs. Discarded.`, N by {§tokenomics-agnostic-ruler};
|
|
2750
|
+
the text itself never enters a packet, is never delivered and never concludes.
|
|
2751
|
+
{§empty-turn} still strikes a turn that holds only text, with unchanged reasoning recovery
|
|
2752
|
+
({§reasoning-empty-turn-read}), reply accounting and completion rules. A log-entry heading in
|
|
2753
|
+
outside text still rejects the attempt ({§fabricated-log-entry}); an unfenced operation line is
|
|
2754
|
+
not response text ({§unfenced-operation}) and so never reaches the source; `KnownToxins` guards
|
|
2755
|
+
only the read-back ({§reasoning-empty-turn-read}). Clients receive the text once through
|
|
2756
|
+
`outside/event` ({§notifications-outside-event}, {§agui-outside-text}); FORK snapshots the
|
|
2757
|
+
source with the turn's others; the digest names its weight. It has no address: there is no
|
|
2758
|
+
`outside://` scheme, and exact emissions remain at `ops://`. This is evidence, not an alternate
|
|
2759
|
+
authoring format: the `plurnk.md` requirement to use only valid Plurnk OPs remains, and
|
|
2760
|
+
{§response-text} alone owns which bytes are operations, quotations or outside text.
|
|
2742
2761
|
- §loop-answer **A loop's address is what it said.** READ `ops://<worker>/<loop>` resolves to
|
|
2743
2762
|
the latest reply the loop gave to the message that started it: the body of a SEND
|
|
2744
2763
|
or accepted final KILL that answered that message. A running loop without one is 425; a loop that
|
|
@@ -2748,10 +2767,9 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2748
2767
|
remains that turn's emission. A concluded child's `loop_termination` row to its parent
|
|
2749
2768
|
READs this same loop resource. Witness: `test/intg/loop-answer.test.ts`.
|
|
2750
2769
|
- §empty-turn **No authored response operation is a recoverable turn, never completion.**
|
|
2751
|
-
Count parsed response operations before
|
|
2752
|
-
|
|
2753
|
-
sources and count one progress-contract strike, whether or not the turn carried text
|
|
2754
|
-
({§response-text-note}). The strike sends no notice of its own: the turn records one `_plurnk`
|
|
2770
|
+
Count parsed response operations before reasoning NOTEs join them; neither they nor outside
|
|
2771
|
+
text ({§outside-text}) enter the count. When none exist and no boundary was lost, retain the turn
|
|
2772
|
+
and its raw sources and count one progress-contract strike, whether or not the turn carried text. The strike sends no notice of its own: the turn records one `_plurnk`
|
|
2755
2773
|
error row, `422` `The turn performed no operation.`, which rides the next packet's errors like
|
|
2756
2774
|
any failure ({§operation-result-uniform-error-channel}), and its reasoning is read back to the
|
|
2757
2775
|
model under {§reasoning-empty-turn-read}; the threshold terminal still says why ({§engine-rails}).
|
|
@@ -2777,8 +2795,8 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2777
2795
|
empty one. Attachment is owned by the one terminal seam.
|
|
2778
2796
|
- §metadata-ignored **Options a scheme does not take are dropped, not refused.** A READ, FIND,
|
|
2779
2797
|
EDIT or KILL carrying `[metadata]` for a scheme whose manifest takes none runs without it,
|
|
2780
|
-
and the packet carries one `metadata_ignored` notice naming the scheme (
|
|
2781
|
-
|
|
2798
|
+
and the packet carries one `metadata_ignored` notice naming the scheme (a warning,
|
|
2799
|
+
never a refusal). The `pattern` option never reaches this
|
|
2782
2800
|
path; it is lifted into the matcher at parse time ({§matcher-option}). SEND recipients,
|
|
2783
2801
|
executions, WORK and FORK own their input and receive it whole ({§send-resource-attachments},
|
|
2784
2802
|
{§env-option}); a key they do not take is their own 400.
|
|
@@ -2858,9 +2876,7 @@ the slot contract in its recovery — the resource is the program and the body i
|
|
|
2858
2876
|
a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
|
|
2859
2877
|
names the working directory only when it is not the project root, and then in the
|
|
2860
2878
|
model's own project-relative form ({§fs-namespace}: the root is the model's `/`, so it
|
|
2861
|
-
is never rendered, and no receipt or Problem carries a host-absolute path —
|
|
2862
|
-
batch of 2026-08-29 showed the absolute `cwd` copied back into the target slot as
|
|
2863
|
-
`(cwd: /host/path)`). The `(path)` is a program — a script for an interpreter, a tool name for a tool
|
|
2879
|
+
is never rendered, and no receipt or Problem carries a host-absolute path). The `(path)` is a program — a script for an interpreter, a tool name for a tool
|
|
2864
2880
|
family — and neither a command nor a working directory is ever a target. The default
|
|
2865
2881
|
shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
|
|
2866
2882
|
|
|
@@ -2873,6 +2889,10 @@ streams are eight hex digits) is the writer's name for the run: with a body, the
|
|
|
2873
2889
|
body runs as if targetless; without one, the source read refuses as before. A
|
|
2874
2890
|
real stream id is always the program source.
|
|
2875
2891
|
|
|
2892
|
+
§exec-target-documentation **Generated reference is never a program.** A resource target under
|
|
2893
|
+
`worker:///_plurnk/` — the executor and scheme documentation the harness generates — is refused
|
|
2894
|
+
at admission, 400 `target-is-documentation`, before any source is realized or run: `` `worker:///_plurnk/plurnk/sh.md` is reference documentation the harness generated, not a program; sh cannot run it. `` With a body the recovery is `Drop the target and keep the command: the opening fence line is sh alone, with the command lines beneath it.`; without one it points to READ for the documentation and to the program's own path or a targetless heading to run something. Admitting it realized the markdown as a host temporary file that a sandboxed runtime could not open (`cannot open /tmp/plurnk-exec-….md`), and ran markdown where it could.
|
|
2895
|
+
|
|
2876
2896
|
§exec-tool-fall-through **A tool run as a shell command is named at the failure
|
|
2877
2897
|
site.** A bare shell command whose program is the name of a tool published by
|
|
2878
2898
|
another enabled runtime (`brave_web_search {…}` under the default shell) exits
|
|
@@ -2915,14 +2935,29 @@ its filename, extension, sibling imports, and source-relative assets remain
|
|
|
2915
2935
|
intact. This neither bypasses admission nor changes the executor's working
|
|
2916
2936
|
directory. A disappeared native source fails; it never runs a stale projection.
|
|
2917
2937
|
Other resources and derived channels supply standalone source, not a filesystem:
|
|
2918
|
-
Core creates one
|
|
2919
|
-
process- and database-coordinate-independent identity. No
|
|
2920
|
-
and no relative-resource filesystem is emulated. The
|
|
2938
|
+
Core creates one file under the scratch directory ({§exec-scratch-directory}), preserving the
|
|
2939
|
+
source extension, with an exclusive, process- and database-coordinate-independent identity. No
|
|
2940
|
+
sibling tree is copied and no relative-resource filesystem is emulated. The file lives through
|
|
2921
2941
|
the executor run and core removes it after the subscription's terminal result
|
|
2922
2942
|
has settled. A removal failure is reported to daemon diagnostics with its
|
|
2923
2943
|
complete cause; it cannot rewrite the execution result, stream state, or
|
|
2924
2944
|
completion wake.
|
|
2925
2945
|
|
|
2946
|
+
§exec-scratch-directory **A realized source is readable by its executor for the execution's
|
|
2947
|
+
lifetime.** The standalone file is written under the one scratch directory
|
|
2948
|
+
`PLURNK_SERVICE_EXEC_SCRATCH` names: empty, it is `$XDG_RUNTIME_DIR/plurnk` when
|
|
2949
|
+
`XDG_RUNTIME_DIR` is set and the platform temporary directory otherwise; an explicit value is
|
|
2950
|
+
an absolute directory (`~` expands), and a relative one fails at boot by name. The directory
|
|
2951
|
+
is created on first use with mode `0700`; each file is created exclusively with mode `0600`
|
|
2952
|
+
under a unique name. An executor that runs in another filesystem namespace — a container that
|
|
2953
|
+
mounts only the repository — is given a directory both sides can see by pointing the knob at
|
|
2954
|
+
it. Admission ensures the directory: one the daemon cannot create or write refuses the
|
|
2955
|
+
execution, 400 `scratch-unavailable`, whose detail names the directory, the knob and the
|
|
2956
|
+
failure code, and whose recovery has the operator point the knob at a writable absolute
|
|
2957
|
+
directory the executor can also read and, meanwhile, has the writer target a program file the
|
|
2958
|
+
executor can reach or run the command beneath a targetless heading. An execution never fails
|
|
2959
|
+
mid-run for this reason.
|
|
2960
|
+
|
|
2926
2961
|
Loop-flag authority follows the selected runtime's declaration:
|
|
2927
2962
|
|
|
2928
2963
|
| Target realization | Schemes that must be active |
|
|
@@ -3050,7 +3085,7 @@ two states and no others:
|
|
|
3050
3085
|
|
|
3051
3086
|
| state | what the model receives |
|
|
3052
3087
|
|---|---|
|
|
3053
|
-
| active | nothing in the Log. The `## Delegation` stream 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, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing
|
|
3088
|
+
| active | nothing in the Log. The `## Delegation` stream 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, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing. |
|
|
3054
3089
|
| terminal | ONE `origin=_plurnk` READ at the execution's channel address, born visible, that is exactly a markerless READ of the channel — its bounded first page ({§read-selection-projection}, the whole channel when it fits, the channel's own mimetype), the `range` or `region`, terminal status and Problem, `terminal: true`, and any producer-supplied integer `exitCode`. The packet writes the read resource as its operand, exactly as an explicit READ does ({§log-address-metadata}). |
|
|
3055
3090
|
|
|
3056
3091
|
§stream-observation-result **One liveness fact.** The durable READ result owns
|
|
@@ -3060,11 +3095,13 @@ observations alike, independently of mimetype: `active` gives false, `closed` or
|
|
|
3060
3095
|
projection preserves that Boolean, any included
|
|
3061
3096
|
integer `exitCode`, and a producer's `page` receipt ({§executor-page-receipt}), even for an empty body. An automatic observation's atomic
|
|
3062
3097
|
publication transition consumes the same result flag; private log attributes
|
|
3063
|
-
retain only the publication offset, not a second liveness value.
|
|
3098
|
+
retain only the publication offset, not a second liveness value. A closed channel
|
|
3099
|
+
publishes only once subscription settlement has installed its terminal result;
|
|
3100
|
+
until then the stream is still in flight and publishes on the turn after it settles.
|
|
3064
3101
|
|
|
3065
3102
|
§exec-concurrency **Bounded admission per workspace (#389).** At most
|
|
3066
|
-
`PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (
|
|
3067
|
-
|
|
3103
|
+
`PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (`-1`
|
|
3104
|
+
unbounded); the scope is the workspace, so neither delegation nor later turns
|
|
3068
3105
|
bypass it and no other workspace can starve it. Every admitted execution still creates its
|
|
3069
3106
|
entry, channels, and open subscription before its receipt returns, so queued work is
|
|
3070
3107
|
cancellable, restart-reconcilable, completion-gated, and observed through the ordinary
|
|
@@ -3098,9 +3135,7 @@ channel that holds content, and an empty sibling channel is a fact on that row
|
|
|
3098
3135
|
(`channels: {"#stderr": 0}`), never a row of its own; only a stream that printed
|
|
3099
3136
|
nothing on any channel lands one bodyless conclusion row, on its default
|
|
3100
3137
|
channel, whose terminal fact, causal execution link, and available exit code make
|
|
3101
|
-
completion explicit without invented narration
|
|
3102
|
-
per-channel empty row was "a useless packet bomb" — 131 of 298 conclusion rows
|
|
3103
|
-
in the candidate4 run). A skipped channel's publication is still marked
|
|
3138
|
+
completion explicit without invented narration. A skipped channel's publication is still marked
|
|
3104
3139
|
terminal, so the stream's termination is delivered and never left pending. KILL may curate
|
|
3105
3140
|
that log row without rewinding the cursor or publishing the terminal result
|
|
3106
3141
|
again; the exact terminal result and channel content remain READable at the
|
|
@@ -3181,7 +3216,7 @@ body prefixes.
|
|
|
3181
3216
|
key WORK or FORK does not take is refused the same way. The durable row redacts the block
|
|
3182
3217
|
wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
|
|
3183
3218
|
provenance as the modifier's.
|
|
3184
|
-
- §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env
|
|
3219
|
+
- §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits an executor fence, optionally followed by WAIT; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
|
|
3185
3220
|
- §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address.
|
|
3186
3221
|
|
|
3187
3222
|
| Input / effect | Consumer behavior |
|
|
@@ -3204,7 +3239,7 @@ body prefixes.
|
|
|
3204
3239
|
|
|
3205
3240
|
- **Client disposition** ({§methods-proposal-resolve}) — a client interface delivers accept, reject, or cancel; AG-UI uses standard resume entries ({§agui-proposal-resolve}).
|
|
3206
3241
|
- **Loop disposition** ({§proposal-disposition}) — core applies the exact automatic accept/reject before observational subscribers run; automatic policy is not an event listener or client fallback.
|
|
3207
|
-
- §proposal-timeout-cancels **Timeout is OPT-IN;
|
|
3242
|
+
- §proposal-timeout-cancels **Timeout is OPT-IN; an empty deadline WAITS** - an empty `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` means a pending proposal - a file edit awaiting review - waits indefinitely for its human: absence is not an answer, so the service does not synthesize a cancellation. A finite positive millisecond value establishes the bound; then elapsing synthesizes `cancel` (outcome `timeout`), server-side, needing no client. Every other explicit value fails at the proposal lifecycle owner and terminalizes an already-written proposal rather than silently choosing an indefinite wait. Indefinite is with respect to the clock alone: the wait ends with its loop. A loop whose signal aborts — its own timeout, `loop.cancel`, worker `KILL` — settles every proposal it is holding through {§proposal-cancel-aborts}, carrying the abort's reason as the outcome, because a cancelled loop is not a loop awaiting a decision.
|
|
3208
3243
|
|
|
3209
3244
|
**The decision drives a one-way state transition** on `log_entries.state` (resolution is idempotent — `WHERE state='proposed'`, so a second resolution 404s):
|
|
3210
3245
|
|
|
@@ -3214,7 +3249,9 @@ body prefixes.
|
|
|
3214
3249
|
| §proposal-reject-fails reject | `failed` | 400 | `rejected` | none — the action did not occur. |
|
|
3215
3250
|
| §proposal-cancel-aborts cancel | `cancelled` | 499 | `loop_aborted` | none — the loop is abandoning. |
|
|
3216
3251
|
|
|
3217
|
-
§proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400).
|
|
3252
|
+
§proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400).
|
|
3253
|
+
|
|
3254
|
+
§proposal-harness-settlement **A settlement the harness itself decided names its condition and its recovery.** Nobody attending, no answer before a deadline, a loop or daemon ending while the proposal waited: each states that condition and an exit in its detail and `recovery`, because a reviewer's outcome is a reviewer's word but these have no author present to explain them; the one-word token stays as the forensic `outcome` either way ({§proposal-outcome-terse-error}).
|
|
3218
3255
|
|
|
3219
3256
|
§proposal-proposed-hidden **A proposed row is invisible until it resolves.** A `state='proposed'` / 202 row is withheld from both packet materialization and `log/entry`; it surfaces exactly once after resolution, carrying its terminal status — models and clients see outcomes, never pending proposals.
|
|
3220
3257
|
|
|
@@ -3425,19 +3462,19 @@ SQLite (`node:sqlite`) with WAL mode and STRICT tables. Hand-written DDL; CI-ali
|
|
|
3425
3462
|
|
|
3426
3463
|
No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, explicit `NOT NULL`, indexed query paths, deliberate FK `ON DELETE`/`ON UPDATE`, `WITHOUT ROWID` where access pattern warrants, generated columns, FTS5.
|
|
3427
3464
|
|
|
3428
|
-
| Concern |
|
|
3465
|
+
| Concern | Rule |
|
|
3429
3466
|
|---|---|
|
|
3430
|
-
| §db-schema-baseline Baseline | `migrations/`
|
|
3431
|
-
|
|
|
3432
|
-
|
|
|
3433
|
-
|
|
|
3467
|
+
| §db-schema-baseline Baseline | Versions 1–8 of `migrations/` are the released baseline, as domain chapters — `001_workspaces`, `002_workers`, `003_loops`, `004_inference`, `005_entries`, `006_log`, `007_subscriptions`, `008_interactions` — each one `MIGRATE` block whose version is the file's numeric prefix. They create the shape 1.21.1 shipped: tables, indexes, views, the constraint triggers that are a table's invariants (guards that only `RAISE`), and a view's `INSTEAD OF` write path. No migration holds an `INIT` block or a trigger that writes a row. |
|
|
3468
|
+
| §db-migrations Evolution | A released version is frozen: only its comments may change. Every shape change is one new file at the next version (`009_effort` renames the #877 columns), applied by sqlrite above the database's `PRAGMA user_version`, ascending, each in its own transaction with its version bump. A fresh database takes the same path as an existing one. Each migration carries upgrade coverage: `test/intg/schema-baseline.test.ts` pins the released shape's fingerprint and migrates a released database, asserting its rows survive. A process trigger's change needs no migration: its `INIT` block re-declares it on the next open ({§db-process-triggers}). A table anything references cannot be rebuilt in a migration: foreign keys stay enforced inside the migration transaction, so the drop cascades through its children (`011_settled.sql`); such a table evolves by adding columns or redeclaring its guard triggers. Only an unreferenced table is rebuilt (`010_outside_text.sql`). |
|
|
3469
|
+
| §validation-topology Where an invariant is enforced | The SQL core owns each invariant: a chapter's CHECK constraints and guard triggers are its one statement, and a rule two tables share is the same expression over each column (`entry_channel_producer_result_contract` and `subscriptions_result_contract_update` hold the settled-result rule as one text, `011_settled`). Contracts (JSON Schema) are enforced at the gates: a scheme's result entering core (`Results` in plurnk-schemes) and the wire leaving to clients (plurnk-agui's `Validator` calls). Everything between trusts core and the gates and carries no defensive re-validation: a result read back from a row is parsed, never re-asserted. Witness: `test/intg/validation-topology.test.ts` applies one corpus of settled results to the gate, to chapter 5 and to chapter 7 and asserts the three agree on every row. |
|
|
3470
|
+
| Open failure | A missing table or column after migration means the file's shape disagrees with its version: a database from a newer release, or one from an unreleased development build. The daemon refuses to open it and names both remedies. |
|
|
3434
3471
|
| §db-process-triggers Processes beside their owners | A trigger that writes rows — a cascade, a capture, an ambient event, a publication cursor, a landed curation — is a process, not shape. It is declared as an `-- INIT: <trigger name>` block in the `.sql` file beside the statements that fire it (`ambient.sql` for the ambient feed, `LoopLifecycle.sql`, `Turn.sql`, `Engine.sql` for model calls, `_entry-crud.sql`, `Log.sql`, `ChannelWrite.sql`), as `DROP TRIGGER IF EXISTS` then `CREATE TRIGGER`, so the definition is current on every open of a database whose shape is current. `MIGRATE` always precedes `INIT` and `INIT` runs on the writer only, so a process may reference any table regardless of file order and never runs on the read pool. `test/intg/schema-composition.test.ts` fails on a baseline trigger that writes, an `INIT` trigger that only guards, a block not named after its trigger or not dropping first, and a live trigger set that differs from the declared set after a first and a second open. |
|
|
3435
3472
|
| §db-fk-indexes Foreign-key check paths | Every foreign-key column a delete, cascade, or parent replacement can check carries an index (partial where the column is nullable), and no registry statement's plan scans a growing table: `test/intg/schema-query-plans.test.ts` runs `EXPLAIN QUERY PLAN` over every `-- PREP` statement against the baseline and fails on a `SCAN` of a growing table, except statements that read a whole table by design (digest, startup recovery, whole-workspace listings, scheduled-loop claims). An index claim is a plan, never a grep of index names. |
|
|
3436
3473
|
| §db-index-owners Every index has an owner | An explicit index earns its place one of three ways: a registry statement's plan uses it, its leading column is a foreign key whose check it serves, or it enforces uniqueness. The same test fails on any other index, naming it: an index nobody reads is a write on every insert. Duplicates of a `UNIQUE` constraint's own index and sort-only indexes no plan selects were removed on this rule; a column no statement reads (`symbol_refs.col`, `ambient_events.created_at`) is not stored. |
|
|
3437
3474
|
| §db-maintenance-optimize Statistics at shutdown | The daemon's last database step before the caller closes SQLite is `PRAGMA optimize` on the writer (`maintenance_optimize`), so `sqlite_stat1` reflects tables the connection planned against, bounded by SQLite's own analysis limit; a failure there is a reported shutdown error, never silent. Retention runs before it under the operator's policy ({§retention-policy}) and ends with a WAL truncation ({§db-space-reclamation}); no periodic `ANALYZE` runs. |
|
|
3438
|
-
| §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental
|
|
3439
|
-
| §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS
|
|
3440
|
-
| §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon construction (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (
|
|
3475
|
+
| §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental` or `none`) names the mode the daemon keeps its file in. At start, before any drain, a database in another mode is converted (set the mode, one `VACUUM`, which rewrites the file and needs free disk about its size) and the journal says so with page counts before and after. Under `incremental`, every retention pass ends by stepping `PRAGMA incremental_vacuum` to completion once free pages reach `PLURNK_SERVICE_RECLAIM_MIN_FREE_BYTES` (0 = every pass), and reports `reclaimedPages`; below the floor, free pages stay for SQLite to reuse. Under `none` the file never shrinks and freed pages are reused. No operator step is involved beyond the knobs. The WAL stays bounded by SQLite's automatic checkpoint (#764). |
|
|
3476
|
+
| §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS`. Witnesses: `test/intg/retention.test.ts`, `test/intg/entries.test.ts`, `test/intg/fulltext-index.test.ts`. |
|
|
3477
|
+
| §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon construction (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (-1 = no age limit) retire a completed turn's packet composition (`turn_sections`, {§packet-items}) once it is beyond the newest N packet-bearing turns of its loop or older than the age; the turn, its bag, its log rows and its accounting stay, and an open turn is never retired. `PLURNK_SERVICE_COLLECT_PACKET_ITEMS` collects items no composition references. `PLURNK_SERVICE_COLLECT_CONTENTS` collects stored bodies nothing holds ({§content-store}), after the collectors that release them. `PLURNK_SERVICE_COLLECT_DERIVATIONS` collects derivations no channel, turn source, or log row cites — superseded editions — with their symbols (cascade) and their full-text shadow (`derivations_delete_fts`, a process trigger beside the FTS statements, on every delete path). `PLURNK_SERVICE_RETAIN_RESPONSE_TURNS` and `PLURNK_SERVICE_RETAIN_RESPONSE_MS` retire a settled call's response body (`model_call_responses`) once it is beyond the newest N body-bearing calls of its loop or its turn is older than the age; the call's identity, failure, capacity, admission and accounting stay, and the digest renders such a call request-only. The durable record is kept; packets and response bodies — transient evidence — age out, so a daemon left running for months stops growing (#788). A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
|
|
3441
3478
|
|
|
3442
3479
|
- DDL = storage truth; JSON Schemas = wire truth. They are allowed to differ where ergonomics demand.
|
|
3443
3480
|
- §entry-identity-no-null **Identity components are never NULL.** `(workspace_id, scheme, authority, pathname)` is a unique key. `workspace_id` references the workspace directly with cascading deletion. Namespace schemes use empty authority; resource schemes retain their canonical authority. File members use nonempty `scheme="file"` and render as bare paths. Registration refuses `storedScheme: null`.
|
|
@@ -3648,14 +3685,15 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
|
|
|
3648
3685
|
| Var | Purpose |
|
|
3649
3686
|
|---|---|
|
|
3650
3687
|
| `PLURNK_SERVICE_DB_PATH` | SQLite file path; an explicit non-empty value overrides the derived default. |
|
|
3688
|
+
| `PLURNK_SERVICE_SHARE_FOLDER` | Parent of the shares written when no folder is named ({§share-folder}); empty is `$XDG_STATE_HOME/plurnk/shares`. |
|
|
3651
3689
|
| §operator-config-shared-keys `PLURNK_HOST`, `PLURNK_PORT` | The listener's bind address and TCP port — THE client surface, the AG-UI+ listener the plurnk-agui module binds at boot; production is single-listener. **A key the daemon and its clients both read has a shared owner**: `@plurnk/plurnk-contracts` declares these two and the optional `PLURNK_AGUI_URL` on its own panel, the one package every side depends on. The daemon folds it like any installed member's, a client folds it beneath its own, and so neither holds the other's default. The service's `--host` and `--port` flags are generated from that panel. |
|
|
3652
3690
|
| §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
|
|
3653
3691
|
| §operator-config-file-create-scope `PLURNK_SERVICE_FILE_CREATE_SCOPE` | Hard file-creation ceiling: `none < root < namespace`. `none` denies new filesystem files, `root` admits only paths inside `project_root`, and `namespace` also admits canonical outside-root paths. Existing-member writes are unaffected. |
|
|
3654
3692
|
| `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` | Byte ceiling in `1..104857600` for one workspace-file snapshot ({§membership-materialization-limit}). |
|
|
3655
|
-
| `PLURNK_SERVICE_MAX_TURNS` | Operator
|
|
3656
|
-
| `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap
|
|
3693
|
+
| `PLURNK_SERVICE_MAX_TURNS` | Operator model-call **ceiling** — `-1` = no cap; a positive value clamps `runLoop({maxTurns})`. The durable worker-tree budget includes descendant calls, BARE, and park/resume under {§turn-cap-counts-the-tree}; non-model chronology consumes none. |
|
|
3694
|
+
| `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap — every generated op dispatches. A positive value caps dispatched actions: overflow ops drop with one durable `max-commands-exceeded` error row on the next packet. The final disposition always dispatch. Tightened per workspace via `settings.maxCommands` (min wins). |
|
|
3657
3695
|
| §operator-config-loop-timeout `PLURNK_SERVICE_LOOP_TIMEOUT` | Positive ms of cumulative active execution per loop ({§loop-execution-allowance}); excludes parked/queued time. Snapshotted on first execution, retained across wakes. Exhaustion aborts in-flight work and terminates `504 loop_timeout`, including a stuck provider call. |
|
|
3658
|
-
| `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms
|
|
3696
|
+
| `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms of recovery after the first recoverable provider failure; `0` disables reissue. Expiry parks attended loops, concludes unattended loops, or returns BARE's failure under {§provider-recovery}. |
|
|
3659
3697
|
| `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF` | First recovery delay (ms); doubles per failure up to `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX` ({§provider-recovery}). |
|
|
3660
3698
|
| `PLURNK_SERVICE_MAX_STRIKES` | Consecutive turn-contract strike threshold ({§engine-rails}). |
|
|
3661
3699
|
| `PLURNK_SERVICE_EMISSION_ATTEMPTS` | Completed provider responses allowed beneath one engine turn before frame admission is exhausted. Bounded interior operation errors are admitted without spending this budget. Exhaustion contributes one frame-contract strike under {§invalid-emission-attempts}. |
|
|
@@ -3671,6 +3709,7 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
|
|
|
3671
3709
|
| `PLURNK_SERVICE_FILES_ITEMS` | Turn-0 catalog preview. Folder-capable schemes render a one-level `*` map with `dir/**` rollups; kernel docs remain recursive and explicitly complete. `-1` = markerless first pages; positive `N` explicitly caps only file-map rows; `0` / unset = off ({§actor-boundary-catalog-preview}). |
|
|
3672
3710
|
| `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` | Ceiling for a model's `members` definitions in the lattice `none < root < namespace`; `none` refuses every model definition ({§members-model-scope}). |
|
|
3673
3711
|
| `PLURNK_SERVICE_EXEC_CONCURRENCY` | Executions admitted at once per workspace; the rest queue FIFO with `202 queued` receipts; `-1` unbounded ({§exec-concurrency}). |
|
|
3712
|
+
| `PLURNK_SERVICE_EXEC_SCRATCH` | Directory a standalone execution source is written to for its run; empty derives `$XDG_RUNTIME_DIR/plurnk`, else the platform temporary directory ({§exec-scratch-directory}). |
|
|
3674
3713
|
| `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` | Finite positive milliseconds before cancellation with outcome `timeout`; empty waits, and every other explicit value fails ({§proposal-timeout-cancels}). |
|
|
3675
3714
|
| §operator-config-worker-warm `PLURNK_SERVICE_WORKSPACE_WARM_MS` | Milliseconds a lease-free workspace Functionality snapshot remains warm; `0` cools without grace and `-1` disables time-based cooling ({§module-workspace-residency}). |
|
|
3676
3715
|
| `PLURNK_SERVICE_WORKSPACE_WARM_MAX` | Maximum lease-free workspace Functionality snapshots retained process-wide; `0` retains none and `-1` disables the idle-LRU bound ({§module-workspace-residency}). |
|
|
@@ -3679,55 +3718,22 @@ Every core knob listed is enforced at its owning read site; `.env.defaults` is t
|
|
|
3679
3718
|
|
|
3680
3719
|
**Two override semantics — ceiling vs default.** Which kind a var is determines what "override" means across the cascade:
|
|
3681
3720
|
|
|
3682
|
-
- **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1`
|
|
3721
|
+
- **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1` = no cap; a positive value caps the per-call request). The sandbox/cost guarantee: the operator caps it; no client widens it.
|
|
3683
3722
|
- **Default** (explicit-wins) — a fallback the most-specific setter replaces freely: `PLURNK_MODEL` (a `runLoop({selector})` request overrides it) and the config-time vars (`HOST` / `PORT` / `DB_PATH`).
|
|
3684
3723
|
|
|
3685
|
-
§operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
|
|
3686
|
-
test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
|
|
3687
|
-
the policy renders in exactly one packet section. Every other tier runs the
|
|
3688
|
-
test cascade, so shipped-default regressions are otherwise invisible by
|
|
3689
|
-
construction.
|
|
3690
|
-
|
|
3691
3724
|
§operator-config-flag-parity The companion **flag-parity** check binds code and
|
|
3692
3725
|
template both ways: every `PLURNK_SERVICE_*` the service reads has a
|
|
3693
3726
|
`.env.defaults` line — a floor, a `--flag`, and a legend entry — and every
|
|
3694
3727
|
declared `PLURNK_SERVICE_*` is read. A half-landed rename therefore fails a test
|
|
3695
3728
|
instead of a user's boot, and a dead knob cannot ship.
|
|
3696
3729
|
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
| Owner | Configuration |
|
|
3700
|
-
|---|---|
|
|
3701
|
-
| `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
3702
|
-
| Live/demo scripts | The repository policy path and runner topology. |
|
|
3703
|
-
| Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
|
|
3704
|
-
| Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
|
|
3705
|
-
| `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
|
|
3706
|
-
|
|
3707
|
-
The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
|
|
3708
|
-
`AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
|
|
3709
|
-
bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
|
|
3710
|
-
none implicitly inherit the daily-driving policy. This does not disable project
|
|
3711
|
-
`AGENTS.md` guidance or modify the operator's file.
|
|
3712
|
-
|
|
3713
|
-
The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
|
|
3730
|
+
Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
|
|
3714
3731
|
|
|
3715
|
-
§operator-config-
|
|
3716
|
-
not another configuration source.** The live/demo `:zeropin` scripts load the
|
|
3717
|
-
ordinary environment cascade, then the test floor removes operator model tuning
|
|
3718
|
-
before assembled package defaults fill unset values:
|
|
3732
|
+
External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
|
|
3719
3733
|
|
|
3720
|
-
|
|
3721
|
-
|-----------------------------------------------------------|--------------------|
|
|
3722
|
-
| Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
|
|
3723
|
-
| Alias-specific output and reasoning budgets | Remove |
|
|
3724
|
-
| Model selection, routes, and credentials | Retain |
|
|
3725
|
-
| Bare shipped generation-envelope defaults | Retain |
|
|
3726
|
-
| Unrelated environment | Retain |
|
|
3734
|
+
§operator-config-cli-flags **Admin CLI flags derive only from the service package's `.env.defaults`.** Every `PLURNK_*` declared there becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). A comment immediately above the declaration becomes its `-h` description. Installed plugin defaults join the environment floor and catalog but do not implicitly expand the service executable's flag surface.
|
|
3727
3735
|
|
|
3728
|
-
|
|
3729
|
-
is red because provider capacity did not derive for
|
|
3730
|
-
a fresh-user configuration.
|
|
3736
|
+
### Loop limits
|
|
3731
3737
|
|
|
3732
3738
|
§turn-cap-counts-the-tree **The turn ceiling is the worker tree's budget of model
|
|
3733
3739
|
calls.** The owner is the current loop of the topmost ancestor-or-self worker that has
|
|
@@ -3740,7 +3746,9 @@ terminal ({§loop-terminals}) when the ceiling is met; a BARE beyond the budget
|
|
|
3740
3746
|
429 `max-turns` before any provider call, so one turn cannot spend past it with a batch. A
|
|
3741
3747
|
child loop inherits the value and binds the same count.
|
|
3742
3748
|
|
|
3743
|
-
§operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS`
|
|
3749
|
+
§operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS` at `-1` is no cap; when an operator sets a positive value, the per-call request is `min()`-capped against it. Other termination rules remain independent ({§loop-terminals}).
|
|
3750
|
+
|
|
3751
|
+
### Workspace settings
|
|
3744
3752
|
|
|
3745
3753
|
§operator-config-workspace-settings **Client open-context (per workspace).**
|
|
3746
3754
|
`workspace.create({ settings })` accepts only the following fields, normalizes
|
|
@@ -3775,18 +3783,55 @@ leak into another.
|
|
|
3775
3783
|
floor — the tightest — admitting a plan and disposition with zero actions.
|
|
3776
3784
|
- §operator-config-workspace-git `settings.git` (`false`) **denies** git for the workspace (`PLURNK_SERVICE_GIT_ALLOWED` AND workspace) — the client opts its workspace out of git membership and working-tree status; it can never re-enable git past the operator's service-wide lockout.
|
|
3777
3785
|
- §operator-config-workspace-file-create-scope `settings.fileCreateScope` narrows `PLURNK_SERVICE_FILE_CREATE_SCOPE` by the ordered lattice `none < root < namespace`; a workspace may disable creation or confine a namespace-enabled service to its root, but never widen the operator's ceiling. Unknown service values fail configuration validation and unknown workspace values fail `workspace.create`.
|
|
3778
|
-
-
|
|
3786
|
+
- `settings.membersModelScope` narrows `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}).
|
|
3779
3787
|
- §operator-config-workspace-capabilities `settings.capabilities` is one
|
|
3780
3788
|
workspace-stable `CapabilityPolicy` layer in {§capability-admission}. It may
|
|
3781
3789
|
narrow any registered operation, scheme, runtime, tool, access class, or
|
|
3782
3790
|
trait through the canonical `only`/`deny` selectors; it cannot register a
|
|
3783
3791
|
capability or restore one removed by the service layer.
|
|
3784
3792
|
|
|
3785
|
-
|
|
3793
|
+
### Gate profiles
|
|
3786
3794
|
|
|
3787
|
-
|
|
3795
|
+
§operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
|
|
3796
|
+
test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
|
|
3797
|
+
the policy renders in exactly one packet section. Every other tier runs the
|
|
3798
|
+
test cascade, so shipped-default regressions are otherwise invisible by
|
|
3799
|
+
construction.
|
|
3788
3800
|
|
|
3789
|
-
§operator-config-
|
|
3801
|
+
§operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, ambient MCP selections and schedules disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. Configuration with a narrower or variable owner stays outside it:
|
|
3802
|
+
|
|
3803
|
+
| Owner | Configuration |
|
|
3804
|
+
|---|---|
|
|
3805
|
+
| `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
3806
|
+
| Live/demo scripts | The repository policy path and runner topology. |
|
|
3807
|
+
| Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
|
|
3808
|
+
| Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
|
|
3809
|
+
| `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
|
|
3810
|
+
|
|
3811
|
+
The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
|
|
3812
|
+
`AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
|
|
3813
|
+
bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
|
|
3814
|
+
none implicitly inherit the daily-driving policy. This does not disable project
|
|
3815
|
+
`AGENTS.md` guidance or modify the operator's file.
|
|
3816
|
+
|
|
3817
|
+
The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
|
|
3818
|
+
|
|
3819
|
+
§operator-config-zero-pin-gate **Zero-pin is a counterfactual real-model gate,
|
|
3820
|
+
not another configuration source.** The live/demo `:zeropin` scripts load the
|
|
3821
|
+
ordinary environment cascade, then the test floor removes operator model tuning
|
|
3822
|
+
before assembled package defaults fill unset values:
|
|
3823
|
+
|
|
3824
|
+
| Configuration family | Zero-pin treatment |
|
|
3825
|
+
|-----------------------------------------------------------|--------------------|
|
|
3826
|
+
| Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
|
|
3827
|
+
| Alias-specific output and reasoning budgets | Remove |
|
|
3828
|
+
| Model selection, routes, and credentials | Retain |
|
|
3829
|
+
| Bare shipped generation-envelope defaults | Retain |
|
|
3830
|
+
| Unrelated environment | Retain |
|
|
3831
|
+
|
|
3832
|
+
The floor reports every removed key. A gate that succeeds only with those pins
|
|
3833
|
+
is red because provider capacity did not derive for
|
|
3834
|
+
a fresh-user configuration.
|
|
3790
3835
|
|
|
3791
3836
|
---
|
|
3792
3837
|
|
|
@@ -3946,8 +3991,8 @@ definition for the submitting client or worker.
|
|
|
3946
3991
|
capability-aware operations, scoped module actions, and retained provider work
|
|
3947
3992
|
lease the workspace's Functionality. Boot, workspace or worker creation,
|
|
3948
3993
|
attachment, listing, naming, idle clients, and parked state alone do not.
|
|
3949
|
-
After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS`
|
|
3950
|
-
`
|
|
3994
|
+
After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` and
|
|
3995
|
+
`PLURNK_SERVICE_WORKSPACE_WARM_MAX` bound idle
|
|
3951
3996
|
residency. `0` disables the respective grace or allowance; `-1` disables that
|
|
3952
3997
|
bound. Concurrent demand coalesces; cooling never closes a leased connection.
|
|
3953
3998
|
|
|
@@ -3963,8 +4008,8 @@ documents and its state. Only a family that holds processes prepares
|
|
|
3963
4008
|
`runtimes` (MCP servers today); the field is absent for every other family, so
|
|
3964
4009
|
warming and cooling bound workspaces, never families, and the two-stage rollback
|
|
3965
4010
|
guards the manager registration of every family alongside the one family's
|
|
3966
|
-
processes. There is no per-family residency policy
|
|
3967
|
-
|
|
4011
|
+
processes. There is no per-family residency policy: residency is
|
|
4012
|
+
MCP-specific.
|
|
3968
4013
|
|
|
3969
4014
|
The version-1 baseline table `workspace_module_state` stores one JSON value
|
|
3970
4015
|
per `(workspace_id, namespace_owner)`. It is configuration, not an executable
|
|
@@ -4299,8 +4344,8 @@ registration; there is no separate per-tool availability system.
|
|
|
4299
4344
|
|
|
4300
4345
|
§model-catalog **Model discovery is a bounded local projection, not provider
|
|
4301
4346
|
activity.** Core composes the release-pinned Models.dev snapshot with
|
|
4302
|
-
provider-owned `{§model-catalog-readiness}` and {§provider-
|
|
4303
|
-
Each entry includes the exact route's admitted `
|
|
4347
|
+
provider-owned `{§model-catalog-readiness}` and {§provider-effort}.
|
|
4348
|
+
Each entry includes the exact route's admitted `efforts`; worker-level
|
|
4304
4349
|
model/spawn intersections and alias tuning are not catalog facts. The default query includes only
|
|
4305
4350
|
providers configured enough to attempt; `availability: "all"` includes every
|
|
4306
4351
|
catalog model with structured missing-configuration causes. Provider and text
|
|
@@ -4322,8 +4367,8 @@ cascade. A WORK/FORK child copies the spawning loop's effective spawn model
|
|
|
4322
4367
|
no live link and begins with no override, so a later parent change affects
|
|
4323
4368
|
only that worker's future loops and descendants. Client operation actors and
|
|
4324
4369
|
Plurnk-owned bookkeeping workers run no model loops and own no model
|
|
4325
|
-
selection; the model, spawn-override, and
|
|
4326
|
-
`409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or
|
|
4370
|
+
selection; the model, spawn-override, and effort controls refuse them with
|
|
4371
|
+
`409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or effort change while
|
|
4327
4372
|
the worker holds any queued, running, or parked loop is a precise
|
|
4328
4373
|
`409 worker-loop-active` ({§worker-lifecycle-live}), independent of a process-local
|
|
4329
4374
|
drain. The policy write checks liveness atomically, including selections carried
|
|
@@ -4333,11 +4378,11 @@ First-time initialization of an unset worker model remains legal and never
|
|
|
4333
4378
|
rewrites an existing loop's generation snapshot.
|
|
4334
4379
|
|
|
4335
4380
|
A client-created branch copies the source worker's durable model, spawn
|
|
4336
|
-
override, and
|
|
4381
|
+
override, and effort by value alongside its history. It retains no
|
|
4337
4382
|
live policy link to the source worker.
|
|
4338
4383
|
|
|
4339
|
-
§worker-
|
|
4340
|
-
worker model has exactly one member of the shared `{§
|
|
4384
|
+
§worker-effort **Reasoning is a durable worker policy.** Each selected
|
|
4385
|
+
worker model has exactly one member of the shared `{§effort-wire}`;
|
|
4341
4386
|
a modelless worker has none. A declared alias's scoped environment value—or the
|
|
4342
4387
|
global provider value for an exact route—seeds the policy only when the worker
|
|
4343
4388
|
first receives its model. Model identity and reasoning
|
|
@@ -4346,17 +4391,17 @@ token ceilings remain separate concerns. An explicit policy change validates
|
|
|
4346
4391
|
the exact policy against both the worker model and its optional spawn model and
|
|
4347
4392
|
is refused while the worker owns a live or parked loop. Effort is identity-grade:
|
|
4348
4393
|
every client-visible model route carries the worker's durable policy as
|
|
4349
|
-
`
|
|
4394
|
+
`effort`, omitted only when the cataloged model has no reasoning
|
|
4350
4395
|
dimension. Client inspection
|
|
4351
4396
|
returns the supported-policy intersection of those two routes. Inspection or
|
|
4352
4397
|
mutation materializes the daemon-default model and policy onto an uninitialized
|
|
4353
4398
|
model worker before answering; a deliberately modelless daemon remains unset.
|
|
4354
4399
|
|
|
4355
|
-
§worker-
|
|
4356
|
-
`
|
|
4357
|
-
or provider configuration, `explicit` only after `worker.
|
|
4358
|
-
returns `source`, and a projected `ModelRoute` carries `
|
|
4359
|
-
`
|
|
4400
|
+
§worker-effort-source **A default never masquerades as a choice.** The worker row records
|
|
4401
|
+
`effort_source` beside `effort`: `default` when the value was seeded from the alias
|
|
4402
|
+
or provider configuration, `explicit` only after `worker.effort.set`. `worker.effort.get`
|
|
4403
|
+
returns `source`, and a projected `ModelRoute` carries `effortSource` exactly when it carries
|
|
4404
|
+
`effort`, so a client can render `deepdumb[low]` differently from a seeded `low` without
|
|
4360
4405
|
inferring anything. Selecting a new model keeps an explicit policy (validated against the new
|
|
4361
4406
|
model) and re-derives a default one from the new alias, so a seeded value never outlives the alias
|
|
4362
4407
|
that supplied it; the source itself is not part of the mid-loop generation-change check, because
|
|
@@ -4377,7 +4422,7 @@ addressed worker before the loop snapshots it; an omitted selector is not a
|
|
|
4377
4422
|
selection and continues the worker's durable model
|
|
4378
4423
|
({§worker-model-selection}). The fully resolved provider identity and reasoning
|
|
4379
4424
|
policy are persisted on the loop and remain immutable through turns, parks,
|
|
4380
|
-
wakes, and restart ({§worker-
|
|
4425
|
+
wakes, and restart ({§worker-effort}).
|
|
4381
4426
|
Injecting into an existing loop with a conflicting explicit selection fails
|
|
4382
4427
|
before work is accepted. Provider instances are cached; no resume path
|
|
4383
4428
|
substitutes a boot default for missing or malformed durable selection.
|
|
@@ -4436,6 +4481,7 @@ adding a loop to it. LOOK text anchors resolve through the same
|
|
|
4436
4481
|
| §notifications-stream-event-on-channel-change `stream/event` | `{ entryId, workerId, target, channel, state, contentLength, mimetype?, loop_seq?, turn_seq?, sequence? }` | Channel content grows or channel state transitions. `workerId` is the initiating actor used for conversation routing, never entry ownership or access control. `target` is the canonical resource URI. Optional numeric coordinates identify the causal log item, independently of that URI. Core-managed channel writes include the current stored `mimetype`, which may change per call ({§channel-mimetype}); the generic plugin notification capability does not require it. It carries metadata, not content; consumers read bytes by canonical workspace address. |
|
|
4437
4482
|
| §notifications-stream-concluded `stream/concluded` | `{ entryId, workerId, target, subscriptionId, scheme, result, summary, wakeAction, loop_seq?, turn_seq?, sequence? }` | A subscription closes. `workerId` identifies the initiating actor; `target` is the canonical resource URI. Optional numeric fields identify the causal log item, never parsed from `target`. Exact result truth is preserved. `wakeAction` reports `wake-pending` before settlement, `no-op-active-loop` when work is already executing, `no-loop`, or `skipped-aborted`/`skipped-cancelled` for an aborted worker scope. A pending wake predicts neither execution nor recipient count; subsequent ordinary loop events report actual progress and completion. |
|
|
4438
4483
|
| §notifications-notice-event `notice/event` | `{ workerId, loopId, notice: Notice }` | A transient observation or progress notice occurs. `workerId` owns loop activity; only workspace derivation progress uses `null` with `loopId=0`. It cannot alter durable history, scheduling, recovery, or model-visible failure truth. |
|
|
4484
|
+
| §notifications-outside-event `outside/event` | `{ workerId, loopId, turnId, coordinate, text, tokens }` | An admitted emission carried text outside every operation ({§outside-text}): once per admitted emission, the exact stored text, its packet weight, and the turn's `<worker>-<loop>-<turn>` coordinate. It is transient presentation evidence; the turn's `outside` source remains the durable authority. |
|
|
4439
4485
|
| §notifications-reasoning-event `reasoning/event` | `{ workerId, loopId, turnId, modelCallId, requestSequence, phase, delta? }` | A main emission call exposes readable reasoning. Each physical request that emits reasoning owns a distinct positive `requestSequence` and balanced start/content/end stream; opening a retry closes the preceding stream before any retry delta. Only content carries a nonempty exact delta. It is transient presentation evidence, never a log row, Notice, packet field, or BARE/child channel. The settled provider response remains the durable authority. |
|
|
4440
4486
|
|
|
4441
4487
|
§notifications-stream-event-failure-isolation The plugin-facing
|
|
@@ -4538,14 +4584,14 @@ time of measurement.
|
|
|
4538
4584
|
| Fact | Owner and unit | Time | Contract |
|
|
4539
4585
|
|:-----|:---------------|:-----|:---------|
|
|
4540
4586
|
| Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, and rendered packet slots | Write/build | Stable, model-independent pressure and curation savings; never a tokenizer claim. |
|
|
4541
|
-
| §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured
|
|
4542
|
-
| Provider generation envelope | Provider
|
|
4587
|
+
| §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured output reservation, in provider tokens | Before every logical request | `min(maxInputTokens, contextWindow - outputBudget)` over the known terms. The provider alone measures the complete request and admits, defers, or rejects it. |
|
|
4588
|
+
| Provider generation envelope | Provider response grant and optional reasoning subset, in provider tokens | Before every logical request | The reservation includes hidden reasoning; its strict reasoning subset is never additive. The response grant follows {§provider-flexed-allowance}. |
|
|
4543
4589
|
| Provider usage and cost | Provider-reported input/output/cache/reasoning tokens and monetary evidence | After every physical request | Durable physical-request forensics under {§provider-usage}; never curation state or a preflight estimate. |
|
|
4544
4590
|
|
|
4545
|
-
- §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
|
|
4591
|
+
- §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write as the persisted mirror of {§logical-line-count} (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
|
|
4546
4592
|
- §tokenomics-render-weight-budget **Packet curation budget.** `logTokensTotal` measures the *complete assembled packet* after section transforms and readout substitution; it is not a sum of log-row `logTokens` fields. Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
|
|
4547
4593
|
- §tokenomics-calibrated-readout **Convert capacity, never content costs.** Before packet assembly, Core obtains the answering model's last five settled emission responses pairing a measured packet weight with a provider-reported prompt count. The conversion factor is `sum(reported) / sum(weight)`; fewer than three samples use 1. `logTokensMax = floor(inputCapacity / factor)` converts provider capacity into curation units. Zero means no whole curation unit fits; unknown input capacity remains `null`. The built packet captures this allowance once for its readout, pressure inventory, overflow admission, and persisted client gauge. Later responses cannot change that packet's allowance. Samples are model-keyed, not worker-local; a model with no samples starts at 1. Calibration never changes stored weights, rendered receipt costs, or the immutable request history ({§tokenomics-agnostic-ruler}).
|
|
4548
|
-
- §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits
|
|
4594
|
+
- §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits, the configured output reservation, and each call's response grant. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. `PLURNK_SERVICE_PROMPT_BUDGET`, `PLURNK_SERVICE_SAFETY`, and the additive reasoning/completion reserve knobs are retired; local and custom deployments tune context window, total output budget, optional reasoning subset, and prompt-projection percentage at their owning layers.
|
|
4549
4595
|
- §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
|
|
4550
4596
|
`PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
|
|
4551
4597
|
`(0, 100)`. It allocates that share of the cold-start curation allowance
|
|
@@ -4706,8 +4752,7 @@ Stream progress remains owned by {§exec-stream}.
|
|
|
4706
4752
|
|
|
4707
4753
|
## §packet Packet shape
|
|
4708
4754
|
|
|
4709
|
-
§packet-markdown **The packet's Markdown projection
|
|
4710
|
-
projection package retired (#626).** Core renders the transformed section list
|
|
4755
|
+
§packet-markdown **The packet's Markdown projection (#626).** Core renders the transformed section list
|
|
4711
4756
|
into one system string and one user string. Within each slot, list order is
|
|
4712
4757
|
preserved. A nonempty section with a header renders as an H2 immediately followed
|
|
4713
4758
|
by its JSON object/array content; non-JSON content has one blank line after the
|
|
@@ -4769,8 +4814,7 @@ directly with `json_extract`. A packet transformed by a plugin, or any non-log s
|
|
|
4769
4814
|
item. Items no composition references are transient data: `retention_collect_packet_items`
|
|
4770
4815
|
collects them under the retention policy ({§retention-policy}), which is how a deleted worker's or
|
|
4771
4816
|
workspace's packets release their space while shared items survive. A fork copies the composition
|
|
4772
|
-
and shares the items ({§worker-fork-trigger}).
|
|
4773
|
-
`turn_packets` view and is recreated, never read, under {§db-schema-baseline}.
|
|
4817
|
+
and shares the items ({§worker-fork-trigger}).
|
|
4774
4818
|
|
|
4775
4819
|
| Field | Presence | Contract |
|
|
4776
4820
|
| ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -4793,23 +4837,30 @@ turn receives a note instead of a fabricated response.
|
|
|
4793
4837
|
§digest-turn-artifact-identity **Digest packet artifacts project durable turns.**
|
|
4794
4838
|
After selectors are applied, digest retains every turn with exact program source, a
|
|
4795
4839
|
valid stored provider request, or malformed stored packet evidence; orders those
|
|
4796
|
-
turns by durable chronology; and names
|
|
4840
|
+
turns by durable chronology; and names each by its log coordinate ({§share-packet-names}). The
|
|
4797
4841
|
producer does not affect projection.
|
|
4798
4842
|
|
|
4843
|
+
§share-packet-names **Packet artifacts carry the coordinate the log uses.** A turn's files are named
|
|
4844
|
+
`<worker>-<loop>-<turn>`, the worker's name and the loop and turn sequences that `log:///<loop>/<turn>/…`
|
|
4845
|
+
addresses: the model's first turn in its first loop is `<worker>-1-2`, because the initialization
|
|
4846
|
+
survey is turn 1 and writes no packet. A digest spanning several workspaces nests each workspace's
|
|
4847
|
+
files in a folder named for it. `digest.json` records each turn's stem as `artifact`, so no
|
|
4848
|
+
consumer reconstructs a name. A name that cannot be a file name, or two turns sharing one, fails.
|
|
4849
|
+
|
|
4799
4850
|
| Artifact | Present when | Authority |
|
|
4800
4851
|
|----------|--------------|-----------|
|
|
4801
|
-
|
|
|
4802
|
-
|
|
|
4852
|
+
| `<stem>.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
|
|
4853
|
+
| `<stem>.system.md`, `<stem>.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
|
|
4803
4854
|
| `digest.json` turn `attachments` | Every turn | Stored native attachment descriptors; `[]` means a request without attachments, `null` means no valid stored request. Selection is not proof of provider acceptance. |
|
|
4804
|
-
|
|
|
4805
|
-
|
|
|
4806
|
-
|
|
|
4807
|
-
|
|
|
4855
|
+
| `<stem>.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
|
|
4856
|
+
| `<stem>.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
|
|
4857
|
+
| `<stem>.packet.raw.txt` | The stored packet fails typed validation | Exact stored packet text |
|
|
4858
|
+
| `<stem>.packet.invalid.json` | The stored packet fails typed validation | Turn identity and complete validation error chain |
|
|
4808
4859
|
|
|
4809
4860
|
A source-backed turn without provider participation therefore produces only
|
|
4810
4861
|
`assistant.md`; a request-only turn produces no fabricated assistant. A
|
|
4811
4862
|
source-less programmatic turn with no provider request has no forensic payload
|
|
4812
|
-
to project and
|
|
4863
|
+
to project and writes no files.
|
|
4813
4864
|
|
|
4814
4865
|
The external tokenless draft and transformation boundary is owned by
|
|
4815
4866
|
{§scheme-packet-transform}. Core alone extends each validated draft with its
|
|
@@ -4905,11 +4956,9 @@ producers notify the same settlement path after durable execution; reply wake-up
|
|
|
4905
4956
|
shows in Open Messages and in an arrival row's `resource`. A client's own identity for the same
|
|
4906
4957
|
message — an AG-UI message UUID, an A2A address — stays the durable `path` that correlation,
|
|
4907
4958
|
delivery and reply accounting use, and remains addressable in its own scheme; the short form is an
|
|
4908
|
-
additional alias. Answering either reaches the same message.
|
|
4909
|
-
packet showed a 77-character `agui://anonymous/threads/…/messages/<uuid>` twice per open message,
|
|
4910
|
-
while the docs taught the short form.
|
|
4959
|
+
additional alias. Answering either reaches the same message.
|
|
4911
4960
|
|
|
4912
|
-
§message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"
|
|
4961
|
+
§message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`; a bare arrival would read as the model's own SEND and hide the request it answers. The Open Messages pointer carries the same attribution ({§message-arrival}).
|
|
4913
4962
|
|
|
4914
4963
|
§message-projection **Message storage is unbounded by model context; automatic materialization is not.** Core persists every accepted message completely before packet assembly. The selected provider's derived `inputCapacity` and the alias-resolved percentage from `PLURNK_SERVICE_PROMPT_PROJECTION` derive one aggregate curation-weight allowance for the visible bodies of arrivals other than a peer worker's — every `source` that is not a `worker://` address, the loop's own assignment included. Complete bodies render when their aggregate weight fits. Otherwise all such visible rows share the allowance: full bodies consume only their required share, unused shares are redistributed, and partial bodies render the largest leading complete-line region that fits their share or an exact character-bound prefix when the first physical line alone is larger. The sum of their rendered body weights never exceeds the allowance. Every partial body carries `preview` under {§packet-extent-metadata}. The row remains complete and READable by coordinate; its `log:///` body additionally obeys deliberate curation under {§log-readable-projection}. A peer worker's message takes the ordinary bounds. When provider input capacity is unknown the percentage is underivable, so arrival rows retain the ordinary bounded projection rather than inventing capacity. This policy never rejects, summarizes, or discards a message because it exceeds a context window.
|
|
4915
4964
|
|
|
@@ -4992,21 +5041,30 @@ retain distinct contracts and lifetimes.
|
|
|
4992
5041
|
|
|
4993
5042
|
§notice-event-notify **Client surface.** Engine Notices broadcast live via the `notice/event` notification — `{ workerId, loopId, notice: { source, kind, level, message?, position?, …kind-specific } }` per the grammar's `Notice` schema — the moment they land. A loop Notice names its owning Worker; workspace derivation progress alone carries `workerId=null, loopId=0`. AG-UI projects the same observation as the custom `plurnk.notice` event. Failures do not broadcast on this surface: they are log rows, and the client reads them through `log.read` / the `log/entry` notification, the durable log.
|
|
4994
5043
|
|
|
5044
|
+
§share **A share is the database's record, ready to send.** `plurnk-service share [<file.db>] [<folder>]`, and `npm run share` from a checkout, take a consistent copy of the database (`VACUUM INTO`; a live database is never read in place), and write its digest into `<folder>`, an ordinary folder the user archives or attaches however they like. Without a database the service's own is shared. Nothing is overwritten: a folder that exists and is not empty is refused, and a caller reusing a place removes it first. The share is the user's bug report and our dogfood, benchmark and forensics artifact alike.
|
|
5045
|
+
|
|
5046
|
+
§share-snapshot **A database is copied by SQLite, never by the filesystem.** `Share.snapshot(dbPath, copy)`, exported as `@plurnk/plurnk-service/share` with `Share.write`, is the one consistent copy: a byte copy of a WAL-mode database drops every committed page still in its `-wal` file. A harness that keeps the database beside its digest takes it through `snapshot`; an existing `copy` is refused.
|
|
5047
|
+
|
|
5048
|
+
§share-scope **A share is unredacted.** `--workspace=<id>` limits a share to one workspace; without it the whole database is shared. Nothing is filtered, redacted or scanned: a share holds what the models saw and wrote in scope, including prompts, file contents read, command output and reasoning, and the command says so. `--requiem` adds the forensic interview ({§digest-requiem}), which calls a model; `plurnk-service requiem <file.db> <folder>` adds it later to a digest already written.
|
|
5049
|
+
|
|
5050
|
+
§share-folder **Shares land in one place.** With no folder named, a share is a stamped child, `share-<UTC stamp>`, of `PLURNK_SERVICE_SHARE_FOLDER` (a leading `~/` expands, as for every explicit Plurnk path) or of `$XDG_STATE_HOME/plurnk/shares`.
|
|
5051
|
+
|
|
4995
5052
|
§digest-programmatic-surface **The digest is an importable forensic surface.**
|
|
4996
5053
|
|
|
4997
5054
|
| Surface | Contract |
|
|
4998
5055
|
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
4999
5056
|
| 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. |
|
|
5000
5057
|
| `run({ dbPath })` | Reads the required database and writes a complete digest to `./test/digest` relative to the caller's working directory. |
|
|
5001
|
-
| `digestDir` | Selects a nonempty output
|
|
5058
|
+
| `digestDir` | Selects a nonempty output path. `run` refuses a folder that exists and is not empty, and `requiem` refuses an existing `requiem.json` or `requiem.md`, before database or provider I/O; neither deletes ({§share}). Concurrent callers use distinct folders. |
|
|
5002
5059
|
| Reader lifetime | `run` reads heavy evidence on demand while rendering, then closes its reader on success or failure. `requiem` closes its reader before awaiting witness inference. |
|
|
5060
|
+
| §candidate-pinned-runtime Candidate runtime | At launch, after its optional build, the candidate copies every workspace's package projection (`files`) into `<state>/runtime`, links third-party dependencies, and resolves `@plurnk/*` to those copies. Its daemon and its digest export both run from that copy, never the shared checkout, so neither source edits nor a concurrent candidate's or developer's rebuild after launch changes the code a run finishes on. The copy is removed once the digest is written. |
|
|
5003
5061
|
| Export completion | Packet and response bodies are read and serialized one record at a time, without discarding evidence. `digest.json` is promoted from a partial file only after every artifact is written; its absence identifies an incomplete export. |
|
|
5004
5062
|
| `workerId` | Narrows workers and every dependent loop, turn, turn-attached logical inference, specialization, physical request, and log row to that one worker. |
|
|
5005
5063
|
| `workspaceId` | Narrows workers plus every logical inference and dependent evidence owned by one workspace, when both selectors are present they intersect. |
|
|
5006
5064
|
|
|
5007
5065
|
§digest-cost-kind **Cost basis named.** A rendered Cost line carries the basis of its dollar figure: `(charged)` only when every settled request's cost is provider-charged; `(estimated — catalog rates)` when any settled request's cost is an estimate, because a mixed sum is no more trustworthy than its weakest term. A dollar figure without its basis reads as billed truth, and an estimate must never impersonate a charge.
|
|
5008
5066
|
|
|
5009
|
-
§output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number
|
|
5067
|
+
§output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number is a fact without a use (#826). Overflow tolerance (#482) is likewise never advertised; a cut's notice names the true per-call grant from the response's own capacity record. When a provider finish is `length`, the engine emits an `output_truncated` notice (source `engine:capacity`) naming the allowance — the fact alone, never advice on what to do about it — on every path — railed or not — and the rails verdict never blames the model's grammar for a cut the engine's own ceiling made. The same precedence governs a cut so deep no operation parses: the rejection notice names the truncation as the cause, not the parser's symptom, overriding {§invalid-emission-attempts}'s parser diagnostic for `length` finishes.
|
|
5010
5068
|
|
|
5011
5069
|
§digest-wire-line **Wire health aggregated.** Each worker summary renders a `Wire:` line — total physical provider requests, error-outcome count, and the error percentage when nonzero. Provider-level failures are absorbed by retries below the packet stream, so without this aggregate a rate-limit storm is invisible in every summary while the model's experience stays clean.
|
|
5012
5070
|
|
|
@@ -5147,8 +5205,7 @@ or an unknown enabled alias fails the daemon at boot.
|
|
|
5147
5205
|
namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). `none`
|
|
5148
5206
|
refuses every model definition — inclusion or exclusion — as `403
|
|
5149
5207
|
members/functionality/model-scope`, naming `git add` and the operator's `/members add` as
|
|
5150
|
-
the paths that remain; `root` admits patterns inside the root; `namespace
|
|
5151
|
-
default, admits `../` too.
|
|
5208
|
+
the paths that remain; `root` admits patterns inside the root; `namespace` admits `../` too.
|
|
5152
5209
|
`auto` loops self-approve proposals, so the ceiling — not the proposal — is the guard
|
|
5153
5210
|
({§membership-baseline}). The coordinator hands `admit` the caller (`action` | `operation`)
|
|
5154
5211
|
so the family bounds the model without a second grammar.
|
|
@@ -5188,7 +5245,7 @@ source it rides the definition. The workspace's durable state owns enablement
|
|
|
5188
5245
|
model-facing trace.
|
|
5189
5246
|
|
|
5190
5247
|
*Discovery is inert.* `discover {query}` searches the ecosystem registry
|
|
5191
|
-
(`PLURNK_SERVICE_SKILLS_REGISTRY_URL
|
|
5248
|
+
(`PLURNK_SERVICE_SKILLS_REGISTRY_URL`; empty disables it
|
|
5192
5249
|
with 501 `registry-not-configured`) and returns one candidate per hit with
|
|
5193
5250
|
`registry` provenance and the exact `owner/repo` source. `discover {source}`
|
|
5194
5251
|
lists the skills one standard package reference contains with `source`
|
|
@@ -5204,8 +5261,8 @@ names, rather than the coordinator's generic default.
|
|
|
5204
5261
|
*Preparation.* For each enabled alias the adapter selects the host-provided
|
|
5205
5262
|
tree for `service` scope or locates the directory at the filesystem scope;
|
|
5206
5263
|
a workspace definition whose directory is absent is installed
|
|
5207
|
-
through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`,
|
|
5208
|
-
|
|
5264
|
+
through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, invoked as
|
|
5265
|
+
`<cli> add <source> --agent universal --skill <name> --yes [--global]`, run with
|
|
5209
5266
|
`HOME` set to the service's user home so the installer's `~` is the global
|
|
5210
5267
|
root) and the installed `SKILL.md` — never the installer's output — is the
|
|
5211
5268
|
evidence.
|
|
@@ -5297,11 +5354,11 @@ section because they are language extensions rather than executable tools.
|
|
|
5297
5354
|
|
|
5298
5355
|
### §inject system.inject — the operator injection
|
|
5299
5356
|
|
|
5300
|
-
§packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard
|
|
5357
|
+
§packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard as {§policy-sections} rules. `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section.
|
|
5301
5358
|
|
|
5302
5359
|
### §policy system.policy — the client's policy injection
|
|
5303
5360
|
|
|
5304
|
-
§policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (
|
|
5361
|
+
§policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (unset resolves to the policy member in {§host-path-layout}), with no engine-generated heading. The policy document owns its Markdown structure. Policy is the client's authoritative rules promoted into the privileged zone — NOT a log entry; the model cannot READ or KILL it. A default-absent path is silent (the section is omitted); an explicit override (env set) that fails to read fails the turn hard — a deliberate setting with a broken path is a misconfig, surfaced not hidden. Read per-turn so edits take effect live. The PROJECT `AGENTS.md` is local guidance, not policy: it rides turn 0 as the foisted `worker:///_plurnk/AGENTS.md` entry ({§turn0-agents-stunt}); references and skills use native discovery ({§skills-functionality}).
|
|
5305
5362
|
|
|
5306
5363
|
On first run, and only when `$XDG_CONFIG_HOME/plurnk` itself is absent, the service seeds
|
|
5307
5364
|
`AGENTS.md` from `@plurnk/plurnk-meta/POLICY.md` ({§teaching-corpus}).
|
|
@@ -5461,6 +5518,17 @@ READ/EDIT/COPY/MOVE scopes use the same physical text:
|
|
|
5461
5518
|
One/two-coordinate line shorthand is newline-aware so deleting a line does not
|
|
5462
5519
|
leave an empty line. A terminal position after a final newline is an exact
|
|
5463
5520
|
insertion anchor, not an additional whole line. `<1,-1>` selects all content.
|
|
5521
|
+
|
|
5522
|
+
§zero-width-column-one-insert **A zero-width region at column 1 inserts whole lines.** The
|
|
5523
|
+
schemes region algebra ({§slicer-text-algebra}) inserts every body verbatim; the missing
|
|
5524
|
+
newline is a fence artifact, so core repairs it where a fenced EDIT body becomes inserted
|
|
5525
|
+
content, through the one schemes helper `wholeLineBody`, at the mutation and again in the
|
|
5526
|
+
receipt and anchor-continuity recomputation so every site sees one body. At `<L,1,L,1>`,
|
|
5527
|
+
an anchored `<@hash,1,@hash,1>`, or `L` = final line + 1 when the content ends with a
|
|
5528
|
+
newline, a non-empty body that does not end in a newline is inserted with the content's
|
|
5529
|
+
line separator appended, so `X` at `<2,1,2,1>` into `a\nb` yields `a\nX\nb`. An empty
|
|
5530
|
+
body inserts nothing. A zero-width region at any other column stays a byte-exact insert with
|
|
5531
|
+
nothing appended. COPY and MOVE transfer source bytes, not a fenced body, and are untouched.
|
|
5464
5532
|
The runtime also tolerates an authored three-coordinate
|
|
5465
5533
|
`<startLine,startColumn,endLine>` scope, immediately lowers it to the complete
|
|
5466
5534
|
four-coordinate region ending after the final code point of `endLine`, and
|
|
@@ -5572,7 +5640,7 @@ Carried from the contract walk; durable.
|
|
|
5572
5640
|
|
|
5573
5641
|
A KILL with a text-coordinate scope aimed at an entry-bearing scheme deletes exactly that span: core prepares and dispatches it as an EDIT with an empty body over the same marker, so anchors resolve, proposals gate it, and the merge facts and receipt are the EDIT path's — while the log row records the model's KILL. Its packet metadata and canonical log body use {§edit-result-receipt-projection}. ```` ```EDIT (path) <scope> ```` with an empty body remains the same act spelled the other way; the teaching names KILL.
|
|
5574
5642
|
|
|
5575
|
-
§kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported
|
|
5643
|
+
§kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported`, {§pattern-dialect-find-only}). The log stays the exception: a pattern on `log:///` selects rows ({§log-curation-set-selection}), and a stream scheme's KILL is process control, so a pattern there is 400 `kill-pattern-unsupported`.
|
|
5576
5644
|
|
|
5577
5645
|
---
|
|
5578
5646
|
|
|
@@ -5617,3 +5685,5 @@ marker file or a sweep; an unstamped invocation is not a special case with its o
|
|
|
5617
5685
|
simply an unstamped run with its own directory. A stamped run that passes is reclaimed when it
|
|
5618
5686
|
exits; a failed suite's evidence is never touched and stays exactly where the run reported it. A cross-package test may reuse Core's migration fixture only by passing a path inside the
|
|
5619
5687
|
caller's own run directory.
|
|
5688
|
+
|
|
5689
|
+
§fs-world-state **The world-state harness — coverage that closes the class.** Op-outcome tests check what an op returned; the harness checks the resulting world. `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, channel orphan-freedom, the closed admission set (every file row's origin is Git or constraint), and sig-coherence. Generated-pick incorporation and lifecycle require filesystem/Git evidence and are covered by the composed creation matrix rather than a false pure-database proxy. The harness runs as a lifecycle-test epilogue and at every soak turn boundary, where the delta half applies: an idle turn grows the entries table by ZERO. A violation names its law and its row.
|