@plurnk/plurnk-contracts 1.16.4 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -29
- package/SPEC.md +605 -518
- package/dist/conformance/agui-v1.json +3 -3
- package/dist/schema/CapabilityProjection.json +2 -4
- package/dist/schema/ClientStatement.json +5 -52
- package/dist/schema/FunctionalityDefinitionState.json +2 -2
- package/dist/schema/LineMarker.json +1 -1
- package/dist/schema/LoopPolicy.json +2 -3
- package/dist/schema/MatcherBody.json +6 -6
- package/dist/schema/McpServerDefinition.json +17 -0
- package/dist/schema/ModelCatalogPage.json +10 -1
- package/dist/schema/ModelRoute.json +9 -0
- package/dist/schema/Notice.json +1 -1
- package/dist/schema/ParsedPath.json +3 -2
- package/dist/schema/Plan.json +9 -5
- package/dist/schema/PlurnkStatement.json +86 -134
- package/dist/schema/ProposalProjection.json +2 -1
- package/dist/schema/ResourceSelection.json +51 -27
- package/dist/schema/SkillDefinition.json +3 -3
- package/dist/src/AcpPlanValue.d.ts +0 -1
- package/dist/src/AcpPlanValue.d.ts.map +1 -1
- package/dist/src/AcpPlanValue.js +14 -13
- package/dist/src/AcpPlanValue.js.map +1 -1
- package/dist/src/ApplicationPort.d.ts +16 -14
- package/dist/src/ApplicationPort.d.ts.map +1 -1
- package/dist/src/AstBuilder.d.ts +3 -2
- package/dist/src/AstBuilder.d.ts.map +1 -1
- package/dist/src/AstBuilder.js +340 -187
- package/dist/src/AstBuilder.js.map +1 -1
- package/dist/src/JsonDocument.d.ts +2 -0
- package/dist/src/JsonDocument.d.ts.map +1 -0
- package/dist/src/JsonDocument.js +14 -0
- package/dist/src/JsonDocument.js.map +1 -0
- package/dist/src/LoopLifecycle.d.ts +3 -0
- package/dist/src/LoopLifecycle.d.ts.map +1 -0
- package/dist/src/LoopLifecycle.js +14 -0
- package/dist/src/LoopLifecycle.js.map +1 -0
- package/dist/src/PlanValue.d.ts +1 -1
- package/dist/src/PlanValue.d.ts.map +1 -1
- package/dist/src/PlanValue.js +10 -6
- package/dist/src/PlanValue.js.map +1 -1
- package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
- package/dist/src/PlurnkErrorStrategy.js +58 -139
- package/dist/src/PlurnkErrorStrategy.js.map +1 -1
- package/dist/src/PlurnkParseError.d.ts +3 -1
- package/dist/src/PlurnkParseError.d.ts.map +1 -1
- package/dist/src/PlurnkParseError.js +4 -1
- package/dist/src/PlurnkParseError.js.map +1 -1
- package/dist/src/PlurnkParser.d.ts +9 -5
- package/dist/src/PlurnkParser.d.ts.map +1 -1
- package/dist/src/PlurnkParser.js +175 -180
- package/dist/src/PlurnkParser.js.map +1 -1
- package/dist/src/RecordingListener.d.ts +1 -1
- package/dist/src/RecordingListener.d.ts.map +1 -1
- package/dist/src/RecordingListener.js +24 -6
- package/dist/src/RecordingListener.js.map +1 -1
- package/dist/src/TurnDisposition.d.ts +11 -0
- package/dist/src/TurnDisposition.d.ts.map +1 -0
- package/dist/src/TurnDisposition.js +32 -0
- package/dist/src/TurnDisposition.js.map +1 -0
- package/dist/src/Validator.js +1 -1
- package/dist/src/Validator.js.map +1 -1
- package/dist/src/generated/plurnkLexer.d.ts +95 -106
- package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
- package/dist/src/generated/plurnkLexer.js +674 -828
- package/dist/src/generated/plurnkLexer.js.map +1 -1
- package/dist/src/generated/plurnkParser.d.ts +111 -152
- package/dist/src/generated/plurnkParser.d.ts.map +1 -1
- package/dist/src/generated/plurnkParser.js +756 -1082
- package/dist/src/generated/plurnkParser.js.map +1 -1
- package/dist/src/generated/plurnkParserVisitor.d.ts +15 -36
- package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
- package/dist/src/generated/plurnkParserVisitor.js +12 -30
- package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +3 -7
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.generated.d.ts +147 -80
- package/dist/src/types.generated.d.ts.map +1 -1
- package/dist/src/types.js +3 -3
- package/dist/src/types.js.map +1 -1
- package/package.json +4 -8
- package/plurnk.md +103 -119
- package/dist/plurnk.gemma.gbnf +0 -141
- package/dist/plurnk.qwen.gbnf +0 -130
package/SPEC.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## 1. Overview
|
|
4
4
|
|
|
5
5
|
§contract-authority This package is the single authority for PLURNK's language, schemas, generated
|
|
6
|
-
types, parser,
|
|
6
|
+
types, parser, and runtime-neutral wire envelopes. Its package root
|
|
7
7
|
is the single code API for those contracts.
|
|
8
8
|
|
|
9
9
|
| Surface | Canonical export or artifact |
|
|
@@ -23,12 +23,12 @@ is the single code API for those contracts.
|
|
|
23
23
|
| AG-UI discovery, client accounting, and shared conformance specimens | `AguiDiscovery`, `AguiClientConformance`, `AguiConformanceKit` |
|
|
24
24
|
| JSON Schemas | `@plurnk/plurnk-contracts/schema/*.json` |
|
|
25
25
|
| Generated JSON result rendering | `renderJsonResult` |
|
|
26
|
-
| Local-model rails | `@plurnk/plurnk-contracts/plurnk.{gemma,qwen}.gbnf` |
|
|
27
26
|
| Model language reference | `plurnk.md` in the package |
|
|
28
27
|
|
|
29
28
|
§contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
|
|
30
29
|
generated from the schemas; ANTLR is authoritative for accepted model-language
|
|
31
|
-
syntax
|
|
30
|
+
syntax. No generation grammar is generated or shipped; an operator's own GBNF
|
|
31
|
+
is carried verbatim to a llama-server route by the providers package.
|
|
32
32
|
|
|
33
33
|
§agui-discovery-contract `AguiDiscovery` is the complete installed AG-UI+
|
|
34
34
|
surface at one instant. `schemaVersion` identifies its discovery shape;
|
|
@@ -46,6 +46,10 @@ not satisfy the advertised `inputSchema`, rejects an owner's successful output
|
|
|
46
46
|
when it does not satisfy `outputSchema`, and validates a known notification
|
|
47
47
|
before projecting it to AG-UI. Schemas are discovery values owned by their
|
|
48
48
|
registrants; validation must not annotate or otherwise mutate them.
|
|
49
|
+
Input Problems retain the structured validation `issues` and name the failing
|
|
50
|
+
instance locations and constraints in `detail`. Parent aggregate errors are
|
|
51
|
+
omitted from that prose when their specific child errors are available; input
|
|
52
|
+
objects are never echoed wholesale or interpreted as intent.
|
|
49
53
|
|
|
50
54
|
§agui-client-conformance `AguiClientConformance` is a language-neutral JSON
|
|
51
55
|
document accounting for every action and notification in one
|
|
@@ -69,30 +73,37 @@ a third protocol implementation: each client feeds the same chunks and events
|
|
|
69
73
|
through its production parser and projection seam, then verifies the declared
|
|
70
74
|
outcome. Specimen names are unique within their transport or lifecycle family.
|
|
71
75
|
|
|
72
|
-
§json-result-rendering `renderJsonResult`
|
|
73
|
-
|
|
76
|
+
§json-result-rendering `renderJsonResult` renders compact aggregate operation
|
|
77
|
+
rows. A top-level array remains one valid,
|
|
74
78
|
compact JSON value but places each item on its own physical line by adding only
|
|
75
79
|
item-boundary newlines; an empty or single-item array and every non-array value
|
|
76
80
|
remain one line. It never rewrites arbitrary stored JSON, whose original lines
|
|
77
81
|
remain source coordinates.
|
|
78
82
|
|
|
83
|
+
§json-document-presentation Generated JSON documents use two-space indentation.
|
|
84
|
+
Normalized remote JSON text uses `formatJsonDocument`: whitespace-only formatting
|
|
85
|
+
of a complete, strict JSON document, preserving key order, duplicate keys, number
|
|
86
|
+
lexemes, and string escapes. Invalid or incomplete input is declined, not repaired.
|
|
87
|
+
Apply formatting at the representation owner before storage, indexing, scoping,
|
|
88
|
+
and previews; never reformat literal resources, JSONL framing, or wire/evidence
|
|
89
|
+
serialization. Compact aggregate rows ({§json-result-rendering}) and packet
|
|
90
|
+
metadata retain their deliberate layouts.
|
|
91
|
+
|
|
79
92
|
## §contract-layers 1.1 Contract layers and admission boundary
|
|
80
93
|
|
|
81
94
|
PLURNK uses one contract with deliberately different projections. A tolerant
|
|
82
95
|
ingester accepting a spelling does not make that spelling canonical model
|
|
83
|
-
teaching, and
|
|
84
|
-
semantics valid.
|
|
96
|
+
teaching, and an operator's sampling grammar admitting a sentence does not
|
|
97
|
+
make its runtime semantics valid.
|
|
85
98
|
|
|
86
99
|
```mermaid
|
|
87
100
|
flowchart LR
|
|
88
101
|
canon["Canonical model teaching<br/>plurnk.md"]
|
|
89
|
-
rail["Optional raw generation rail<br/>Gemma or Qwen template profile"]
|
|
90
102
|
free["Other admitted input"]
|
|
91
103
|
syntax["ANTLR lexer + parser<br/>syntax and document tier"]
|
|
92
104
|
ast["AstBuilder<br/>typed, serializable AST"]
|
|
93
105
|
runtime["Runtime owners<br/>stateful semantics and effects"]
|
|
94
|
-
canon -->
|
|
95
|
-
rail --> syntax
|
|
106
|
+
canon --> syntax
|
|
96
107
|
canon --> free
|
|
97
108
|
free --> syntax
|
|
98
109
|
syntax --> ast
|
|
@@ -103,20 +114,19 @@ flowchart LR
|
|
|
103
114
|
|--------------------------|-------------------------------------|---------------------------------------------------------------------------------|
|
|
104
115
|
| Stable current law | `SPEC.md` | Owns invariants and boundaries; forge issues retain history |
|
|
105
116
|
| Canonical model teaching | `plurnk.md` | Teaches the lean spelling and operational model the model should emit |
|
|
106
|
-
|
|
|
107
|
-
| Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, heading lanes, slot shape, and section boundaries |
|
|
117
|
+
| Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, operation fences, slot shape, and section boundaries |
|
|
108
118
|
| Typed admission | `AstBuilder` | Produces JSON-serializable unions and validates deterministic body/path syntax |
|
|
109
119
|
| Shared wire data | `schema/*.json` | Defines runtime-neutral data shapes projected into generated TypeScript |
|
|
110
120
|
| Stateful behavior | consuming runtime | Resolves addresses, permissions, selection arithmetic, effects, and lifecycle |
|
|
111
121
|
|
|
112
|
-
ANTLR owns statement structure,
|
|
122
|
+
ANTLR owns statement structure, fence matching, slot multiplicity, accepted
|
|
113
123
|
slot permutations, scope-number syntax, and interstatement text recognition.
|
|
114
124
|
AstBuilder owns URL decomposition and deterministic matcher validation through
|
|
115
125
|
WHATWG `URL`, ECMAScript `RegExp`, XPath 1.0, and RFC 9535 JSONPath parsers.
|
|
116
126
|
|
|
117
127
|
The runtime owner decides facts that require state or operation-specific
|
|
118
128
|
meaning, including registered scheme resolution, target existence, tag
|
|
119
|
-
selection, text-region bounds, result ordering,
|
|
129
|
+
selection, text-region bounds, result ordering, full-text ranking, mutation
|
|
120
130
|
effects, executor behavior, and numeric operation-code semantics.
|
|
121
131
|
|
|
122
132
|
### §contract-proposal-projection Loop policy and stopped-world projection
|
|
@@ -126,9 +136,9 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
|
|
|
126
136
|
| Contract | Shape invariant | Runtime responsibility |
|
|
127
137
|
| ------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
128
138
|
| `CapabilityDescriptor` | One routed operation demand with its operation, access class, resource/runtime/tool coordinates, and declared traits | Derive every demand before dispatch |
|
|
129
|
-
| `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service
|
|
130
|
-
| `CapabilityProjection` | Exact service, workspace,
|
|
131
|
-
| `LoopPolicy` |
|
|
139
|
+
| `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service and workspace layers |
|
|
140
|
+
| `CapabilityProjection` | Exact service, workspace, and normalized effective policies | Expose the resolver's workspace cascade without claiming one layer is effective authority |
|
|
141
|
+
| `LoopPolicy` | One `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
|
|
132
142
|
| `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
|
|
133
143
|
| `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 |
|
|
134
144
|
| `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
|
|
@@ -144,25 +154,24 @@ least one selector must match. An empty policy admits everything and an empty
|
|
|
144
154
|
|
|
145
155
|
§capability-policy-cascade Capability layers are purely subtractive and
|
|
146
156
|
order-independent: a descriptor is admitted only when every layer admits it.
|
|
147
|
-
|
|
157
|
+
Workspace policy cannot restore authority denied by the service. A
|
|
148
158
|
composed operation is admitted only when every routed demand survives. These
|
|
149
159
|
descriptors govern routed external authority, not every grammar statement:
|
|
150
|
-
log/program control such as
|
|
151
|
-
creates no capability demand. A known interactive runtime is represented by
|
|
160
|
+
log/program control such as log KILL, the native dispositions, and
|
|
161
|
+
targetless SEND creates no capability demand. A known interactive runtime is represented by
|
|
152
162
|
access class `interact`; scheme and runtime manifests contribute traits rather
|
|
153
163
|
than hidden policy behavior.
|
|
154
164
|
|
|
155
165
|
§capability-policy-projection A `CapabilityProjection` reports every durable
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
is therefore absent from this durable Worker projection.
|
|
166
|
+
workspace layer and their normalized intersection: `service`, `workspace`, and
|
|
167
|
+
`effective`. Only `workspace` is client-mutable. Workers and loops have no
|
|
168
|
+
capability policy or inherited bound; every actor uses the same live workspace
|
|
169
|
+
policy. A client never derives effective authority from the mutable layer alone.
|
|
161
170
|
|
|
162
171
|
§loop-policy `DEFAULT_CAPABILITY_POLICY` and `DEFAULT_LOOP_POLICY` are the
|
|
163
172
|
contracts-owned complete defaults. A loop policy is immutable after creation;
|
|
164
|
-
its `
|
|
165
|
-
|
|
173
|
+
its `proposals` field chooses one downstream settlement posture, independently
|
|
174
|
+
of workspace capability policy. Capability
|
|
166
175
|
admission precedes effect classification and proposal settlement.
|
|
167
176
|
|
|
168
177
|
§reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
|
|
@@ -178,6 +187,10 @@ physical limits, capabilities, and local `ModelReadiness`. A readiness cause
|
|
|
178
187
|
contains alternative environment-variable sets—every name within a set is
|
|
179
188
|
required and any set may satisfy the cause. It carries names only, never values,
|
|
180
189
|
and asserts neither credential validity nor endpoint reachability.
|
|
190
|
+
`capabilities.reasoningPolicies` lists the route's admitted members of
|
|
191
|
+
{§reasoning-policy-wire}, including supported activation policies; clients do
|
|
192
|
+
not infer fixed efforts from the `reasoning` capability bit. It is not a
|
|
193
|
+
worker's model/spawn intersection or an alias-specific tuning projection.
|
|
181
194
|
|
|
182
195
|
### §client-interaction-wire Client-owned interaction wire
|
|
183
196
|
|
|
@@ -229,195 +242,224 @@ USD-expressible request and is `null` only when none is expressible. The empty
|
|
|
229
242
|
request set projects explicit zero usage and cost. Consumers do not recompute
|
|
230
243
|
provider rates or convert currencies while reading the projection.
|
|
231
244
|
|
|
232
|
-
The parser returns ordered statement
|
|
245
|
+
The parser ignores outside text and returns ordered statement and error items
|
|
246
|
+
under {§whitespace-contract}. It recovers at a
|
|
233
247
|
trustworthy statement boundary when possible and sets `unparsedTail` when a
|
|
234
|
-
boundary-destroying failure makes later input undefined.
|
|
248
|
+
boundary-destroying failure makes later input undefined. Operation status codes
|
|
235
249
|
and parse diagnostics are separate contracts.
|
|
236
250
|
|
|
237
|
-
## 1.2 GBNF Generation Rail
|
|
238
|
-
|
|
239
|
-
§gbnf-rail-purpose ANTLR and AstBuilder define accepted PLURNK input. The generated
|
|
240
|
-
`dist/plurnk.{gemma,qwen}.gbnf` are optional local llama.cpp sampling rails kept lean
|
|
241
|
-
to make useful, ANTLR-compliant turns more likely without reproducing every
|
|
242
|
-
parser or semantic validator. Parse compatibility is a design goal balanced
|
|
243
|
-
against rail size and sampling efficiency, not a language-subset guarantee. A
|
|
244
|
-
rail-legal operation can therefore produce a parser or AstBuilder error; consumers
|
|
245
|
-
apply their ordinary admission and bounded-operation recovery contract.
|
|
246
|
-
The complete package build emits both rails; they are not source-controlled. Source and
|
|
247
|
-
differential tests serialize the owning generator directly, while installation
|
|
248
|
-
coverage verifies the packed export.
|
|
249
|
-
|
|
250
|
-
The rails share one turn shape but begin at their respective sampled-token
|
|
251
|
-
boundaries:
|
|
252
|
-
|
|
253
|
-
```ebnf
|
|
254
|
-
root-gemma ::= channel sep framed-turn
|
|
255
|
-
root-qwen ::= think-body think-close sep framed-turn
|
|
256
|
-
root-qwen-response ::= think-open root-qwen
|
|
257
|
-
framed-turn ::= turn | fence-open turn fence-close
|
|
258
|
-
turn ::= plan sep tail-0
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
§gbnf-turn-shape Neither rail admits an empty thought: the `gemma` channel body and the `qwen` think body each begin with at least one character, so a constrained call reasons before it acts. The `gemma` transport root samples one complete
|
|
262
|
-
`<|channel>thought\n … <channel|>` enclosure. A Qwen-style chat template has
|
|
263
|
-
already supplied `<think>\n` when the `qwen` transport root begins, so that root
|
|
264
|
-
samples the body and required `</think>` closer. Each generated artifact declares
|
|
265
|
-
an `@plurnk-response-root`; for `qwen`, that root composes the template opener
|
|
266
|
-
back onto the sampled text so the complete pre-projection response can be graded.
|
|
267
|
-
Either body may be empty and cannot contain its profile's opener or closer.
|
|
268
|
-
`sep` is zero through seven whitespace characters. The projected PLURNK content
|
|
269
|
-
is either bare or enclosed once in a paired `plurnk` Markdown fence; the turn
|
|
270
|
-
begins with `## PLAN0`, and every following operation is a same-lane `## OP0`
|
|
271
|
-
section.
|
|
272
|
-
`tail-0` admits zero through fourteen internal operations followed by exactly
|
|
273
|
-
one terminal SEND under the existing terminal-eligibility rules.
|
|
274
|
-
|
|
275
|
-
```mermaid
|
|
276
|
-
flowchart LR
|
|
277
|
-
sampled["Constrained sampled text<br/>profile reasoning bytes · sep · optional fence · PLAN0 turn"]
|
|
278
|
-
raw["Pre-projection response<br/>one complete reasoning envelope · PLURNK turn"]
|
|
279
|
-
split["llama.cpp<br/>reasoning_format: auto"]
|
|
280
|
-
reasoning["reasoning_content<br/>envelope body"]
|
|
281
|
-
content["content<br/>bare or fenced PLAN through terminal SEND"]
|
|
282
|
-
parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
|
|
283
|
-
sampled --> raw
|
|
284
|
-
raw --> split
|
|
285
|
-
split --> reasoning
|
|
286
|
-
split --> content
|
|
287
|
-
content --> parser
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
§gbnf-reasoning-boundary GBNF applies from sampled token zero before response
|
|
291
|
-
projection. The declared response root composes any template-provided prefix for
|
|
292
|
-
independent validation of the pre-projection evidence. The two projected fields
|
|
293
|
-
are not separate GBNF languages, and `content` alone is not revalidated as though
|
|
294
|
-
it still contained the required reasoning envelope. Provider and core own the
|
|
295
|
-
projection evidence and rail-verdict boundary; this package owns the sampled and
|
|
296
|
-
response roots plus the parser/AstBuilder result.
|
|
297
|
-
|
|
298
|
-
§rail-heading-boundaries On the GBNF rail, PLAN and every operation use lane `0`.
|
|
299
|
-
Every reserved PLAN or operation heading stem is structural, regardless of the
|
|
300
|
-
delimiter a model attempts next, so a non-`0` pseudo-heading cannot be swallowed as
|
|
301
|
-
literal body text. Rail bodies therefore cannot quote reserved headings from any
|
|
302
|
-
lane. This makes both the canonical delimiter and section boundary structurally
|
|
303
|
-
available during constrained generation; ANTLR remains the wider language and
|
|
304
|
-
accepts intentional alternate-lane literals during ingestion.
|
|
305
|
-
|
|
306
|
-
§gbnf-kill-shaping The rail shapes KILL as one required target, an optional
|
|
307
|
-
text-coordinate scope (numeric or anchored), and an optional one-line matcher body,
|
|
308
|
-
without proving that the selection resolves; ANTLR and AstBuilder own the statement's
|
|
309
|
-
shape, and runtime owns target resolution ({§kill-scope}).
|
|
310
|
-
|
|
311
251
|
## §canonical-statement 2. Canonical statement form
|
|
312
252
|
|
|
313
|
-
|
|
314
|
-
|
|
253
|
+
`````text
|
|
254
|
+
```OP (path)? <scope>? [metadata]? <!-- aside -->?
|
|
315
255
|
body
|
|
316
|
-
|
|
317
|
-
## OPdelimiter (path)? {metadata}* <scope>? <!-- annotation -->?
|
|
318
|
-
body?
|
|
319
256
|
```
|
|
320
257
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
§
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
that
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
258
|
+
```OP (path)? <scope>?```
|
|
259
|
+
|
|
260
|
+
```executor (program-or-tool)?
|
|
261
|
+
input
|
|
262
|
+
```
|
|
263
|
+
`````
|
|
264
|
+
|
|
265
|
+
§section-boundary Every statement is one backtick block. Its header occupies one
|
|
266
|
+
physical line: a fence of at least three backticks, an optional numeric delimiter
|
|
267
|
+
({§numeric-delimiter}), then the name and its slots. A closer is shown by
|
|
268
|
+
convention and never demanded ({§fence-closer}, {§closer-fallback}). There are no
|
|
269
|
+
operation suffixes or heading levels. Nothing in the language is counted by the
|
|
270
|
+
author: every boundary is an anchored line the parser recognizes by its first
|
|
271
|
+
characters (operator, 2026-09-12: counted pairs failed 52 of 363 turns on
|
|
272
|
+
GLM-5.3-flash; anchored tokens failed none).
|
|
273
|
+
|
|
274
|
+
§fence-closer A block opened with N backticks and delimiter D (its digits, possibly
|
|
275
|
+
none) closes at the first line at column zero made of at least N backticks,
|
|
276
|
+
exactly D, and nothing else but horizontal whitespace. Count follows CommonMark: a
|
|
277
|
+
shorter fence inside the body is body; an equal or longer bare fence closes a bare
|
|
278
|
+
block. The delimiter compares exactly: a bare fence never closes a delimited block,
|
|
279
|
+
and a delimited fence never closes a bare one. The compact one-line form closes on
|
|
280
|
+
its heading line after the modifiers under the same rule.
|
|
281
|
+
|
|
282
|
+
§numeric-delimiter Digits between the opening backticks and the name (an opener
|
|
283
|
+
carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it. This is how a
|
|
284
|
+
block nests fences of its own width: with a delimiter, a body may carry bare fences
|
|
285
|
+
and headings of the same count. The delimiter is syntax, never AST or persistence
|
|
286
|
+
state; `PlurnkParser.frame` chooses one when the body it wraps holds a heading line
|
|
287
|
+
of four or more backticks ({§statement-rendering}).
|
|
288
|
+
|
|
289
|
+
§fence-heading-in-body A fence line of four or more backticks, optional digits, and
|
|
290
|
+
a name that is a native operation or a known executor is a heading wherever it
|
|
291
|
+
stands. Inside an open block it ends that block without closing it
|
|
292
|
+
({§closer-fallback}) and opens the next statement. Fence lines of fewer than four
|
|
293
|
+
backticks are headings only outside any block. Known executors are `sh` plus what
|
|
294
|
+
the host names in `ParseOptions.executors`. Consequences: a closer glued to the next
|
|
295
|
+
opener (eight backticks then `READ`) can never swallow the rest of a turn, and a quoted
|
|
296
|
+
heading of four or more backticks inside a body needs the numeric delimiter to stay body.
|
|
297
|
+
|
|
298
|
+
§closer-fallback A block that ends at a heading or at the end of the input has no
|
|
299
|
+
closer of its own. Its body is cut back to its last bare fence line (any count,
|
|
300
|
+
optional digits), which is the closer the author meant, and one terminating line
|
|
301
|
+
ending goes with it; when no bare fence line exists the body is the whole span less
|
|
302
|
+
one terminating line ending. This carries no diagnostic: a missing closer is never
|
|
303
|
+
an admission failure, and {§unparsed-tail-boundary} is not involved.
|
|
304
|
+
|
|
305
|
+
§fence-boundary Inside a body, fences are read by count and delimiter, never by
|
|
306
|
+
name, except for the heading rule above:
|
|
307
|
+
|
|
308
|
+
| Fence encountered inside a body | Meaning |
|
|
309
|
+
|---|---|
|
|
310
|
+
| Fewer backticks than the block's own | Body |
|
|
311
|
+
| At least the block's backticks, bare, block undelimited | The block's closer |
|
|
312
|
+
| At least the block's backticks carrying the block's delimiter | The block's closer |
|
|
313
|
+
| At least the block's backticks with any other delimiter | Body |
|
|
314
|
+
| Four or more backticks naming a native operation or known executor | A heading: ends the block, opens the next statement |
|
|
315
|
+
|
|
316
|
+
§indented-fences Leading horizontal whitespace before an opener or a closer is
|
|
317
|
+
not part of the fence: an indented fence line is a fence line, on openers,
|
|
318
|
+
closers, headings that end a block, and the closer fallback. A body keeps its own
|
|
319
|
+
lines' indentation. CommonMark allows three spaces; this allows any, because a
|
|
320
|
+
model that indents an emission indents all of it (operator, 2026-09-12: measured
|
|
321
|
+
at five to ten percent of emissions on GLM-5.3-flash).
|
|
322
|
+
|
|
323
|
+
§inline-chain A closer on a heading line, or on a body's closing line, may be
|
|
324
|
+
followed on that same line by the next opener; the closer still closes, and the
|
|
325
|
+
opener opens. This absorbs the habit of writing several operations in one
|
|
326
|
+
paragraph after prose. Prose after a closer on its line ends the chain.
|
|
327
|
+
|
|
328
|
+
§executor-case **An executor tag in any case.** A fence tag that matches a
|
|
329
|
+
registered executor's name case-insensitively opens that executor (`SH` opens
|
|
330
|
+
`sh`), and the statement's `executor` is the registered spelling, so a lookup
|
|
331
|
+
by that name never misses. Operation names stay uppercase by teaching and were
|
|
332
|
+
never written otherwise in 12,000 fences; one `SH` in 1,300 `sh` fences was
|
|
333
|
+
(2026-09-13 census). An unregistered name in any case is still prose
|
|
334
|
+
({§interstitial-fence}).
|
|
335
|
+
|
|
336
|
+
§one-line-turn **A whole turn on one line.** The most frequent private rejection
|
|
337
|
+
across the 2026-09-12/13 dumbox runs (five of eleven) was a turn emitted as a
|
|
338
|
+
single line: prose, then heading after heading with no line ending anywhere. Two
|
|
339
|
+
rules absorb it. The next opener on a heading's own line, after the heading's
|
|
340
|
+
slots, ends that heading's block bodyless and opens ({§empty-section}), so
|
|
341
|
+
`````EDIT (a.rs) <60,66> <!-- drop --> ````EDIT (b.rs) <31,38>` is two scoped
|
|
342
|
+
deletions; and a TASK whose inventory rides its heading line as a `[…]` block
|
|
343
|
+
(`````TASK [{"content": "…", "status": "in_progress"}] ````) takes that block as
|
|
344
|
+
its body when nothing sits beneath the heading, with one warning-severity
|
|
345
|
+
advisory naming the body as where the inventory belongs. A block beneath the
|
|
346
|
+
heading still wins.
|
|
347
|
+
|
|
348
|
+
§anchor-digits In a text scope, `@` followed by one to four digits cannot be a
|
|
349
|
+
hash and is read as that line number, with one warning-severity advisory naming
|
|
350
|
+
the five-character anchor form. Five characters after `@` are always an anchor.
|
|
351
|
+
|
|
352
|
+
§unclosed-aside A heading whose aside opens with `<!--` and never closes on its
|
|
353
|
+
line takes the rest of the line as the aside, with one warning-severity advisory.
|
|
354
|
+
A closed aside followed by more text is unchanged.
|
|
355
|
+
|
|
356
|
+
§interstitial-fence A fence line that names no native operation and no known
|
|
357
|
+
executor opens nothing: unlabeled, or tagged like a code block (`ts`, `json`),
|
|
358
|
+
outside a block it is prose and ignored like every other outside line
|
|
359
|
+
({§whitespace-contract}); inside a body it is body. Nothing is promoted into a
|
|
360
|
+
header or recursively parsed. There is no implicit SEND: a reply is an explicit
|
|
361
|
+
`SEND` block. (This replaces the retired unlabeled-fence SEND of the fences
|
|
362
|
+
chapter, whose unlabeled fences turned displaced headings into silent messages.)
|
|
363
|
+
|
|
364
|
+
§bare-heading-advisory An operation name that opens a line outside any block in the
|
|
365
|
+
shape of a heading (`READ (…)`, `TASK`, …) is prose and runs nothing. The parser
|
|
366
|
+
emits one warning-severity advisory naming the fence form, placed after the parsed
|
|
367
|
+
items, so the loss is never quiet.
|
|
368
|
+
|
|
369
|
+
§empty-section Both the compact bodyless form and an empty multiline block
|
|
370
|
+
normalize optional bodies to null. TASK normalizes an empty body to `[]`
|
|
371
|
+
under {§plan-value}. Closing fences are conventional, never required
|
|
372
|
+
({§closer-fallback}).
|
|
373
|
+
|
|
374
|
+
§statement-rendering `PlurnkParser.stringify` renders native OP names and named
|
|
375
|
+
EXEC executors from the shared AST, with one blank line between operations.
|
|
376
|
+
Every closing fence occupies its own line, including bodyless operations;
|
|
377
|
+
inline fences remain accepted input, not generated examples.
|
|
378
|
+
It chooses at least four backticks and more than any run within the body, and a
|
|
379
|
+
numeric delimiter whenever the body holds a heading line of four or more backticks
|
|
380
|
+
({§fence-heading-in-body}), preserving body bytes on reparse. Fence length and
|
|
381
|
+
delimiter are syntax, not AST or persistence state. Core-authored programs use
|
|
382
|
+
this serializer and the ordinary admission parser.
|
|
383
|
+
|
|
384
|
+
| Element | Contract |
|
|
385
|
+
|---|---|
|
|
386
|
+
| Fence name | Reserved native OP, otherwise a registered executor or attached MCP service |
|
|
387
|
+
| `(path)` | Target/program/tool slot; COPY and MOVE each have two resource operands |
|
|
388
|
+
| `[metadata]` | One JSON array of option objects, owner-interpreted; options, never the op's input |
|
|
389
|
+
| `<scope>` | Operation-specific numeric or anchored coordinates |
|
|
390
|
+
| `<!-- … -->` | Optional final, single-line aside |
|
|
391
|
+
| Body | Literal content between framing newlines |
|
|
392
|
+
| Closing fence | The opening backtick count and delimiter, on its own line |
|
|
393
|
+
|
|
394
|
+
§slot-order Producers put target, scope, metadata, then aside, separated
|
|
395
|
+
by one ASCII space. Target and scope form one resource selection; COPY/MOVE
|
|
396
|
+
repeat the complete selection/metadata group per operand. ANTLR accepts
|
|
397
|
+
adjacent slots and scope/metadata permutations within a selection without
|
|
398
|
+
changing ownership or making them distinct canonical forms. Each selection
|
|
399
|
+
has at most one scope; its metadata blocks retain their authored order.
|
|
400
|
+
|
|
401
|
+
§plan-slotless TASK accepts no target or metadata. Its optional scope carries
|
|
402
|
+
waiting timing; its inventory body begins below the header.
|
|
403
|
+
|
|
404
|
+
§heading-inline-body Nonempty body text belongs below the fence header.
|
|
405
|
+
The ingester tolerates body text after horizontal whitespace on the header,
|
|
406
|
+
preserves it, and emits one warning stating that normalization. This does not
|
|
407
|
+
change the meaning of a compact empty block or permit unmatched fences.
|
|
408
|
+
|
|
409
|
+
§operation-aside The final header modifier may be one single-line HTML
|
|
410
|
+
comment. AstBuilder removes its delimiters and surrounding whitespace into
|
|
411
|
+
`aside: string | null`. It is durable descriptive text, not authority,
|
|
412
|
+
routing, timing, or body input. Comments inside a body remain literal except
|
|
413
|
+
for the narrowly owned {§misplaced-aside-advisory}.
|
|
414
|
+
|
|
415
|
+
§scheme-metadata-modifier A target may carry one single-line `[metadata]`
|
|
416
|
+
block after its scope; an executor fence also admits it without a target.
|
|
417
|
+
Read with its brackets, the block is a JSON array of option objects, merged
|
|
418
|
+
left to right with later keys winning; the keys belong to the selected scheme
|
|
419
|
+
or executor, which owns interpretation, validation and authority. The language
|
|
420
|
+
assigns no meaning to the content and stores each block's exact inner text:
|
|
421
|
+
balanced brackets inside the block are retained, and double-quoted strings
|
|
422
|
+
protect their brackets. Brackets inside `(path)` remain ordinary path and
|
|
423
|
+
glob characters. A block that is not valid JSON, or a second block on one
|
|
424
|
+
operand, is the owner's `400`, never a parser diagnostic. An unfinished block
|
|
425
|
+
or multiline metadata loses its boundary. One key is the language's own:
|
|
426
|
+
`pattern` ({§matcher-option}).
|
|
427
|
+
|
|
428
|
+
§matcher-option **`pattern` is the matcher, and it lives in the heading.** On
|
|
429
|
+
FIND, READ, KILL, EDIT, and each COPY/MOVE operand, the option
|
|
430
|
+
`[{"pattern": "<matcher>"}]` carries the matcher string exactly as a body once
|
|
431
|
+
did: the leading prefix claims its dialect under {§matcher-prefix-claims}, and
|
|
432
|
+
AstBuilder lifts it into the statement's `matcher` (`MatcherBody | null`),
|
|
433
|
+
positioned dialect errors included. A block that carries only `pattern`
|
|
434
|
+
leaves `metadata: null` for the owner; beside other keys the block stays with
|
|
435
|
+
the owner verbatim, and the owner's reader treats `pattern` as reserved. The
|
|
436
|
+
language lifts only from one block that parses as a JSON array of objects;
|
|
437
|
+
anything else lifts nothing and reaches the owner's `400` untouched. A
|
|
438
|
+
`pattern` that is present but not a string is the language's own positioned
|
|
439
|
+
diagnostic, as is a matcher of a claimed dialect that fails admission. FIND,
|
|
440
|
+
READ, and KILL take no body at all: a body beneath their heading is ignored and
|
|
441
|
+
the operation still runs, with one warning-severity advisory naming the
|
|
442
|
+
heading-line form (`FIND takes no body; the body was ignored. A pattern belongs
|
|
443
|
+
on the opening fence line after the path.`); it is never silently read as a
|
|
444
|
+
matcher, and it never strikes ({§matcher-body-redirect}). The heading line itself
|
|
445
|
+
is the matcher's home ({§naked-pattern}). A body that is only an HTML comment is
|
|
446
|
+
still the aside under {§misplaced-aside-advisory}. EDIT keeps its literal body:
|
|
447
|
+
with a matcher it is the replacement for every selected span ({§edit-pattern}),
|
|
448
|
+
and an absent body deletes them. `PlurnkParser.stringify` writes a lifted matcher
|
|
449
|
+
whose block left no metadata back bare when the bare form reads back identically
|
|
450
|
+
({§naked-pattern}), otherwise as `[{"pattern": "…"}]`.
|
|
409
451
|
|
|
410
452
|
## 3. Lexical elements
|
|
411
453
|
|
|
412
|
-
| Element
|
|
413
|
-
|
|
414
|
-
|
|
|
415
|
-
|
|
|
416
|
-
|
|
|
417
|
-
| `
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
454
|
+
| Element | Shape or role |
|
|
455
|
+
|---|---|
|
|
456
|
+
| Native OP | `FIND READ EDIT COPY MOVE SEND EXEC BARE WORK FORK KILL TASK` |
|
|
457
|
+
| Executor name | Letters, digits, `_`, `.`, `+`, or `-`; reserved OPs win |
|
|
458
|
+
| Fence | Three or more backticks, matched by exact count |
|
|
459
|
+
| `(path)` | Local path, URI, program or tool name; §5 |
|
|
460
|
+
| `[metadata]` | One JSON array of owner-defined option objects |
|
|
461
|
+
| `<scope>` | Numeric or anchored coordinates; §7 |
|
|
462
|
+
| Body | Literal text; never recursively interpreted as operations |
|
|
421
463
|
|
|
422
464
|
## §op-shapes 4. Per-operation semantics
|
|
423
465
|
|
|
@@ -426,83 +468,114 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
|
|
|
426
468
|
|
|
427
469
|
| OP | `(path)` | `<scope>` | `body` |
|
|
428
470
|
|------|----------------------------------------------|---------------------------------|--------------------------------|
|
|
429
|
-
| PLAN | none | none | required Plurnk Plan JSON array |
|
|
430
471
|
| FIND | required target or glob | optional result range | optional matcher |
|
|
431
472
|
| READ | required target | optional text region | empty |
|
|
432
473
|
| EDIT | required file or entry | required for an existing target | literal text |
|
|
433
474
|
| COPY | required source and destination | optional region after each path | empty |
|
|
434
475
|
| MOVE | required source and destination | optional region after each path | empty |
|
|
435
|
-
| EXEC |
|
|
436
|
-
| BARE |
|
|
437
|
-
| WORK |
|
|
438
|
-
| FORK |
|
|
439
|
-
| KILL | required target, including a log item | optional text region ({§kill-scope}) |
|
|
440
|
-
| SEND |
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
476
|
+
| EXEC | fence names executor; optional program/tool path ({§exec-executor-slot}) | optional timeout, poll | optional program input |
|
|
477
|
+
| BARE | optional prompt resource | none | prompt; optional with a path |
|
|
478
|
+
| WORK | optional fresh `worker://name`, or a prompt resource ({§worker-spawn-prompt-resource}) | none | prompt; optional with a resource |
|
|
479
|
+
| FORK | optional context-inheriting `worker://name`, or a prompt resource | none | prompt; optional with a resource |
|
|
480
|
+
| KILL | required target, including a log item | optional text region ({§kill-scope}) | none; the matcher is the `pattern` option |
|
|
481
|
+
| SEND | optional recipient | optional recipient timing | message |
|
|
482
|
+
| TASK | none | optional timeout and poll for waiting intent | Plurnk Plan JSON array |
|
|
483
|
+
|
|
484
|
+
§operation-code-polymorphism Operation-result statuses and turn dispositions are
|
|
485
|
+
distinct facts. TASK derives lifecycle intent from its inventory;
|
|
486
|
+
SEND and KILL carry no disposition operand.
|
|
487
|
+
|
|
488
|
+
§plan-value **TASK carries the complete current task inventory.** Admission
|
|
489
|
+
parses one JSON array in any whitespace layout, including
|
|
490
|
+
{§json-result-rendering}, strips unknown entry keys, and validates string
|
|
491
|
+
`content` and native `status`. Opaque `_meta` remains optional. Nonempty plain
|
|
492
|
+
text, malformed JSON, or an invalid array becomes one `in_progress` entry
|
|
493
|
+
containing the exact body, with one factual warning. No partial repair or list
|
|
494
|
+
inference occurs. A blank body becomes `[]`, never inferred completion.
|
|
495
|
+
The normalized array is the sole semantic value in AST, persistence and model
|
|
496
|
+
log; exact authored bytes remain in `turnOps`. Earlier inventories are history,
|
|
497
|
+
not accumulated obligations. Task descriptions are not executable dependencies.
|
|
498
|
+
|
|
499
|
+
§task-inventory-intent The first matching row determines intent, independently
|
|
500
|
+
of entry order. Actual execution adjudicates intent under {§wait-obligation-matrix}.
|
|
501
|
+
|
|
502
|
+
| Inventory condition | Intent | Derived lifecycle status |
|
|
503
|
+
|---|---|---|
|
|
504
|
+
| TASK omitted | Continue silently | 102 |
|
|
505
|
+
| Explicit empty inventory | Recover empty inventory | 102 |
|
|
506
|
+
| Any `in_progress` | Continue independent actionable work | 102 |
|
|
507
|
+
| Any `waiting`, no `in_progress` | Await work or an event | 202 |
|
|
508
|
+
| Any `pending`, no actionable or waiting entry | Review blocked dependencies | 102 |
|
|
509
|
+
| All terminal, any `completed` | End successfully | 200 |
|
|
510
|
+
| All `failed`, nonempty | End unsuccessfully | 499 |
|
|
511
|
+
|
|
512
|
+
`pending` is blocked on another task; `in_progress` can be actively advanced;
|
|
513
|
+
`waiting` awaits an ongoing stream, worker or external event. `completed` is
|
|
514
|
+
successful resolution; `failed` is unsuccessful resolution. A failed sibling
|
|
515
|
+
does not terminate independent unfinished work. The engine does not infer a
|
|
516
|
+
dependency graph from task text.
|
|
466
517
|
|
|
467
518
|
§plan-acp-projection **Only an ACP-facing boundary projects the model-native
|
|
468
519
|
Plan.** It constructs ACP's `{ "entries": [...] }` Plan object from the internal
|
|
469
520
|
array, synthesizes the ACP-required neutral `medium` priority on every entry
|
|
470
|
-
(the model-native Plan carries none)
|
|
471
|
-
`
|
|
472
|
-
|
|
473
|
-
|
|
521
|
+
(the model-native Plan carries none). The internal value is never mutated.
|
|
522
|
+
Native `waiting` maps to ACP `in_progress`, with `Waiting:` and a space prepended
|
|
523
|
+
to its display content; native `failed` maps to ACP `completed`, with `Failed:` and a space.
|
|
524
|
+
Both carry their native status in `_meta["plurnk.xyz/status"]`, an edge-owned
|
|
525
|
+
key derived from the native status rather than trusted from authored metadata.
|
|
526
|
+
Other statuses and unrelated metadata remain unchanged. The labels preserve
|
|
527
|
+
meaning even when a generic client ignores extension metadata.
|
|
474
528
|
The projected value validates against the separately owned ACP Plan schema pinned
|
|
475
529
|
to ACP v1
|
|
476
530
|
[`schema-v1.21.0`](https://github.com/agentclientprotocol/agent-client-protocol/tree/schema-v1.21.0)
|
|
477
531
|
commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
|
|
478
532
|
|
|
479
|
-
§exec-executor-slot
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
533
|
+
§exec-executor-slot The fence name selects the executor directly: for example,
|
|
534
|
+
`python3 (tools/report.py)` or `gitea (issue_list)` on the opening fence line.
|
|
535
|
+
Reserved native OP names take precedence. Other names lower to the same EXEC
|
|
536
|
+
AST with `executor`, `target`, metadata, timing and body fields.
|
|
537
|
+
Registration is checked by the runtime, not by the syntax parser. An attached
|
|
538
|
+
MCP service uses that executor path and its owner validates the named tool and
|
|
539
|
+
input-body JSON against its schema. Unknown names do not fall back to a shell.
|
|
540
|
+
The native `EXEC` form without a selected executor retains the runtime's
|
|
541
|
+
default executor contract; canonical shell examples name `sh` explicitly.
|
|
542
|
+
The path names a program or tool and is never split. Metadata such as
|
|
543
|
+
`[{"cwd": "…"}]` remains interpreted by the selected executor.
|
|
544
|
+
|
|
545
|
+
§turn-disposition TASK is the sole workflow declaration. `TurnDisposition`
|
|
546
|
+
derives intent from its canonical inventory under {§task-inventory-intent}.
|
|
547
|
+
The AST has no independently settable lifecycle status, target or metadata.
|
|
548
|
+
SEND deliberately messages its recipient, or the user when targetless; it
|
|
549
|
+
neither changes task status nor terminates a run. A program contains one final
|
|
550
|
+
TASK, not last-wins competing inventories. Former lifecycle names are not aliases.
|
|
551
|
+
|
|
552
|
+
§send-wait-scope TASK accepts `<timeout[,poll]>` in whole minutes. It applies
|
|
553
|
+
only to a waiting intent ({§park-202-only}); the dispatcher validates its bounds.
|
|
554
|
+
Otherwise it is unused, with a factual warning rather than a changed outcome.
|
|
555
|
+
|
|
556
|
+
§send-directed-scope A recipient SEND preserves an optional numeric scope after
|
|
557
|
+
its target and metadata. The addressed owner assigns its semantics; worker
|
|
558
|
+
actors use `<delay[,interval]>` ({§worker-scheduled-send}). A targetless message
|
|
559
|
+
takes no scope. Scheduling does not change the message body or disposition.
|
|
493
560
|
|
|
494
561
|
§kill-scope KILL takes an optional text-coordinate scope beside its target, numeric or
|
|
495
|
-
anchored (
|
|
496
|
-
<@aB3dE,@0Aa9Z
|
|
497
|
-
|
|
562
|
+
anchored (```` ```KILL (log:///**/READ) <17,-1>``` ```` or
|
|
563
|
+
```` ```KILL (worker:///notes.md) <@aB3dE,@0Aa9Z>``` ````), and an optional matcher option that
|
|
564
|
+
selects rows or lines (```` ```KILL (log:///**) [{"pattern": "~stale"}]``` ````, {§matcher-option}).
|
|
565
|
+
The AST is `{ op: "KILL", target, lineMarker: TextLineMarker | null, matcher: MatcherBody | null, body: null }`.
|
|
498
566
|
Without a scope, KILL retires or deletes the whole target; with one, it removes exactly
|
|
499
567
|
that span — of a log body's packet projection or of an entry's content. Core owns the
|
|
500
568
|
one-way semantics: there is no operation that restores a scoped-away log body.
|
|
501
569
|
|
|
502
|
-
§legacy-bracket-slot
|
|
570
|
+
§legacy-bracket-slot Brackets are the metadata modifier, never an executor
|
|
571
|
+
selector: the runtime or MCP service is the fence name, and tool input belongs
|
|
572
|
+
in the body. A bracket block that leads an executor fence or follows a target
|
|
573
|
+
is metadata, so a legacy `[node]` selector reaches its owner as metadata text
|
|
574
|
+
and is refused there; a bracket before the target of a non-executor OP is one
|
|
575
|
+
bounded header diagnostic that selects nothing.
|
|
503
576
|
|
|
504
577
|
The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
|
|
505
|
-
scopes ordered results. EXEC and
|
|
578
|
+
scopes ordered results. EXEC and SEND scope owner-defined timing. READ, EDIT, COPY,
|
|
506
579
|
MOVE, and KILL use one universal text algebra independent of mimetype; a log
|
|
507
580
|
KILL admits only its one- and two-line forms for canonical log-body visibility:
|
|
508
581
|
|
|
@@ -525,22 +598,37 @@ four-coordinate region ending after the final code point of `endLine`.
|
|
|
525
598
|
Producers never emit that form. Other arities and decimal text coordinates are
|
|
526
599
|
runtime 416 failures.
|
|
527
600
|
|
|
528
|
-
§bare-statement **BARE requests one isolated model inference.** Its
|
|
529
|
-
body
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
601
|
+
§bare-statement **BARE requests one isolated model inference.** Its optional
|
|
602
|
+
path names a prompt resource; its body supplies inline prompt text. At least
|
|
603
|
+
one must supply nonempty text at execution. With both, the complete resource
|
|
604
|
+
text precedes the body, separated by two newlines. The target's scheme owns any
|
|
605
|
+
metadata modifier. No scope, persistent worker identity, or output-language
|
|
606
|
+
shape is represented. Provider selection, source admission, batching,
|
|
607
|
+
accounting, and observation timing belong to the consuming service.
|
|
608
|
+
|
|
609
|
+
§read-find-normalization An authored READ is never rewritten into a FIND. A
|
|
610
|
+
glob target on READ keeps its glob, and the runtime fans it out into one exact
|
|
611
|
+
READ per matching path, with the authored scope and matcher ({§read-fan-out}
|
|
612
|
+
in the core SPEC; operator, 2026-09-13: "give it what it asked for" — a model
|
|
613
|
+
that asks to read every file under a glob gets those files, bounded by the
|
|
614
|
+
FIND page and the preview scope, not a catalog it did not ask for). A matcher
|
|
615
|
+
never changes the operation either: READ with a `pattern` on an exact target
|
|
616
|
+
stays READ and renders the selected lines ({§read-pattern}). The survey of
|
|
617
|
+
paths is FIND, and only FIND.
|
|
618
|
+
|
|
619
|
+
§local-path-fragment **A bare path takes `#channel` like a URL.** `data/users.html#readable`
|
|
620
|
+
parses as `{ kind: "local", raw: "data/users.html", fragment: "readable" }`: the first `#` ends
|
|
621
|
+
the path and the rest is the channel (a spelling that opens with `#` names no path and stays
|
|
622
|
+
whole), exactly as `worker:///a.html#readable` decomposes, so
|
|
623
|
+
the `#channel` a READ receipt advertises (`channels: {"#readable": N}`) is addressable in the
|
|
624
|
+
same bare spelling the receipt used (2026-09-13 dumbox demo: the model appended it and was
|
|
625
|
+
told no entry existed at `users.html#readable`). `raw` is therefore always the path alone; a
|
|
626
|
+
bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
|
|
627
|
+
Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
|
|
628
|
+
|
|
629
|
+
§read-exact-target READ targets one exact resource (a local path or scheme
|
|
630
|
+
URL, with optional `#channel` fragment or `[metadata]`) and has no body. A
|
|
631
|
+
`<scope>` on READ selects
|
|
544
632
|
a text region from that exact target. Without a scope, READ defaults to
|
|
545
633
|
`<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope components are
|
|
546
634
|
invalid on READ.
|
|
@@ -554,7 +642,7 @@ Mutation semantics:
|
|
|
554
642
|
- `<0>` prepends and `<-1>` appends.
|
|
555
643
|
- §empty-mutation-scope Empty mutation content has one writable position: `<0>`, `<1>`, `<-1>`, and `<1,-1>` all insert the body as its complete value. Other scopes resolve against that same empty value through the ordinary coordinate algebra.
|
|
556
644
|
- `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
|
|
557
|
-
- §transfer-resource-selections COPY and MOVE require two singular `ResourceSelection` operands on the heading, source first and destination second, and admit no body. Each operand consists of `(path)`,
|
|
645
|
+
- §transfer-resource-selections COPY and MOVE require two singular `ResourceSelection` operands on the heading, source first and destination second, and admit no body. Each operand consists of `(path)`, optional `<scope>`, and optional `{metadata}` blocks; modifiers bind only to that operand. The two operands independently select their resource, channel, scheme metadata, and text region.
|
|
558
646
|
|
|
559
647
|
### §operation-observation Per-operation observations
|
|
560
648
|
|
|
@@ -571,7 +659,7 @@ Mutation semantics:
|
|
|
571
659
|
| WORK | Spawn acknowledgement; the deliverable arrives through the log |
|
|
572
660
|
| FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
|
|
573
661
|
| KILL | Status of deletion or termination |
|
|
574
|
-
|
|
|
662
|
+
| TASK | Current inventory and adjudicated lifecycle outcome |
|
|
575
663
|
|
|
576
664
|
§find-result-unit For FIND, authored target shape fixes the paginated result
|
|
577
665
|
unit. An exact target with a matcher pages flat match locations; a glob or
|
|
@@ -588,8 +676,8 @@ EDIT. Whole-channel transfers remain bodyless structural effects; runtime
|
|
|
588
676
|
owners reject binary markers rather than treating a text field as a byte lane.
|
|
589
677
|
|
|
590
678
|
Every operation returns the runtime-neutral `OperationResult` defined by
|
|
591
|
-
{§operation-result}. Its `status` belongs to the result envelope
|
|
592
|
-
|
|
679
|
+
{§operation-result}. Its `status` belongs to the result envelope; TASK supplies
|
|
680
|
+
the authored lifecycle intent. Durable operation observations are projected into a later packet;
|
|
593
681
|
retrieval never returns inline within the emitting turn.
|
|
594
682
|
|
|
595
683
|
## §path-syntax 5. Target and path grammar
|
|
@@ -599,7 +687,7 @@ and path globs share the slot; content matchers belong in the body.
|
|
|
599
687
|
|
|
600
688
|
| Form | Typed admission | Runtime meaning |
|
|
601
689
|
|-------------------------|---------------------------------------------------------------------|------------------------------------------------------|
|
|
602
|
-
| Bare path | `LocalPath { kind: "local", raw }`
|
|
690
|
+
| Bare path | `LocalPath { kind: "local", raw, fragment? }` | Resolves through the runtime's file surface; `#channel` is `fragment` ({§local-path-fragment}) |
|
|
603
691
|
| `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
|
|
604
692
|
| Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
|
|
605
693
|
| `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
|
|
@@ -655,60 +743,60 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
|
|
|
655
743
|
|
|
656
744
|
## §matcher-prefix-claims 6. Bulk pattern matching
|
|
657
745
|
|
|
658
|
-
FIND,
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
746
|
+
FIND, READ, KILL, EDIT, and the COPY/MOVE operands accept an optional matcher
|
|
747
|
+
through the `pattern` option ({§matcher-option}); the client-tier LOOK still
|
|
748
|
+
carries its matcher as a body. AstBuilder assigns the dialect from the
|
|
749
|
+
matcher's leading characters. A leading prefix claims its dialect. Invalid
|
|
750
|
+
claimed syntax is a positioned visitor error and never falls back to glob
|
|
751
|
+
matching.
|
|
664
752
|
|
|
665
753
|
- §heading-boundary-recovery A column-0 heading is the trustworthy boundary. After a
|
|
666
754
|
statement-level error the parser discards the rest of that statement and resumes at the
|
|
667
|
-
next heading; the turn shape is decided locally (a
|
|
668
|
-
|
|
669
|
-
diagnostic and every later statement, the
|
|
755
|
+
next heading; the turn shape is decided locally (a turn disposition is recognized by its own
|
|
756
|
+
token, never by a whole-turn alternative), so one malformed heading costs one
|
|
757
|
+
diagnostic and every later statement, the turn disposition included, stands on its own. Any
|
|
670
758
|
other second path slot names the one-slot rule.
|
|
671
|
-
- §scope-slot-tolerance A line scope written inside a path slot (
|
|
759
|
+
- §scope-slot-tolerance A line scope written inside a path slot (```` ```COPY (worker:///src.md<2,3>) ````)
|
|
672
760
|
is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
|
|
673
761
|
before a slot's closing paren can only be a scope; every path slot of a statement is repaired
|
|
674
762
|
the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
|
|
675
763
|
statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
|
|
676
764
|
never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
|
|
677
765
|
- §second-path-slot A second `(path)` on a heading that already closed one is a parser
|
|
678
|
-
error at the second paren stating the one-slot rule and that a pattern belongs in the
|
|
679
|
-
the statement is dropped and its siblings run.
|
|
766
|
+
error at the second paren stating the one-slot rule and that a pattern belongs in the
|
|
767
|
+
`[{"pattern": …}]` option; the statement is dropped and its siblings run.
|
|
680
768
|
|
|
681
|
-
| Prefix | Dialect | Canonical
|
|
769
|
+
| Prefix | Dialect | Canonical form | Typed admission | Runtime owner |
|
|
682
770
|
|-----------|----------|--------------------------------------|-----------------------------------|---------------------|
|
|
683
771
|
| `//` | XPath | `//selector` | XPath 1.0 `xpath.parse()` | Mimetype projection |
|
|
684
772
|
| `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
|
|
773
|
+
| `^` | Regex | `^pattern`, no slashes or flags | ECMAScript `RegExp` construction | Mimetype projection |
|
|
685
774
|
| `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
|
|
686
|
-
| `~` |
|
|
775
|
+
| `~` | Full-text | `~query` | Single-line raw string | SQLite FTS5 index |
|
|
687
776
|
| `&` | Graph | `&symbol`, `&<symbol`, or `&>symbol` | Exact shape validation | Symbol index |
|
|
688
777
|
| none | Glob | Shell glob or literal text | Single-line raw string | Mimetype projection |
|
|
689
778
|
|
|
690
779
|
XPath is classified before regex because its prefix is two slashes. Regex
|
|
691
780
|
splitting respects escapes and character classes; `\/` represents a literal
|
|
692
781
|
slash. The AST stores regex `pattern` and `flags`, not a compiled object.
|
|
693
|
-
|
|
782
|
+
SQLite validates full-text query expressions at execution. Graph admission validates its direction
|
|
694
783
|
and non-whitespace symbol before runtime. Every other leading character remains
|
|
695
784
|
in the fallback glob/literal dialect; `@(...)` is therefore an extglob group and
|
|
696
785
|
bare `@text` remains literal matcher text. Rendered READ coordinates are
|
|
697
|
-
structural output rows, not a reserved matcher prefix.
|
|
698
|
-
|
|
786
|
+
structural output rows, not a reserved matcher prefix. FIND scope selects result
|
|
787
|
+
positions without changing the matcher.
|
|
699
788
|
|
|
700
789
|
AstBuilder validation is compile-only and never evaluates a document. Matcher
|
|
701
|
-
evaluation belongs to the runtime's selected mimetype,
|
|
790
|
+
evaluation belongs to the runtime's selected mimetype, FTS5, or symbol
|
|
702
791
|
implementation. A matcher admission error is local to its statement; later
|
|
703
792
|
statements remain recoverable when their boundaries are trustworthy.
|
|
704
793
|
|
|
705
|
-
- §pattern-body-single-line Every matcher
|
|
706
|
-
|
|
707
|
-
line
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
`/^:needle/` expresses a pattern beginning with a literal colon.
|
|
794
|
+
- §pattern-body-single-line Every matcher is one physical line. On the protocol
|
|
795
|
+
operations it is the heading line's text ({§naked-pattern}) or the `pattern` option's
|
|
796
|
+
JSON string ({§matcher-option}), one line by construction; the client-tier LOOK still carries its matcher as a body, and AstBuilder
|
|
797
|
+
rejects a multiline one before dialect classification. A regex that matches a
|
|
798
|
+
newline uses the two-character `\n` escape. Non-matcher operation bodies remain
|
|
799
|
+
multiline.
|
|
712
800
|
|
|
713
801
|
## §scope-slot 7. Scope markers
|
|
714
802
|
|
|
@@ -729,14 +817,14 @@ The operation column names the canonical AST operation after
|
|
|
729
817
|
| COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
|
|
730
818
|
| KILL | 0/1/2 text coordinates | Whole target when absent; one physical line or inclusive range when present ({§kill-scope}) |
|
|
731
819
|
| EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
|
|
732
|
-
|
|
|
820
|
+
| ```` ```TASK ```` | `timeout[,poll]` | Waiting intent: bounded or indefinite wait and optional poll cadence ({§send-wait-scope}) |
|
|
821
|
+
| Directed SEND | Owner-defined numeric scope | Worker actors schedule a task with `delay[,interval]` ({§send-directed-scope}) |
|
|
733
822
|
|
|
734
823
|
Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
|
|
735
824
|
whole line, two integers are an inclusive whole-line range, and four integers
|
|
736
825
|
are an exact start-inclusive/end-exclusive region. Mutation scopes additionally
|
|
737
|
-
admit `0` as prepend and `-1` as append.
|
|
738
|
-
|
|
739
|
-
does not admit decimal scope components. A log KILL intersects a valid body-relative line
|
|
826
|
+
admit `0` as prepend and `-1` as append. FIND result positions and READ
|
|
827
|
+
text coordinates do not admit decimal scope components. A log KILL intersects a valid body-relative line
|
|
740
828
|
scope with each selected body; an absent line is a successful no-op for that
|
|
741
829
|
body, while unsupported arity is a runtime failure.
|
|
742
830
|
|
|
@@ -765,95 +853,74 @@ reinterpreting them. FIND owns a deterministic result order so the same
|
|
|
765
853
|
inclusive range selects the same positions from unchanged state. The parser
|
|
766
854
|
does not enforce either condition.
|
|
767
855
|
|
|
768
|
-
##
|
|
769
|
-
|
|
770
|
-
The delimiter is a turn-wide heading lane. A heading carrying the active lane
|
|
771
|
-
is structural; an otherwise valid PLURNK heading carrying another lane is body
|
|
772
|
-
text. The lane therefore makes literal or nested PLURNK unambiguous.
|
|
856
|
+
## 8. Literal programs and code blocks
|
|
773
857
|
|
|
774
|
-
|
|
858
|
+
A producer carrying literal fences uses an outer backtick count absent from
|
|
859
|
+
standalone fence lines in its body ({§fence-boundary}). The serializer chooses
|
|
860
|
+
a count greater than every backtick run in the body; parsed AST values carry
|
|
861
|
+
no framing state.
|
|
775
862
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
- An empty delimiter is accepted only by ANTLR ingestion. Canonical teaching and
|
|
780
|
-
the generated rail use `0` on PLAN and every operation.
|
|
781
|
-
- A body may contain any heading whose delimiter differs from the active lane.
|
|
782
|
-
- To carry a nested turn written with lane `0`, choose another delimiter for the
|
|
783
|
-
outer turn and repeat it on every outer heading.
|
|
784
|
-
- The GBNF deliberately emits only lane `0`. It cannot emit body content that
|
|
785
|
-
contains a same-lane structural heading; unconstrained producers use another
|
|
786
|
-
outer lane when that representation is required.
|
|
863
|
+
`````text
|
|
864
|
+
````EDIT (README.md) <1,-1>
|
|
865
|
+
Run the tests:
|
|
787
866
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
```example
|
|
791
|
-
## PLAN2
|
|
792
|
-
[{"content":"Store the quoted turn.","status":"in_progress"}]
|
|
793
|
-
|
|
794
|
-
### EDIT2 (worker:///quoted.plurnk)
|
|
795
|
-
## PLAN0
|
|
796
|
-
[{"content":"Answer from memory.","status":"in_progress"}]
|
|
797
|
-
|
|
798
|
-
### SEND0 (TERM)
|
|
799
|
-
Paris.
|
|
800
|
-
|
|
801
|
-
### SEND2 (TERM)
|
|
802
|
-
Stored the quoted turn.
|
|
867
|
+
```sh
|
|
868
|
+
npm test
|
|
803
869
|
```
|
|
870
|
+
````
|
|
871
|
+
`````
|
|
804
872
|
|
|
805
|
-
The
|
|
806
|
-
|
|
807
|
-
every operation, not to EDIT semantics.
|
|
873
|
+
The inner shell example is EDIT content, not an EXEC invocation. The same
|
|
874
|
+
rule protects code examples in SEND, WORK, FORK, BARE and every other body.
|
|
808
875
|
|
|
809
|
-
## 9.
|
|
876
|
+
## 9. Turn dispositions
|
|
810
877
|
|
|
811
|
-
|
|
812
|
-
|
|
878
|
+
TASK inventory intent maps to the existing HTTP-shaped lifecycle statuses
|
|
879
|
+
({§task-inventory-intent}). The runtime adjudicates that intent against actual
|
|
880
|
+
results, obligations and timing:
|
|
813
881
|
|
|
814
|
-
|
|
|
815
|
-
|
|
816
|
-
|
|
|
817
|
-
|
|
|
818
|
-
|
|
|
819
|
-
|
|
|
882
|
+
| Intent | Nominal status | Meaning |
|
|
883
|
+
|---|---|---|
|
|
884
|
+
| TASK omitted | 102 | Continue silently, without a receipt or strike for omission |
|
|
885
|
+
| empty, continue, pending | 102 | Continue or recover; an explicit empty inventory is refused with a soft 409 receipt, no strike |
|
|
886
|
+
| wait | 202 | Park when a live obligation or explicit timing exists |
|
|
887
|
+
| complete | 200 | Conclude once execution results permit completion |
|
|
888
|
+
| fail | 499 | End unsuccessfully and cancel unresolved descendant scope |
|
|
889
|
+
| Runtime or infrastructure failure | 5xx | Not a model-authored task status |
|
|
820
890
|
|
|
821
891
|
### §waitpid-dispositions The terminal contract (waitpid)
|
|
822
892
|
|
|
823
|
-
The model
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
streams, pending
|
|
893
|
+
The model may supply one current inventory per turn; its statuses determine
|
|
894
|
+
one intention. Without TASK, an operation-bearing turn continues silently.
|
|
895
|
+
The engine verifies an explicit intention against the loop's actual
|
|
896
|
+
obligations (spawned children, open streams, pending results); the grammar
|
|
897
|
+
polices *shape* only. Asking
|
|
827
898
|
the human is the native `question` EXEC tool ({§question-tool}), not a
|
|
828
899
|
disposition. The shape rules ARE structural:
|
|
829
900
|
|
|
830
|
-
- §send-mid-reservation
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
901
|
+
- §send-mid-reservation TASK has a reserved token ({§turn-disposition}).
|
|
902
|
+
A turn admits at most one TASK, anywhere among its operations
|
|
903
|
+
({§disposition-anywhere}); the runtime executes it last. A second
|
|
904
|
+
disposition is a structural error, not a choice between competing outcomes.
|
|
905
|
+
- §disposition-anywhere The disposition may sit anywhere in a model turn
|
|
906
|
+
(operator, 2026-09-12: models state the plan first; the inventory is a
|
|
907
|
+
statement about state, not a boundary). `PlurnkParser.parse` admits every
|
|
908
|
+
operation before and after it in authored order; the runtime defers only the
|
|
909
|
+
disposition until the other admitted operations settle
|
|
910
|
+
({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
|
|
911
|
+
position. TASK omission does not synthesize a disposition ({§turn-shape}).
|
|
912
|
+
- SEND is communication: an optional recipient path and an optional body.
|
|
913
|
+
- §park-202-only TASK wait intent applies `<T>` (wait up to T minutes),
|
|
841
914
|
`<T,P>` (adds a poll cadence, mirroring EXEC's slot), `<-1>`
|
|
842
|
-
(indefinite; the join's own liveness bounds it). See §7 for the
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
downstream validation — survives the rail by nature; the engine's
|
|
852
|
-
idle-turn 409 backstops that class.
|
|
853
|
-
|
|
854
|
-
SEND with no `(path)` broadcasts to the default control channel — the
|
|
855
|
-
turn's disposition. SEND with `(path)` directs the message at a
|
|
856
|
-
specific recipient URI (a worker, a stream, a peer).
|
|
915
|
+
(indefinite; the join's own liveness bounds it). See §7 for the scope
|
|
916
|
+
slot's shape. Other intents leave timing unapplied
|
|
917
|
+
with a factual warning; timing does not override the inventory's intent.
|
|
918
|
+
- §inventory-only-turn A TASK-only turn is valid for every inventory intent.
|
|
919
|
+
Actionable work does not require an invented OP and does not imply parking.
|
|
920
|
+
Ordinary repetition, strike and execution limits still apply.
|
|
921
|
+
|
|
922
|
+
SEND with no `(path)` responds to the Active Prompts without ending the turn. SEND with
|
|
923
|
+
`(path)` directs the message to that recipient. Neither changes loop status.
|
|
857
924
|
|
|
858
925
|
### §send-body SEND body projection
|
|
859
926
|
|
|
@@ -865,78 +932,39 @@ defines no synthetic scheme or READ-back convention for them.
|
|
|
865
932
|
|
|
866
933
|
## §parser-architecture 10. Parser architecture
|
|
867
934
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
AST. Generated TypeScript targets the `antlr4ng` runtime.
|
|
935
|
+
ANTLR owns framing, slots and statement composition; AstBuilder produces the
|
|
936
|
+
schema-owned AST. Registration, effects and authority remain runtime concerns.
|
|
871
937
|
|
|
872
938
|
```mermaid
|
|
873
939
|
stateDiagram-v2
|
|
874
940
|
[*] --> DEFAULT
|
|
875
|
-
DEFAULT -->
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
METADATA --> SLOTS: metadata close at depth zero
|
|
885
|
-
SLOTS --> SLOTS: scope token
|
|
886
|
-
SLOTS --> SLOTS: trailing annotation
|
|
887
|
-
SLOTS --> BODY: heading line end
|
|
888
|
-
BODY --> DEFAULT: same-lane heading boundary
|
|
889
|
-
BODY --> [*]: end of input
|
|
941
|
+
DEFAULT --> SLOTS: fenced native OP or executor
|
|
942
|
+
SLOTS --> TARGET: (
|
|
943
|
+
TARGET --> SLOTS: )
|
|
944
|
+
SLOTS --> METADATA: {
|
|
945
|
+
METADATA --> SLOTS: }
|
|
946
|
+
SLOTS --> BODY: header newline or tolerated inline body
|
|
947
|
+
SLOTS --> DEFAULT: matching compact closer
|
|
948
|
+
BODY --> DEFAULT: matching standalone closer, no nested block
|
|
949
|
+
BODY --> BODY: nested literal block or other body content
|
|
890
950
|
```
|
|
891
951
|
|
|
892
|
-
The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
|
|
893
|
-
PLAN or H2 minted operation carrying that exact lane. SLOTS admits
|
|
894
|
-
operation-appropriate signal, target-with-metadata, and scope openers in any
|
|
895
|
-
order, followed by an optional annotation; the parser grammar enforces
|
|
896
|
-
at-most-once slot multiplicity and keeps repeatable metadata attached to its target.
|
|
897
|
-
Signal submodes select tags, integer,
|
|
898
|
-
or identifier tokens by operation family. TARGET preserves balanced inner
|
|
899
|
-
parentheses and recognized target escapes. BODY emits opaque text until a
|
|
900
|
-
same-lane heading boundary or EOF.
|
|
901
|
-
|
|
902
|
-
A differently delimited heading stays BODY text. Multi-turn logs are plain
|
|
903
|
-
sequences of independently lane-anchored PLAN turns. Complete native reasoning
|
|
904
|
-
enclosures before PLAN remain one TEXT token so an operation drafted inside
|
|
905
|
-
provider reasoning cannot become the turn anchor.
|
|
906
|
-
|
|
907
|
-
RecordingListener captures lexer and parser failures; AstBuilder adds visitor
|
|
908
|
-
failures. PlurnkErrorStrategy recovers at structural heading boundaries where
|
|
909
|
-
possible. EOF is a valid body boundary. An unfinished signal, target, or metadata block produces
|
|
910
|
-
`unparsedTail`; no later input is trustworthy.
|
|
911
|
-
|
|
912
952
|
## §whitespace-contract 11. Whitespace and interstatement text
|
|
913
953
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
PLURNK never escape-decodes body text: `\n` reaches the owning operation as
|
|
930
|
-
backslash plus `n`. A matcher or executor may interpret those characters under
|
|
931
|
-
its own body dialect. Producers that need a physical newline in literal EDIT
|
|
932
|
-
content emit an actual newline.
|
|
933
|
-
|
|
934
|
-
`parse` admits TEXT before its PLAN, including without an intervening line
|
|
935
|
-
break, and returns it as ordered text items without assigning semantics. Once
|
|
936
|
-
a heading begins, all nonstructural text belongs to that section body.
|
|
937
|
-
`parseStatements` and `parseClient` admit H2 statements;
|
|
938
|
-
`parseLog` admits consecutive H1 PLAN turns. PLURNK defines no general comment
|
|
939
|
-
syntax; only the trailing heading position gives `<!-- … -->` annotation meaning.
|
|
954
|
+
Body framing removes the header line ending and the single line ending
|
|
955
|
+
immediately before the closing fence. Every other body character is preserved,
|
|
956
|
+
including leading/trailing blank lines, indentation, CRLF and literal
|
|
957
|
+
backslash escapes. A formatter adds its own framing newline even when a body
|
|
958
|
+
already ends in one. Interstatement whitespace belongs to no body.
|
|
959
|
+
|
|
960
|
+
A header starts at column zero; the first operation may follow provider preamble
|
|
961
|
+
without a separating newline. Text outside operation blocks is ignored in every
|
|
962
|
+
parser tier: before, between, and after operations. It produces no AST item,
|
|
963
|
+
message, receipt, or diagnostic. Exact source remains in `ops:///` under
|
|
964
|
+
{§turn-ops-log-curation}; body bytes and source positions are unchanged. A matching
|
|
965
|
+
closer still ends its body, and no missing closer is inferred. No generic Markdown
|
|
966
|
+
rendering, indentation stripping or recursive code-block extraction occurs.
|
|
967
|
+
Only a header aside has aside semantics.
|
|
940
968
|
|
|
941
969
|
## §public-api 12. Public API
|
|
942
970
|
|
|
@@ -945,39 +973,34 @@ and wire types come from generated schemas; the small hand-maintained parser
|
|
|
945
973
|
types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
|
|
946
974
|
express. Consumers never receive ANTLR parse-tree or token types.
|
|
947
975
|
|
|
948
|
-
§turn-shape `PlurnkParser.parse` accepts
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
when present, is ```` ``` ````. The fence encloses the complete
|
|
965
|
-
model turn and projects neither text nor body content into the AST. Its opener
|
|
966
|
-
commits the document to either that closer or EOF immediately after the turn.
|
|
967
|
-
This is document framing, not another statement grammar, and
|
|
968
|
-
no other parser tier admits it. GBNF continues to shape the paired form.
|
|
976
|
+
§turn-shape `PlurnkParser.parse` accepts one model turn. A turn without any
|
|
977
|
+
operation is reported by one hard diagnostic (`no valid Plurnk operation was
|
|
978
|
+
found.`), which the host may admit as an empty turn rather than reject
|
|
979
|
+
(plurnk-core `§empty-turn`). An explicit disposition may sit anywhere in it
|
|
980
|
+
({§disposition-anywhere}). Omitted TASK
|
|
981
|
+
means silent continuation: no synthesized statement, diagnostic, receipt,
|
|
982
|
+
warning, or strike. The authored operations and source remain unchanged.
|
|
983
|
+
Explicit empty or malformed inventories retain their own handling.
|
|
984
|
+
Unfinished blocks never receive inferred closers.
|
|
985
|
+
Bounded operation errors retain valid siblings. Duplicate dispositions
|
|
986
|
+
and failed document boundaries remain structural failures.
|
|
987
|
+
|
|
988
|
+
`parseLog` reads consecutive saved turns separated by their dispositions and
|
|
989
|
+
requires their dispositions; a saved turn is stored per turn, so a mid-turn
|
|
990
|
+
disposition never needs splitting. There is no outer Markdown program wrapper;
|
|
991
|
+
the executable blocks themselves are the program.
|
|
969
992
|
|
|
970
993
|
§tier-entrypoints Each parser entry point owns one document tier:
|
|
971
994
|
|
|
972
995
|
| Entry point | Accepted document | Result statement type |
|
|
973
996
|
|--------------------------------|----------------------------------------------------------------|-----------------------|
|
|
974
|
-
| `PlurnkParser.parse` | One operation-bearing model turn
|
|
975
|
-
| `PlurnkParser.parseStatements` | Zero or more protocol statements
|
|
976
|
-
| `PlurnkParser.parseLog` | One or more consecutive
|
|
977
|
-
| `PlurnkParser.parseClient` |
|
|
997
|
+
| `PlurnkParser.parse` | One operation-bearing model turn; at most one TASK, anywhere | `PlurnkStatement` |
|
|
998
|
+
| `PlurnkParser.parseStatements` | Zero or more protocol statements | `PlurnkStatement` |
|
|
999
|
+
| `PlurnkParser.parseLog` | One or more consecutive disposition-ended turns | `PlurnkStatement` |
|
|
1000
|
+
| `PlurnkParser.parseClient` | Executable blocks, including the read-shaped LOOK command | `ClientStatement` |
|
|
978
1001
|
|
|
979
|
-
Every entry point
|
|
980
|
-
`
|
|
1002
|
+
Every entry point ignores outside text under {§whitespace-contract} and returns
|
|
1003
|
+
ordered `statement` and `error` items. When present, {§unparsed-tail-boundary} governs the result's item
|
|
981
1004
|
extent. The statement `op` field discriminates the generated per-operation
|
|
982
1005
|
union.
|
|
983
1006
|
|
|
@@ -1242,16 +1265,30 @@ from user-authored prompt content. An adapter may expose no public means to set
|
|
|
1242
1265
|
it; Core validates and records it through the same prompt admission path.
|
|
1243
1266
|
|
|
1244
1267
|
§application-worker-observation Worker observation exposes durable identity,
|
|
1245
|
-
origin,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1268
|
+
origin, immediate parent identity, minted `kind` (`conversation`; `fork` for a
|
|
1269
|
+
child carrying a fork boundary; `work` for any other child), and `lifecycle`,
|
|
1270
|
+
the worker's latest work loop projected through {§loop-lifecycle-vocabulary} (`idle`
|
|
1271
|
+
when it has none). Maintenance-only loops do not change this projection;
|
|
1272
|
+
their ordinary turns and results remain durable history. `readWorker` resolves exactly one id or name and returns
|
|
1273
|
+
`null` when absent. `listWorkers` filters collections by origin or lineage
|
|
1274
|
+
position; an omitted parent filter means every position and an explicit `null`
|
|
1275
|
+
means roots. Singular and plural cardinalities are distinct contracts.
|
|
1276
|
+
Observation is not a client binding or permission grant; a client renders kind
|
|
1277
|
+
and lifecycle, it never infers them.
|
|
1278
|
+
|
|
1279
|
+
§loop-lifecycle-vocabulary One projection maps a loop's durable status onto the
|
|
1280
|
+
lifecycle words every client renders, shared by the status gauge and the worker
|
|
1281
|
+
directory: no loop `idle`; 100 `queued`; 102 `running`; 202 `parked`; 200
|
|
1282
|
+
`completed`; any status of 400 or more `failed` (413 budget, 429 turn ceiling,
|
|
1283
|
+
499 cancel, 500 fail, 504 execution timeout, 508 runaway). `lifecycleOfLoopStatus`
|
|
1284
|
+
in `@plurnk/plurnk-contracts` is that projection's one owner.
|
|
1250
1285
|
|
|
1251
1286
|
§application-loop-observation Loop observation exposes the durable scheduler
|
|
1252
|
-
state, exact terminal `OperationResult`, and exact count of packet-bearing
|
|
1287
|
+
state of work loops (excluding maintenance-only administrative loops), exact terminal `OperationResult`, and exact count of packet-bearing
|
|
1253
1288
|
Turns for one owned Worker. Packetless producer Turns and physical provider
|
|
1254
|
-
retries do not contribute to `packetCount`.
|
|
1289
|
+
retries do not contribute to `packetCount`. Scheduled tasks expose `scheduledAt`
|
|
1290
|
+
(ISO date), optional `intervalMinutes`, and `recurrenceId` (the original task id).
|
|
1291
|
+
Packet notifications carry the same timing; ordinary tasks omit it. Exterior
|
|
1255
1292
|
adapters consume this projection instead of reconstructing lifecycle from
|
|
1256
1293
|
events or persistence; events remain the live notification edge.
|
|
1257
1294
|
|
|
@@ -1270,13 +1307,14 @@ class PlurnkParseError extends Error {
|
|
|
1270
1307
|
readonly column: number;
|
|
1271
1308
|
readonly source: ErrorSource;
|
|
1272
1309
|
readonly severity: Severity;
|
|
1310
|
+
readonly code?: "invalid-turn-structure";
|
|
1273
1311
|
}
|
|
1274
1312
|
```
|
|
1275
1313
|
|
|
1276
1314
|
§parser-position Parser source locations are points, not text regions. An AST
|
|
1277
|
-
statement's `position` identifies the first
|
|
1278
|
-
identifies the offending or recovery point;
|
|
1279
|
-
|
|
1315
|
+
statement's `position` identifies the first backtick of its header; a diagnostic
|
|
1316
|
+
identifies the offending or recovery point; `unparsedTail.from` identifies where
|
|
1317
|
+
the undefined tail begins. A
|
|
1280
1318
|
statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
|
|
1281
1319
|
the unknown sentinel; its dispatch origin remains a separate fact.
|
|
1282
1320
|
|
|
@@ -1307,40 +1345,86 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
|
|
|
1307
1345
|
the sole and complete owner of syntax-error messaging because it holds the
|
|
1308
1346
|
parse state, lexer mode, and expected-token set that no consumer has. It
|
|
1309
1347
|
produces the final diagnostic message, deduplicated expected-token lists, and
|
|
1310
|
-
turn-shape diagnostics.
|
|
1311
|
-
|
|
1312
|
-
|
|
1348
|
+
turn-shape diagnostics ({§turn-shape}). Omitted TASK produces no diagnostic, and
|
|
1349
|
+
neither does the position of a present one ({§disposition-anywhere}). A failed
|
|
1350
|
+
document boundary carries `code: "invalid-turn-structure"`, which cannot be
|
|
1351
|
+
recovered as an individual failed operation. Source with no
|
|
1313
1352
|
parsed operation yields `no valid Plurnk operation was found.` Targeted
|
|
1314
1353
|
diagnostics are:
|
|
1315
1354
|
|
|
1316
|
-
- §
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
(
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1355
|
+
- §inline-flag-tolerance **PCRE inline modifiers.** A regex whose pattern opens with
|
|
1356
|
+
`(?i)`, `(?m)`, `(?s)` or a combination — the pretrained spelling of a flag, which
|
|
1357
|
+
ECMAScript refuses as an invalid group — is read with those letters lifted into
|
|
1358
|
+
its flags (`/(?i)shutdown|reactor/` is `/shutdown|reactor/i`; `^(?i)note:` is the
|
|
1359
|
+
anchored regex with `i`), with one warning-severity advisory naming the flag
|
|
1360
|
+
position after the closing `/`. Only a leading group of `i`, `m` and `s` lifts;
|
|
1361
|
+
`(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched. From
|
|
1362
|
+
the 2026-09-13 dumbox demo pass, where the model wrote the inline form, was
|
|
1363
|
+
refused, and rewrote it as a trailing flag one turn later.
|
|
1364
|
+
- §regex-trailing-text A valid `/pattern/flags` prefix followed by horizontal
|
|
1365
|
+
whitespace and trailing text receives one concise trailing-content
|
|
1366
|
+
diagnostic, with or without flags, without assuming what the extra text was
|
|
1367
|
+
intended to represent. Invalid patterns or flags retain the native
|
|
1368
|
+
regex failure; no branch silently removes or executes trailing content.
|
|
1369
|
+
- §naked-pattern **The matcher rides the heading bare.** After the path, and any
|
|
1370
|
+
scope or option block, the rest of a FIND, READ or KILL heading line is the
|
|
1371
|
+
matcher, in whichever dialect its first characters claim
|
|
1372
|
+
({§matcher-prefix-claims}): `/re/i`, `^anchored`, `//xpath`, `$.json`, `~words`,
|
|
1373
|
+
`&symbol`, or a sigil-less glob or literal such as `TODO` or `*.ts`. Those
|
|
1374
|
+
operations take no body, so heading-line text can mean nothing else. On EDIT only
|
|
1375
|
+
a sigil lifts, because plain heading-line text is the replacement body it always
|
|
1376
|
+
was; the lines beneath the heading are then the replacement, and none deletes each
|
|
1377
|
+
match ({§edit-pattern}). A trailing `<!-- aside -->` on the same line stays the
|
|
1378
|
+
aside. The lift is exactly what `[{"pattern": "…"}]` produces, and that option
|
|
1379
|
+
remains the escape for a matcher the heading cannot hold bare: one opening with
|
|
1380
|
+
`(`, `<`, `[` or a backtick, one containing `<!--`, and every COPY/MOVE operand.
|
|
1381
|
+
`FIND (src/parser.ts) /\bparse\w+\b/` is the same operation as
|
|
1382
|
+
`FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
|
|
1383
|
+
dialect without slashes or flags: the whole text is the pattern, so
|
|
1384
|
+
`READ (reasoning:///1/1) ^NOTE:.*` selects a turn's note lines (operator,
|
|
1385
|
+
2026-09-12: "Recursive Reasoning").
|
|
1386
|
+
- §trailing-slots **Slots after the matcher peel off the right.** The heading text after
|
|
1387
|
+
the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
|
|
1388
|
+
shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
|
|
1389
|
+
trailing `[option block]` that parses as an array of objects come off the right end in
|
|
1390
|
+
any order, each taken once and only when the heading did not already carry that slot,
|
|
1391
|
+
until what remains is the matcher. `READ (a.rs) /fn resolve_/ <1,-1> <!-- entities -->`
|
|
1392
|
+
is the same operation as `READ (a.rs) <1,-1> /fn resolve_/ <!-- entities -->`, with
|
|
1393
|
+
one warning-severity advisory naming the canonical order for the scope or block
|
|
1394
|
+
(operator, 2026-09-13: "swallow up anything that passes as legitimate plurnk"; the
|
|
1395
|
+
2026-09-13 dumbox run refused three headings for this in one turn). A matcher that
|
|
1396
|
+
itself ends in one of those shapes takes the option escape.
|
|
1397
|
+
- §matcher-body-redirect **A body beneath those headings.** Text below the heading
|
|
1398
|
+
of a FIND, READ or KILL is a body, and those operations take none: the builder
|
|
1399
|
+
keeps the statement without it and raises one warning-severity advisory (`READ
|
|
1400
|
+
takes no body; the body was ignored. A pattern belongs on the opening fence line
|
|
1401
|
+
after the path.`), delivered like {§misplaced-aside-advisory} as a
|
|
1402
|
+
`parse_advisory` notice (operator, 2026-09-12: a gentle warning, never an error
|
|
1403
|
+
the model must recover from). One sigil line beneath the heading is the bare form
|
|
1404
|
+
written a line low and still lifts; nothing else is promoted into a matcher from
|
|
1405
|
+
below the heading, and the advisory never echoes the body.
|
|
1406
|
+
- §combined-anchor-tolerance **Combined anchor and line number in a scope.** A
|
|
1407
|
+
text-coordinate scope position written `@hash:L` or `@hash L` is the displayed
|
|
1408
|
+
`@abcde 42:` prefix copied whole (a koota-entity turn refused nine of them in a
|
|
1409
|
+
row, 2026-09-12): the position is the anchor, the number is dropped, and one
|
|
1410
|
+
warning-severity advisory names the anchor-only form. The scope lexes as one
|
|
1411
|
+
ordinary marker at any text-coordinate operation, either COPY/MOVE operand
|
|
1412
|
+
included; nothing cascades.
|
|
1413
|
+
- §invalid-scope-diagnostic **Malformed scope content.** After a properly spaced
|
|
1414
|
+
scope opener, report the offending scope (at most 64 code points, ending at
|
|
1415
|
+
`>` or the heading's line end) and its operation's constraint: FIND result
|
|
1416
|
+
positions, EXEC/TASK minutes, text coordinates, or no scope. Do not append advice for
|
|
1417
|
+
other operations or infer why the producer supplied the value. Spacing and
|
|
1418
|
+
boundary-loss diagnostics retain their own contracts.
|
|
1419
|
+
- §misplaced-aside-advisory **Aside in the body.** A READ or FIND whose
|
|
1337
1420
|
body is solely an HTML comment (`<!-- … -->`) can never carry a matcher: it is
|
|
1338
|
-
the
|
|
1339
|
-
the comment as the
|
|
1421
|
+
the aside the model put on the line below the heading. The builder takes
|
|
1422
|
+
the comment as the aside when the heading has none, builds the operation
|
|
1340
1423
|
with no body, and raises one warning-severity advisory stating that observed
|
|
1341
1424
|
normalization; the parser places the advisory right after its statement and
|
|
1342
1425
|
the service delivers it as a `parse_advisory` notice with its position. A body
|
|
1343
|
-
with any other content is
|
|
1426
|
+
with any other content is ignored under the same advisory path
|
|
1427
|
+
({§matcher-body-redirect}).
|
|
1344
1428
|
|
|
1345
1429
|
§error-shape The diagnostic class determines how much guidance the parser may
|
|
1346
1430
|
provide:
|
|
@@ -1352,18 +1436,20 @@ provide:
|
|
|
1352
1436
|
| Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
|
|
1353
1437
|
| Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
|
|
1354
1438
|
|
|
1355
|
-
All messages use PLURNK protocol vocabulary:
|
|
1439
|
+
All messages use PLURNK protocol vocabulary: opening fence, closing fence, target,
|
|
1356
1440
|
scope, line marker, body, section boundary, or space between slots. They never
|
|
1357
1441
|
expose ANTLR rule or token names. They refer to a slot or
|
|
1358
1442
|
feature rather than an implementation rule. Generic tutoring, speculative
|
|
1359
1443
|
intent, coordinate restatement, and multiple repair strategies are forbidden.
|
|
1444
|
+
Unexpected top-level text immediately after a closed operation identifies that
|
|
1445
|
+
operation's opening line, closing line, and matching backtick count.
|
|
1360
1446
|
|
|
1361
1447
|
Examples of canonical hard facts:
|
|
1362
1448
|
|
|
1363
1449
|
- `unrecognized character '<' in target`
|
|
1364
|
-
- `
|
|
1450
|
+
- `unexpected bracket modifier; the fence name selects the executor`
|
|
1365
1451
|
- `unrecognized character 'X' in statement header`
|
|
1366
|
-
- `
|
|
1452
|
+
- `TASK's body begins below the header`
|
|
1367
1453
|
- `expected ')'; got ':'`
|
|
1368
1454
|
|
|
1369
1455
|
Each malformed statement produces at most one hard error. The first recorded
|
|
@@ -1374,8 +1460,9 @@ Independent malformed statements each retain one hard error. Advisories remain
|
|
|
1374
1460
|
separate because they do not represent failed admission.
|
|
1375
1461
|
|
|
1376
1462
|
§unparsed-tail-boundary When the lexer cannot determine where a malformed
|
|
1377
|
-
statement ends
|
|
1378
|
-
|
|
1463
|
+
statement ends — an unfinished `(target` or `[metadata` slot on a heading line —
|
|
1464
|
+
the result's `unparsedTail` marks the position from which parsing gave up. A block
|
|
1465
|
+
without a closer is not such a case: it ends under {§closer-fallback}. `ParseResult.items` contains only facts that begin strictly
|
|
1379
1466
|
before that point; recovered contexts and diagnostics at or beyond it are not
|
|
1380
1467
|
public results. The tail is one separate boundary fact, not an additional
|
|
1381
1468
|
malformed-statement diagnostic. Consumers must treat anything from that point
|
|
@@ -1398,6 +1485,6 @@ runtime constructs this; the parser provides the fields):
|
|
|
1398
1485
|
"column": 12,
|
|
1399
1486
|
"source": "parser",
|
|
1400
1487
|
"severity": "error",
|
|
1401
|
-
"message": "
|
|
1488
|
+
"message": "READ block opened at line 1 but was not closed with 3 backticks"
|
|
1402
1489
|
}
|
|
1403
1490
|
```
|