@plurnk/plurnk-contracts 2.0.0 → 3.0.1

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 CHANGED
@@ -7,5 +7,5 @@ PLURNK_HOST=127.0.0.1
7
7
  # The daemon's TCP port, shared by all client interfaces.
8
8
  PLURNK_PORT=1066
9
9
  # The whole URL a client dials instead: a remote portal, or a daemon bound to an address no
10
- # client can dial (0.0.0.0). Unset = http://$PLURNK_HOST:$PLURNK_PORT.
10
+ # client can dial (0.0.0.0). Unset = http://$PLURNK_HOST:$PLURNK_PORT/agui.
11
11
  # PLURNK_AGUI_URL=
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # @plurnk/plurnk-contracts
2
2
 
3
3
  The single authority for PLURNK's model-facing language contract, its AST,
4
- generated model rail, shared schemas and types, runtime-neutral Problems,
5
- operation results, Notices, and text coordinates. See SPEC
6
- {§root-value-api}.
4
+ shared schemas and types, runtime-neutral Problems, operation results,
5
+ Notices, and text coordinates. See SPEC {§root-value-api}.
7
6
 
8
7
  ## Install
9
8
 
@@ -27,8 +26,7 @@ Requires Node.js 26 or newer.
27
26
  JSON Schema owns shared wire shapes, generated TypeScript projects those
28
27
  shapes, and ANTLR owns accepted model-language syntax. See SPEC
29
28
  {§contract-representations}. An operator who wants constrained sampling on a
30
- llama-server route writes their own GBNF and points `PLURNK_PROVIDERS_GBNF` at
31
- it; this package ships no grammar profile.
29
+ llama-server route writes their own GBNF and points `PLURNK_PROVIDERS_GBNF` at it.
32
30
 
33
31
  ## Parser
34
32
 
package/SPEC.md CHANGED
@@ -10,7 +10,7 @@ runtime-neutral wire envelopes; `@plurnk/plurnk-parser` implements the language
10
10
  | Surface | Canonical export or artifact |
11
11
  | ------------------------------------------------------------------------------- | --------------------------------------------------- |
12
12
  | AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
13
- | Capability and loop policies | `CapabilityPolicy`, `LoopPolicy`, `LoopPolicyRequest`, `PROPOSAL_POLICIES` |
13
+ | Capability admission and approval ownership | `CapabilityPolicy`, `WorkerOwner`, `ProposalPolicy`, `PROPOSAL_POLICIES` |
14
14
  | Durable effort | `Effort`, `EFFORTS` |
15
15
  | Model route and catalog discovery | `ModelRoute`, `ModelCatalogQuery`, `ModelCatalogPage`, `ModelReadiness` |
16
16
  | Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
@@ -28,8 +28,8 @@ runtime-neutral wire envelopes; `@plurnk/plurnk-parser` implements the language
28
28
 
29
29
  §contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
30
30
  generated from the schemas; ANTLR is authoritative for accepted model-language
31
- syntax. No generation grammar is generated or shipped; an operator's own GBNF
32
- is carried verbatim to a llama-server route by the providers package.
31
+ syntax. An operator's own GBNF is carried verbatim to a llama-server route by
32
+ the providers package.
33
33
 
34
34
  §agui-discovery-contract `AguiDiscovery` is the complete installed AG-UI+
35
35
  surface at one instant. `schemaVersion` identifies its discovery shape;
@@ -128,7 +128,7 @@ meaning, including registered scheme resolution, target existence, tag
128
128
  selection, text-region bounds, result ordering, full-text ranking, mutation
129
129
  effects, and executor behavior.
130
130
 
131
- ### §contract-proposal-projection Loop policy and stopped-world projection
131
+ ### §contract-proposal-projection Admission policy and stopped-world projection
132
132
 
133
133
  The schemas own the runtime-neutral shapes; core owns their stateful values.
134
134
 
@@ -137,9 +137,10 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
137
137
  | `CapabilityDescriptor` | One routed operation demand with its operation, access class, resource/runtime/tool coordinates, and declared traits | Derive every demand before dispatch |
138
138
  | `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service and workspace layers |
139
139
  | `CapabilityProjection` | Exact service, workspace, and normalized effective policies | Expose the resolver's workspace cascade without claiming one layer is effective authority |
140
- | `LoopPolicy` | One `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
141
- | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
142
- | `ProposalProjection` | Identity, `{ scheme, authority, pathname }` review target, body/attrs, effective policy, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
140
+ | `WorkerOwner` | Nonempty address, unique client-tool names, and whether it is interactive | Persist by workspace; inherit through worker creation |
141
+ | `ProposalPolicy` | `review`, `accept`, or `reject` | Read the server's disposition knob at the proposal boundary |
142
+ | `ProposalDisposition` | Review, or automatic accept/reject with optional outcome | Select once for the pending gate |
143
+ | `ProposalProjection` | Identity, review target, body/attrs, owner, stale signal, disposition | One validated projection for live delivery and reconnect |
143
144
  | `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
144
145
  | `ProviderCost` | Exact charged, estimated, or unknown monetary evidence | Normalize one monetary disposition for each physical provider request |
145
146
  | `ProviderRequestAccounting` | Usage and cost evidence for one physical provider request | Preserve request order across retries, failover, success, and failure |
@@ -163,19 +164,18 @@ than hidden policy behavior.
163
164
 
164
165
  §capability-policy-projection A `CapabilityProjection` reports every durable
165
166
  workspace layer and their normalized intersection: `service`, `workspace`, and
166
- `effective`. Only `workspace` is client-mutable. Workers and loops have no
167
- capability policy or inherited bound; every actor uses the same live workspace
168
- policy. A client never derives effective authority from the mutable layer alone.
169
-
170
- §loop-policy A `LoopPolicy` is complete and immutable after creation:
171
- `proposals` chooses one downstream settlement posture and `attended` says
172
- whether anyone can answer, independently of workspace capability policy. An
173
- unattended loop cannot hold a proposal for review, so the schema refuses that
174
- pair. A `LoopPolicyRequest` is the part of a policy its creator chose to state.
175
- Contracts hold no default for the rest: the daemon's panel supplies it
176
- ({§loop-policy-composition}). `PROPOSAL_POLICIES` is the schema-owned
177
- vocabulary of `proposals`. Capability admission precedes effect
178
- classification and proposal settlement.
167
+ `effective`. Only `workspace` is client-mutable. Workers and loops store no
168
+ capability policy and inherit no bound; every actor uses the same live workspace
169
+ policy. Core derives one further ring per asking worker from its owner, and it only
170
+ removes interaction runtimes ({§owner-interaction-ring}). A client never derives
171
+ effective authority from the mutable layer alone.
172
+
173
+ §proposal-policy `ProposalPolicy` names one server disposition; `PROPOSAL_POLICIES`
174
+ is its schema-owned vocabulary. `WorkerOwner` carries an address, the client-tool
175
+ names that recipient implements, and `interactive`: whether a person attends it. Neither
176
+ contains resource permissions or
177
+ message-carried authority. Core owns persistence, inheritance and settlement
178
+ ({§worker-ownership}, {§proposal-disposition}); capability admission precedes approval.
179
179
 
180
180
  §effort-wire `Effort` is exactly `off | adaptive | low |
181
181
  medium | high | xhigh | max`. The schema owns this shared wire vocabulary. Providers own the
@@ -314,7 +314,8 @@ column-zero line outside any block containing a native operation's name and at m
314
314
  `<!-- aside -->`, with only horizontal whitespace otherwise, opens that operation as if it were
315
315
  fenced with the taught three backticks. The aside retains its ordinary meaning: no target or
316
316
  modifiers are inferred. Its body runs to a line that is exactly the name
317
- again, to the next heading ({§fence-heading-in-body}), or to the end of the turn. A fence inside
317
+ again, to the next heading ({§fence-heading-in-body}), or to the end of the turn, except
318
+ parameterless KILL always takes the remaining answer region ({§terminal-kill}). A fence inside
318
319
  naming nothing known is body; the block expects no closer, so its body is never cut
319
320
  back ({§closer-fallback}). A naked `KILL` alone holds every fence inside it as text ({§naked-kill}). It runs, and one warning-severity receipt follows its statement —
320
321
  `` `KILL` opened with no fence; the taught form is three backticks. `` One rule for every native
@@ -330,8 +331,8 @@ read this way.
330
331
  §fence-heading-in-body Outside a complete nested block ({§balanced-fences}), a fence
331
332
  line of three or more backticks and a name that is a native operation or a known executor
332
333
  is a heading. Inside an open block it either ends that block without closing it and opens the
333
- next statement, or it is a literal example held by the block; the block holds it only while
334
- the block ends at a real closer, and {§fence-pairing} chooses between the two. Fence lines of
334
+ next statement, or it is a literal example held by the block. {§fence-pairing} chooses between
335
+ the two; parameterless KILL always holds the remaining input ({§terminal-kill}). Fence lines of
335
336
  fewer than three backticks are never headings ({§operation-fences}). Known executors are `sh`
336
337
  plus what the host names in `ParseOptions.executors`. Inside a parameterless `KILL` a
337
338
  heading never ends the block ({§terminal-kill}).
@@ -445,11 +446,13 @@ heading. An operation example quoted in a body of its own width therefore reads
445
446
  closing and the example running; the wider outer fence or the tab offset quotes it
446
447
  ({§operation-fences}).
447
448
 
448
- §terminal-kill **A KILL body is the deliverable.** Inside a parameterless `KILL`, at any depth, a
449
- heading is a literal example or text and never ends the block; the block ends at its closer or
450
- the end of the input. Of 796 recorded parameterless KILLs, 9 were followed by any other
451
- operation, while 62 of 120 SENDs were, so the rule is KILL's alone. It keeps a final report whole
452
- and never runs a command the report only shows.
449
+ §terminal-kill **A parameterless KILL starts the final answer region.** Once its heading is
450
+ recognized as an operation, the rest of the input is its literal body. A fence cannot return
451
+ that region to executable operations or outside text. Apparent operations, receipts and malformed
452
+ fences within it are answer text. An actual enclosing closer at the end is framing, not body;
453
+ an earlier fence remains body. Compact and recovered naked spellings obey the same boundary.
454
+ Quoted KILL examples do not open this region; targeted KILL retains ordinary statement framing.
455
+ Earlier operations still execute and completion remains subject to {§kill-conclusion}.
453
456
 
454
457
  §prose-code-blocks **A message or a prompt shows code.** Inside a `SEND`, `WORK`, `FORK` or `BARE`
455
458
  body, at any depth, a heading that names an executor (` ```sh `, ` ```python3 `) is a code block the
@@ -460,8 +463,8 @@ operation. In the distinct recorded benchmark emissions an executor heading ende
460
463
  run218, run114 and run118, deepdumb run44) was the child's task, the prompt's code or code quoted in
461
464
  a report, and reading it as an operation truncated the task and ran the example in the parent.
462
465
 
463
- §message-run-on **A message runs to the end of the turn.** A parameterless `KILL`, or a `SEND`,
464
- `WORK`, `FORK` or `BARE`, whose block reaches the end of the input without its closer takes it
466
+ §message-run-on **A message runs to the end of the turn.** A `SEND`, `WORK`, `FORK` or `BARE`
467
+ whose block reaches the end of the input without its closer takes it
465
468
  there with no repair: the text after its last inner block is still the deliverable, the message
466
469
  or the task, never outside text. The run counts as a supplied closer, so a reading that closes at
467
470
  a real fence still wins, and it never costs an operation: when the reading that runs on hides more
@@ -470,11 +473,9 @@ operations the author wrote than the best repaired reading, the repaired reading
470
473
  inner fence keep what followed it (the qflash run192, run90 and run210 deliverables regain their last
471
474
  sections), one echoed transcript (glm run155) runs one more operation, and none loses one.
472
475
 
473
- §naked-kill **A naked KILL is the whole rest of the turn.** A parameterless `KILL` opened without
474
- a fence ({§naked-operation}), with or without an aside, is a completion: every fenced
475
- block inside it, a native operation heading included, is text the deliverable shows, exactly as
476
- inside a fenced KILL ({§terminal-kill}), and the block ends only at its name alone on a line or at
477
- the end of the input. No operation the deliverable shows runs.
476
+ §naked-kill **A naked KILL obeys the same answer boundary.** A parameterless `KILL` opened without
477
+ a fence ({§naked-operation}), with or without an aside, takes the remaining input under
478
+ {§terminal-kill}. A repeated name can close it only at the end; it never exposes later operations.
478
479
 
479
480
  §pairing-witness **Witnesses.**
480
481
 
@@ -683,10 +684,7 @@ repeat the complete selection/metadata group per operand. ANTLR accepts
683
684
  adjacent slots and scope/metadata permutations within a selection without
684
685
  changing ownership or making them distinct canonical forms. Each selection
685
686
  has at most one scope; its metadata blocks retain their authored order. A second scope
686
- is refused, and the recovery reads the pair: a line number followed by its anchor
687
- (`<91> <@abcde>`, a copied row prefix) recovers to the anchor alone, two other single
688
- positions to one scope with both ends (`<@abcde> <-1>` to `<@abcde,-1>`), and anything
689
- else to one operation per selection.
687
+ is refused; the recovery is the scope's working form ({§parse-recovery}).
690
688
 
691
689
  §lifecycle-slots NOTE accepts no target, scope, or metadata. WAIT retains its
692
690
  optional target and duration under {§send-wait-scope}. Their literal bodies begin
@@ -716,14 +714,11 @@ are never split. Each URI inherits its slot's scope and metadata and compiles th
716
714
  BARE and NOTE take no scope, and neither does a SEND without a recipient; a `<…>` slot on such a
717
715
  heading, in any position, is skipped and the operation runs, with one warning-severity advisory
718
716
  naming the operation's slots and the dropped scope — `` `WORK` takes a target only; the scope
719
- `<1,-1>` was ignored. A scope selects lines in READ, EDIT and KILL. `` (`` `NOTE` takes no target or
717
+ `<1,-1>` was ignored. `` (`` `NOTE` takes no target or
720
718
  scope; … ``, `` `SEND` without a recipient takes no scope; … ``). An aside is never a scope, a
721
719
  recipient SEND keeps its scope for the recipient ({§send-directed-scope}), and WAIT follows
722
720
  {§send-wait-scope}. There is no ambiguity: the operation has one reading with or without
723
- the slot. Before this, the heading drew the grammar's expected-token diagnostic and the turn was
724
- dead; in the distinct recorded emissions one heading carried the form (zai run426,
725
- ```` ```WORK (worker://deprecation-implementer) <1,-1> ````), beside two recipient SENDs whose scope
726
- the recipient refuses at runtime.
721
+ the slot.
727
722
 
728
723
  §heading-inline-body Nonempty body text belongs below the fence header. Text after the slots
729
724
  on the heading line is read by the operation's shape, by the builder that reads that operation,
@@ -755,9 +750,8 @@ written on the heading line — under {§heading-inline-body}. The host names th
755
750
  (`ParseOptions.jsonBodyExecutors`). A heading that already carries a block keeps the object as
756
751
  inline body. On FIND, READ and KILL the same text is the matcher
757
752
  ({§naked-pattern}), because a search for JSON text is legitimate; a bare matcher
758
- that parses as a JSON object draws one advisory naming the option form ("`{…}`
759
- was read as the matcher; an option block is `[{…}]`."), so the collision is
760
- learned in the turn it happens.
753
+ that parses as a JSON object draws one advisory saying so: "`{…}` was read as the
754
+ matcher." ({§diagnostic-observation}).
761
755
 
762
756
  §operation-aside The final header modifier may be one single-line HTML
763
757
  comment. AstBuilder removes its delimiters and surrounding whitespace into
@@ -790,8 +784,8 @@ Worker; the shared reader withholds both from the owner's options.
790
784
 
791
785
  §matcher-option **`pattern` is the matcher, and it lives in the heading.** On
792
786
  FIND, READ, KILL, EDIT, and each COPY/MOVE operand, the option
793
- `[{"pattern": "<matcher>"}]` carries the matcher string exactly as a body once
794
- did: the leading prefix claims its dialect under {§matcher-prefix-claims}, and
787
+ `[{"pattern": "<matcher>"}]` carries the matcher string: the leading prefix
788
+ claims its dialect under {§matcher-prefix-claims}, and
795
789
  AstBuilder lifts it into the statement's `matcher` (`MatcherBody | null`),
796
790
  positioned dialect errors included. A block that carries only `pattern`
797
791
  leaves `metadata: null` for the owner; beside other keys the block stays with
@@ -800,10 +794,9 @@ language lifts only from one block that parses as a JSON array of objects;
800
794
  anything else lifts nothing and reaches the owner's `400` untouched. A
801
795
  `pattern` that is present but not a string is the language's own positioned
802
796
  diagnostic, as is a matcher of a claimed dialect that fails admission. FIND,
803
- READ, and targeted KILL take no body: a body beneath their heading is ignored and
804
- the operation still runs, with one warning-severity advisory naming the
805
- heading-line form (`FIND takes no body; the body was ignored. A pattern belongs
806
- on the opening fence line after the path.`); it is never silently read as a
797
+ READ, and targeted KILL take no body: a body beneath their heading is not used and
798
+ the operation still runs, with one warning-severity advisory naming the lines not
799
+ used (`FIND takes no body; lines 4–6 were not used.`); it is never silently read as a
807
800
  matcher, and it never strikes ({§matcher-body-redirect}). The heading line itself
808
801
  is the matcher's home ({§naked-pattern}). A body that is only an HTML comment is
809
802
  still the aside under {§misplaced-aside-advisory}. EDIT keeps its literal body:
@@ -853,10 +846,10 @@ repetition and strike rules still apply.
853
846
 
854
847
  §reasoning-operations The shared fence parser admits complete, line-leading NOTE,
855
848
  FIND and READ statements from exposed provider reasoning. Admission is part of
856
- the language, not an optional compatibility mode. Other backtick or tilde
857
- code blocks are opaque; heading and missing-closer recovery cannot escape them or
858
- complete an unfinished reasoning operation. Blockquoted and inline examples are
859
- not headings. Other reasoned operations never execute.
849
+ the language. Other backtick or tilde code blocks are opaque; heading and
850
+ missing-closer recovery cannot escape them or complete an unfinished reasoning
851
+ operation. Blockquoted and inline examples are not headings. Other reasoned
852
+ operations never execute.
860
853
 
861
854
  Operations are admitted only after the provider response completes; their
862
855
  presence never interrupts generation. Selected operations precede the content
@@ -905,8 +898,7 @@ one park with the earliest bound, including the configured bound for each bare W
905
898
 
906
899
  §send-directed-scope A recipient SEND carries an optional numeric scope after
907
900
  its target and metadata through to the addressed owner, which assigns its
908
- semantics or refuses it; worker actors refuse one (`scope-unsupported`, 400),
909
- because later and recurring delivery belong to the schedule family. A
901
+ semantics or refuses it; worker actors refuse one (`scope-unsupported`, 400). A
910
902
  targetless message takes no scope. A scope never changes the message body or
911
903
  disposition.
912
904
 
@@ -925,12 +917,11 @@ Without a scope, KILL retires or deletes the whole target; with one, it removes
925
917
  that span — of a log body's packet projection or of an entry's content. Core owns the
926
918
  one-way semantics: there is no operation that restores a scoped-away log body.
927
919
 
928
- §legacy-bracket-slot Brackets are the metadata modifier, never an executor
929
- selector: the runtime or MCP service is the fence name, and tool input belongs
930
- in the body. A bracket block that leads an executor fence or follows a target
931
- is metadata, so a legacy `[node]` selector reaches its owner as metadata text
932
- and is refused there; a bracket before the target of a non-executor OP is one
933
- bounded header diagnostic that selects nothing.
920
+ §bracket-metadata-slot Brackets are the owner's metadata modifier: the runtime
921
+ or MCP service is the fence name, and tool input belongs in the body. A bracket
922
+ block that leads an executor fence or follows a target is metadata for that
923
+ owner; a bracket before the target of a non-executor OP is one bounded header
924
+ diagnostic that selects nothing.
934
925
 
935
926
  The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
936
927
  scopes ordered results. Executions and SEND scope owner-defined timing. READ, EDIT, COPY,
@@ -987,7 +978,7 @@ same bare spelling the receipt used: a model that appends the channel to the pat
987
978
  shown addresses the same entry, and so does one that writes it after the parenthesis,
988
979
  `(page.html) #readable`, which {§naked-pattern} reads as the channel. `raw` is therefore always the path alone; a
989
980
  bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
990
- Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
981
+ Without a `#` the field is absent.
991
982
 
992
983
  §read-exact-target READ targets one exact resource (a local path or scheme
993
984
  URL, with optional `#channel` fragment or `[metadata]`) and has no body. A
@@ -1299,8 +1290,8 @@ operation receives empty-turn recovery, not successful completion ({§empty-turn
1299
1290
 
1300
1291
  The model may supply WAIT or request completion with parameterless KILL. The host, not the grammar, owns
1301
1292
  turn boundaries and adjudicates the loop's actual obligations. Asking
1302
- the human is the native `question` executor tool ({§question-tool}), not a
1303
- disposition. The shape rules ARE structural:
1293
+ the human uses the native `question` executor tool, offered only when a person attends the
1294
+ worker's owner ({§owner-interaction-ring}), not a disposition. The shape rules ARE structural:
1304
1295
 
1305
1296
  - §send-mid-reservation WAIT is reserved ({§turn-disposition}).
1306
1297
  A turn admits any number of lifecycle declarations, anywhere among its
@@ -1311,6 +1302,8 @@ disposition. The shape rules ARE structural:
1311
1302
  dispositions until the other admitted operations settle
1312
1303
  ({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
1313
1304
  position. Omission does not synthesize a disposition ({§turn-shape}).
1305
+ This applies to WAIT, not to the literal answer region opened by parameterless KILL
1306
+ ({§terminal-kill}).
1314
1307
  - SEND is communication: an optional recipient path and an optional body.
1315
1308
  - §park-202-only WAIT joins live work: an open stream or a live
1316
1309
  child. With none, it continues. Its optional duration bounds parking ({§send-wait-scope});
@@ -1459,7 +1452,7 @@ among them: the parser is `@plurnk/plurnk-parser`'s ({§parser-consumers}).
1459
1452
  | `TurnDisposition` | The one turn disposition and its recognition | {§turn-disposition} |
1460
1453
  | `CapabilityAdmission` | Admission of a capability descriptor against policy layers | {§capability-admission} |
1461
1454
  | `PLURNK_OPS`, `INTERNAL_ROW_OPS`, `PLURNK_FENCE` | The closed operation alphabet and the language's fence | {§canonical-statement} |
1462
- | `PROPOSAL_POLICIES` | The vocabulary a loop policy chooses from | {§loop-policy} |
1455
+ | `PROPOSAL_POLICIES` | Server proposal disposition vocabulary | {§proposal-policy} |
1463
1456
  | `WORKER_NAME` | Authority minting predicate | {§worker-name} |
1464
1457
  | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
1465
1458
 
@@ -1562,12 +1555,19 @@ schema, this suffix invariant, and Problem status equality.
1562
1555
  | 100–399 | `problem` is forbidden |
1563
1556
  | 400–599 | One RFC 9457 `problem` object is required |
1564
1557
 
1565
- The legacy top-level `error` field is forbidden. Producer-specific success
1566
- fields and Problem Details extension members remain open. A malformed result is
1558
+ Producer-specific success fields and Problem Details extension members remain
1559
+ open. A malformed result is
1567
1560
  an internal producer contract violation; it is not converted into a second
1568
1561
  model-facing failure envelope.
1569
1562
 
1570
- ### 13.5 Problem Details
1563
+ ### 13.5 Diagnostics and Problem Details
1564
+
1565
+ §diagnostic-observation **A diagnostic says what happened, never why.** Every Problem `detail`
1566
+ and `recovery`, parse diagnostic and notice names what was read, what was done with it, and
1567
+ where, and may show the working form of the construct it read or refused. It never names a cause
1568
+ or an intent the input does not establish: no likely mistake, no reconstruction of what the author
1569
+ meant, no alternative the producer did not apply, and no form the language no longer has. Each
1570
+ sentence presupposes only what the producer itself decided.
1571
1571
 
1572
1572
  §problem-details `ProblemDetails` requires `type`, `title`, `status`, and `detail`;
1573
1573
  `instance` is optional until a durable host can attach the occurrence URI.
@@ -1585,8 +1585,8 @@ model-facing failure envelope.
1585
1585
  | extensions | Factual producer-known operands or constraints |
1586
1586
 
1587
1587
  `detail` is failure truth; `recovery` is not a second explanation. Producers do
1588
- not infer motives, blame the model, restate status, or expose an implementation
1589
- accident as the cause. General syntax and workflow teaching remain in the model
1588
+ not blame the model, restate status, or expose an implementation accident as the
1589
+ cause ({§diagnostic-observation}). General syntax and workflow teaching remain in the model
1590
1590
  packet rather than being repeated in every Problem.
1591
1591
 
1592
1592
  `Problems.create(owner, code, status, detail, extensions?, options?)` derives a
@@ -1764,17 +1764,14 @@ class PlurnkParseError extends Error {
1764
1764
  ```
1765
1765
 
1766
1766
  §parse-recovery **Every hard diagnostic carries its working form.** A `severity: "error"`
1767
- diagnostic names, in `recovery`, the form that runs, in the model's terms: a refused heading
1768
- carries the operation's canonical line (`` `READ (path) <L,M>? pattern? <!-- aside -->?` on the
1769
- opening fence line; READ takes no body. ``), a refused matcher the dialect's form with an example,
1770
- and a refused regex the regex that matches the words the model wrote — `` A pattern is a regex:
1771
- write `/url/` to match lines containing url; `*` repeats what precedes it. To select files by
1772
- name, put the glob in the target: `FIND (tests/*url*)`. `` — where the glob suggestion is derived
1773
- from the pattern only when it is glob-shaped, and the regex sentence stands alone otherwise. The
1774
- runtime projects `recovery` as the Problem's `recovery` beside the verbatim `message`; an advisory
1775
- carries none, since its statement ran. A refused matcher carries the same pair into its own
1776
- operation's Problem ({§matcher-refusal}). Before this, a refused regex (`/*url*/`, DeepSeek run429)
1777
- reported `Nothing to repeat` with no way forward and the turn was spent.
1767
+ diagnostic names, in `recovery`, the form of the construct it refused, in the model's terms: a
1768
+ refused heading carries the operation's canonical line (`` `READ (path) <L,M>? pattern? <!-- aside -->?`
1769
+ on the opening fence line; READ takes no body. ``), a refused scope the scope's form, and a refused
1770
+ matcher its dialect's form with an example (`` A pattern is a regex written `/pattern/flags`, such
1771
+ as `/timeout/i`. ``). The working form is the language's, never a reconstruction of the input
1772
+ ({§diagnostic-observation}). The runtime projects `recovery` as the Problem's `recovery` beside the
1773
+ verbatim `message`; an advisory carries none, since its statement ran. A refused matcher carries
1774
+ the same pair into its own operation's Problem ({§matcher-refusal}).
1778
1775
 
1779
1776
  §parser-position Parser source locations are points, not text regions. An AST
1780
1777
  statement's `position` identifies the first backtick of its header; a diagnostic
@@ -1870,15 +1867,9 @@ diagnostics are:
1870
1867
  `FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
1871
1868
  dialect without slashes or flags: the whole text is the pattern, so
1872
1869
  `READ (notes.md) ^Decision:.*` selects lines beginning with `Decision:`.
1873
- `^` is deliberately absent from `plurnk.md`'s dialect table and belongs here instead
1874
- (#804). It is carried because `^` meaning "anchor" is among the
1875
- strongest instincts a model arrives with: it will write `^Decision:.*` whether or not it
1876
- was taught to, and the engine honours what it will reach for anyway. Teaching it would
1877
- spend hot-path weight on a line that changes no behaviour. The omission is therefore not
1878
- drift between the teaching and the engine, and closing it in either direction is a
1879
- regression: restoring the row pays for nothing, and retiring the prefix makes the
1880
- pretrained spelling a glob that matches a literal caret — silence in place of the
1881
- selection the model asked for.
1870
+ `^` is deliberately absent from `plurnk.md`'s dialect table: a model writes the anchor
1871
+ whether or not it is taught, so the engine honours it without spending hot-path teaching
1872
+ on it. The omission is not drift between the teaching and the engine.
1882
1873
  - §trailing-slots **Slots after the matcher peel off the right.** The heading text after
1883
1874
  the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
1884
1875
  shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
@@ -1916,9 +1907,9 @@ diagnostics are:
1916
1907
  - §bare-target **A target written without its parentheses is refused with the line that runs.**
1917
1908
  A FIND, READ or EDIT heading with no target whose heading text opens with a word that is no
1918
1909
  matcher sigil, `READ a.py <1,4>`, cannot run: the word stands where the target goes, but on FIND it
1919
- could as well be a pattern. The statement is one hard diagnostic that writes the corrected line —
1920
- `` `READ` has no target: `a.py` stands where the target goes. Write the target in parentheses:
1921
- `READ (a.py) <1,4>`. `` A targetless KILL's heading text remains its inline deliverable.
1910
+ could as well be a pattern. The statement is one hard diagnostic,
1911
+ `` `READ` has no target: `a.py` stands where the target goes. ``, whose recovery is the operation's
1912
+ working form ({§parse-recovery}). A targetless KILL's heading text remains its inline deliverable.
1922
1913
  - §bare-anchor-scope **An EDIT's anchor without its angle brackets is its scope.** On an EDIT
1923
1914
  heading, `@abcde` or `@abcde,@fghij` standing alone where the scope goes — nothing but an aside, a
1924
1915
  closer or the line end after it — is the scope `<@abcde>`, with one warning-severity advisory:
@@ -1927,9 +1918,9 @@ diagnostics are:
1927
1918
  marker (14 distinct recorded headings). On READ and KILL the same text stays a literal matcher,
1928
1919
  since `@patch` is a search a model means.
1929
1920
  - §matcher-body-redirect **Unread body text on bodyless operations.** FIND, READ and bodyless
1930
- KILL keep the statement but discard text the matcher did not consume, with one warning:
1931
- `READ takes no body; the body was ignored. A pattern belongs on the opening fence line
1932
- after the path.` KILL's advisory says `This KILL`, limiting the claim to that invocation:
1921
+ KILL keep the statement but do not use text the matcher did not consume, with one warning
1922
+ naming where it stands: `READ takes no body; lines 4–38 were not used.`, or `the text after its
1923
+ heading on line 3` when it shares the heading's line. KILL's advisory says `This KILL`, limiting the claim to that invocation:
1933
1924
  parameterless completion and log distillation bodies remain valid. Delivered like
1934
1925
  {§misplaced-aside-advisory} as a `parse_advisory` notice (a warning, never an error).
1935
1926
  One sigil line beneath a bodyless heading is the bare form written a line low and still lifts;
@@ -1960,33 +1951,33 @@ diagnostics are:
1960
1951
  with any other content is ignored under the same advisory path
1961
1952
  ({§matcher-body-redirect}).
1962
1953
 
1963
- §error-shape The diagnostic class determines how much guidance the parser may
1964
- provide. Advisories belong to one successfully built statement. A rejected
1954
+ §error-shape The diagnostic class determines the parts of a message; every class says what
1955
+ happened, never why ({§diagnostic-observation}). Advisories belong to one successfully built statement. A rejected
1965
1956
  statement emits its hard diagnostic, not normalization advisories; neither a
1966
1957
  rejection nor an internal exception carries advisories into another statement
1967
1958
  or parser invocation.
1968
1959
 
1969
- | Class | Surface | Message contract |
1970
- |------------------------|-----------------------|-------------------------------------------------------------------------------------------|
1971
- | Hard fact | `severity: "error"` | One concise observed fact and violated constraint in PLURNK vocabulary. |
1972
- | Targeted hard redirect | `severity: "error"` | One canonical correction only when parser state makes the intended structure unambiguous. |
1973
- | Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
1974
- | Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
1960
+ | Class | Surface | Message contract |
1961
+ |---------------|-----------------------|-------------------------------------------------------------------------------------------------------------------|
1962
+ | Hard fact | `severity: "error"` | What was read, where, and the constraint it violates, in PLURNK vocabulary; `recovery` is the construct's working form ({§parse-recovery}). |
1963
+ | Advisory | `severity: "warning"` | What was read and what was done with it, read as a named construct or not used, and where; the statement remains admitted. |
1964
+ | Boundary loss | `unparsedTail` | Where trust ends and which header slot remains open; later input is undefined. |
1975
1965
 
1976
1966
  All messages use PLURNK protocol vocabulary: opening fence, closing fence, target,
1977
1967
  scope, line marker, body, section boundary, or space between slots. They never
1978
1968
  expose ANTLR rule or token names. They refer to a slot or
1979
- feature rather than an implementation rule. Generic tutoring, speculative
1980
- intent, coordinate restatement, and multiple repair strategies are forbidden.
1969
+ feature rather than an implementation rule. Generic tutoring, coordinate
1970
+ restatement and multiple repair strategies are forbidden, and no message names an
1971
+ intent ({§diagnostic-observation}).
1981
1972
  Unexpected top-level text immediately after a closed operation identifies that
1982
1973
  operation's opening line, closing line, and matching backtick count.
1983
1974
 
1984
1975
  Examples of canonical hard facts:
1985
1976
 
1986
1977
  - `unrecognized character '<' in target`
1987
- - `unexpected bracket modifier; the fence name selects the executor`
1978
+ - `unrecognized character '[' in the sh header`
1988
1979
  - `unrecognized character 'X' in statement header`
1989
- - `WAIT's body begins below the header`
1980
+ - `unrecognized character '[' in the WAIT header`
1990
1981
  - `expected ')'; got ':'`
1991
1982
 
1992
1983
  Each malformed statement produces at most one hard error. The first recorded
@@ -70,8 +70,11 @@
70
70
  "model": null,
71
71
  "loopId": null,
72
72
  "packetCount": 0,
73
+ "waitUntil": null,
73
74
  "preparation": [],
74
- "activity": null
75
+ "activity": null,
76
+ "children": 0,
77
+ "descendants": { "requests": 0, "usage": null, "knownUsage": null, "costUsd": null, "knownCostUsd": null }
75
78
  }
76
79
  },
77
80
  "budget": {}
@@ -4,13 +4,14 @@
4
4
  "title": "ClientInteractionProjection",
5
5
  "description": "The runtime-neutral client-interface projection of one pending client interaction. Live delivery and reconnect discovery expose this same shape.",
6
6
  "type": "object",
7
- "required": ["interactionId", "workerId", "loopId", "turnId", "request"],
7
+ "required": ["interactionId", "workerId", "loopId", "turnId", "recipient", "request"],
8
8
  "additionalProperties": false,
9
9
  "properties": {
10
10
  "interactionId": { "type": "integer", "minimum": 1 },
11
11
  "workerId": { "type": "integer", "minimum": 1 },
12
12
  "loopId": { "type": "integer", "minimum": 1 },
13
13
  "turnId": { "type": "integer", "minimum": 1 },
14
+ "recipient": { "type": "string", "minLength": 1 },
14
15
  "request": { "$ref": "https://schemas.plurnk.xyz/v0/ClientInteractionRequest.json" }
15
16
  }
16
17
  }
@@ -15,7 +15,7 @@
15
15
  "kind": {
16
16
  "type": "string",
17
17
  "minLength": 1,
18
- "description": "Open discriminator within a source. Examples include `grammar_unenforced`, `search_progress`, and `turn_awaiting_model`."
18
+ "description": "Open discriminator within a source. Examples include `output_unaccounted`, `search_progress`, and `turn_awaiting_model`."
19
19
  },
20
20
  "level": {
21
21
  "enum": ["error", "warn", "info"],
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://schemas.plurnk.xyz/v0/OperationResult.json",
4
4
  "title": "OperationResult",
5
- "description": "Universal PLURNK operation result. Producer-owned metadata is open, but failures always carry one RFC 9457 Problem Details object and never a parallel error field.",
5
+ "description": "Universal PLURNK operation result. Producer-owned metadata is open, but failures always carry one RFC 9457 Problem Details object.",
6
6
  "type": "object",
7
7
  "required": ["status"],
8
8
  "additionalProperties": true,
@@ -17,8 +17,7 @@
17
17
  },
18
18
  "range": {
19
19
  "$ref": "https://schemas.plurnk.xyz/v0/RangeExtent.json"
20
- },
21
- "error": false
20
+ }
22
21
  },
23
22
  "oneOf": [
24
23
  {
@@ -2,22 +2,21 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://schemas.plurnk.xyz/v0/ProposalDisposition.json",
4
4
  "title": "ProposalDisposition",
5
- "description": "The one core-owned settlement decision for a pending proposal. Client-owned proposals require an external resolution; loop-owned proposals carry the exact automatic resolution core applies.",
5
+ "description": "The one core-owned settlement decision. Review waits for the worker's owner; accept and reject carry the automatic resolution core applies.",
6
6
  "oneOf": [
7
7
  {
8
8
  "type": "object",
9
- "required": ["owner"],
9
+ "required": ["decision"],
10
10
  "additionalProperties": false,
11
11
  "properties": {
12
- "owner": { "const": "client" }
12
+ "decision": { "const": "review" }
13
13
  }
14
14
  },
15
15
  {
16
16
  "type": "object",
17
- "required": ["owner", "decision"],
17
+ "required": ["decision"],
18
18
  "additionalProperties": false,
19
19
  "properties": {
20
- "owner": { "const": "loop" },
21
20
  "decision": { "enum": ["accept", "reject"] },
22
21
  "outcome": { "type": "string", "minLength": 1 }
23
22
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.xyz/v0/ProposalPolicy.json",
4
+ "title": "ProposalPolicy",
5
+ "description": "Server disposition for proposals. Review requires the worker's owner; accept and reject settle within core.",
6
+ "enum": ["review", "accept", "reject"]
7
+ }
@@ -13,7 +13,7 @@
13
13
  "target",
14
14
  "body",
15
15
  "attrs",
16
- "policy",
16
+ "owner",
17
17
  "disposition"
18
18
  ],
19
19
  "additionalProperties": false,
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "body": { "type": "string" },
44
44
  "attrs": { "type": "object", "additionalProperties": true },
45
- "policy": { "$ref": "https://schemas.plurnk.xyz/v0/LoopPolicy.json" },
45
+ "owner": { "type": "string", "minLength": 1 },
46
46
  "disposition": { "$ref": "https://schemas.plurnk.xyz/v0/ProposalDisposition.json" }
47
47
  }
48
48
  }