@plurnk/plurnk-contracts 1.18.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +11 -0
- package/README.md +2 -2
- package/SPEC.md +325 -265
- package/dist/conformance/agui-v1.json +3 -3
- package/dist/schema/ClientStatement.json +0 -10
- package/dist/schema/LoopPolicy.json +32 -3
- package/dist/schema/LoopPolicyRequest.json +16 -0
- package/dist/schema/McpConfigurationOverlay.json +1 -14
- package/dist/schema/PlurnkStatement.json +20 -31
- package/dist/schema/ProposalProjection.json +2 -2
- package/dist/schema/SkillDefinition.json +3 -3
- package/dist/schema/TextLineMarker.json +1 -1
- package/dist/src/ApplicationPort.d.ts +37 -10
- package/dist/src/ApplicationPort.d.ts.map +1 -1
- package/dist/src/LoopLifecycle.d.ts +5 -0
- package/dist/src/LoopLifecycle.d.ts.map +1 -1
- package/dist/src/LoopLifecycle.js +15 -0
- package/dist/src/LoopLifecycle.js.map +1 -1
- package/dist/src/MessageResource.d.ts +30 -0
- package/dist/src/MessageResource.d.ts.map +1 -0
- package/dist/src/MessageResource.js +2 -0
- package/dist/src/MessageResource.js.map +1 -0
- package/dist/src/PlurnkParseError.d.ts +1 -3
- package/dist/src/PlurnkParseError.d.ts.map +1 -1
- package/dist/src/PlurnkParseError.js +1 -4
- package/dist/src/PlurnkParseError.js.map +1 -1
- package/dist/src/Problems.d.ts +1 -0
- package/dist/src/Problems.d.ts.map +1 -1
- package/dist/src/Problems.js +19 -1
- package/dist/src/Problems.js.map +1 -1
- package/dist/src/TurnDisposition.d.ts +2 -3
- package/dist/src/TurnDisposition.d.ts.map +1 -1
- package/dist/src/TurnDisposition.js +4 -20
- package/dist/src/TurnDisposition.js.map +1 -1
- package/dist/src/Validator.d.ts +3 -3
- package/dist/src/Validator.d.ts.map +1 -1
- package/dist/src/Validator.js +14 -15
- package/dist/src/Validator.js.map +1 -1
- package/dist/src/index.d.ts +3 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +5 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.generated.d.ts +32 -67
- package/dist/src/types.generated.d.ts.map +1 -1
- package/dist/src/types.js +12 -10
- package/dist/src/types.js.map +1 -1
- package/package.json +4 -3
- package/plurnk.md +39 -66
- package/dist/schema/AcpPlan.json +0 -63
- package/dist/schema/Plan.json +0 -65
- package/dist/src/AcpPlanValue.d.ts +0 -6
- package/dist/src/AcpPlanValue.d.ts.map +0 -1
- package/dist/src/AcpPlanValue.js +0 -36
- package/dist/src/AcpPlanValue.js.map +0 -1
- package/dist/src/PlanValue.d.ts +0 -9
- package/dist/src/PlanValue.d.ts.map +0 -1
- package/dist/src/PlanValue.js +0 -63
- package/dist/src/PlanValue.js.map +0 -1
package/SPEC.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## 1. Overview
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
is the single code API for those contracts
|
|
5
|
+
This package is the single authority for PLURNK's language, schemas, generated types and
|
|
6
|
+
runtime-neutral wire envelopes; `@plurnk/plurnk-parser` implements the language it specifies
|
|
7
|
+
({§parser-consumers}). Its package root is the single code API for those contracts
|
|
8
|
+
({§root-value-api}).
|
|
8
9
|
|
|
9
10
|
| Surface | Canonical export or artifact |
|
|
10
11
|
| ------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
11
|
-
|
|
|
12
|
-
| Capability and loop policies
|
|
12
|
+
| AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
|
|
13
|
+
| Capability and loop policies | `CapabilityPolicy`, `LoopPolicy`, `LoopPolicyRequest`, `PROPOSAL_POLICIES` |
|
|
13
14
|
| Durable reasoning intent | `ReasoningPolicy`, `REASONING_POLICIES` |
|
|
14
15
|
| Model route and catalog discovery | `ModelRoute`, `ModelCatalogQuery`, `ModelCatalogPage`, `ModelReadiness` |
|
|
15
16
|
| Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
|
|
@@ -89,12 +90,10 @@ and previews; never reformat literal resources, JSONL framing, or wire/evidence
|
|
|
89
90
|
serialization. Compact aggregate rows ({§json-result-rendering}) and packet
|
|
90
91
|
metadata retain their deliberate layouts.
|
|
91
92
|
|
|
92
|
-
##
|
|
93
|
+
## 1.1 Contract layers and admission boundary
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
teaching, and an operator's sampling grammar admitting a sentence does not
|
|
97
|
-
make its runtime semantics valid.
|
|
95
|
+
One contract, deliberately different projections (ARCHITECTURE.md). Each layer
|
|
96
|
+
below owns what it alone can decide.
|
|
98
97
|
|
|
99
98
|
```mermaid
|
|
100
99
|
flowchart LR
|
|
@@ -127,7 +126,7 @@ WHATWG `URL`, ECMAScript `RegExp`, XPath 1.0, and RFC 9535 JSONPath parsers.
|
|
|
127
126
|
The runtime owner decides facts that require state or operation-specific
|
|
128
127
|
meaning, including registered scheme resolution, target existence, tag
|
|
129
128
|
selection, text-region bounds, result ordering, full-text ranking, mutation
|
|
130
|
-
effects, executor behavior
|
|
129
|
+
effects, and executor behavior.
|
|
131
130
|
|
|
132
131
|
### §contract-proposal-projection Loop policy and stopped-world projection
|
|
133
132
|
|
|
@@ -168,11 +167,15 @@ workspace layer and their normalized intersection: `service`, `workspace`, and
|
|
|
168
167
|
capability policy or inherited bound; every actor uses the same live workspace
|
|
169
168
|
policy. A client never derives effective authority from the mutable layer alone.
|
|
170
169
|
|
|
171
|
-
§loop-policy `
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
§loop-policy A `LoopPolicy` is complete and immutable after creation:
|
|
171
|
+
`proposals` chooses one downstream settlement posture and `attended` says
|
|
172
|
+
whether anyone can answer, independently of workspace capability policy. An
|
|
173
|
+
unattended loop cannot hold a proposal for review, so the schema refuses that
|
|
174
|
+
pair. A `LoopPolicyRequest` is the part of a policy its creator chose to state.
|
|
175
|
+
Contracts hold no default for the rest: the daemon's panel supplies it
|
|
176
|
+
({§loop-policy-composition}). `PROPOSAL_POLICIES` is the schema-owned
|
|
177
|
+
vocabulary of `proposals`. Capability admission precedes effect
|
|
178
|
+
classification and proposal settlement.
|
|
176
179
|
|
|
177
180
|
§reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
|
|
178
181
|
medium | high`. The schema owns this shared wire vocabulary. Providers own the
|
|
@@ -264,37 +267,56 @@ input
|
|
|
264
267
|
`````
|
|
265
268
|
|
|
266
269
|
§section-boundary Every statement is one backtick block. Its header occupies one
|
|
267
|
-
physical line: a fence of at least
|
|
270
|
+
physical line: a fence of at least four backticks ({§four-backtick-operations}), an optional numeric delimiter
|
|
268
271
|
({§numeric-delimiter}), then the name and its slots. A closer is shown by
|
|
269
272
|
convention and never demanded ({§fence-closer}, {§closer-fallback}). There are no
|
|
270
|
-
operation suffixes or heading levels.
|
|
271
|
-
|
|
272
|
-
characters (operator, 2026-09-12: counted pairs failed 52 of 363 turns on
|
|
273
|
-
GLM-5.3-flash; anchored tokens failed none).
|
|
273
|
+
operation suffixes or heading levels. Complete nested matches take precedence
|
|
274
|
+
over local recovery ({§balanced-fences}).
|
|
274
275
|
|
|
275
276
|
§fence-closer A block opened with N backticks and delimiter D (its digits, possibly
|
|
276
|
-
none) closes at the first line
|
|
277
|
+
none) closes at the first unclaimed line made of at least N backticks,
|
|
277
278
|
exactly D, and nothing else but horizontal whitespace. Count follows CommonMark: a
|
|
278
279
|
shorter fence inside the body is body; an equal or longer bare fence closes a bare
|
|
279
|
-
block
|
|
280
|
+
block unless it closes a balanced nested block ({§balanced-fences}). The delimiter
|
|
281
|
+
compares exactly: a bare fence never closes a delimited block,
|
|
280
282
|
and a delimited fence never closes a bare one. The compact one-line form closes on
|
|
281
283
|
its heading line after the modifiers under the same rule.
|
|
282
284
|
|
|
285
|
+
§balanced-fences A complete nested interpretation takes precedence over missing-closer
|
|
286
|
+
recovery. Within an undelimited body, line-leading labeled fences open literal
|
|
287
|
+
blocks; their matching closers close the innermost block first. The enclosing
|
|
288
|
+
block and its nested blocks must all close, with matching widths and delimiters
|
|
289
|
+
under {§fence-closer}. Equal opener/closer totals alone are insufficient. A complete
|
|
290
|
+
inline block is already closed; a numerically delimited block is opaque until its
|
|
291
|
+
own closer. Preserve every nested body byte, including apparent OPs and known
|
|
292
|
+
executors, without dispatching them. If no complete enclosing interpretation exists,
|
|
293
|
+
retain {§fence-heading-in-body} and {§closer-fallback}. These rules apply equally to
|
|
294
|
+
model programs, stored programs, client operations, and reasoning quotations.
|
|
295
|
+
|
|
283
296
|
§numeric-delimiter Digits between the opening backticks and the name (an opener
|
|
284
|
-
carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it.
|
|
285
|
-
|
|
286
|
-
|
|
297
|
+
carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it.
|
|
298
|
+
This explicitly protects bare fences and headings, including incomplete examples.
|
|
299
|
+
The delimiter is syntax, never AST or persistence
|
|
287
300
|
state; `PlurnkParser.frame` chooses one when the body it wraps holds a heading line
|
|
288
301
|
of four or more backticks ({§statement-rendering}).
|
|
289
302
|
|
|
290
|
-
§
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
§four-backtick-operations **An operation opens with four backticks.** A heading is a fence of
|
|
304
|
+
four or more backticks; a three-backtick fence is markdown wherever it stands, so an answer's
|
|
305
|
+
code blocks (```` ```sh ````, ```` ```ts ````) are prose and never run (operator,
|
|
306
|
+
2026-09-18, #761). A three-backtick fence naming an operation or known executor draws one warning that it
|
|
307
|
+
needs four backticks; any other three-backtick fence draws none. `plurnk.md` teaches exactly
|
|
308
|
+
four; longer fences are tolerated, not taught. Every producer of a statement — the parser, a
|
|
309
|
+
client composing `/look`, a client's tab-completion — writes `PLURNK_FENCE` rather than its own
|
|
310
|
+
literal, so no surface can ship a width the parser will quote (plurnk/plurnk#92).
|
|
311
|
+
|
|
312
|
+
§fence-heading-in-body Outside a complete nested block ({§balanced-fences}), a fence
|
|
313
|
+
line of four or more backticks, optional digits, and a name that is a native operation
|
|
314
|
+
or a known executor is a heading. Inside an open block it ends that block without closing it
|
|
293
315
|
({§closer-fallback}) and opens the next statement. Fence lines of fewer than four
|
|
294
|
-
backticks are headings
|
|
316
|
+
backticks are never headings ({§four-backtick-operations}). Known executors are `sh` plus what
|
|
295
317
|
the host names in `ParseOptions.executors`. Consequences: a closer glued to the next
|
|
296
|
-
opener (eight backticks then `READ`) can never swallow
|
|
297
|
-
|
|
318
|
+
opener (eight backticks then `READ`) can never swallow an unbalanced turn, and a numeric
|
|
319
|
+
delimiter preserves quoted headings even when their own fences are incomplete.
|
|
298
320
|
|
|
299
321
|
§closer-fallback A block that ends at a heading or at the end of the input has no
|
|
300
322
|
closer of its own. Its body is cut back to its last bare fence line (any count,
|
|
@@ -303,23 +325,27 @@ ending goes with it; when no bare fence line exists the body is the whole span l
|
|
|
303
325
|
one terminating line ending. This carries no diagnostic: a missing closer is never
|
|
304
326
|
an admission failure, and {§unparsed-tail-boundary} is not involved.
|
|
305
327
|
|
|
306
|
-
§fence-boundary
|
|
307
|
-
|
|
328
|
+
§fence-boundary Balanced nesting is resolved before local recovery. Otherwise,
|
|
329
|
+
fences are read by count and delimiter, except for the heading rule above:
|
|
308
330
|
|
|
309
331
|
| Fence encountered inside a body | Meaning |
|
|
310
332
|
|---|---|
|
|
333
|
+
| Part of a complete nested block | Literal body, including its openers and closers |
|
|
311
334
|
| Fewer backticks than the block's own | Body |
|
|
312
335
|
| At least the block's backticks, bare, block undelimited | The block's closer |
|
|
313
336
|
| At least the block's backticks carrying the block's delimiter | The block's closer |
|
|
314
337
|
| At least the block's backticks with any other delimiter | Body |
|
|
315
338
|
| Four or more backticks naming a native operation or known executor | A heading: ends the block, opens the next statement |
|
|
316
339
|
|
|
317
|
-
§indented-fences Leading horizontal whitespace before
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
at five to ten percent of emissions on
|
|
340
|
+
§indented-fences Leading horizontal whitespace before a CLOSER is not part of the fence: an
|
|
341
|
+
indented closer, heading-that-ends-a-block, or closer fallback still closes, and a body keeps its
|
|
342
|
+
own lines' indentation. An OPENER is different: an operation's backticks follow a newline
|
|
343
|
+
directly (operator, 2026-09-18), so an indented fence opens a quotation, never an operation —
|
|
344
|
+
CommonMark reads an indented block as code, and `plurnk.md` shows its own examples that way. This
|
|
345
|
+
reverses the 2026-09-12 tolerance (then measured at five to ten percent of emissions on
|
|
346
|
+
GLM-5.3-flash; 2.8% of that lane's emissions today). The cost is paid loudly: an indented fence
|
|
347
|
+
naming a known operation draws `must start its line to run` and is an operation attempt, never an
|
|
348
|
+
answer ({§prose-conclusion}), so the loop continues instead of delivering a program as prose.
|
|
323
349
|
|
|
324
350
|
§inline-chain A closer on a heading line, or on a body's closing line, may be
|
|
325
351
|
followed on that same line by the next opener; the closer still closes, and the
|
|
@@ -331,7 +357,7 @@ text there is the heading's own and is read under {§transparent-inline-closer}.
|
|
|
331
357
|
closing fence on a heading line followed by more of that heading — a `<scope>`, an
|
|
332
358
|
`[option block]`, a `<!-- aside -->`, or a naked matcher — does not end the reading: the
|
|
333
359
|
heading keeps taking its slots under {§trailing-slots} and {§naked-pattern}, exactly as though
|
|
334
|
-
the closer were absent, so
|
|
360
|
+
the closer were absent, so ````` ````READ (a.md)```` ````` `<1,2>` is the same operation as `READ (a.md) <1,2>`.
|
|
335
361
|
The closer is still a closer: the block ends with that physical line and never reaches down for
|
|
336
362
|
the next operation, which is what a bare heading carrying a matcher would do. A closer followed
|
|
337
363
|
by the next opener is {§inline-chain}, and by nothing is the ordinary {§fence-closer}. There is
|
|
@@ -344,20 +370,22 @@ registered executor's name case-insensitively opens that executor (`SH` opens
|
|
|
344
370
|
`sh`), and the statement's `executor` is the registered spelling, so a lookup
|
|
345
371
|
by that name never misses. Operation names stay uppercase by teaching and were
|
|
346
372
|
never written otherwise in 12,000 fences; one `SH` in 1,300 `sh` fences was
|
|
347
|
-
(2026-09-13 census). An unregistered name
|
|
348
|
-
({§interstitial-fence}).
|
|
349
|
-
|
|
350
|
-
§
|
|
351
|
-
|
|
352
|
-
|
|
373
|
+
(2026-09-13 census). An unregistered name without an accepted spelling
|
|
374
|
+
({§executor-js-spelling}) is still prose ({§interstitial-fence}).
|
|
375
|
+
|
|
376
|
+
§executor-js-spelling When `node` is registered and `js` is not, `js` names
|
|
377
|
+
`node` under {§executor-case}. Parsing normalizes the runtime before admission
|
|
378
|
+
and dispatch; execution, policy, receipts, and output addresses remain Node's.
|
|
379
|
+
The authored source remains unchanged. This spelling adds no executor, discovery
|
|
380
|
+
entry, or model-facing teaching. An explicitly registered `js` retains its own
|
|
381
|
+
identity; absent `node`, the shorthand grants no executable capability.
|
|
382
|
+
|
|
383
|
+
§one-line-turn **A whole turn on one line.** A model may emit a turn as a single
|
|
384
|
+
line: prose, then heading after heading with no line ending anywhere. Two
|
|
353
385
|
rules absorb it. The next opener on a heading's own line, after the heading's
|
|
354
386
|
slots, ends that heading's block bodyless and opens ({§empty-section}), so
|
|
355
387
|
`````EDIT (a.rs) <60,66> <!-- drop --> ````EDIT (b.rs) <31,38>` is two scoped
|
|
356
|
-
deletions
|
|
357
|
-
(`````TASK [{"content": "…", "status": "in_progress"}] ````) takes that block as
|
|
358
|
-
its body when nothing sits beneath the heading, with one warning-severity
|
|
359
|
-
advisory naming the body as where the inventory belongs. A block beneath the
|
|
360
|
-
heading still wins.
|
|
388
|
+
deletions. Literal inline bodies follow {§heading-inline-body}.
|
|
361
389
|
|
|
362
390
|
§anchor-digits In a text scope, `@` followed by one to four digits cannot be a
|
|
363
391
|
hash and is read as that line number, with one warning-severity advisory naming
|
|
@@ -367,22 +395,69 @@ the five-character anchor form. Five characters after `@` are always an anchor.
|
|
|
367
395
|
line takes the rest of the line as the aside, with one warning-severity advisory.
|
|
368
396
|
A closed aside followed by more text is unchanged.
|
|
369
397
|
|
|
370
|
-
§
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
398
|
+
§quotation **A fence that opens no operation quotes.** Outside a body, a line-start fence that
|
|
399
|
+
is not an operation heading — unlabeled, tagged like a code block (`ts`, `json`), three
|
|
400
|
+
backticks ({§four-backtick-operations}), indented, or four or more with an unknown name — opens a
|
|
401
|
+
quotation that runs to its matching closer (same character, width at least the opener's) or to
|
|
402
|
+
the end of the input. Everything inside is data: no operation runs there, native tool-call
|
|
403
|
+
markup is not read ({§native-tool-calls}), and no heading draws an advisory. So a model may show
|
|
404
|
+
plurnk's own operations in an answer. Three exceptions keep programs whole:
|
|
405
|
+
CommonMark's own rule that a backtick opener's line carries no further backtick, so
|
|
406
|
+
```` ```READ (x)``` ```` is inline code and quotes nothing after it; a bare fence directly under a
|
|
407
|
+
line carrying a fence run, which is an orphaned closer and quotes nothing; and a tag that is a
|
|
408
|
+
missed operation — a known name under four backticks or off column zero, or an unknown name at
|
|
409
|
+
operation width — which still draws one warning (`markdown` and `md` are polite envelopes and
|
|
410
|
+
draw none). There is no implicit SEND: a reply is prose ({§prose-conclusion}) or an explicit
|
|
411
|
+
`SEND` block. Origin (#767, 2026-09-18): under prose answers, a quoted example executed.
|
|
412
|
+
|
|
413
|
+
§interstitial-fence Superseded by {§quotation}: an unlabeled fence no longer opens nothing, it
|
|
414
|
+
quotes. (It in turn replaced the retired unlabeled-fence SEND of the fences chapter, whose
|
|
415
|
+
unlabeled fences turned displaced headings into silent messages.)
|
|
416
|
+
|
|
417
|
+
§closer-aside A closing fence followed on its line by one aside and nothing else
|
|
418
|
+
is the closer; the aside is outside text. Read as body, that line would be written
|
|
419
|
+
into the edited resource (#758: a recorded EDIT deleting a line wrote
|
|
420
|
+
```` ```` <!-- remove duplicated Result import --> ```` into a Python file). A
|
|
421
|
+
closing fence followed by any other text is still body.
|
|
422
|
+
|
|
423
|
+
§heading-slot-order A heading near-miss with exactly one reading is read as that
|
|
424
|
+
reading, with no diagnostic and no teaching (#758):
|
|
425
|
+
|
|
426
|
+
- an aside written before the heading's remaining scope or JSON option block is
|
|
427
|
+
read after them (`READ (a.md) <!-- why --> <1,-1>`); an aside followed by a
|
|
428
|
+
target or a non-JSON block keeps its place and stays refused;
|
|
429
|
+
- zero-width characters (U+200B–U+200D, U+2060, U+FEFF) on a heading line are skipped;
|
|
430
|
+
- a matcher that begins with a sigil and is quoted in single backticks
|
|
431
|
+
(`` `^def test_` ``) is that matcher; quoted text without a sigil stays refused.
|
|
432
|
+
|
|
433
|
+
Tokens keep their source positions; only their order in the stream changes.
|
|
434
|
+
|
|
435
|
+
§native-tool-calls An emission that yields no operation may be a model's native
|
|
436
|
+
tool-call markup (DeepSeek's `<||DSML|| calls>` block) naming a plurnk operation or
|
|
437
|
+
a known executor. Each `invoke` is read as that operation's canonical fence:
|
|
438
|
+
`path`/`target` fill the target, `scope`/`range`/`lines` the scope, `pattern` a
|
|
439
|
+
matcher option, `aside` the aside, and `body`/`content`/`command` or plain lines
|
|
440
|
+
inside the invoke the body; plurnk slots written after the invoke name are kept.
|
|
441
|
+
Each block keeps its line count, so statement positions still name the source
|
|
442
|
+
line. An invoke with an unknown name or parameter leaves the whole emission as it
|
|
443
|
+
was. An emission that already yields an operation is never rewritten. No
|
|
444
|
+
diagnostic, notice or teaching mentions the reading (#760).
|
|
445
|
+
|
|
446
|
+
§operation-attempt An emission with no operation is either prose, which a host may take
|
|
447
|
+
as the model's answer, or an operation attempt. `PlurnkParser.operationAttempt(input,
|
|
448
|
+
executors)` names the attempt: a line opening a four-backtick fence, a heading outside
|
|
449
|
+
any fence ({§bare-heading-advisory}; a known executor's name is a heading only when a
|
|
450
|
+
slot follows it), native tool-call markup that {§native-tool-calls} did not read, or
|
|
451
|
+
echoed packet rows (`### log://…`). Anything else, three-backtick code blocks included,
|
|
452
|
+
is prose (#761).
|
|
377
453
|
|
|
378
454
|
§bare-heading-advisory An operation name that opens a line outside any block in the
|
|
379
|
-
shape of a heading (`READ (…)`, `
|
|
455
|
+
shape of a heading (`READ (…)`, `NOTE`, …) is prose and runs nothing. The parser
|
|
380
456
|
emits one warning-severity advisory naming the fence form, placed after the parsed
|
|
381
457
|
items, so the loss is never quiet.
|
|
382
458
|
|
|
383
459
|
§empty-section Both the compact bodyless form and an empty multiline block
|
|
384
|
-
normalize optional bodies to null.
|
|
385
|
-
under {§plan-value}. Closing fences are conventional, never required
|
|
460
|
+
normalize optional bodies to null. Closing fences are conventional, never required
|
|
386
461
|
({§closer-fallback}).
|
|
387
462
|
|
|
388
463
|
§statement-rendering `PlurnkParser.stringify` renders native OP names and named
|
|
@@ -393,7 +468,8 @@ It chooses at least four backticks and more than any run within the body, and a
|
|
|
393
468
|
numeric delimiter whenever the body holds a heading line of four or more backticks
|
|
394
469
|
({§fence-heading-in-body}), preserving body bytes on reparse. Fence length and
|
|
395
470
|
delimiter are syntax, not AST or persistence state. Core-authored programs use
|
|
396
|
-
this serializer and the ordinary admission parser.
|
|
471
|
+
this serializer and the ordinary admission parser. Rendering preserves a matcher
|
|
472
|
+
beside owner metadata, not only a matcher carried inside its `pattern` option.
|
|
397
473
|
|
|
398
474
|
| Element | Contract |
|
|
399
475
|
|---|---|
|
|
@@ -412,8 +488,9 @@ adjacent slots and scope/metadata permutations within a selection without
|
|
|
412
488
|
changing ownership or making them distinct canonical forms. Each selection
|
|
413
489
|
has at most one scope; its metadata blocks retain their authored order.
|
|
414
490
|
|
|
415
|
-
§
|
|
416
|
-
|
|
491
|
+
§lifecycle-slots NOTE accepts no target, scope, or metadata. WAIT retains its
|
|
492
|
+
optional target and ignores syntactically valid scope and metadata without diagnostics
|
|
493
|
+
({§send-wait-scope}). Their literal bodies begin below the header.
|
|
417
494
|
|
|
418
495
|
§heading-inline-body Nonempty body text belongs below the fence header.
|
|
419
496
|
The ingester tolerates body text after horizontal whitespace on the header,
|
|
@@ -427,7 +504,7 @@ routing, timing, or body input. Comments inside a body remain literal except
|
|
|
427
504
|
for the narrowly owned {§misplaced-aside-advisory}.
|
|
428
505
|
|
|
429
506
|
§scheme-metadata-modifier A target may carry one single-line `[metadata]`
|
|
430
|
-
block after its scope;
|
|
507
|
+
block after its scope; executor and SEND fences also admit it without a target.
|
|
431
508
|
Read with its brackets, the block is a JSON array of option objects, merged
|
|
432
509
|
left to right with later keys winning; the keys belong to the selected scheme
|
|
433
510
|
or executor, which owns interpretation, validation and authority. The language
|
|
@@ -469,7 +546,7 @@ whose block left no metadata back bare when the bare form reads back identically
|
|
|
469
546
|
|
|
470
547
|
| Element | Shape or role |
|
|
471
548
|
|---|---|
|
|
472
|
-
| Native OP | `FIND READ EDIT COPY MOVE SEND BARE WORK FORK KILL
|
|
549
|
+
| Native OP | `FIND READ EDIT COPY MOVE SEND BARE WORK FORK KILL NOTE WAIT` |
|
|
473
550
|
| Executor name | Letters, digits, `_`, `.`, `+`, or `-`; reserved OPs win |
|
|
474
551
|
| Fence | Three or more backticks, matched by exact count |
|
|
475
552
|
| `(path)` | Local path, URI, program or tool name; §5 |
|
|
@@ -494,58 +571,27 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
|
|
|
494
571
|
| WORK | optional fresh `worker://name`, or a prompt resource ({§worker-spawn-prompt-resource}) | none | prompt; optional with a resource |
|
|
495
572
|
| FORK | optional context-inheriting `worker://name`, or a prompt resource | none | prompt; optional with a resource |
|
|
496
573
|
| KILL | required target, including a log item | optional text region ({§kill-scope}) | none; the matcher is the `pattern` option |
|
|
497
|
-
| SEND | optional recipient |
|
|
498
|
-
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
| Inventory condition | Intent | Derived lifecycle status |
|
|
519
|
-
|---|---|---|
|
|
520
|
-
| TASK omitted | Continue silently | 102 |
|
|
521
|
-
| Explicit empty inventory | Recover empty inventory | 102 |
|
|
522
|
-
| Any `in_progress` | Continue independent actionable work | 102 |
|
|
523
|
-
| Any `waiting`, no `in_progress` | Await work or an event | 202 |
|
|
524
|
-
| Any `todo`, no actionable or waiting entry | Continue | 102 |
|
|
525
|
-
| All terminal, any `completed` | End successfully | 200 |
|
|
526
|
-
| All `failed`, nonempty | End unsuccessfully | 499 |
|
|
527
|
-
|
|
528
|
-
`todo` is not started; `in_progress` can be actively advanced;
|
|
529
|
-
`waiting` awaits an ongoing stream, worker or external event. `completed` is
|
|
530
|
-
successful resolution; `failed` is unsuccessful resolution. A failed sibling
|
|
531
|
-
does not terminate independent unfinished work. The engine does not infer a
|
|
532
|
-
dependency graph from task text.
|
|
533
|
-
|
|
534
|
-
§plan-acp-projection **Only an ACP-facing boundary projects the model-native
|
|
535
|
-
Plan.** It constructs ACP's `{ "entries": [...] }` Plan object from the internal
|
|
536
|
-
array, synthesizes the ACP-required neutral `medium` priority on every entry
|
|
537
|
-
(the model-native Plan carries none). The internal value is never mutated.
|
|
538
|
-
Native `todo` maps to ACP `pending`: the same state under ACP's name, so it carries no marker.
|
|
539
|
-
Native `waiting` maps to ACP `in_progress`, with `Waiting:` and a space prepended
|
|
540
|
-
to its display content; native `failed` maps to ACP `completed`, with `Failed:` and a space.
|
|
541
|
-
Both carry their native status in `_meta["plurnk.xyz/status"]`, an edge-owned
|
|
542
|
-
key derived from the native status rather than trusted from authored metadata.
|
|
543
|
-
Other statuses and unrelated metadata remain unchanged. The labels preserve
|
|
544
|
-
meaning even when a generic client ignores extension metadata.
|
|
545
|
-
The projected value validates against the separately owned ACP Plan schema pinned
|
|
546
|
-
to ACP v1
|
|
547
|
-
[`schema-v1.21.0`](https://github.com/agentclientprotocol/agent-client-protocol/tree/schema-v1.21.0)
|
|
548
|
-
commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
|
|
574
|
+
| SEND | optional recipient | recipient-defined; none for workers ({§send-directed-scope}) | message |
|
|
575
|
+
| NOTE | none | none | literal working memory |
|
|
576
|
+
| WAIT | optional event source ({§send-wait-scope}) | ignored | explanation of the wait |
|
|
577
|
+
|
|
578
|
+
§note-value NOTE retains its literal body as ordinary model-owned working memory.
|
|
579
|
+
It has no target, scope, metadata, or lifecycle effect. Its full body participates
|
|
580
|
+
in ordinary log token accounting and model-driven curation; prior notes are not
|
|
581
|
+
automatically hidden. A NOTE-only turn is subject to ordinary conclusion,
|
|
582
|
+
repetition and strike rules.
|
|
583
|
+
|
|
584
|
+
§reasoning-notes NOTE is the only operation admitted from exposed provider
|
|
585
|
+
reasoning. The shared fence parser selects line-leading NOTE statements in a
|
|
586
|
+
quotation-preserving reasoning context: other backtick or tilde code blocks are
|
|
587
|
+
opaque, and operation-heading recovery cannot escape them. Blockquoted and inline
|
|
588
|
+
examples are not headings. Program parsing is unchanged; other reasoned operations
|
|
589
|
+
never execute.
|
|
590
|
+
Selected notes precede the content program in the admitted turn and use the
|
|
591
|
+
ordinary dispatcher, persistence and log projection. Reasoning bytes and content
|
|
592
|
+
bytes remain separate, unchanged forensic sources. Rejected or superseded
|
|
593
|
+
provider attempts cannot commit notes. Non-thinking models use NOTE in their
|
|
594
|
+
ordinary program. No task inventory is inferred from notes or lifecycle prose.
|
|
549
595
|
|
|
550
596
|
§exec-executor-slot The fence name selects the executor directly: for example,
|
|
551
597
|
`python3 (tools/report.py)` or `gitea (issue_list)` on the opening fence line.
|
|
@@ -559,21 +605,29 @@ shell examples name `sh` explicitly.
|
|
|
559
605
|
The path names a program or tool and is never split. Metadata such as
|
|
560
606
|
`[{"cwd": "…"}]` remains interpreted by the selected executor.
|
|
561
607
|
|
|
562
|
-
§turn-disposition
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
608
|
+
§turn-disposition WAIT requests parking; its literal body does not control
|
|
609
|
+
scheduling, and its optional target is the label the row keeps, never a join;
|
|
610
|
+
the AST has no independently settable lifecycle status or metadata.
|
|
611
|
+
A turn admits any number of WAITs, all deferred until its other operations
|
|
612
|
+
settle and together one park. End-of-program adjudication owns continuation, joining and completion
|
|
613
|
+
under {§wait-obligation-matrix}; no terminal verb or synthetic receipt is required.
|
|
614
|
+
SEND delivers messages and NOTE retains memory, neither declaring an outcome.
|
|
615
|
+
|
|
616
|
+
§send-wait-scope WAIT's optional target is retained as the row's label; no
|
|
617
|
+
scheme handler runs for it, so every WAIT is the bare park. Scope and metadata
|
|
618
|
+
are discarded without diagnostics,
|
|
619
|
+
including structured scopes. The body, aside, and exact submitted program
|
|
620
|
+
remain intact. WAIT neither creates a schedule nor restricts which ordinary
|
|
621
|
+
events may awaken the loop. A scope slot's content is skipped unread whatever it
|
|
622
|
+
holds (`<sh:///…>` included; #756). Ordinary malformed-header rules still apply;
|
|
623
|
+
a second WAIT is one more label on the same park, never a refusal.
|
|
624
|
+
|
|
625
|
+
§send-directed-scope A recipient SEND carries an optional numeric scope after
|
|
626
|
+
its target and metadata through to the addressed owner, which assigns its
|
|
627
|
+
semantics or refuses it; worker actors refuse one (`scope-unsupported`, 400),
|
|
628
|
+
because later and recurring delivery belong to the schedule family. A
|
|
629
|
+
targetless message takes no scope. A scope never changes the message body or
|
|
630
|
+
disposition.
|
|
577
631
|
|
|
578
632
|
§kill-scope KILL takes an optional text-coordinate scope beside its target, numeric or
|
|
579
633
|
anchored (```` ```KILL (log:///**/READ) <17,-1>``` ```` or
|
|
@@ -638,8 +692,8 @@ parses as `{ kind: "local", raw: "data/users.html", fragment: "readable" }`: the
|
|
|
638
692
|
the path and the rest is the channel (a spelling that opens with `#` names no path and stays
|
|
639
693
|
whole), exactly as `worker:///a.html#readable` decomposes, so
|
|
640
694
|
the `#channel` a READ receipt advertises (`channels: {"#readable": N}`) is addressable in the
|
|
641
|
-
same bare spelling the receipt used
|
|
642
|
-
|
|
695
|
+
same bare spelling the receipt used: a model that appends the channel to the path it was just
|
|
696
|
+
shown addresses the same entry. `raw` is therefore always the path alone; a
|
|
643
697
|
bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
|
|
644
698
|
Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
|
|
645
699
|
|
|
@@ -664,7 +718,7 @@ Mutation semantics:
|
|
|
664
718
|
no body. Each selection binds its own target, scope, matcher, and metadata
|
|
665
719
|
under {§slot-order}, {§matcher-option}, and {§scheme-metadata-modifier}.
|
|
666
720
|
|
|
667
|
-
###
|
|
721
|
+
### Per-operation observations
|
|
668
722
|
|
|
669
723
|
| OP | Successful observation |
|
|
670
724
|
|------|-----------------------------------------------------------------------------------|
|
|
@@ -679,7 +733,7 @@ Mutation semantics:
|
|
|
679
733
|
| WORK | Spawn acknowledgement; the deliverable arrives through the log |
|
|
680
734
|
| FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
|
|
681
735
|
| KILL | Status of deletion or termination |
|
|
682
|
-
|
|
|
736
|
+
| NOTE / WAIT | Literal memory or wait explanation |
|
|
683
737
|
|
|
684
738
|
§find-result-unit For FIND, authored target shape fixes the paginated result
|
|
685
739
|
unit. An exact target with a matcher pages flat match locations; a glob or
|
|
@@ -696,7 +750,7 @@ EDIT. Whole-channel transfers remain bodyless structural effects; runtime
|
|
|
696
750
|
owners reject binary markers rather than treating a text field as a byte lane.
|
|
697
751
|
|
|
698
752
|
Every operation returns the runtime-neutral `OperationResult` defined by
|
|
699
|
-
{§operation-result}. Its `status` belongs to the result envelope;
|
|
753
|
+
{§operation-result}. Its `status` belongs to the result envelope; a lifecycle operation supplies
|
|
700
754
|
the authored lifecycle intent. Durable operation observations are projected into a later packet;
|
|
701
755
|
retrieval never returns inline within the emitting turn.
|
|
702
756
|
|
|
@@ -753,11 +807,11 @@ never target content. Glob metacharacters remain legal path data.
|
|
|
753
807
|
Matching and folder-scope semantics remain runtime concerns.
|
|
754
808
|
|
|
755
809
|
§worker-name The exported `WORKER_NAME` contract governs names minted for URI
|
|
756
|
-
authority slots:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
810
|
+
authority slots: `[A-Za-z0-9][A-Za-z0-9_-]{0,62}` (1–63 ASCII characters,
|
|
811
|
+
starting with a letter or digit). Case is preserved and significant;
|
|
812
|
+
`Approach_A` and `approach_a` are distinct names. There is no reserved-name list: the
|
|
813
|
+
runtime's own actor is named `_plurnk`, a spelling the predicate never admits.
|
|
814
|
+
Every matching value, including `self` and `plurnk`, is an ordinary
|
|
761
815
|
literal worker name. This is a minting and registry invariant, not an
|
|
762
816
|
ingestion restriction: the parser decomposes arbitrary URL authorities.
|
|
763
817
|
|
|
@@ -774,17 +828,17 @@ matching.
|
|
|
774
828
|
statement-level error the parser discards the rest of that statement and resumes at the
|
|
775
829
|
next heading; the turn shape is decided locally (a turn disposition is recognized by its own
|
|
776
830
|
token, never by a whole-turn alternative), so one malformed heading costs one
|
|
777
|
-
diagnostic and every later statement, the turn disposition included, stands on its own.
|
|
778
|
-
other second path slot names the one-slot rule.
|
|
831
|
+
diagnostic and every later statement, the turn disposition included, stands on its own.
|
|
779
832
|
- §scope-slot-tolerance A line scope written inside a path slot (```` ```COPY (worker:///src.md<2,3>) ````)
|
|
780
833
|
is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
|
|
781
834
|
before a slot's closing paren can only be a scope; every path slot of a statement is repaired
|
|
782
835
|
the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
|
|
783
836
|
statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
|
|
784
837
|
never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
|
|
785
|
-
- §
|
|
786
|
-
error at
|
|
787
|
-
|
|
838
|
+
- §extra-path-slot A path slot beyond the operation's admitted operands is a parser
|
|
839
|
+
error at its opening paren. Report the unexpected slot and the grammar's expected
|
|
840
|
+
alternatives when available, without inferring pattern intent or imposing another
|
|
841
|
+
operation's operand count. The statement is dropped and its siblings run.
|
|
788
842
|
|
|
789
843
|
| Prefix | Dialect | Canonical form | Typed admission | Runtime owner |
|
|
790
844
|
|-----------|----------|--------------------------------------|-----------------------------------|---------------------|
|
|
@@ -837,8 +891,8 @@ The operation column names the canonical AST operation after
|
|
|
837
891
|
| COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
|
|
838
892
|
| KILL | 0/1/2 text coordinates | Whole target when absent; one physical line or inclusive range when present ({§kill-scope}) |
|
|
839
893
|
| execution | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
|
|
840
|
-
|
|
|
841
|
-
| Directed SEND | Owner-defined numeric scope |
|
|
894
|
+
| WAIT | None | Scope is ignored ({§send-wait-scope}) |
|
|
895
|
+
| Directed SEND | Owner-defined numeric scope | Carried to the addressed owner; worker actors refuse it ({§send-directed-scope}) |
|
|
842
896
|
|
|
843
897
|
Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
|
|
844
898
|
whole line, two integers are an inclusive whole-line range, and four integers
|
|
@@ -854,8 +908,7 @@ its `L`, `SL`, or `EL` position denotes a line. Columns, prepend `0`, and append
|
|
|
854
908
|
`-1` remain numeric. Exact READ, EDIT, COPY/MOVE source and destination,
|
|
855
909
|
KILL, and client LOOK preserve these positions in `TextLineMarker`; core resolves them
|
|
856
910
|
against the addressed current text before operation-specific numeric scope
|
|
857
|
-
semantics run.
|
|
858
|
-
result positions remain numeric and reject anchors. Numeric text scopes remain
|
|
911
|
+
semantics run. FIND's result positions remain numeric and reject anchors. Numeric text scopes remain
|
|
859
912
|
canonical and fully supported. Parser acceptance does not imply model-facing
|
|
860
913
|
recommendation.
|
|
861
914
|
|
|
@@ -895,51 +948,44 @@ rule protects code examples in SEND, WORK, FORK, BARE and every other body.
|
|
|
895
948
|
|
|
896
949
|
## 9. Turn dispositions
|
|
897
950
|
|
|
898
|
-
|
|
899
|
-
({§
|
|
900
|
-
|
|
951
|
+
The runtime adjudicates a nonempty admitted program against actual messages,
|
|
952
|
+
results and live obligations ({§wait-obligation-matrix}). A response with no
|
|
953
|
+
operation receives empty-turn recovery, not successful completion ({§empty-turn}).
|
|
901
954
|
|
|
902
955
|
| Intent | Nominal status | Meaning |
|
|
903
956
|
|---|---|---|
|
|
904
|
-
|
|
|
905
|
-
|
|
|
906
|
-
|
|
|
907
|
-
|
|
|
908
|
-
|
|
|
957
|
+
| Unanswered messages or unobserved results | 102 | Continue silently |
|
|
958
|
+
| WAIT | 202 | Park when a live obligation exists; otherwise continue at 102 |
|
|
959
|
+
| All messages answered, live work remains | 202 | Join the held work |
|
|
960
|
+
| All messages answered, results observed, no held work | 200 | The admitted program concludes; no repeated response is required |
|
|
961
|
+
| KILL own worker | 499 | Cancel unfinished work in that worker and its descendants |
|
|
909
962
|
| Runtime or infrastructure failure | 5xx | Not a model-authored task status |
|
|
910
963
|
|
|
911
|
-
###
|
|
964
|
+
### The terminal contract (waitpid)
|
|
912
965
|
|
|
913
|
-
The model may supply one
|
|
914
|
-
|
|
915
|
-
The engine verifies an explicit intention against the loop's actual
|
|
916
|
-
obligations (spawned children, open streams, pending results); the grammar
|
|
917
|
-
polices *shape* only. Asking
|
|
966
|
+
The model may supply one WAIT per turn. The host, not the grammar, owns
|
|
967
|
+
turn boundaries and adjudicates the loop's actual obligations. Asking
|
|
918
968
|
the human is the native `question` executor tool ({§question-tool}), not a
|
|
919
969
|
disposition. The shape rules ARE structural:
|
|
920
970
|
|
|
921
|
-
- §send-mid-reservation
|
|
922
|
-
A turn admits
|
|
923
|
-
({§disposition-anywhere}); the runtime executes
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
(operator, 2026-09-12: models state the plan first; the inventory is a
|
|
927
|
-
statement about state, not a boundary). `PlurnkParser.parse` admits every
|
|
971
|
+
- §send-mid-reservation WAIT is reserved ({§turn-disposition}).
|
|
972
|
+
A turn admits any number of lifecycle declarations, anywhere among its
|
|
973
|
+
operations ({§disposition-anywhere}); the runtime executes them last, as one park.
|
|
974
|
+
- §disposition-anywhere A disposition may sit anywhere in a model turn
|
|
975
|
+
without imposing a program boundary. `PlurnkParser.parse` admits every
|
|
928
976
|
operation before and after it in authored order; the runtime defers only the
|
|
929
|
-
|
|
977
|
+
dispositions until the other admitted operations settle
|
|
930
978
|
({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
|
|
931
|
-
position.
|
|
979
|
+
position. Omission does not synthesize a disposition ({§turn-shape}).
|
|
932
980
|
- SEND is communication: an optional recipient path and an optional body.
|
|
933
|
-
- §park-202-only
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
- §inventory-only-turn A TASK-only turn is valid for every inventory intent.
|
|
939
|
-
Actionable work does not require an invented OP and does not imply parking.
|
|
981
|
+
- §park-202-only WAIT joins live work: an open stream or a live
|
|
982
|
+
child. With none, it continues. It takes no scope ({§send-wait-scope});
|
|
983
|
+
a future message is scheduled through the schedule family.
|
|
984
|
+
- §lifecycle-only-turn A WAIT-, SEND-, or NOTE-only turn is valid.
|
|
985
|
+
NOTE does not request parking or acknowledge messages.
|
|
940
986
|
Ordinary repetition, strike and execution limits still apply.
|
|
941
987
|
|
|
942
|
-
SEND with no `(path)`
|
|
988
|
+
SEND with no `(path)` answers the open messages without ending the turn. SEND with
|
|
943
989
|
`(path)` directs the message to that recipient. Neither changes loop status.
|
|
944
990
|
|
|
945
991
|
### §send-body SEND body projection
|
|
@@ -953,7 +999,7 @@ defines no synthetic scheme or READ-back convention for them.
|
|
|
953
999
|
## §parser-architecture 10. Parser architecture
|
|
954
1000
|
|
|
955
1001
|
The implementation this section describes lives in `@plurnk/plurnk-parser`
|
|
956
|
-
({§parser-
|
|
1002
|
+
({§parser-consumers}); this section remains the contract it implements.
|
|
957
1003
|
|
|
958
1004
|
ANTLR owns framing, slots and statement composition; AstBuilder produces the
|
|
959
1005
|
schema-owned AST. Registration, effects and authority remain runtime concerns.
|
|
@@ -961,11 +1007,13 @@ schema-owned AST. Registration, effects and authority remain runtime concerns.
|
|
|
961
1007
|
```mermaid
|
|
962
1008
|
stateDiagram-v2
|
|
963
1009
|
[*] --> DEFAULT
|
|
1010
|
+
DEFAULT --> QUOTATION: a fence that opens no operation
|
|
1011
|
+
QUOTATION --> DEFAULT: its matching closer
|
|
964
1012
|
DEFAULT --> SLOTS: fenced native OP or executor
|
|
965
1013
|
SLOTS --> TARGET: (
|
|
966
1014
|
TARGET --> SLOTS: )
|
|
967
|
-
SLOTS --> METADATA:
|
|
968
|
-
METADATA --> SLOTS:
|
|
1015
|
+
SLOTS --> METADATA: [
|
|
1016
|
+
METADATA --> SLOTS: ]
|
|
969
1017
|
SLOTS --> BODY: header newline or tolerated inline body
|
|
970
1018
|
SLOTS --> DEFAULT: matching compact closer
|
|
971
1019
|
BODY --> DEFAULT: matching standalone closer, no nested block
|
|
@@ -983,13 +1031,13 @@ already ends in one. Interstatement whitespace belongs to no body.
|
|
|
983
1031
|
A header starts at column zero; the first operation may follow provider preamble
|
|
984
1032
|
without a separating newline. Text outside operation blocks is ignored in every
|
|
985
1033
|
parser tier: before, between, and after operations. It produces no AST item,
|
|
986
|
-
message, receipt, or diagnostic. Exact source remains in `ops
|
|
1034
|
+
message, receipt, or diagnostic. Exact source remains in `ops://<worker>/` under
|
|
987
1035
|
{§turn-ops-log-curation}; body bytes and source positions are unchanged. A matching
|
|
988
1036
|
closer still ends its body, and no missing closer is inferred. No generic Markdown
|
|
989
1037
|
rendering, indentation stripping or recursive code-block extraction occurs.
|
|
990
1038
|
Only a header aside has aside semantics.
|
|
991
1039
|
|
|
992
|
-
##
|
|
1040
|
+
## 12. Public API
|
|
993
1041
|
|
|
994
1042
|
The package root is the single JavaScript and TypeScript entry point. Shared AST
|
|
995
1043
|
and wire types come from generated schemas; the small hand-maintained parser
|
|
@@ -1000,26 +1048,23 @@ express. Consumers never receive ANTLR parse-tree or token types.
|
|
|
1000
1048
|
operation is reported by one hard diagnostic (`no valid Plurnk operation was
|
|
1001
1049
|
found.`), which the host may admit as an empty turn rather than reject
|
|
1002
1050
|
(plurnk-core `§empty-turn`). An explicit disposition may sit anywhere in it
|
|
1003
|
-
({§disposition-anywhere}).
|
|
1004
|
-
means silent continuation: no synthesized statement, diagnostic, receipt,
|
|
1051
|
+
({§disposition-anywhere}). An omitted WAIT produces no synthesized statement, diagnostic, receipt,
|
|
1005
1052
|
warning, or strike. The authored operations and source remain unchanged.
|
|
1006
|
-
Explicit empty or malformed inventories retain their own handling.
|
|
1007
1053
|
Unfinished blocks never receive inferred closers.
|
|
1008
|
-
Bounded operation errors retain valid siblings
|
|
1009
|
-
|
|
1054
|
+
Bounded operation errors retain valid siblings, and so does a lost boundary:
|
|
1055
|
+
the statements that closed before `unparsedTail.from` are facts, and only what
|
|
1056
|
+
follows is undefined ({§unparsed-tail-boundary}).
|
|
1010
1057
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
the executable blocks themselves are the program.
|
|
1058
|
+
The host records programs per turn; no operation acts as a separator between
|
|
1059
|
+
saved programs. There is no outer Markdown program wrapper; the executable
|
|
1060
|
+
blocks themselves are the program.
|
|
1015
1061
|
|
|
1016
1062
|
§tier-entrypoints Each parser entry point owns one document tier:
|
|
1017
1063
|
|
|
1018
1064
|
| Entry point | Accepted document | Result statement type |
|
|
1019
1065
|
|--------------------------------|----------------------------------------------------------------|-----------------------|
|
|
1020
|
-
| `PlurnkParser.parse` | One operation-bearing model turn; at most one
|
|
1066
|
+
| `PlurnkParser.parse` | One operation-bearing model turn; at most one lifecycle declaration, anywhere | `PlurnkStatement` |
|
|
1021
1067
|
| `PlurnkParser.parseStatements` | Zero or more protocol statements | `PlurnkStatement` |
|
|
1022
|
-
| `PlurnkParser.parseLog` | One or more consecutive disposition-ended turns | `PlurnkStatement` |
|
|
1023
1068
|
| `PlurnkParser.parseClient` | Executable blocks, including the read-shaped LOOK command | `ClientStatement` |
|
|
1024
1069
|
|
|
1025
1070
|
Every entry point ignores outside text under {§whitespace-contract} and returns
|
|
@@ -1027,26 +1072,28 @@ ordered `statement` and `error` items. When present, {§unparsed-tail-boundary}
|
|
|
1027
1072
|
extent. The statement `op` field discriminates the generated per-operation
|
|
1028
1073
|
union.
|
|
1029
1074
|
|
|
1030
|
-
§root-value-api The package-root runtime namespace is closed
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
|
1036
|
-
|
|
1037
|
-
| `
|
|
1038
|
-
| `
|
|
1039
|
-
| `
|
|
1040
|
-
| `
|
|
1041
|
-
| `
|
|
1042
|
-
| `
|
|
1043
|
-
| `
|
|
1044
|
-
| `
|
|
1045
|
-
| `
|
|
1046
|
-
| `
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1075
|
+
§root-value-api The package-root runtime namespace is closed. Its exact membership has one home,
|
|
1076
|
+
`src/index.test.ts`, which fails on any addition or removal; the table names the families and
|
|
1077
|
+
their owners. All other root exports are TypeScript types. `PlurnkParser` and `parsePath` are not
|
|
1078
|
+
among them: the parser is `@plurnk/plurnk-parser`'s ({§parser-consumers}).
|
|
1079
|
+
|
|
1080
|
+
| Root value(s) | Consumer contract | Exact owner |
|
|
1081
|
+
|-----------------------------------------------------|---------------------------------------------------------------------|-------------------------------------------|
|
|
1082
|
+
| `Validator` and one `Invalid…Error` per assertion | Validation and typed failure against the owning JSON Schemas | {§wire-entrypoint} |
|
|
1083
|
+
| `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
|
|
1084
|
+
| `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
|
|
1085
|
+
| `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
|
|
1086
|
+
| `TurnDisposition` | The one turn disposition and its recognition | {§turn-disposition} |
|
|
1087
|
+
| `CapabilityAdmission` | Admission of a capability descriptor against policy layers | {§capability-admission} |
|
|
1088
|
+
| `PLURNK_OPS`, `INTERNAL_ROW_OPS`, `PLURNK_FENCE` | The closed operation alphabet and the language's fence | {§canonical-statement} |
|
|
1089
|
+
| `PROPOSAL_POLICIES` | The vocabulary a loop policy chooses from | {§loop-policy} |
|
|
1090
|
+
| `WORKER_NAME` | Authority minting predicate | {§worker-name} |
|
|
1091
|
+
| `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
|
|
1092
|
+
|
|
1093
|
+
The remaining values are small pure helpers over those contracts (`isExecution`, `writtenOp`,
|
|
1094
|
+
`lifecycleOfLoopStatus`, `selectWorkerLoop`, `renderJsonResult`, `formatJsonDocument`,
|
|
1095
|
+
`aguiConformanceReport`) and the closed name patterns and vocabularies (`RUNTIME_TAG`,
|
|
1096
|
+
`SKILL_NAME`, `REASONING_POLICIES`).
|
|
1050
1097
|
|
|
1051
1098
|
§parser-construction-boundary Parser construction components are internal rather
|
|
1052
1099
|
than alternate consumer entry points:
|
|
@@ -1056,20 +1103,10 @@ than alternate consumer entry points:
|
|
|
1056
1103
|
| `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser` and `parsePath` own its API |
|
|
1057
1104
|
| `PlurnkErrorStrategy`, `RecordingListener` | Assemble parser recovery and diagnostics around `antlr4ng`; consumers receive `PlurnkParseError` values |
|
|
1058
1105
|
|
|
1059
|
-
### CLI
|
|
1060
|
-
|
|
1061
|
-
```text
|
|
1062
|
-
plurnk-contracts [file] parse a file, or standard input when omitted
|
|
1063
|
-
plurnk-contracts --help show usage
|
|
1064
|
-
```
|
|
1065
|
-
|
|
1066
|
-
The CLI prints the parse result as JSON. It exits `0` when no error item or
|
|
1067
|
-
`unparsedTail` exists and `1` otherwise.
|
|
1068
|
-
|
|
1069
1106
|
## 13. Runtime-neutral wire contracts
|
|
1070
1107
|
|
|
1071
1108
|
§wire-entrypoint The package root exports generated wire types, `Problems`, and
|
|
1072
|
-
`Validator` alongside the
|
|
1109
|
+
`Validator` alongside the AST types. Their owning JSON Schemas are published
|
|
1073
1110
|
through `@plurnk/plurnk-contracts/schema/*.json`, not re-exported as root values.
|
|
1074
1111
|
|
|
1075
1112
|
### §text-region 13.1 Text regions
|
|
@@ -1187,6 +1224,19 @@ Internal invariant violations throw and preserve their cause. An external
|
|
|
1187
1224
|
protocol may require its own error envelope; its adapter maps that envelope to
|
|
1188
1225
|
or from the canonical Problem without creating another PLURNK failure contract.
|
|
1189
1226
|
|
|
1227
|
+
§problem-error-carrier `Problems.fromError(error)` recognizes existing failure
|
|
1228
|
+
carriers without depending on a producer's exception class:
|
|
1229
|
+
|
|
1230
|
+
| Carrier | Interpretation |
|
|
1231
|
+
|---------|----------------|
|
|
1232
|
+
| `error.result` present | Validate the complete {§operation-result}; return its Problem, if any |
|
|
1233
|
+
| Otherwise, `error.problem` present | Validate and return {§problem-details} unchanged |
|
|
1234
|
+
| Absent or malformed carrier | Return `null`; the caller retains the original exception as an unexpected failure |
|
|
1235
|
+
|
|
1236
|
+
Recognition never derives recovery text from an exception message, changes its
|
|
1237
|
+
status, or rescues a malformed result through a second Problem field. Unexpected
|
|
1238
|
+
accessor or validator exceptions propagate.
|
|
1239
|
+
|
|
1190
1240
|
§problem-projection `ProblemProjection` is the sole compact model-packet view of
|
|
1191
1241
|
an exact `ProblemDetails`. `Problems.project(problem, context)` validates both
|
|
1192
1242
|
representations and rejects a status that contradicts the enclosing row.
|
|
@@ -1248,22 +1298,23 @@ client ID plus symbolic secret, or neither for server-advertised Dynamic Client
|
|
|
1248
1298
|
Registration fallback. A definition cannot combine those identity modes.
|
|
1249
1299
|
|
|
1250
1300
|
§mcp-configuration-overlay `McpConfigurationOverlay` is the bounded raw
|
|
1251
|
-
configuration projection a client may carry to MCP list and enable actions
|
|
1252
|
-
|
|
1253
|
-
|
|
1301
|
+
configuration projection a client may carry to MCP list and enable actions: its
|
|
1302
|
+
string-valued `PLURNK_MCP_*` variables, whole. Which of those names are the
|
|
1303
|
+
host's own controls is the host's fact alone — its parser skips every control
|
|
1304
|
+
it owns, so a carried timeout or enabled list has no effect and no client or
|
|
1305
|
+
contract restates that vocabulary.
|
|
1254
1306
|
The client does not interpret this map. The MCP host composes it over the
|
|
1255
1307
|
lower normalized definition through the same parser that admits service
|
|
1256
1308
|
environment declarations, then validates the resulting
|
|
1257
1309
|
`McpServerDefinition`. Carrying the overlay does not connect, persist, or
|
|
1258
1310
|
expand credentials by itself.
|
|
1259
1311
|
|
|
1260
|
-
`SkillDefinition` is the one definition the
|
|
1261
|
-
family accepts and persists: the standard
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
truth about installation.
|
|
1312
|
+
`SkillDefinition` is the one definition the workspace `skills` Functionality
|
|
1313
|
+
family accepts and persists: the standard `name`, source `scope`, and optional
|
|
1314
|
+
installer `source`. `Validator.assertSkillDefinition` validates the wire shape;
|
|
1315
|
+
the schema's name grammar is exposed as `SKILL_NAME` for loaders and discovery
|
|
1316
|
+
({§agent-skills-name}). Core owns installation truth and lifecycle
|
|
1317
|
+
({§skills-functionality}).
|
|
1267
1318
|
|
|
1268
1319
|
`A2aAgentDefinition` is the one definition the Worker `a2a` Functionality
|
|
1269
1320
|
family accepts and persists: the local alias `name` (the `a2a://<name>`
|
|
@@ -1287,11 +1338,22 @@ interaction, and event owners through this port.
|
|
|
1287
1338
|
from user-authored prompt content. An adapter may expose no public means to set
|
|
1288
1339
|
it; Core validates and records it through the same prompt admission path.
|
|
1289
1340
|
|
|
1341
|
+
`runLoop.attachments` carries typed bytes selected by the adapter;
|
|
1342
|
+
`runLoop.envelope` retains opaque protocol evidence under
|
|
1343
|
+
{§message-envelope-evidence}. `readMessages` projects durable inbox messages
|
|
1344
|
+
and successful conversation replies independently of log visibility, retaining
|
|
1345
|
+
each reply's `answers` addresses (empty for incoming messages).
|
|
1346
|
+
`resolveClientInteraction` may carry the accepted answer's message evidence;
|
|
1347
|
+
Core validates the resolution, retains the arrival, then resumes the operation.
|
|
1348
|
+
|
|
1290
1349
|
§application-worker-observation Worker observation exposes durable identity,
|
|
1291
1350
|
origin, immediate parent identity, minted `kind` (`conversation`; `fork` for a
|
|
1292
1351
|
child carrying a fork boundary; `work` for any other child), and `lifecycle`,
|
|
1293
|
-
the worker's
|
|
1294
|
-
when it has none).
|
|
1352
|
+
the worker's representative work loop projected through {§loop-lifecycle-vocabulary} (`idle`
|
|
1353
|
+
when it has none). `selectWorkerLoop` chooses running before parked before queued;
|
|
1354
|
+
ties select the oldest unresolved sequence. With no live work, the latest terminal
|
|
1355
|
+
settlement wins (sequence breaks equal timestamps). Newer terminal history never
|
|
1356
|
+
hides unfinished work. Maintenance-only loops do not change this projection;
|
|
1295
1357
|
their ordinary turns and results remain durable history. `readWorker` resolves exactly one id or name and returns
|
|
1296
1358
|
`null` when absent. `listWorkers` filters collections by origin or lineage
|
|
1297
1359
|
position; an omitted parent filter means every position and an explicit `null`
|
|
@@ -1309,9 +1371,7 @@ in `@plurnk/plurnk-contracts` is that projection's one owner.
|
|
|
1309
1371
|
§application-loop-observation Loop observation exposes the durable scheduler
|
|
1310
1372
|
state of work loops (excluding maintenance-only administrative loops), exact terminal `OperationResult`, and exact count of packet-bearing
|
|
1311
1373
|
Turns for one owned Worker. Packetless producer Turns and physical provider
|
|
1312
|
-
retries do not contribute to `packetCount`.
|
|
1313
|
-
(ISO date), optional `intervalMinutes`, and `recurrenceId` (the original task id).
|
|
1314
|
-
Packet notifications carry the same timing; ordinary tasks omit it. Exterior
|
|
1374
|
+
retries do not contribute to `packetCount`. Exterior
|
|
1315
1375
|
adapters consume this projection instead of reconstructing lifecycle from
|
|
1316
1376
|
events or persistence; events remain the live notification edge.
|
|
1317
1377
|
|
|
@@ -1330,7 +1390,6 @@ class PlurnkParseError extends Error {
|
|
|
1330
1390
|
readonly column: number;
|
|
1331
1391
|
readonly source: ErrorSource;
|
|
1332
1392
|
readonly severity: Severity;
|
|
1333
|
-
readonly code?: "invalid-turn-structure";
|
|
1334
1393
|
}
|
|
1335
1394
|
```
|
|
1336
1395
|
|
|
@@ -1368,10 +1427,8 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
|
|
|
1368
1427
|
the sole and complete owner of syntax-error messaging because it holds the
|
|
1369
1428
|
parse state, lexer mode, and expected-token set that no consumer has. It
|
|
1370
1429
|
produces the final diagnostic message, deduplicated expected-token lists, and
|
|
1371
|
-
turn-shape diagnostics ({§turn-shape}).
|
|
1372
|
-
neither does the position of a present one ({§disposition-anywhere}).
|
|
1373
|
-
document boundary carries `code: "invalid-turn-structure"`, which cannot be
|
|
1374
|
-
recovered as an individual failed operation. Source with no
|
|
1430
|
+
turn-shape diagnostics ({§turn-shape}). An omitted lifecycle declaration produces no diagnostic, and
|
|
1431
|
+
neither does the position of a present one ({§disposition-anywhere}). Source with no
|
|
1375
1432
|
parsed operation yields `no valid Plurnk operation was found.` Targeted
|
|
1376
1433
|
diagnostics are:
|
|
1377
1434
|
|
|
@@ -1381,9 +1438,7 @@ diagnostics are:
|
|
|
1381
1438
|
its flags (`/(?i)shutdown|reactor/` is `/shutdown|reactor/i`; `^(?i)note:` is the
|
|
1382
1439
|
anchored regex with `i`), with one warning-severity advisory naming the flag
|
|
1383
1440
|
position after the closing `/`. Only a leading group of `i`, `m` and `s` lifts;
|
|
1384
|
-
`(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched.
|
|
1385
|
-
the 2026-09-13 dumbox demo pass, where the model wrote the inline form, was
|
|
1386
|
-
refused, and rewrote it as a trailing flag one turn later.
|
|
1441
|
+
`(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched.
|
|
1387
1442
|
- §regex-trailing-text A valid `/pattern/flags` prefix followed by horizontal
|
|
1388
1443
|
whitespace and trailing text receives one concise trailing-content
|
|
1389
1444
|
diagnostic, with or without flags, without assuming what the extra text was
|
|
@@ -1394,7 +1449,9 @@ diagnostics are:
|
|
|
1394
1449
|
matcher, in whichever dialect its first characters claim
|
|
1395
1450
|
({§matcher-prefix-claims}): `/re/i`, `^anchored`, `//xpath`, `$.json`, `~words`,
|
|
1396
1451
|
`&symbol`, or a sigil-less glob or literal such as `TODO` or `*.ts`. Those
|
|
1397
|
-
|
|
1452
|
+
matchers remain independent of owner options: a block without `pattern` never
|
|
1453
|
+
erases the heading matcher, and invalid blocks still reach the owning validator.
|
|
1454
|
+
These operations take no body, so heading-line text can mean nothing else. On EDIT only
|
|
1398
1455
|
a sigil lifts, because plain heading-line text is the replacement body it always
|
|
1399
1456
|
was; the lines beneath the heading are then the replacement, and none deletes each
|
|
1400
1457
|
match ({§edit-pattern}). A trailing `<!-- aside -->` on the same line stays the
|
|
@@ -1404,8 +1461,7 @@ diagnostics are:
|
|
|
1404
1461
|
`FIND (src/parser.ts) /\bparse\w+\b/` is the same operation as
|
|
1405
1462
|
`FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
|
|
1406
1463
|
dialect without slashes or flags: the whole text is the pattern, so
|
|
1407
|
-
`READ (
|
|
1408
|
-
2026-09-12: "Recursive Reasoning").
|
|
1464
|
+
`READ (notes.md) ^Decision:.*` selects lines beginning with `Decision:`.
|
|
1409
1465
|
- §trailing-slots **Slots after the matcher peel off the right.** The heading text after
|
|
1410
1466
|
the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
|
|
1411
1467
|
shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
|
|
@@ -1413,9 +1469,8 @@ diagnostics are:
|
|
|
1413
1469
|
any order, each taken once and only when the heading did not already carry that slot,
|
|
1414
1470
|
until what remains is the matcher. `READ (a.rs) /fn resolve_/ <1,-1> <!-- entities -->`
|
|
1415
1471
|
is the same operation as `READ (a.rs) <1,-1> /fn resolve_/ <!-- entities -->`, with
|
|
1416
|
-
one warning-severity advisory naming the canonical order for the scope or block
|
|
1417
|
-
|
|
1418
|
-
2026-09-13 dumbox run refused three headings for this in one turn). A matcher that
|
|
1472
|
+
one warning-severity advisory naming the canonical order for the scope or block:
|
|
1473
|
+
the grammar swallows up anything that passes as legitimate plurnk. A matcher that
|
|
1419
1474
|
itself ends in one of those shapes takes the option escape.
|
|
1420
1475
|
- §matcher-body-redirect **A body beneath those headings.** Text below the heading
|
|
1421
1476
|
of a FIND, READ or KILL is a body, and those operations take none: the builder
|
|
@@ -1436,7 +1491,7 @@ diagnostics are:
|
|
|
1436
1491
|
- §invalid-scope-diagnostic **Malformed scope content.** After a properly spaced
|
|
1437
1492
|
scope opener, report the offending scope (at most 64 code points, ending at
|
|
1438
1493
|
`>` or the heading's line end) and its operation's constraint: FIND result
|
|
1439
|
-
positions, execution
|
|
1494
|
+
positions, execution minutes, text coordinates, or no scope. Do not append advice for
|
|
1440
1495
|
other operations or infer why the producer supplied the value. Spacing and
|
|
1441
1496
|
boundary-loss diagnostics retain their own contracts.
|
|
1442
1497
|
- §misplaced-aside-advisory **Aside in the body.** A READ or FIND whose
|
|
@@ -1450,7 +1505,10 @@ diagnostics are:
|
|
|
1450
1505
|
({§matcher-body-redirect}).
|
|
1451
1506
|
|
|
1452
1507
|
§error-shape The diagnostic class determines how much guidance the parser may
|
|
1453
|
-
provide
|
|
1508
|
+
provide. Advisories belong to one successfully built statement. A rejected
|
|
1509
|
+
statement emits its hard diagnostic, not normalization advisories; neither a
|
|
1510
|
+
rejection nor an internal exception carries advisories into another statement
|
|
1511
|
+
or parser invocation.
|
|
1454
1512
|
|
|
1455
1513
|
| Class | Surface | Message contract |
|
|
1456
1514
|
|------------------------|-----------------------|-------------------------------------------------------------------------------------------|
|
|
@@ -1472,7 +1530,7 @@ Examples of canonical hard facts:
|
|
|
1472
1530
|
- `unrecognized character '<' in target`
|
|
1473
1531
|
- `unexpected bracket modifier; the fence name selects the executor`
|
|
1474
1532
|
- `unrecognized character 'X' in statement header`
|
|
1475
|
-
- `
|
|
1533
|
+
- `WAIT's body begins below the header`
|
|
1476
1534
|
- `expected ')'; got ':'`
|
|
1477
1535
|
|
|
1478
1536
|
Each malformed statement produces at most one hard error. The first recorded
|
|
@@ -1489,7 +1547,9 @@ without a closer is not such a case: it ends under {§closer-fallback}. `ParseRe
|
|
|
1489
1547
|
before that point; recovered contexts and diagnostics at or beyond it are not
|
|
1490
1548
|
public results. The tail is one separate boundary fact, not an additional
|
|
1491
1549
|
malformed-statement diagnostic. Consumers must treat anything from that point
|
|
1492
|
-
onward as undefined and must never dispatch a recovered statement from it
|
|
1550
|
+
onward as undefined and must never dispatch a recovered statement from it; the
|
|
1551
|
+
facts before it are ordinary facts, and a consumer that runs them owes the
|
|
1552
|
+
author the tail's reason.
|
|
1493
1553
|
|
|
1494
1554
|
| Consumer duty | Contract |
|
|
1495
1555
|
|--------------------|----------------------------------------------------------------------------------------------------------------|
|
|
@@ -1508,6 +1568,6 @@ runtime constructs this; the parser provides the fields):
|
|
|
1508
1568
|
"column": 12,
|
|
1509
1569
|
"source": "parser",
|
|
1510
1570
|
"severity": "error",
|
|
1511
|
-
"message": "READ block opened at line 1 but was not closed with
|
|
1571
|
+
"message": "READ block opened at line 1 but was not closed with 4 backticks"
|
|
1512
1572
|
}
|
|
1513
1573
|
```
|