@plurnk/plurnk-contracts 1.10.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +2 -3
  2. package/SPEC.md +190 -85
  3. package/dist/conformance/agui-v1.json +5 -5
  4. package/dist/plurnk.gemma.gbnf +4 -3
  5. package/dist/plurnk.qwen.gbnf +4 -3
  6. package/dist/schema/CapabilityDescriptor.json +23 -0
  7. package/dist/schema/CapabilityPolicy.json +18 -0
  8. package/dist/schema/CapabilityProjection.json +16 -0
  9. package/dist/schema/CapabilitySelector.json +24 -0
  10. package/dist/schema/ClientStatement.json +10 -2
  11. package/dist/schema/LoopPolicy.json +13 -0
  12. package/dist/schema/MatcherBody.json +8 -8
  13. package/dist/schema/ParsedPath.json +1 -11
  14. package/dist/schema/PlurnkStatement.json +76 -34
  15. package/dist/schema/ProblemProjection.json +38 -0
  16. package/dist/schema/ProposalProjection.json +2 -2
  17. package/dist/schema/ProviderUsage.json +1 -1
  18. package/dist/schema/ResourceSelection.json +12 -2
  19. package/dist/src/ApplicationPort.d.ts +9 -40
  20. package/dist/src/ApplicationPort.d.ts.map +1 -1
  21. package/dist/src/AstBuilder.d.ts +1 -7
  22. package/dist/src/AstBuilder.d.ts.map +1 -1
  23. package/dist/src/AstBuilder.js +127 -102
  24. package/dist/src/AstBuilder.js.map +1 -1
  25. package/dist/src/CapabilityAdmission.d.ts +9 -0
  26. package/dist/src/CapabilityAdmission.d.ts.map +1 -0
  27. package/dist/src/CapabilityAdmission.js +96 -0
  28. package/dist/src/CapabilityAdmission.js.map +1 -0
  29. package/dist/src/PlurnkErrorStrategy.d.ts +1 -0
  30. package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
  31. package/dist/src/PlurnkErrorStrategy.js +140 -1
  32. package/dist/src/PlurnkErrorStrategy.js.map +1 -1
  33. package/dist/src/PlurnkParser.d.ts +4 -1
  34. package/dist/src/PlurnkParser.d.ts.map +1 -1
  35. package/dist/src/PlurnkParser.js +151 -22
  36. package/dist/src/PlurnkParser.js.map +1 -1
  37. package/dist/src/Problems.d.ts +6 -1
  38. package/dist/src/Problems.d.ts.map +1 -1
  39. package/dist/src/Problems.js +34 -0
  40. package/dist/src/Problems.js.map +1 -1
  41. package/dist/src/TagSignal.d.ts.map +1 -1
  42. package/dist/src/TagSignal.js +5 -0
  43. package/dist/src/TagSignal.js.map +1 -1
  44. package/dist/src/Validator.d.ts +18 -4
  45. package/dist/src/Validator.d.ts.map +1 -1
  46. package/dist/src/Validator.js +69 -10
  47. package/dist/src/Validator.js.map +1 -1
  48. package/dist/src/generated/plurnkLexer.d.ts +52 -40
  49. package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
  50. package/dist/src/generated/plurnkLexer.js +519 -431
  51. package/dist/src/generated/plurnkLexer.js.map +1 -1
  52. package/dist/src/generated/plurnkParser.d.ts +165 -92
  53. package/dist/src/generated/plurnkParser.d.ts.map +1 -1
  54. package/dist/src/generated/plurnkParser.js +1336 -722
  55. package/dist/src/generated/plurnkParser.js.map +1 -1
  56. package/dist/src/generated/plurnkParserVisitor.d.ts +49 -0
  57. package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
  58. package/dist/src/generated/plurnkParserVisitor.js +42 -0
  59. package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
  60. package/dist/src/index.d.ts +4 -4
  61. package/dist/src/index.d.ts.map +1 -1
  62. package/dist/src/index.js +3 -3
  63. package/dist/src/index.js.map +1 -1
  64. package/dist/src/types.d.ts +9 -2
  65. package/dist/src/types.d.ts.map +1 -1
  66. package/dist/src/types.generated.d.ts +142 -36
  67. package/dist/src/types.generated.d.ts.map +1 -1
  68. package/dist/src/types.js +4 -6
  69. package/dist/src/types.js.map +1 -1
  70. package/package.json +2 -2
  71. package/plurnk.md +68 -61
  72. package/dist/schema/LoopFlags.json +0 -16
package/README.md CHANGED
@@ -49,12 +49,11 @@ The parser entry points deliberately accept different document tiers:
49
49
 
50
50
  | Entry point | Accepted input |
51
51
  |--------------------------------|-------------------------------------------------------|
52
- | `PlurnkParser.parse` | One PLAN-anchored model turn ending in terminal SEND |
52
+ | `PlurnkParser.parse` | One operation-bearing model turn; omitted PLAN/SEND are recovered |
53
53
  | `PlurnkParser.parseStatements` | A strict sequence of protocol statements |
54
- | `PlurnkParser.parseLog` | TURN-wrapped multi-turn script or log input |
54
+ | `PlurnkParser.parseLog` | Strict consecutive PLAN-through-SEND turns |
55
55
  | `PlurnkParser.parseClient` | Protocol statements plus client-only LOOK and BUFF |
56
56
  | `parsePath` | One path or URI using parser-equivalent decomposition |
57
- | `parseResourceSelection` | One COPY/MOVE destination and optional text scope |
58
57
 
59
58
  See SPEC {§turn-shape} and {§tier-entrypoints} for the tier boundaries. All
60
59
  AST, parse-result, schema-derived, and runtime-neutral wire types are exported
package/SPEC.md CHANGED
@@ -9,7 +9,7 @@ is the single code API for those contracts.
9
9
  | Surface | Canonical export or artifact |
10
10
  | ------------------------------------------------------------------------------- | --------------------------------------------------- |
11
11
  | Parser, AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
12
- | Effective loop policy and its default | `LoopFlags`, `DEFAULT_LOOP_FLAGS` |
12
+ | Capability and loop policies with their defaults | `CapabilityPolicy`, `LoopPolicy`, `DEFAULT_CAPABILITY_POLICY`, `DEFAULT_LOOP_POLICY` |
13
13
  | Durable reasoning intent | `ReasoningPolicy`, `REASONING_POLICIES` |
14
14
  | Model route and catalog discovery | `ModelRoute`, `ModelCatalogQuery`, `ModelCatalogPage`, `ModelReadiness` |
15
15
  | Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
@@ -125,15 +125,45 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
125
125
 
126
126
  | Contract | Shape invariant | Runtime responsibility |
127
127
  | ------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
128
- | `LoopFlags` | Complete effective `mode`, `auto`, `noWeb`, `noInteraction`, and `noProposals` | Validate/expand persisted partial policy before use |
128
+ | `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, workspace, Worker, and loop layers |
130
+ | `CapabilityProjection` | Exact service, workspace, immutable Worker bound, mutable Worker, and normalized effective policies | Expose the resolver's Worker-level cascade to clients without claiming one layer is effective authority |
131
+ | `LoopPolicy` | Complete capability attenuation plus one `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
129
132
  | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
130
- | `ProposalProjection` | Identity, `{ scheme, authority, pathname }` review target, body/attrs, effective flags, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
133
+ | `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 |
131
134
  | `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
132
135
  | `ProviderCost` | Exact charged, estimated, or unknown monetary evidence | Normalize one monetary disposition for each physical provider request |
133
136
  | `ProviderRequestAccounting` | Usage and cost evidence for one physical provider request | Preserve request order across retries, failover, success, and failure |
134
137
  | `ProviderAccounting` | Ordered requests plus deterministic usage and exact-USD projections | Derive loop, protocol, telemetry, and client reporting without a second authority |
135
138
 
136
- `DEFAULT_LOOP_FLAGS` is the contracts-owned effective default value. A consumer may persist a partial object as an implementation detail, but it never exposes or acts on that partial representation as though it were the complete contract.
139
+ §capability-policy-matching A selector is an exact conjunction: every field it
140
+ declares must equal the descriptor, while every selected `trait` must occur in
141
+ the descriptor's trait set. `deny` wins within a layer. When `only` is present, at
142
+ least one selector must match. An empty policy admits everything and an empty
143
+ `only` list admits nothing.
144
+
145
+ §capability-policy-cascade Capability layers are purely subtractive and
146
+ order-independent: a descriptor is admitted only when every layer admits it.
147
+ No Worker or loop can restore service, workspace, or parent authority. A
148
+ composed operation is admitted only when every routed demand survives. These
149
+ descriptors govern routed external authority, not every grammar statement:
150
+ log/program control such as PLAN, OPEN, FOLD, log KILL, and targetless SEND
151
+ creates no capability demand. A known interactive runtime is represented by
152
+ access class `interact`; scheme and runtime manifests contribute traits rather
153
+ than hidden policy behavior.
154
+
155
+ §capability-policy-projection A `CapabilityProjection` reports every durable
156
+ Worker-level layer and their normalized intersection. The `worker` field is the
157
+ only client-mutable layer; `effective` is the authority a new unattenuated loop
158
+ would receive. A client never derives effective authority from the mutable
159
+ layer alone. Per-loop attenuation remains an immutable input to that loop and
160
+ is therefore absent from this durable Worker projection.
161
+
162
+ §loop-policy `DEFAULT_CAPABILITY_POLICY` and `DEFAULT_LOOP_POLICY` are the
163
+ contracts-owned complete defaults. A loop policy is immutable after creation;
164
+ its `capabilities` field only narrows broader authority and its `proposals`
165
+ field chooses one unambiguous downstream settlement posture. Capability
166
+ admission precedes effect classification and proposal settlement.
137
167
 
138
168
  §reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
139
169
  medium | high`. The schema owns this shared wire vocabulary. Providers own the
@@ -167,11 +197,12 @@ facts; core and client interfaces carry only this standard interaction.
167
197
  §provider-usage `ProviderUsage` records only known non-negative safe-integer
168
198
  quantities. `inputTokens` includes every input category; cache reads and cache
169
199
  writes are details within it. `outputTokens` includes reasoning; text and
170
- reasoning are details within it. `totalTokens` equals input plus output whenever
171
- all three are present. A detail is no greater than its containing total, and a
172
- complete detail partition sums to that total. An omitted field is unknown; an
173
- explicit zero is provider evidence or an exact derivation from complete known
174
- components. Consumers never estimate a token category from text length.
200
+ reasoning are details within it. Within one physical request, `totalTokens`
201
+ equals input plus output whenever all three are present, a detail is no greater
202
+ than its containing total, and a complete detail partition sums to that total.
203
+ An omitted field is unknown; an explicit zero is provider evidence or an exact
204
+ derivation from complete known components. Consumers never estimate a token
205
+ category from text length.
175
206
 
176
207
  §provider-cost `ProviderCost` represents one physical provider request's
177
208
  monetary disposition. `charged` preserves a provider-reported canonical decimal
@@ -189,11 +220,14 @@ capacity failover; a later response never replaces an earlier request.
189
220
 
190
221
  §provider-accounting `ProviderAccounting.requests` is the source evidence.
191
222
  `usage` and `costUsd` are deterministic projections of that ordered set, not
192
- independent inputs. Each usage field is present only when every contributing
193
- request has that field known; the empty set totals to explicit zero. `costUsd`
194
- is the exact decimal sum only when every request is expressible in USD and is
195
- `null` otherwise. Consumers do not recompute provider rates or convert
196
- currencies while reading the projection.
223
+ independent inputs. Each usage field sums the requests that report that exact
224
+ quantity; an unreported quantity is skipped rather than invented as zero or
225
+ allowed to erase known evidence. Detail fields are likewise independent sums,
226
+ so heterogeneous request telemetry never implies a complete aggregate
227
+ partition merely because their reported keys overlap. `costUsd` sums every
228
+ USD-expressible request and is `null` only when none is expressible. The empty
229
+ request set projects explicit zero usage and cost. Consumers do not recompute
230
+ provider rates or convert currencies while reading the projection.
197
231
 
198
232
  The parser returns ordered statement, error, and text items. It recovers at a
199
233
  trustworthy statement boundary when possible and sets `unparsedTail` when a
@@ -305,6 +339,7 @@ under {§plan-value}.
305
339
  | `delimiter` | Heading lane, joined directly to PLAN or OP |
306
340
  | `[signal]` | Optional operation-specific signal, preceded by one space |
307
341
  | `(path)` | Optional target slot, preceded by one space |
342
+ | `{metadata}` | Optional repeatable scheme-metadata modifier after a target |
308
343
  | `<scope>` | Optional numeric scope, preceded by one space |
309
344
  | `<!-- … -->` | Optional trailing operation annotation, preceded by one space |
310
345
  | line ending | Ends the single-line heading |
@@ -318,21 +353,25 @@ The following constraints are structural:
318
353
  ordinary body text.
319
354
  - PLAN is the only H1 operation and every non-PLAN operation is H2.
320
355
  - A header occupies one physical line.
321
- - Each admitted signal, target, and scope slot appears at most once.
356
+ - Each admitted signal, target, and scope slot appears at most once. Metadata
357
+ blocks may repeat only immediately after the target.
358
+ - §plan-slotless PLAN accepts no signal, target, metadata, or scope modifier;
359
+ observed modifiers are a bounded hard error naming only the rejected slots.
322
360
  - An annotation follows every present modifier and appears at most once.
323
361
  - BARE, WORK, FORK, and KILL do not admit a scope slot.
324
362
  - An ingested delimiter is `[A-Za-z0-9_]*`; canonical teaching and the GBNF use `0`.
325
363
 
326
364
  §slot-order Canonical producers and the GBNF rail emit signal, then target, then
327
- scope, then annotation, with one ASCII space before every present slot. Slot delimiters make
365
+ metadata, then scope, then annotation, with one ASCII space before every present modifier. Slot delimiters make
328
366
  their boundaries unambiguous, so the tolerant ANTLR ingester accepts zero or
329
367
  more horizontal whitespace characters before each slot and any permutation of
330
- the slots admitted by that operation, at most once each. Accepted spacing and
331
- permutation are not second canonical spellings.
368
+ the signal, target-with-metadata, and scope admitted by that operation, at most
369
+ once each. Metadata remains attached immediately after its target. Accepted
370
+ spacing and permutation are not second canonical spellings.
332
371
 
333
372
  §heading-inline-body Text that follows the last slot on a heading line — after
334
373
  horizontal whitespace, beginning with a character that cannot open a slot (not `[`,
335
- `(`, or `<`) — is the first body line: `## EXEC0 [crm] (crm_query) {"soql": "…"}` and
374
+ `(`, or `<`, nor `{` after a target) — is the first body line: `## EXEC0 [crm] (crm_query) SELECT Id FROM Case` and
336
375
  `## FIND0 (src/**) /createCoder/i` parse as their canonical two-line forms. Nothing is
337
376
  lost and the stored statement is canonical; the spelling is tolerated and announced:
338
377
  one warning-severity advisory follows the statement, naming the heading and the rule
@@ -348,14 +387,24 @@ authorization, status, or body. An empty comment normalizes to the empty string.
348
387
  Text containing a newline or lacking the closing `-->` is not an annotation;
349
388
  `<!--` elsewhere remains ordinary body text.
350
389
 
390
+ §scheme-metadata-modifier A target may be followed by zero or more
391
+ single-line `{metadata}` blocks. AstBuilder preserves each block's exact inner
392
+ text and order as the statement's `metadata: string[] | null`; nested braces
393
+ remain balanced content. The blocks are not part of the target: braces inside
394
+ `(path)` remain ordinary path and glob syntax, including `{PLAN,READ}`. The
395
+ language assigns metadata no meaning. A runtime admits it only for a scheme
396
+ that declares the capability, and that scheme exclusively owns interpretation,
397
+ validation, and authorization. An unfinished block or a newline before its
398
+ closing brace is a structural failure.
399
+
351
400
  The ingester also accepts several bounded noncanonical forms so it can explain
352
401
  or safely execute understandable input:
353
402
 
354
403
  | Tolerated input | Canonical or runtime disposition |
355
404
  |---------------------------------------------------|---------------------------------------------------------------------|
356
- | Reordered admitted slots | Producers retain signal → target → scope order |
405
+ | Reordered admitted slots | Producers retain signal → target → metadata → scope order |
357
406
  | Missing target on a generally targeted operation | AST carries `null`; the runtime rejects when the target is required |
358
- | PLAN modifiers or a non-`0` lane | Model canon keeps PLAN slotless and uses lane `0` |
407
+ | A non-`0` PLAN lane | Model canon uses lane `0` |
359
408
  | KILL annotation body | AST preserves it; model teaching leaves the KILL section empty |
360
409
  | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
361
410
  | Empty content where semantics require a body | The empty section normalizes null; the operation owner rejects it |
@@ -368,6 +417,7 @@ or safely execute understandable input:
368
417
  | `delimiter` | `[A-Za-z0-9_]*`, adjacent to PLAN or OP |
369
418
  | `[signal]` | Operation-specific tags, identifier, branch, or integer |
370
419
  | `(path)` | Local path or scheme URL target; detailed in §5 |
420
+ | `{metadata}` | Repeatable opaque scheme modifier attached after a target |
371
421
  | `<scope>` | One or more signed integers or decimals; detailed in §7 |
372
422
  | annotation | Optional trailing `<!-- … -->` descriptive text |
373
423
  | `body` | Opaque section text before the next same-lane heading or EOF |
@@ -383,14 +433,14 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
383
433
  | FIND | optional add log tags | required target or glob | optional result range | optional matcher |
384
434
  | READ | optional add log tags | required target | optional text region | empty |
385
435
  | EDIT | optional add log tags | required file or entry | required for an existing target | literal text |
386
- | COPY | optional add log tags | required source | optional source region | required destination selection |
387
- | MOVE | optional add log tags | required source | optional source region | required destination selection |
436
+ | COPY | optional add log tags | required source and destination | optional region after each path | empty |
437
+ | MOVE | optional add log tags | required source and destination | optional region after each path | empty |
388
438
  | FOLD | optional filter/change tags | optional log selection | optional log-body line scope | optional matcher |
389
439
  | OPEN | optional filter/change tags | optional log selection | optional log-body line scope | optional matcher |
390
440
  | EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
391
441
  | BARE | optional add log tags | none | none | required prompt |
392
- | WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
393
- | FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
442
+ | WORK | optional Git branch (refused unless the service enables branch delegation) | required fresh `worker://name` | none | required prompt |
443
+ | FORK | optional Git branch (refused unless the service enables branch delegation) | required context-inheriting `worker://name` | none | required prompt |
394
444
  | KILL | optional target-specific code | required target, including a log item | none | empty |
395
445
  | SEND | optional target-specific code | optional recipient | optional timeout, poll | message; terminal is nonempty |
396
446
 
@@ -400,10 +450,10 @@ Directed SEND and KILL delegate any present code to the addressed target's
400
450
  operation contract; a live process may interpret a KILL code as a Unix signal,
401
451
  but that interpretation does not define KILL generally.
402
452
 
403
- §plan-value **PLAN carries one complete Plurnk Plan.** Its entries are the
404
- model's current working-memory inventory: durable findings are `memory`, finished
405
- actions are `completed`, open inquiries are `pending`, and active priorities are
406
- `in_progress`. Admission parses the JSON body — one JSON array document in any whitespace layout, including the {§json-result-rendering} spread the log projects — strips unknown
453
+ §plan-value **PLAN carries one installment of the model's running work journal.**
454
+ Its entries record newly made working-memory items: durable findings and decisions
455
+ are `memory`, finished actions are `completed`, open work is `pending`, and active
456
+ priorities are `in_progress`. Admission parses the JSON body — one JSON array document in any whitespace layout, including the {§json-result-rendering} spread the log projects — strips unknown
407
457
  entry keys (the model-facing Plan carries no priority: struck 2026-08-24 so the
408
458
  log echoes only the canonical shape, starving stale-field habits), and validates
409
459
  the canonical bare array: every entry has string `content` and `status` in
@@ -411,10 +461,11 @@ the canonical bare array: every entry has string `content` and `status` in
411
461
  malformed-JSON, or otherwise invalid body becomes one `in_progress`
412
462
  entry whose content is the exact authored body; admission performs no partial
413
463
  repair or list inference. An empty body becomes the planless `[]`
414
- value. Each PLAN completely replaces the current Plan; it never expresses a
415
- delta. The exact `turnOps` source remains forensic program evidence, while the
416
- normalized array is the sole semantic value used by AST, persistence, durable
417
- log bodies, and model-packet materialization. PLAN is public log content—not
464
+ value. Each PLAN is the complete semantic value of that journal installment;
465
+ prior installments remain ordinary curatable log items. The exact `turnOps`
466
+ source remains forensic program evidence, while the normalized array is the sole
467
+ semantic value used by AST, persistence, durable log bodies, and model-packet
468
+ materialization. PLAN is public log content—not
418
469
  provider reasoning—and Plurnk initially mints no `_meta` values. Dispatch records
419
470
  the canonical value and has no other runtime effect.
420
471
 
@@ -432,7 +483,7 @@ commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
432
483
 
433
484
  §exec-tag-signal EXEC's bracket slot names its runtime. A bracket whose first character is
434
485
  a sign is a tag signal instead — a runtime never starts with `+` or `-` — so
435
- `## EXEC0 [+fetch] (.)` and `## EXEC0 [crm] [+schema] (crm_describe)` admit: the tags
486
+ `## EXEC0 [+fetch]` and `## EXEC0 [crm] [+schema] (crm_describe)` admit: the tags
436
487
  classify the log row exactly as on FIND or READ, the runtime is the explicit one or the
437
488
  default shell, and each slot appears at most once.
438
489
 
@@ -498,7 +549,7 @@ Mutation semantics:
498
549
  - An empty body deletes the selected text.
499
550
  - `<0>` prepends and `<-1>` appends.
500
551
  - `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
501
- - §destination-scope-boundary COPY and MOVE parse the body as a destination `ResourceSelection`: a target plus an optional destination scope. A destination scope is final body content; a scope-shaped suffix followed by residue before the section boundary is rejected rather than reinterpreted as target data. Scope-shaped text elsewhere remains target data, and a URL requiring the reserved terminal spelling percent-encodes its angle brackets. Header target, fragment, and scope independently select the source resource, channel, and region.
552
+ - §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)`, any following `{metadata}`, and an optional following `<scope>`; modifiers bind only to the immediately preceding path. The two operands independently select their resource, channel, scheme metadata, and text region.
502
553
 
503
554
  ### §operation-observation Per-operation observations
504
555
 
@@ -549,7 +600,6 @@ and path globs share the slot; content matchers belong in the body.
549
600
  | `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
550
601
  | Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
551
602
  | `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
552
- | Trailing `{key: value}` | Removed before URL parsing and preserved as ordered `headers` pairs | Addressed scheme interprets request metadata |
553
603
  | `?query` | Preserved as ordered `UrlPath.query`; `null` = absent, `""` = `?` | Participates in scheme resource identity |
554
604
 
555
605
  AstBuilder recognizes a URL with the case-insensitive prefix
@@ -591,15 +641,6 @@ never target content. Glob metacharacters remain legal path data.
591
641
 
592
642
  Matching and folder-scope semantics remain runtime concerns.
593
643
 
594
- §path-request-metadata A scheme URL may append one or more
595
- `{key: value}` request-metadata blocks. AstBuilder removes the blocks before
596
- WHATWG decomposition and preserves them as ordered pairs so order and duplicate
597
- names survive. Local paths retain braces as ordinary path text. Scheme handlers,
598
- not the language parser, define the meaning and authorization of the metadata.
599
- The admitted AST retains exact values for execution; malformed-metadata visitor
600
- diagnostics identify only the structural fault and source position, never quote
601
- metadata contents or a native URL parser's input-bearing diagnostic.
602
-
603
644
  §worker-name The exported `WORKER_NAME` contract governs names minted for URI
604
645
  authority slots: a lowercase DNS label matching
605
646
  `[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. `RESERVED_AUTHORITIES` contains the
@@ -611,26 +652,49 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
611
652
 
612
653
  ## §matcher-prefix-claims 6. Bulk pattern matching
613
654
 
614
- FIND, OPEN, FOLD, and authored READ accept an optional body matcher. The lexer
615
- preserves the body opaquely; AstBuilder assigns the dialect from its leading
616
- characters, then normalizes matcher-bearing READ to FIND under
655
+ FIND, authored READ, OPEN, FOLD, LOOK, and BUFF accept an optional body matcher.
656
+ The lexer preserves the body opaquely; AstBuilder assigns the dialect from its
657
+ leading characters, then normalizes matcher-bearing READ to FIND under
617
658
  {§read-find-normalization}.
618
659
  A leading prefix claims its dialect. Invalid claimed syntax is a positioned
619
660
  visitor error and never falls back to glob matching.
620
661
 
662
+ - §heading-boundary-recovery A column-0 heading is the trustworthy boundary. After a
663
+ statement-level error the parser discards the rest of that statement and resumes at the
664
+ next heading; the turn shape is decided locally (a terminal SEND is recognized by its own
665
+ disposition signal, never by a whole-turn alternative), so one malformed heading costs one
666
+ diagnostic and every later statement, the terminal SEND included, stands on its own. Any
667
+ other second path slot names the one-slot rule.
668
+ - §scope-slot-tolerance A line scope written inside a path slot (`## COPY0 (worker:///src.md<2,3>)`)
669
+ is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
670
+ before a slot's closing paren can only be a scope; every path slot of a statement is repaired
671
+ the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
672
+ statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
673
+ never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
674
+ - §tag-slot-tolerance A tag written as a path ahead of the real path (`## READ0 (+diff) (a.ts)`)
675
+ is read as `[+diff] (a.ts)` — the rewrite keeps the heading's length, so every later position
676
+ stays true — and the slip is one error-severity diagnostic at the first paren, placed right
677
+ after its statement, stating the `[+diff]` correction and that it was used. The statement
678
+ runs with the tag. A heading that already carries a signal is not rewritten: its second path
679
+ slot is blamed at the first paren and the statement is dropped.
680
+
621
681
  | Prefix | Dialect | Canonical body | Typed admission | Runtime owner |
622
682
  |-----------|----------|--------------------------------------|-----------------------------------|---------------------|
623
683
  | `//` | XPath | `//selector` | XPath 1.0 `xpath.parse()` | Mimetype projection |
624
684
  | `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
625
685
  | `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
626
686
  | `~` | Semantic | `~phrase` | Any text after the prefix | Embedding index |
627
- | `@` | Graph | `@symbol`, `@<symbol`, or `@>symbol` | Direction and symbol preserved | Symbol index |
628
- | none | Glob | Shell glob or literal text | Raw string | Mimetype projection |
687
+ | `&` | Graph | `&symbol`, `&<symbol`, or `&>symbol` | Exact shape validation | Symbol index |
688
+ | none | Glob | Shell glob or literal text | Single-line raw string | Mimetype projection |
629
689
 
630
690
  XPath is classified before regex because its prefix is two slashes. Regex
631
691
  splitting respects escapes and character classes; `\/` represents a literal
632
692
  slash. The AST stores regex `pattern` and `flags`, not a compiled object.
633
- Semantic and graph matchers require no parse step. Scope carries semantic
693
+ Semantic matchers require no parse step. Graph admission validates its direction
694
+ and non-whitespace symbol before runtime. Every other leading character remains
695
+ in the fallback glob/literal dialect; `@(...)` is therefore an extglob group and
696
+ bare `@text` remains literal matcher text. Rendered READ coordinates are
697
+ structural output rows, not a reserved matcher prefix. Scope carries semantic
634
698
  threshold and result-range information rather than changing the matcher body.
635
699
 
636
700
  AstBuilder validation is compile-only and never evaluates a document. Matcher
@@ -638,11 +702,10 @@ evaluation belongs to the runtime's selected mimetype, embedding, or symbol
638
702
  implementation. A matcher admission error is local to its statement; later
639
703
  statements remain recoverable when their boundaries are trustworthy.
640
704
 
641
- - §pattern-body-single-line The GBNF rail permits only single-line matcher
642
- bodies. A regex that matches a newline uses the two-character `\n` escape.
643
- ANTLR preserves the complete section body; the same-lane heading boundary
644
- keeps every following statement independently parseable without a matcher-
645
- specific implicit close rule.
705
+ - §pattern-body-single-line Every matcher body is one physical line. AstBuilder
706
+ rejects multiline bodies before dialect classification, while GBNF excludes
707
+ line terminators. A regex that matches a newline uses the two-character `\n`
708
+ escape. Non-matcher operation bodies remain multiline.
646
709
  - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
647
710
  character. Empty matchers and later colons remain valid; a regex such as
648
711
  `/^:needle/` expresses a pattern beginning with a literal colon.
@@ -665,7 +728,7 @@ The operation column names the canonical AST operation after
665
728
  | COPY/MOVE source | 0/1/2/4 text coordinates | Region copied or moved from the selected source |
666
729
  | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
667
730
  | OPEN / FOLD | 0/1/2 body-relative line coordinates | Whole log body when absent; one physical line or inclusive range when present |
668
- | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
731
+ | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
669
732
  | Terminal SEND `[202]` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence |
670
733
 
671
734
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
@@ -775,7 +838,7 @@ disposition. The shape rules ARE structural:
775
838
  non-disposition code, targeted or pathless, empty body allowed.
776
839
  - §terminal-body-nonempty The GBNF rail requires a non-empty terminal SEND body — a constrained
777
840
  turn cannot end empty-handed. ANTLR remains tolerant during ingestion.
778
- - §park-202-only The **park** rides `[202]` only: `<T>` (wait up to T seconds),
841
+ - §park-202-only The **park** rides `[202]` only: `<T>` (wait up to T minutes),
779
842
  `<T,P>` (adds a poll cadence, mirroring EXEC's slot), `<-1>`
780
843
  (indefinite; the join's own liveness bounds it). See §7 for the
781
844
  GBNF-strict / ANTLR-tolerant split.
@@ -817,6 +880,9 @@ stateDiagram-v2
817
880
  SLOTS --> TARGET: target opener
818
881
  TARGET --> TARGET: balanced literals / target escapes
819
882
  TARGET --> SLOTS: target close at depth zero
883
+ SLOTS --> METADATA: metadata opener after target
884
+ METADATA --> METADATA: balanced inner braces
885
+ METADATA --> SLOTS: metadata close at depth zero
820
886
  SLOTS --> SLOTS: scope token
821
887
  SLOTS --> SLOTS: trailing annotation
822
888
  SLOTS --> BODY: heading line end
@@ -826,8 +892,9 @@ stateDiagram-v2
826
892
 
827
893
  The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
828
894
  PLAN or H2 minted operation carrying that exact lane. SLOTS admits
829
- operation-appropriate signal, target, and scope openers in any order, followed
830
- by an optional annotation; the parser grammar enforces at-most-once multiplicity.
895
+ operation-appropriate signal, target-with-metadata, and scope openers in any
896
+ order, followed by an optional annotation; the parser grammar enforces
897
+ at-most-once slot multiplicity and keeps repeatable metadata attached to its target.
831
898
  Signal submodes select tags, integer,
832
899
  or identifier tokens by operation family. TARGET preserves balanced inner
833
900
  parentheses and recognized target escapes. BODY emits opaque text until a
@@ -840,7 +907,7 @@ provider reasoning cannot become the turn anchor.
840
907
 
841
908
  RecordingListener captures lexer and parser failures; AstBuilder adds visitor
842
909
  failures. PlurnkErrorStrategy recovers at structural heading boundaries where
843
- possible. EOF is a valid body boundary. An unfinished signal or target produces
910
+ possible. EOF is a valid body boundary. An unfinished signal, target, or metadata block produces
844
911
  `unparsedTail`; no later input is trustworthy.
845
912
 
846
913
  ## §whitespace-contract 11. Whitespace and interstatement text
@@ -849,9 +916,10 @@ possible. EOF is a valid body boundary. An unfinished signal or target produces
849
916
  |-----------------------------|---------------------------------------|-----------------------------------------------------------|
850
917
  | Heading marker | `# PLAN0` or `## OP0` at column zero | The initial PLAN may directly follow leading TEXT; subsequent headings retain exact depth and column |
851
918
  | Between OP and delimiter | Adjacent | Must remain adjacent |
852
- | Before each header slot | One ASCII space | Zero or more horizontal whitespace characters |
919
+ | Before each modifier | One ASCII space | Zero or more horizontal whitespace characters |
853
920
  | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
854
921
  | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline is invalid |
922
+ | Inside scheme metadata | Scheme-defined single-line content | Balanced braces tolerated; newline is invalid |
855
923
  | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
856
924
  | Before annotation | One ASCII space | Zero or more horizontal whitespace characters |
857
925
  | Inside annotation | One-line prose padded by one space | Any single-line text through the first closing `-->` |
@@ -878,25 +946,33 @@ and wire types come from generated schemas; the small hand-maintained parser
878
946
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
879
947
  express. Consumers never receive ANTLR parse-tree or token types.
880
948
 
881
- §turn-shape `PlurnkParser.parse` accepts exactly one model turn. H1 PLAN is the
882
- first operation, a disposition-coded H2 SEND is the terminal operation, and PLAN
883
- cannot recur mid-turn. Tolerated TEXT may appear only before PLAN; after PLAN,
884
- nonstructural text is section body content. Missing either anchor or placing a
885
- same-lane operation after the terminal SEND is an error.
949
+ §turn-shape `PlurnkParser.parse` accepts exactly one model turn containing at
950
+ least one parsed source operation. Canonical generation begins with H1 PLAN,
951
+ ends with a disposition-coded H2 SEND, and never repeats PLAN mid-turn. When no
952
+ valid leading PLAN was parsed, the parser prepends an empty PLAN; when no valid
953
+ terminal SEND was parsed, it appends a bodyless `SEND [102]`. Either default
954
+ carries {§parser-position} `UNKNOWN_POSITION` and one exact hard diagnostic
955
+ stating the observed boundary failure and applied default. The source text
956
+ remains unchanged.
957
+ An authored terminal SEND still ends the source turn: a same-lane operation or
958
+ other hard error after it is outside the trustworthy boundary. Tolerated TEXT
959
+ may appear only before the first operation; after that point, nonstructural text
960
+ is section body content. `parseLog` remains strict canonical PLAN-through-SEND
961
+ input, and GBNF remains strict canonical generation.
886
962
 
887
963
  §document-fence `PlurnkParser.parse` additionally admits one outer Markdown code
888
964
  fence whose opening line is exactly ```` ```plurnk ```` and whose closing line,
889
965
  when present, is ```` ``` ````. The fence encloses the complete
890
- PLAN-through-SEND turn and projects neither text nor body content into the AST.
891
- Its opener commits the document to either that closer or EOF immediately after
892
- the complete turn. This is document framing, not another statement grammar, and
966
+ model turn and projects neither text nor body content into the AST. Its opener
967
+ commits the document to either that closer or EOF immediately after the turn.
968
+ This is document framing, not another statement grammar, and
893
969
  no other parser tier admits it. GBNF continues to shape the paired form.
894
970
 
895
971
  §tier-entrypoints Each parser entry point owns one document tier:
896
972
 
897
973
  | Entry point | Accepted document | Result statement type |
898
974
  |--------------------------------|----------------------------------------------------------------|-----------------------|
899
- | `PlurnkParser.parse` | One PLAN turn: bare with optional TEXT, or outer `plurnk` fence ending at its closer or EOF | `PlurnkStatement` |
975
+ | `PlurnkParser.parse` | One operation-bearing model turn, bare with optional TEXT or inside one outer `plurnk` fence; omitted PLAN/SEND recover to defaults | `PlurnkStatement` |
900
976
  | `PlurnkParser.parseStatements` | Zero or more protocol statements and hidden whitespace | `PlurnkStatement` |
901
977
  | `PlurnkParser.parseLog` | One or more consecutive same-lane PLAN-anchored turns | `PlurnkStatement` |
902
978
  | `PlurnkParser.parseClient` | H2 protocol statements plus read-shaped LOOK/BUFF commands | `ClientStatement` |
@@ -913,15 +989,16 @@ following supported consumer values. All other root exports are TypeScript types
913
989
  |---------------------------------------|---------------------------------------------------------------------|---------------------------------------------|
914
990
  | `PlurnkParser` | Four document-tier entry points listed above | {§parser-architecture}, {§tier-entrypoints} |
915
991
  | `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
916
- | `parsePath`, `parseResourceSelection` | Parser-equivalent target and COPY/MOVE destination admission | {§path-syntax}, {§tier-entrypoints} |
992
+ | `parsePath` | Parser-equivalent target admission | {§path-syntax}, {§tier-entrypoints} |
917
993
  | `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
918
994
  | `Validator` | Validation and assertion against the owning JSON Schemas | {§wire-entrypoint} |
919
995
  | `InvalidNoticeError` | Typed failure from `Validator.assertNotice` | {§notice} |
920
996
  | `InvalidProblemDetailsError` | Typed failure from `Validator.assertProblemDetails` | {§problem-details} |
997
+ | `InvalidProblemProjectionError` | Typed failure from `Validator.assertProblemProjection` | {§problem-projection} |
921
998
  | `InvalidOperationResultError` | Typed failure from `Validator.assertOperationResult` | {§operation-result} |
922
999
  | `InvalidTextRegionError` | Typed failure from `Validator.assertTextRegion` | {§text-region} |
923
1000
  | `InvalidRangeExtentError` | Typed failure from `Validator.assertRangeExtent` | {§range-extent} |
924
- | `Problems` | RFC 9457 Problem construction | {§problem-details} |
1001
+ | `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
925
1002
  | `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
926
1003
  | `WORKER_NAME`, `RESERVED_AUTHORITIES` | Authority minting predicate and internal reserved names | {§worker-name} |
927
1004
  | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
@@ -931,7 +1008,7 @@ than alternate consumer entry points:
931
1008
 
932
1009
  | Internal component | Boundary |
933
1010
  |--------------------------------------------|---------------------------------------------------------------------------------------------------------------|
934
- | `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser`, `parsePath`, and `parseResourceSelection` own its API |
1011
+ | `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser` and `parsePath` own its API |
935
1012
  | `PlurnkErrorStrategy`, `RecordingListener` | Assemble parser recovery and diagnostics around `antlr4ng`; consumers receive `PlurnkParseError` values |
936
1013
  | `Jsonplurnk` test helper | Independently checks the Core-owned {§jsonplurnk} renderer corpus; it is neither shipped code nor a root API |
937
1014
 
@@ -1063,6 +1140,20 @@ Internal invariant violations throw and preserve their cause. An external
1063
1140
  protocol may require its own error envelope; its adapter maps that envelope to
1064
1141
  or from the canonical Problem without creating another PLURNK failure contract.
1065
1142
 
1143
+ §problem-projection `ProblemProjection` is the sole compact model-packet view of
1144
+ an exact `ProblemDetails`. `Problems.project(problem, context)` validates both
1145
+ representations and rejects a status that contradicts the enclosing row.
1146
+
1147
+ | Projection member | Contract |
1148
+ |-------------------|----------|
1149
+ | `type`, `detail` | Always retained; together they identify the stable class and occurrence-specific cause |
1150
+ | `stage`, `recovery`, `retryable`, extensions | Retained only when present and not exactly duplicated by the enclosing row |
1151
+ | `title`, `status`, `instance` | Forbidden; the exact Problem owns stable title and occurrence identity while the enclosing packet row already owns model-facing status and address |
1152
+
1153
+ Projection never mutates or replaces the exact Problem. Durable storage,
1154
+ external protocols, clients, and forensic artifacts continue to receive the
1155
+ complete RFC 9457 object.
1156
+
1066
1157
  ### 13.6 Notices
1067
1158
 
1068
1159
  §notice A `Notice` is a transient, nonterminal observation. It cannot determine durable
@@ -1214,9 +1305,12 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
1214
1305
  `severity` distinguishes a hard error from a non-fatal advisory. The parser is
1215
1306
  the sole and complete owner of syntax-error messaging because it holds the
1216
1307
  parse state, lexer mode, and expected-token set that no consumer has. It
1217
- produces the final diagnostic message, deduplicated expected-token lists,
1218
- turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1219
- `## SEND0 [code]`), and these targeted diagnostics:
1308
+ produces the final diagnostic message, deduplicated expected-token lists, and
1309
+ turn-shape diagnostics. No valid leading PLAN yields ``No valid leading PLAN
1310
+ was parsed; an empty `# PLAN0` was used.``; no valid terminal SEND yields ``No
1311
+ valid terminal SEND was parsed; `## SEND0 [102]` was used.``; source with no
1312
+ parsed operation yields `no valid Plurnk operation was found.` Targeted
1313
+ diagnostics are:
1220
1314
 
1221
1315
  - §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
1222
1316
  `[signal]` slot (executor-ident mode) hits a leading `-` or digit —
@@ -1225,17 +1319,21 @@ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1225
1319
  `## EXEC0 <-1,300>`” instead of a raw `unrecognized character`. The redirect is
1226
1320
  EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
1227
1321
  - §matcher-body-redirect **Matcher body in the slot region.** When the
1228
- post-target header region begins with `$`, `~`, or `@` with no whitespace
1322
+ post-target modifier region begins with `$`, `~`, or `@` with no whitespace
1229
1323
  before it, the lexer redirects the unambiguous matcher to body content below the
1230
1324
  OP heading instead of returning the generic slot list (after whitespace it is
1231
1325
  already the inline body, {§heading-inline-body}). Slash-led regex and XPath are
1232
1326
  excluded because `/` can be target data.
1327
+ - §bare-target-redirect **A `(target)` on BARE.** BARE takes no `(path)`; a model that
1328
+ writes its prompt, or the prompt's address, into a parenthesized slot (`## BARE0
1329
+ (What day is it?)`, `## BARE0 (prompt:///1/1)`) is told that the prompt is the body
1330
+ line beneath the heading, with the heading's own opener, instead of the generic
1331
+ slot list. Two operator sessions on 2026-08-26 produced exactly these shapes.
1233
1332
  - §combined-anchor-line-redirect **Combined anchor and line number in a scope.**
1234
1333
  A text-coordinate scope containing `@hash:L` or `@hash L` is one bounded hard
1235
1334
  error: `a scope position accepts one line coordinate; use the \`@hash\` anchor
1236
1335
  without its displayed line number`. A malformed header scope is consumed as
1237
- one token, while a COPY/MOVE destination selection fails at its visitor
1238
- boundary; neither produces a punctuation cascade.
1336
+ one token at either COPY/MOVE operand; neither produces a punctuation cascade.
1239
1337
  - §misplaced-target-advisory **Mutation target in the signal slot.** When a
1240
1338
  mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
1241
1339
  `[signal]` element (a `/` or a dotted extension), the message redirects the
@@ -1243,15 +1341,22 @@ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1243
1341
  \`[…]\` tag slot; a target goes in \`(…)\`. Try \`## EDIT0 (path)\``). It is
1244
1342
  gated on a path-shaped signal so a genuine additive-tag signal is not mis-steered
1245
1343
  toward a path it lacks.
1344
+ - §matcher-in-signal-slot **Matcher in the signal slot.** A tag term whose name
1345
+ begins with a matcher prefix (`/`, `$`, `~`, `&`) is never a tag: `## FIND0
1346
+ [/require/] (**.go)` used to apply a folksonomic tag named `/require/` and
1347
+ return unsearched rows. The canonicalizer refuses the term, so the builder
1348
+ raises one heading-positioned error naming the body rule (`` `[/require/]` is not
1349
+ a tag - a matcher belongs in the body beneath the heading; `[+tag]` adds,
1350
+ `[tag]` filters ``); the statement drops and its siblings run. Matcher
1351
+ characters inside a name (`slash/inside`) stay legal.
1246
1352
  - §misplaced-annotation-advisory **Annotation in the body.** A READ or FIND whose
1247
1353
  body is solely an HTML comment (`<!-- … -->`) can never carry a matcher: it is
1248
1354
  the annotation the model put on the line below the heading. The builder takes
1249
1355
  the comment as the annotation when the heading has none, builds the operation
1250
- with no body, and raises one warning-severity advisory naming what happened
1251
- and the correct form (`## READ0 (…) <…> <!-- -->`); the parser places the
1252
- advisory right after its statement and the service delivers it as a
1253
- `parse_advisory` notice with its position. A body with any other content is a
1254
- matcher, as before.
1356
+ with no body, and raises one warning-severity advisory stating that observed
1357
+ normalization; the parser places the advisory right after its statement and
1358
+ the service delivers it as a `parse_advisory` notice with its position. A body
1359
+ with any other content is a matcher, as before.
1255
1360
 
1256
1361
  §error-shape The diagnostic class determines how much guidance the parser may
1257
1362
  provide: