@plurnk/plurnk-contracts 1.5.0 → 1.6.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 (57) hide show
  1. package/README.md +9 -5
  2. package/SPEC.md +327 -227
  3. package/dist/plurnk.gemma.gbnf +160 -0
  4. package/dist/plurnk.qwen.gbnf +149 -0
  5. package/dist/schema/ClientStatement.json +7 -1
  6. package/dist/schema/EntryReadResult.json +1 -9
  7. package/dist/schema/PlurnkStatement.json +56 -11
  8. package/dist/schema/ProposalProjection.json +1 -1
  9. package/dist/schema/ProviderAccounting.json +30 -0
  10. package/dist/schema/ProviderCost.json +6 -15
  11. package/dist/schema/ProviderRequestAccounting.json +17 -0
  12. package/dist/schema/ProviderUsage.json +40 -0
  13. package/dist/schema/ResourceSelection.json +1 -1
  14. package/dist/schema/TextLineMarker.json +21 -0
  15. package/dist/src/AstBuilder.d.ts.map +1 -1
  16. package/dist/src/AstBuilder.js +108 -20
  17. package/dist/src/AstBuilder.js.map +1 -1
  18. package/dist/src/PlurnkErrorStrategy.d.ts +1 -0
  19. package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
  20. package/dist/src/PlurnkErrorStrategy.js +42 -19
  21. package/dist/src/PlurnkErrorStrategy.js.map +1 -1
  22. package/dist/src/PlurnkParser.d.ts.map +1 -1
  23. package/dist/src/PlurnkParser.js +22 -145
  24. package/dist/src/PlurnkParser.js.map +1 -1
  25. package/dist/src/TagSignal.d.ts +15 -0
  26. package/dist/src/TagSignal.d.ts.map +1 -0
  27. package/dist/src/TagSignal.js +48 -0
  28. package/dist/src/TagSignal.js.map +1 -0
  29. package/dist/src/Validator.d.ts +1 -0
  30. package/dist/src/Validator.d.ts.map +1 -1
  31. package/dist/src/Validator.js +7 -0
  32. package/dist/src/Validator.js.map +1 -1
  33. package/dist/src/generated/plurnkLexer.d.ts +99 -64
  34. package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
  35. package/dist/src/generated/plurnkLexer.js +879 -526
  36. package/dist/src/generated/plurnkLexer.js.map +1 -1
  37. package/dist/src/generated/plurnkParser.d.ts +99 -109
  38. package/dist/src/generated/plurnkParser.d.ts.map +1 -1
  39. package/dist/src/generated/plurnkParser.js +943 -1070
  40. package/dist/src/generated/plurnkParser.js.map +1 -1
  41. package/dist/src/generated/plurnkParserVisitor.d.ts +19 -5
  42. package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
  43. package/dist/src/generated/plurnkParserVisitor.js +16 -4
  44. package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
  45. package/dist/src/index.d.ts +2 -0
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +1 -0
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/types.d.ts +1 -1
  50. package/dist/src/types.d.ts.map +1 -1
  51. package/dist/src/types.generated.d.ts +79 -22
  52. package/dist/src/types.generated.d.ts.map +1 -1
  53. package/dist/src/types.js +1 -1
  54. package/dist/src/types.js.map +1 -1
  55. package/package.json +3 -2
  56. package/plurnk.md +140 -96
  57. package/dist/plurnk.gbnf +0 -394
package/SPEC.md CHANGED
@@ -14,7 +14,7 @@ is the single code API for those contracts.
14
14
  | Client capability presentation | `ClientDisplayCapabilities` |
15
15
  | JSON Schemas | `@plurnk/plurnk-contracts/schema/*.json` |
16
16
  | Generated JSON result rendering | `renderJsonResult` |
17
- | Local-model rail | `@plurnk/plurnk-contracts/plurnk.gbnf` |
17
+ | Local-model rails | `@plurnk/plurnk-contracts/plurnk.{gemma,qwen}.gbnf` |
18
18
  | Model language reference | `plurnk.md` in the package |
19
19
 
20
20
  §contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
@@ -38,7 +38,7 @@ semantics valid.
38
38
  ```mermaid
39
39
  flowchart LR
40
40
  canon["Canonical model teaching<br/>plurnk.md"]
41
- rail["Optional raw generation rail<br/>plurnk.gbnf"]
41
+ rail["Optional raw generation rail<br/>Gemma or Qwen template profile"]
42
42
  free["Other admitted input"]
43
43
  syntax["ANTLR lexer + parser<br/>syntax and document tier"]
44
44
  ast["AstBuilder<br/>typed, serializable AST"]
@@ -55,8 +55,8 @@ flowchart LR
55
55
  |--------------------------|-------------------------------------|---------------------------------------------------------------------------------|
56
56
  | Stable current law | `SPEC.md` | Owns invariants and boundaries; forge issues retain history |
57
57
  | Canonical model teaching | `plurnk.md` | Teaches the lean spelling and operational model the model should emit |
58
- | Constrained generation | generated `plurnk.gbnf` | Increases likely ANTLR compliance without reproducing all parser/runtime checks |
59
- | Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, statement shape, slot shape, and body fencing |
58
+ | Constrained generation | generated `plurnk.*.gbnf` | Increases likely ANTLR compliance without reproducing all parser/runtime checks |
59
+ | Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, heading lanes, slot shape, and section boundaries |
60
60
  | Typed admission | `AstBuilder` | Produces JSON-serializable unions and validates deterministic body/path syntax |
61
61
  | Shared wire data | `schema/*.json` | Defines runtime-neutral data shapes projected into generated TypeScript |
62
62
  | Stateful behavior | consuming runtime | Resolves addresses, permissions, selection arithmetic, effects, and lifecycle |
@@ -80,20 +80,43 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
80
80
  | `LoopFlags` | Complete effective `mode`, `auto`, `noWeb`, `noInteraction`, and `noProposals` | Validate/expand persisted partial policy before use |
81
81
  | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
82
82
  | `ProposalProjection` | Identity, review target/body/attrs, effective flags, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
83
- | `ProviderCost` | Discriminated authoritative, estimated, free, or unknown monetary evidence | Acquire evidence, preserve it per attempt, and derive nullable aggregate projections |
83
+ | `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
84
+ | `ProviderCost` | Exact charged, estimated, or unknown monetary evidence | Normalize one monetary disposition for each physical provider request |
85
+ | `ProviderRequestAccounting` | Usage and cost evidence for one physical provider request | Preserve request order across retries, failover, success, and failure |
86
+ | `ProviderAccounting` | Ordered requests plus deterministic usage and exact-USD projections | Derive loop, protocol, telemetry, and client reporting without a second authority |
84
87
 
85
88
  `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.
86
89
 
87
- §provider-cost `ProviderCost` represents one provider attempt's monetary truth.
88
- An authoritative result preserves the provider's canonical decimal amount and
89
- currency plus its decimal USD equivalent; an estimate carries a decimal USD
90
- amount; free requires an explicit source; unknown requires a reason. Decimal
91
- strings preserve evidence without binary floating-point rewriting. Unknown is
92
- not zero, and no consumer may infer free from absent rates or a legacy numeric
93
- zero. A numeric `costUsd` projection includes authoritative USD equivalents,
94
- Models.dev-derived estimates, and explicit zero-cost evidence. It is `null`
95
- when any contributing result is unknown; there is no second projected-cost
96
- surface.
90
+ §provider-usage `ProviderUsage` records only known non-negative safe-integer
91
+ quantities. `inputTokens` includes every input category; cache reads and cache
92
+ writes are details within it. `outputTokens` includes reasoning; text and
93
+ reasoning are details within it. `totalTokens` equals input plus output whenever
94
+ all three are present. A detail is no greater than its containing total, and a
95
+ complete detail partition sums to that total. An omitted field is unknown; an
96
+ explicit zero is provider evidence or an exact derivation from complete known
97
+ components. Consumers never estimate a token category from text length.
98
+
99
+ §provider-cost `ProviderCost` represents one physical provider request's
100
+ monetary disposition. `charged` preserves a provider-reported canonical decimal
101
+ amount and currency, with an optional decimal USD equivalent for a non-USD
102
+ charge. `estimated` preserves a calculated decimal amount and currency.
103
+ `unknown` requires a reason. Zero is an ordinary exact amount under `charged`
104
+ or `estimated`; there is no separate free mechanism. Decimal strings preserve
105
+ evidence without binary floating-point rewriting. Unknown is not zero.
106
+
107
+ §provider-request-accounting `ProviderRequestAccounting` is the indivisible
108
+ accounting fact for one issued physical request. Its provider, model, outcome,
109
+ optional protocol status, optional {§provider-usage}, and required
110
+ {§provider-cost} travel together. Ordered request records preserve retries and
111
+ capacity failover; a later response never replaces an earlier request.
112
+
113
+ §provider-accounting `ProviderAccounting.requests` is the source evidence.
114
+ `usage` and `costUsd` are deterministic projections of that ordered set, not
115
+ independent inputs. Each usage field is present only when every contributing
116
+ request has that field known; the empty set totals to explicit zero. `costUsd`
117
+ is the exact decimal sum only when every request is expressible in USD and is
118
+ `null` otherwise. Consumers do not recompute provider rates or convert
119
+ currencies while reading the projection.
97
120
 
98
121
  The parser returns ordered statement, error, and text items. It recovers at a
99
122
  trustworthy statement boundary when possible and sets `unparsedTail` when a
@@ -103,110 +126,152 @@ and parse diagnostics are separate contracts.
103
126
  ## 1.2 GBNF Generation Rail
104
127
 
105
128
  §gbnf-rail-purpose ANTLR and AstBuilder define accepted PLURNK input. The generated
106
- `dist/plurnk.gbnf` is an optional local llama.cpp sampling rail: it is kept lean
129
+ `dist/plurnk.{gemma,qwen}.gbnf` are optional local llama.cpp sampling rails kept lean
107
130
  to make useful, ANTLR-compliant turns more likely without reproducing every
108
131
  parser or semantic validator. Parse compatibility is a design goal balanced
109
132
  against rail size and sampling efficiency, not a language-subset guarantee. A
110
133
  rail-legal operation can therefore produce a parser or AstBuilder error; consumers
111
134
  apply their ordinary admission and bounded-operation recovery contract.
135
+ The complete package build emits both rails; they are not source-controlled. Source and
136
+ differential tests serialize the owning generator directly, while installation
137
+ coverage verifies the packed export.
112
138
 
113
139
 
114
- The shipped raw turn has one shape:
140
+ The rails share one turn shape but begin at their respective sampled-token
141
+ boundaries:
115
142
 
116
143
  ```ebnf
117
- root-turn ::= channel sep plan sep tail-0
144
+ root-gemma ::= channel sep framed-turn
145
+ root-qwen ::= think-body think-close sep framed-turn
146
+ root-qwen-response ::= think-open root-qwen
147
+ framed-turn ::= turn | fence-open turn fence-close
148
+ turn ::= plan sep tail-0
118
149
  ```
119
150
 
120
- §gbnf-turn-shape `channel` is exactly one Gemma Harmony enclosure at byte zero, beginning
121
- `<|channel>thought\n` and ending `<channel|>`. Its body may be empty but cannot
122
- contain another opener or the closer. `sep` is zero through seven whitespace
123
- characters. No channel is legal after the leading one. `tail-0` is unchanged
124
- apart from that removal: zero through fourteen internal statements, separated
125
- only by `sep`, followed by exactly one terminal SEND under the existing terminal
126
- eligibility rules.
151
+ §gbnf-turn-shape The `gemma` transport root samples one complete
152
+ `<|channel>thought\n <channel|>` enclosure. A Qwen-style chat template has
153
+ already supplied `<think>\n` when the `qwen` transport root begins, so that root
154
+ samples the body and required `</think>` closer. Each generated artifact declares
155
+ an `@plurnk-response-root`; for `qwen`, that root composes the template opener
156
+ back onto the sampled text so the complete pre-projection response can be graded.
157
+ Either body may be empty and cannot contain its profile's opener or closer.
158
+ `sep` is zero through seven whitespace characters. The projected PLURNK content
159
+ is either bare or enclosed once in a paired `plurnk` Markdown fence; the turn
160
+ begins with `# PLAN0`, and every following operation is a same-lane `## OP0`
161
+ section.
162
+ `tail-0` admits zero through fourteen internal operations followed by exactly
163
+ one terminal SEND under the existing terminal-eligibility rules.
127
164
 
128
165
  ```mermaid
129
166
  flowchart LR
130
- raw["Raw constrained decode<br/>channel · sep · PLAN · sep · tail-0"]
167
+ sampled["Constrained sampled text<br/>profile reasoning bytes · sep · optional fence · PLAN0 turn"]
168
+ raw["Pre-projection response<br/>one complete reasoning envelope · PLURNK turn"]
131
169
  split["llama.cpp<br/>reasoning_format: auto"]
132
- reasoning["reasoning_content<br/>channel body"]
133
- content["content<br/>PLAN through terminal SEND"]
170
+ reasoning["reasoning_content<br/>envelope body"]
171
+ content["content<br/>bare or fenced PLAN through terminal SEND"]
134
172
  parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
173
+ sampled --> raw
135
174
  raw --> split
136
175
  split --> reasoning
137
176
  split --> content
138
177
  content --> parser
139
178
  ```
140
179
 
141
- §gbnf-reasoning-boundary GBNF applies to the raw sentence on the left, before projection. The two
142
- projected fields are not separate GBNF languages and `content` alone is not
143
- revalidated as though it still contained the required channel. Provider and
144
- core own the projection evidence and rail-verdict boundary; this package owns
145
- only the raw language and the parser/AstBuilder result.
146
-
147
- §plan-body-no-openers On the GBNF rail, PLAN is unsuffixed and its body excludes
148
- the literal `<<`. A single `<` remains legal. This prevents an omitted `:PLAN`
149
- from consuming following operations as intended-goals text; ANTLR remains
150
- tolerant during ingestion.
180
+ §gbnf-reasoning-boundary GBNF applies from sampled token zero before response
181
+ projection. The declared response root composes any template-provided prefix for
182
+ independent validation of the pre-projection evidence. The two projected fields
183
+ are not separate GBNF languages, and `content` alone is not revalidated as though
184
+ it still contained the required reasoning envelope. Provider and core own the
185
+ projection evidence and rail-verdict boundary; this package owns the sampled and
186
+ response roots plus the parser/AstBuilder result.
187
+
188
+ §rail-heading-boundaries On the GBNF rail, PLAN and every operation use lane `0`.
189
+ Every reserved PLAN or operation heading stem is structural, regardless of the
190
+ suffix a model attempts next, so a non-`0` pseudo-heading cannot be swallowed as
191
+ literal body text. Rail bodies therefore cannot quote reserved headings from any
192
+ lane. This makes both the canonical suffix and section boundary structurally
193
+ available during constrained generation; ANTLR remains the wider language and
194
+ accepts intentional alternate-lane literals during ingestion.
195
+
196
+ §gbnf-curation-shaping The rail admits OPEN/FOLD curation terms, a canonical
197
+ `log:` target, and a matcher independently. It accepts any ordered mixture of
198
+ unsigned, `+`, and `-` terms without proving that the combination selects a log
199
+ item; ANTLR and AstBuilder own that condition, while runtime owns wider ingested
200
+ target resolution.
151
201
 
152
202
  ## §canonical-statement 2. Canonical statement form
153
203
 
154
204
  ```text
155
- <<OPsuffix[signal]?(path)?<scope>?:body?:OPsuffix
156
- ```
205
+ # PLANsuffix
206
+ body
157
207
 
158
- §body-fence The two colons fence the body. Everything after the opening colon and
159
- before the matching `:OPsuffix` close is preserved verbatim and is opaque to
160
- operation keywords, slot characters, and nested PLURNK-looking text. An empty
161
- body retains both delimiters: `::OPsuffix`.
208
+ ## OPsuffix [signal]? (path)? <scope>?
209
+ body?
210
+ ```
162
211
 
163
- | Element | Canonical contract |
164
- |-------------|--------------------------------------------------------------------|
165
- | `<<` | Required statement opener |
166
- | `OP` | One protocol operation |
167
- | `suffix` | Optional enclosure suffix, joined directly to `OP` |
168
- | `[signal]` | Optional operation-specific signal |
169
- | `(path)` | Optional syntax slot whose operation contract may require a target |
170
- | `<scope>` | Optional operation-specific numeric scope |
171
- | `:` | Required body opener, even when the body is empty |
172
- | `body` | Operation-specific, character-perfect content |
173
- | `:OPsuffix` | Required close matching the opener exactly |
212
+ §section-boundary A statement is one Markdown section. PLAN alone uses a level-one
213
+ heading; every other operation uses a level-two heading. Its body is the
214
+ character-perfect section content before the next structural heading or EOF.
215
+ Canonical adjacent sections have one blank separator line. That final separator
216
+ line is syntax and is not body content; any additional preceding blank lines
217
+ remain body content. The tolerant ingester also admits a structural heading on
218
+ the immediately following line.
219
+
220
+ §empty-section An empty section has no body lines between its heading and the
221
+ section separator (or EOF) and normalizes to a null body.
222
+
223
+ | Element | Canonical contract |
224
+ |--------------|---------------------------------------------------------------------------|
225
+ | `# PLAN` | Required level-one turn anchor |
226
+ | `## OP` | Level-two protocol operation |
227
+ | `suffix` | Heading lane, joined directly to PLAN or OP |
228
+ | `[signal]` | Optional operation-specific signal, preceded by one space |
229
+ | `(path)` | Optional target slot, preceded by one space |
230
+ | `<scope>` | Optional numeric scope, preceded by one space |
231
+ | line ending | Ends the single-line heading |
232
+ | `body` | Zero or more characters of operation-specific, character-perfect content |
233
+ | blank line | Canonical section separator; excluded from the preceding body |
174
234
 
175
235
  The following constraints are structural:
176
236
 
177
- - §close-tag-match The close `:OPsuffix` must character-match the opening operation and suffix.
237
+ - §lane-match PLAN establishes one lane for the turn. A heading is structural
238
+ only when its suffix character-matches that lane; a different suffix remains
239
+ ordinary body text.
240
+ - PLAN is the only H1 operation and every non-PLAN operation is H2.
241
+ - A header occupies one physical line.
178
242
  - Each admitted signal, target, and scope slot appears at most once.
179
- - OPEN, FOLD, WORK, FORK, and KILL do not admit a scope slot.
180
- - A suffix is `[A-Za-z0-9_]*`; generation canon uses digit suffixes when enclosure is needed.
243
+ - OPEN, FOLD, BARE, WORK, FORK, and KILL do not admit a scope slot.
244
+ - An ingested suffix is `[A-Za-z0-9_]*`; canonical teaching and the GBNF use `0`.
181
245
 
182
246
  §slot-order Canonical producers and the GBNF rail emit signal, then target, then
183
- scope. The tolerant ANTLR ingester accepts any permutation of the slots admitted
184
- by that operation, at most once each. Accepted permutation is not a second
185
- canonical spelling.
247
+ scope, with one ASCII space before every present slot. Slot delimiters make
248
+ their boundaries unambiguous, so the tolerant ANTLR ingester accepts zero or
249
+ more horizontal whitespace characters before each slot and any permutation of
250
+ the slots admitted by that operation, at most once each. Accepted spacing and
251
+ permutation are not second canonical spellings.
186
252
 
187
253
  The ingester also accepts several bounded noncanonical forms so it can explain
188
254
  or safely execute understandable input:
189
255
 
190
- | Tolerated input | Canonical or runtime disposition |
191
- |--------------------------------------------------|---------------------------------------------------------------------|
192
- | Reordered admitted slots | Producers retain signal → target → scope order |
256
+ | Tolerated input | Canonical or runtime disposition |
257
+ |---------------------------------------------------|---------------------------------------------------------------------|
258
+ | Reordered admitted slots | Producers retain signal → target → scope order |
193
259
  | Missing target on a generally targeted operation | AST carries `null`; the runtime rejects when the target is required |
194
- | PLAN modifiers or suffix | Model canon keeps PLAN slotless and unsuffixed |
195
- | KILL annotation body | AST preserves it; model teaching uses an empty body |
196
- | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
197
- | One-colon empty body at a statement boundary | Producers retain the explicit `::OPsuffix` empty body |
260
+ | PLAN modifiers or a non-`0` lane | Model canon keeps PLAN slotless and uses lane `0` |
261
+ | KILL annotation body | AST preserves it; model teaching leaves the KILL section empty |
262
+ | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
263
+ | Empty content where semantics require a body | The empty section normalizes null; the operation owner rejects it |
198
264
 
199
265
  ## 3. Lexical elements
200
266
 
201
267
  | Element | Accepted shape or role |
202
268
  |-------------|--------------------------------------------------------------------|
203
- | `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC WORK FORK KILL PLAN` |
204
- | `suffix` | `[A-Za-z0-9_]*`, adjacent to `OP` |
269
+ | `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC BARE WORK FORK KILL PLAN` |
270
+ | `suffix` | `[A-Za-z0-9_]*`, adjacent to PLAN or OP |
205
271
  | `[signal]` | Operation-specific tags, identifier, branch, or integer |
206
272
  | `(path)` | Local path or scheme URL target; detailed in §5 |
207
273
  | `<scope>` | One or more signed integers or decimals; detailed in §7 |
208
- | `body` | Opaque text between the body opener and matching close |
209
- | `:OPsuffix` | Adjacent colon plus the exact opening operation and suffix |
274
+ | `body` | Opaque section text before the next same-lane heading or EOF |
210
275
 
211
276
  ## §op-shapes 4. Per-operation semantics
212
277
 
@@ -216,14 +281,15 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
216
281
  | OP | `[signal]` | `(path)` | `<scope>` | `body` |
217
282
  |------|-------------------------------|----------------------------------------------|---------------------------------|--------------------------------|
218
283
  | PLAN | none | none | none | required intended goals |
219
- | FIND | optional filter tags | required target or glob | optional result range | optional matcher |
220
- | READ | optional filter tags | required target | optional text region | empty |
221
- | EDIT | optional apply tags | required file or entry | required for an existing target | literal text |
222
- | COPY | optional apply tags | required source | optional source region | required destination selection |
223
- | MOVE | optional apply tags | required source | optional source region | required destination selection |
224
- | FOLD | optional apply tags | required log selection | none | optional matcher |
225
- | OPEN | optional filter tags | optional log selection when tags are present | none | optional matcher |
284
+ | FIND | optional add log tags | required target or glob | optional result range | optional matcher |
285
+ | READ | optional add log tags | required target | optional text region | empty |
286
+ | EDIT | optional add log tags | required file or entry | required for an existing target | literal text |
287
+ | COPY | optional add log tags | required source | optional source region | required destination selection |
288
+ | MOVE | optional add log tags | required source | optional source region | required destination selection |
289
+ | FOLD | optional filter/change tags | optional log selection | none | optional matcher |
290
+ | OPEN | optional filter/change tags | optional log selection | none | optional matcher |
226
291
  | EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
292
+ | BARE | optional add log tags | none | none | required prompt |
227
293
  | WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
228
294
  | FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
229
295
  | KILL | optional target-specific code | required target, including a log item | none | empty |
@@ -235,15 +301,19 @@ Directed SEND and KILL delegate any present code to the addressed target's
235
301
  operation contract; a live process may interpret a KILL code as a Unix signal,
236
302
  but that interpretation does not define KILL generally.
237
303
 
238
- §plan-intended-goals **PLAN records intended goals.** The PLAN body is the model's
239
- concise statement of intended goals. It is public, durable log content—not provider
304
+ §plan-intended-goals **PLAN records working-state deltas.** Its concise body adds
305
+ new material conclusions or unresolved questions and the current turn's priorities;
306
+ it does not restate settled context. PLAN is public, durable log content—not provider
240
307
  reasoning. Dispatch records it and has no other runtime effect.
241
308
 
242
- OPEN and FOLD are log-curation operations owned by the core log surface. FOLD
243
- selects a set, hides its bodies, and applies any signal tags. OPEN filters by its
244
- signal tags and may omit the target to recall that tagged working set across the
245
- worker log. FOLD requires a target or matcher; OPEN requires a target, matcher,
246
- or filter tags. Neither operation exposes a positional scope.
309
+ §log-tag-signal FIND, READ, EDIT, COPY, MOVE, and BARE canonically express additions
310
+ as `+tag`. Because those operations have no tag-selection semantics, ANTLR also
311
+ tolerates unsigned `tag` as an equivalent addition; `-tag` is invalid. Core
312
+ strips any `+`; the signal neither filters nor modifies resources. OPEN and
313
+ FOLD treat every unsigned `tag` as an ALL-tags selector, then add each `+tag`
314
+ and remove each `-tag` from the selected log items. Signed terms never select,
315
+ so either a target, matcher, or unsigned tag is required. Adding and removing
316
+ the same tag conflicts. Neither curation operation exposes a positional scope.
247
317
 
248
318
  The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
249
319
  scopes ordered results. EXEC and SEND scope timing. READ, EDIT, COPY, and
@@ -259,19 +329,34 @@ MOVE use one universal text algebra independent of mimetype:
259
329
  two-integer line selections normalize to the same exclusive-end `TextRegion`
260
330
  used by four-coordinate selections. Whole-line replacement deliberately
261
331
  accounts for newline separators; it is an ergonomic projection over exact
262
- replacement, not a different mimetype navigation mode. As an unadvertised
332
+ replacement, not a different mimetype navigation mode. An end bound beyond
333
+ the available content clamps to the final addressable endpoint; the start bound
334
+ must resolve. As an unadvertised
263
335
  ingestion tolerance, the runtime accepts three integers as
264
336
  `startLine,startColumn,endLine` and immediately normalizes them to the complete
265
337
  four-coordinate region ending after the final code point of `endLine`.
266
338
  Producers never emit that form. Other arities and decimal text coordinates are
267
339
  runtime 416 failures.
268
340
 
269
- §read-exact-target READ targets one exact resource (a local path or scheme URL,
270
- with optional `#channel` fragment or `{header: value}` metadata). READ does not
271
- admit globs or matcher bodies; pattern matching belongs to FIND. A `<scope>` on
272
- READ selects a text region from that exact target. Without a scope, READ
273
- defaults to `<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope
274
- components are invalid on READ.
341
+ §bare-statement **BARE requests one isolated model inference.** Its required
342
+ body is the complete prompt. It admits only optional additive log tags: no
343
+ target, scope, persistent worker identity, or output-language statement shape
344
+ is represented in the AST. Runtime provider selection, batching, accounting,
345
+ and observation timing belong to the consuming service.
346
+
347
+ §read-find-normalization An authored READ with a nonempty matcher body or a
348
+ target path classified as a glob normalizes during AST construction to one
349
+ ordinary FIND statement. Target, signals, scope, and matcher are preserved;
350
+ FIND's result pagination and projection contract then applies. The canonical
351
+ AST retains no parallel matcher-READ mode, and the runtime performs no READ
352
+ fan-out.
353
+
354
+ §read-exact-target After normalization, READ targets one exact resource (a
355
+ local path or scheme URL, with optional `#channel` fragment or
356
+ `{header: value}` metadata) and has no matcher body. A `<scope>` on READ selects
357
+ a text region from that exact target. Without a scope, READ defaults to
358
+ `<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope components are
359
+ invalid on READ.
275
360
 
276
361
  Mutation semantics:
277
362
 
@@ -281,13 +366,13 @@ Mutation semantics:
281
366
  - An empty body deletes the selected text.
282
367
  - `<0>` prepends and `<-1>` appends.
283
368
  - `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
284
- - §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 only by one or more colons is rejected as close-fence residue 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.
369
+ - §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.
285
370
 
286
371
  ### §operation-observation Per-operation observations
287
372
 
288
373
  | OP | Successful observation |
289
374
  |------|-----------------------------------------------------------------------------------|
290
- | FIND | Resource catalog rows or exact-target match locations |
375
+ | FIND | Resource catalog groups or exact-target match locations |
291
376
  | READ | Complete or scoped body projections plus optional text match evidence |
292
377
  | EDIT | Status plus a bounded receipt for the effect that landed |
293
378
  | COPY | Source and destination selections plus ordered destination effects |
@@ -296,6 +381,7 @@ Mutation semantics:
296
381
  | FOLD | Status and matched log-item count |
297
382
  | SEND | Status and recipient acknowledgement when applicable |
298
383
  | EXEC | Spawn acknowledgement; output arrives through named stream channels |
384
+ | BARE | The one-shot model response |
299
385
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
300
386
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
301
387
  | KILL | Status of deletion or termination |
@@ -362,7 +448,7 @@ pairs such as `\*` retain both characters for glob interpretation. Encoding
362
448
  escapes backslashes before parentheses, so every target string round-trips.
363
449
  Pathname producers retain the deliberate `%28`/`%29` alias and `%3C` spelling,
364
450
  but must use the lexical layer for identity-bearing query and fragment text
365
- rather than changing their percent-encoded spelling. Newlines and `<<` are
451
+ rather than changing their percent-encoded spelling. Newlines and raw `<` are
366
452
  never target content. Glob metacharacters remain legal path data.
367
453
 
368
454
  §path-glob `PathSyntax` owns exact-path versus path-pattern classification.
@@ -394,9 +480,10 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
394
480
 
395
481
  ## §matcher-prefix-claims 6. Bulk pattern matching
396
482
 
397
- FIND, OPEN, and FOLD accept an optional body matcher. READ requires an exact target
398
- and an empty body. The lexer preserves the body opaquely; AstBuilder assigns the dialect
399
- from its leading characters.
483
+ FIND, OPEN, FOLD, and authored READ accept an optional body matcher. The lexer
484
+ preserves the body opaquely; AstBuilder assigns the dialect from its leading
485
+ characters, then normalizes matcher-bearing READ to FIND under
486
+ {§read-find-normalization}.
400
487
  A leading prefix claims its dialect. Invalid claimed syntax is a positioned
401
488
  visitor error and never falls back to glob matching.
402
489
 
@@ -422,9 +509,9 @@ statements remain recoverable when their boundaries are trustworthy.
422
509
 
423
510
  - §pattern-body-single-line The GBNF rail permits only single-line matcher
424
511
  bodies. A regex that matches a newline uses the two-character `\n` escape.
425
- During ingestion, a FIND body that reaches a physical newline without its
426
- close tag closes at that boundary with one recoverable lexer diagnostic, so
427
- the following statement remains independently parseable.
512
+ ANTLR preserves the complete section body; the same-lane heading boundary
513
+ keeps every following statement independently parseable without a matcher-
514
+ specific implicit close rule.
428
515
  - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
429
516
  character. Empty matchers and later colons remain valid; a regex such as
430
517
  `/^:needle/` expresses a pattern beginning with a literal colon.
@@ -432,18 +519,22 @@ statements remain recoverable when their boundaries are trustworthy.
432
519
  ## §scope-slot 7. Scope markers
433
520
 
434
521
  The model-facing slot is `<scope>`; the AST field remains the historical
435
- `lineMarker: { marks: number[] }`. The parser preserves ordered numeric
436
- components, while the operation owner assigns their roles.
437
-
438
- | Operation | Canonical components | Meaning |
439
- |-----------------------|----------------------------------------------|--------------------------------------------------------------|
440
- | FIND | optional threshold, then 0–2 positions | Inclusive resource or exact-target location positions ({§find-result-unit}; defaults to `<1,16>`) |
441
- | READ | 0/1/2/4 coordinates | Text projection from the exact selected file, entry, or log item |
442
- | EDIT | text coordinates | Text replacement, deletion, prepend, or append |
443
- | COPY/MOVE source | text coordinates | Region copied or moved from the selected source |
444
- | COPY/MOVE destination | text coordinates after destination | Region replaced or insertion point at the destination |
445
- | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
446
- | Terminal SEND `[202]` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence |
522
+ `lineMarker`. Numeric scopes preserve ordered components in `LineMarker`;
523
+ text-coordinate operations use `TextLineMarker`, whose line positions may also
524
+ carry rendered anchors. The operation owner assigns every component's role.
525
+
526
+ The operation column names the canonical AST operation after
527
+ read-find-normalization}.
528
+
529
+ | Operation | Canonical components | Meaning |
530
+ |-----------------------|----------------------------------------|----------------------------------------------------------------------------|
531
+ | FIND | optional threshold, then 0–2 positions | Inclusive resource or exact-target location positions ({§find-result-unit}; defaults to `<1,16>`) |
532
+ | READ / client LOOK | 0/1/2/4 text coordinates | Text projection from one exact selected file, entry, or log item |
533
+ | EDIT | 0/1/2/4 text coordinates | Text replacement, deletion, prepend, or append |
534
+ | COPY/MOVE source | 0/1/2/4 text coordinates | Region copied or moved from the selected source |
535
+ | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
536
+ | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
537
+ | Terminal SEND `[202]` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence |
447
538
 
448
539
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
449
540
  whole line, two integers are an inclusive whole-line range, and four integers
@@ -452,11 +543,23 @@ admit `0` as prepend and `-1` as append. A leading decimal on semantic FIND
452
543
  is a similarity threshold; any remaining integers select result positions. READ
453
544
  does not admit decimal scope components.
454
545
 
546
+ §text-line-anchor-syntax A text coordinate admits a case-sensitive line anchor
547
+ spelled `@` followed by exactly five Base62 characters (`0-9A-Za-z`) wherever
548
+ its `L`, `SL`, or `EL` position denotes a line. Columns, prepend `0`, and append
549
+ `-1` remain numeric. Exact READ, EDIT, COPY/MOVE source and destination, and
550
+ client LOOK preserve these positions in `TextLineMarker`; core resolves them
551
+ against the addressed current text before operation-specific numeric scope
552
+ semantics run. A matcher-bearing or path-glob READ normalizes to FIND, whose
553
+ result positions remain numeric and reject anchors. Numeric text scopes remain
554
+ canonical and fully supported. Parser acceptance does not imply model-facing
555
+ recommendation.
556
+
455
557
  §scope-marker-forms Canonical producers separate components with commas and no
456
558
  spaces. ANTLR tolerates a dash separator and one space after a comma. Each
457
559
  component greedily consumes an optional leading minus sign, digits, and an
458
560
  optional decimal fraction, so the ingester preserves even noncanonical numeric
459
- shapes for runtime validation.
561
+ shapes for runtime validation. An anchor-bearing text scope uses commas; ANTLR
562
+ tolerates one space after each comma.
460
563
 
461
564
  Apart from the unadvertised three-coordinate text-scope tolerance in
462
565
  {§text-scope-semantics}, the runtime rejects invalid arity, out-of-range or
@@ -467,45 +570,44 @@ does not enforce either condition.
467
570
 
468
571
  ## §suffix-discipline 8. Suffix Discipline
469
572
 
470
- The `:body:` fencing handles the vast majority of grammatical-enclosure
471
- concerns: body content is fully opaque to OP keywords and modifier-like
472
- characters. The suffix is reserved for the residual edge case where
473
- body content literally contains the close-tag pattern `:OPkeyword`.
474
- That happens in two scenarios:
475
-
476
- 1. **Nesting plurnk statements inside a body** (recording a plurnk
477
- transcript, storing examples, etc.). The inner statement's close
478
- `:OP` would prematurely terminate the outer's body.
479
- 2. **Body content contains `:OPkeyword` as literal text** (e.g., a
480
- stored JSON object with a value mentioning plurnk syntax).
573
+ The suffix is a turn-wide heading lane. A heading carrying the active lane
574
+ is structural; an otherwise valid PLURNK heading carrying another lane is body
575
+ text. The lane therefore makes literal or nested PLURNK unambiguous.
481
576
 
482
577
  Suffix rules:
483
578
 
484
- - `suffix` is `[A-Za-z0-9_]*`, concatenated to `OP` with no separator, on both open and close.
485
- - Open `<<OPsuffix` and close `:OPsuffix` must character-match.
486
- - A non-empty suffix on the outer statement ensures its close tag
487
- (`:OPsuffix`) is distinct from any `:OP` substring that may appear in
488
- body content (whether as nested plurnk or as literal text).
489
- - The body of a statement cannot contain its own exact close-tag
490
- literal; choose a suffix that does not collide.
491
- - Empty suffix is the default. Most statements need no suffix.
492
- - Generation-side canon dictates **digit** suffixes (`<<EDIT1 :EDIT1`)
493
- so the shipped GBNF (`dist/plurnk.gbnf`) can enumerate close tags —
494
- the HEREDOC tag match is not context-free. The parser remains
495
- permissive: any matching `[A-Za-z0-9_]*` suffix is valid.
496
-
497
- Example — a nested operation inside a suffixed outer body:
579
+ - `suffix` is `[A-Za-z0-9_]*`, concatenated to PLAN or OP with no separator.
580
+ - The H1 PLAN establishes the lane; every real H2 operation heading in that
581
+ turn has the exact same suffix.
582
+ - An empty suffix is accepted only by ANTLR ingestion. Canonical teaching and
583
+ the generated rail use `0` on PLAN and every operation.
584
+ - A body may contain any heading whose suffix differs from the active lane.
585
+ - To carry a nested turn written with lane `0`, choose another suffix for the
586
+ outer turn and repeat it on every outer heading.
587
+ - The GBNF deliberately emits only lane `0`. It cannot emit body content that
588
+ contains a same-lane structural heading; unconstrained producers use another
589
+ outer lane when that representation is required.
590
+
591
+ Example — a lane-0 turn stored inside a lane-2 EDIT body:
498
592
 
499
593
  ```
500
- <<SEND1[400]:
501
- The following is a quoted plurnk operation, preserved verbatim:
502
- <<SEND[400](worker://reviewer):still working:SEND
503
- :SEND1
594
+ # PLAN2
595
+ Store the quoted turn.
596
+
597
+ ## EDIT2 (worker:///quoted.plurnk)
598
+ # PLAN0
599
+ Answer from memory.
600
+
601
+ ## SEND0 [200]
602
+ Paris.
603
+
604
+ ## SEND2 [200]
605
+ Stored the quoted turn.
504
606
  ```
505
607
 
506
- The inner's `:SEND` close is ordinary body text because the outer close
507
- is `:SEND1`. This rule belongs to the body fence and applies to every
508
- operation, not to EDIT semantics.
608
+ The lane-0 headings are ordinary EDIT body text because the outer turn's
609
+ structural lane is `2`. This rule belongs to section framing and applies to
610
+ every operation, not to EDIT semantics.
509
611
 
510
612
  ## 9. SEND Codes
511
613
 
@@ -547,7 +649,7 @@ structural:
547
649
  - §no-idle-102 A **zero-statement turn may not conclude `[102]`** — "continue"
548
650
  with nothing submitted is a spin. The GBNF's `tail-0` exits through
549
651
  a terminal trie without the `[102]` tail, so the idle turn (`PLAN`
550
- straight into `SEND[102]`) is unemittable; one statement restores
652
+ straight into `## SEND0 [102]`) is unemittable; one statement restores
551
653
  the full disposition set. The other four stay legal bare (a zero-op
552
654
  `[202]` is the engine's obligation check). ANTLR stays tolerant
553
655
  (ingest side). A dispatch-emptied turn — ops emitted but failing
@@ -576,61 +678,60 @@ AST. Generated TypeScript targets the `antlr4ng` runtime.
576
678
  stateDiagram-v2
577
679
  [*] --> DEFAULT
578
680
  DEFAULT --> DEFAULT: whitespace or TEXT
579
- DEFAULT --> SLOTS: minted <<OPsuffix
681
+ DEFAULT --> SLOTS: H1 PLANlane or H2 OPlane
580
682
  SLOTS --> SIGNAL: signal opener
581
683
  SIGNAL --> SLOTS: signal close
582
684
  SLOTS --> TARGET: target opener
583
685
  TARGET --> TARGET: balanced literals / target escapes
584
686
  TARGET --> SLOTS: target close at depth zero
585
687
  SLOTS --> SLOTS: scope token
586
- SLOTS --> BODY: body-opening colon
587
- BODY --> DEFAULT: matching close tag
688
+ SLOTS --> BODY: heading line end
689
+ BODY --> DEFAULT: same-lane heading boundary
690
+ BODY --> [*]: end of input
588
691
  ```
589
692
 
590
- SLOTS admits operation-appropriate signal, target, and scope openers in any
591
- order; the parser grammar enforces at-most-once multiplicity. Signal submodes
592
- select tags, integer, or identifier tokens by operation family. TARGET preserves
593
- balanced inner parentheses and recognized target escapes. BODY emits opaque text until the semantic predicate
594
- recognizes the exact close from the captured opener.
693
+ The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
694
+ PLAN or H2 minted operation carrying that exact lane. SLOTS admits
695
+ operation-appropriate signal, target, and scope openers in any order; the parser
696
+ grammar enforces at-most-once multiplicity. Signal submodes select tags, integer,
697
+ or identifier tokens by operation family. TARGET preserves balanced inner
698
+ parentheses and recognized target escapes. BODY emits opaque text until a
699
+ same-lane heading boundary or EOF.
595
700
 
596
- TURN is the single exception to opaque-body mode: its opener returns to DEFAULT
597
- so a complete inner turn parses structurally, and a suffix stack recognizes its
598
- matching close. The narrow one-colon empty-body tolerance closes directly from
599
- SLOTS only at a trustworthy statement boundary.
600
-
601
- DEFAULT recognizes minted operation openers and otherwise emits non-whitespace
602
- input as TEXT. A `<<word` whose word is not a minted operation remains TEXT.
603
- Complete native reasoning enclosures are likewise one TEXT token so an operation
604
- drafted inside pre-PLAN reasoning cannot become the turn anchor.
701
+ A differently suffixed heading stays BODY text. Multi-turn logs are plain
702
+ sequences of independently lane-anchored PLAN turns. Complete native reasoning
703
+ enclosures before PLAN remain one TEXT token so an operation drafted inside
704
+ provider reasoning cannot become the turn anchor.
605
705
 
606
706
  RecordingListener captures lexer and parser failures; AstBuilder adds visitor
607
- failures. PlurnkErrorStrategy recovers at statement boundaries where possible.
608
- An unfinished signal, target, body, or TURN leaves the lexer outside DEFAULT and
609
- therefore produces `unparsedTail`; no later input is trustworthy.
707
+ failures. PlurnkErrorStrategy recovers at structural heading boundaries where
708
+ possible. EOF is a valid body boundary. An unfinished signal or target produces
709
+ `unparsedTail`; no later input is trustworthy.
610
710
 
611
711
  ## §whitespace-contract 11. Whitespace and interstatement text
612
712
 
613
- | Location | Canonical generation | Tolerant ANTLR ingestion |
614
- |-------------------------|--------------------------------------|-------------------------------------------------------------|
615
- | Between header slots | Adjacent | Spaces, tabs, and newlines are ignored |
616
- | Between `OP` and suffix | Adjacent | Must remain adjacent |
617
- | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
618
- | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline and `<<` are invalid |
619
- | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
620
- | Inside body | Character-perfect | Character-perfect |
621
- | Inside close tag | Colon and `OPsuffix` are adjacent | Must remain adjacent and match the opener |
622
- | Between turn statements | GBNF `sep`: zero to seven whitespace | Whitespace is hidden; non-whitespace may surface as TEXT |
713
+ | Location | Canonical generation | Tolerant ANTLR ingestion |
714
+ |-----------------------------|---------------------------------------|-----------------------------------------------------------|
715
+ | Heading marker | `# PLAN0` or `## OP0` at column zero | The initial PLAN may directly follow leading TEXT; subsequent headings retain exact depth and column |
716
+ | Between OP and suffix | Adjacent | Must remain adjacent |
717
+ | Before each header slot | One ASCII space | Zero or more horizontal whitespace characters |
718
+ | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
719
+ | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline is invalid |
720
+ | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
721
+ | Inside body | Character-perfect | Character-perfect |
722
+ | Between canonical sections | One empty separator line | A directly following structural heading is also admitted |
723
+ | Before the first PLAN | Nothing | Whitespace or TEXT may surface as preamble items without requiring a separator before PLAN |
623
724
 
624
725
  PLURNK never escape-decodes body text: `\n` reaches the owning operation as
625
726
  backslash plus `n`. A matcher or executor may interpret those characters under
626
727
  its own body dialect. Producers that need a physical newline in literal EDIT
627
728
  content emit an actual newline.
628
729
 
629
- `parse` and `parseLog` admit TEXT around their PLAN-anchored operations and
630
- return it as ordered text items without assigning semantics. `parseStatements`
631
- and `parseClient` admit statements and hidden whitespace only. PLURNK defines no
632
- comment syntax; a consumer may treat script-tier text as commentary, but that is
633
- consumer policy rather than language syntax.
730
+ `parse` admits TEXT before its PLAN, including without an intervening line
731
+ break, and returns it as ordered text items without assigning semantics. Once
732
+ a heading begins, all nonstructural text belongs to that section body.
733
+ `parseStatements` and `parseClient` admit H2 statements;
734
+ `parseLog` admits consecutive H1 PLAN turns. PLURNK defines no comment syntax.
634
735
 
635
736
  ## §public-api 12. Public API
636
737
 
@@ -639,20 +740,28 @@ and wire types come from generated schemas; the small hand-maintained parser
639
740
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
640
741
  express. Consumers never receive ANTLR parse-tree or token types.
641
742
 
642
- §turn-shape `PlurnkParser.parse` accepts exactly one model turn. PLAN is the
643
- first operation, a disposition-coded SEND is the terminal operation, and PLAN
644
- cannot recur mid-turn. Tolerated TEXT may appear before PLAN, between operations,
645
- and after SEND; it remains an ordered text item. Missing either anchor or placing
646
- an operation after the terminal SEND is an error.
743
+ §turn-shape `PlurnkParser.parse` accepts exactly one model turn. H1 PLAN is the
744
+ first operation, a disposition-coded H2 SEND is the terminal operation, and PLAN
745
+ cannot recur mid-turn. Tolerated TEXT may appear only before PLAN; after PLAN,
746
+ nonstructural text is section body content. Missing either anchor or placing a
747
+ same-lane operation after the terminal SEND is an error.
748
+
749
+ §document-fence `PlurnkParser.parse` additionally admits one outer Markdown code
750
+ fence whose opening line is exactly ```` ```plurnk ```` and whose closing line,
751
+ when present, is ```` ``` ````. The fence encloses the complete
752
+ PLAN-through-SEND turn and projects neither text nor body content into the AST.
753
+ Its opener commits the document to either that closer or EOF immediately after
754
+ the complete turn. This is document framing, not another statement grammar, and
755
+ no other parser tier admits it. GBNF continues to shape the paired form.
647
756
 
648
757
  §tier-entrypoints Each parser entry point owns one document tier:
649
758
 
650
759
  | Entry point | Accepted document | Result statement type |
651
760
  |--------------------------------|----------------------------------------------------------------|-----------------------|
652
- | `PlurnkParser.parse` | One PLAN-anchored model turn with optional TEXT | `PlurnkStatement` |
761
+ | `PlurnkParser.parse` | One PLAN turn: bare with optional TEXT, or outer `plurnk` fence ending at its closer or EOF | `PlurnkStatement` |
653
762
  | `PlurnkParser.parseStatements` | Zero or more protocol statements and hidden whitespace | `PlurnkStatement` |
654
- | `PlurnkParser.parseLog` | One or more TURN-wrapped model turns | `PlurnkStatement` |
655
- | `PlurnkParser.parseClient` | Protocol statements plus read-shaped client LOOK/BUFF commands | `ClientStatement` |
763
+ | `PlurnkParser.parseLog` | One or more consecutive same-lane PLAN-anchored turns | `PlurnkStatement` |
764
+ | `PlurnkParser.parseClient` | H2 protocol statements plus read-shaped LOOK/BUFF commands | `ClientStatement` |
656
765
 
657
766
  Every entry point returns ordered `statement`, `error`, and, where admitted,
658
767
  `text` items. When present, {§unparsed-tail-boundary} governs the result's item
@@ -756,7 +865,6 @@ scope, or other persistence columns.
756
865
  | `entryId` | Positive durable entry identifier |
757
866
  | `target` | Client selector for the resolved entry, with any channel fragment removed |
758
867
  | `channels` | Every channel for a full read, or exactly the selected channel for a sliced read |
759
- | `tags` | Unique non-empty entry tags |
760
868
 
761
869
  | Channel field | Contract |
762
870
  |-----------------|---------------------------------------------------------------------------------------------------------------|
@@ -861,7 +969,7 @@ class PlurnkParseError extends Error {
861
969
  ```
862
970
 
863
971
  §parser-position Parser source locations are points, not text regions. An AST
864
- statement's `position` identifies the first `<` of its open tag; a diagnostic
972
+ statement's `position` identifies the first `#` of its heading; a diagnostic
865
973
  identifies the offending or recovery point; a text item and `unparsedTail.from`
866
974
  identify the first point at which that item or undefined tail begins. A
867
975
  statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
@@ -887,48 +995,40 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
887
995
  | Source | Boundary |
888
996
  |-------------|----------------------------------------------------------------------------------------|
889
997
  | `"lexer"` | Token-level failure, such as an unrecognized character or malformed `<L>` integer. |
890
- | `"parser"` | Structural failure, such as a missing close tag or wrong token order. |
998
+ | `"parser"` | Structural failure, such as the wrong heading depth or slot order. |
891
999
  | `"visitor"` | Semantic AST-construction failure, such as an invalid matcher dialect or signal shape. |
892
1000
 
893
1001
  `severity` distinguishes a hard error from a non-fatal advisory. The parser is
894
1002
  the sole and complete owner of syntax-error messaging because it holds the
895
1003
  parse state, lexer mode, and expected-token set that no consumer has. It
896
1004
  produces the final diagnostic message, deduplicated expected-token lists,
897
- turn-shape imperatives (begin with `<<PLAN`, end with a terminal `<<SEND`), and
898
- these targeted diagnostics:
899
-
900
- - §invented-closer-advisory **Invented closer.** When the forgiving parser
901
- swallows a `<<Word…:Word` heredoc whose keyword is a known op confusion
902
- (`<<CLOSE` → did you mean `<<FOLD`), it emits a `warning`-severity near-miss
903
- advisory. On a never-closed body, when the swallowed text carries an
904
- `:ALLCAPS` tag that is not the op's closer, the `unparsedTail` reason names it
905
- (`found \`:COMPARISON_TASK\`, which is body text - the closer echoes the op's
906
- name`) so a cap-cut runaway's recovery turn learns what happened, not just
907
- that something did.
1005
+ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1006
+ `## SEND0 [code]`), and these targeted diagnostics:
1007
+
908
1008
  - §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
909
1009
  `[signal]` slot (executor-ident mode) hits a leading `-` or digit —
910
1010
  mark-shaped `<timeout, poll>` scope content mistyped into the brackets — the
911
1011
  lexer message becomes `timeout/poll ride the \`<scope>\` slot; try
912
- \`EXEC<-1,300>\`` instead of a raw `unrecognized character`. The redirect is
1012
+ \`## EXEC0 <-1,300>\`` instead of a raw `unrecognized character`. The redirect is
913
1013
  EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
914
1014
  - §matcher-body-redirect **Matcher body in the slot region.** When the
915
- post-target slot region begins with `$`, `~`, or `@`, the lexer redirects the
916
- unambiguous body matcher into `:body:` instead of returning the generic slot
917
- list. Slash-led regex and XPath are excluded because the same character can
918
- be a forgotten target wrap.
919
- - §plan-body-op-advisory **Operation text swallowed by PLAN.** When an
920
- unsuffixed PLAN body contains op-shaped text (`<<EXEC`…), an advisory reports
921
- that the likely omitted `:PLAN` swallowed the turn's ops (`ops belong after
922
- the plan closes; did you omit \`:PLAN\`?`). A non-empty suffix deliberately
923
- invokes {§suffix-discipline} and suppresses this advisory.
1015
+ post-target header region begins with `$`, `~`, or `@`, the lexer redirects
1016
+ the unambiguous matcher to the first body line instead of returning the
1017
+ generic slot list. Slash-led regex and XPath are excluded because `/` can be
1018
+ target data.
1019
+ - §combined-anchor-line-redirect **Combined anchor and line number in a scope.**
1020
+ A text-coordinate scope containing `@hash:L` or `@hash L` is one bounded hard
1021
+ error: `a scope position accepts one line coordinate; use the \`@hash\` anchor
1022
+ without its displayed line number`. A malformed header scope is consumed as
1023
+ one token, while a COPY/MOVE destination selection fails at its visitor
1024
+ boundary; neither produces a punctuation cascade.
924
1025
  - §misplaced-target-advisory **Mutation target in the signal slot.** When a
925
1026
  mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
926
1027
  `[signal]` element (a `/` or a dotted extension), the message redirects the
927
- path into `(…)` (`\`<<EDIT\` has no \`(target)\` - that path sits in the
928
- \`[…]\` tag slot; a target goes in \`(…)\`. Try \`EDIT(path):…\``). This
929
- catches the markdown `[label](url)` reading at the parse where the engine
930
- otherwise returns only a bare 400. It is gated on a path-shaped signal so a
931
- genuine tags-only slip is not mis-steered toward a path it lacks.
1028
+ path into `(…)` (`\`## EDIT0\` has no \`(target)\` - that path sits in the
1029
+ \`[…]\` tag slot; a target goes in \`(…)\`. Try \`## EDIT0 (path)\``). It is
1030
+ gated on a path-shaped signal so a genuine additive-tag signal is not mis-steered
1031
+ toward a path it lacks.
932
1032
 
933
1033
  §error-shape The diagnostic class determines how much guidance the parser may
934
1034
  provide:
@@ -938,20 +1038,20 @@ provide:
938
1038
  | Hard fact | `severity: "error"` | One concise observed fact and violated constraint in PLURNK vocabulary. |
939
1039
  | Targeted hard redirect | `severity: "error"` | One canonical correction only when parser state makes the intended structure unambiguous. |
940
1040
  | Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
941
- | Boundary loss | `unparsedTail` | Where trust ends, what remains open, and the required closer; later input is undefined. |
1041
+ | Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
942
1042
 
943
- All messages use PLURNK protocol vocabulary: open tag, close tag, signal,
944
- target, scope, line marker, body, statement header, or space between
945
- statements. They never expose ANTLR rule or token names. They refer to a slot or
1043
+ All messages use PLURNK protocol vocabulary: heading, lane, signal, target,
1044
+ scope, line marker, body, section boundary, or space between slots. They never
1045
+ expose ANTLR rule or token names. They refer to a slot or
946
1046
  feature rather than an implementation rule. Generic tutoring, speculative
947
1047
  intent, coordinate restatement, and multiple repair strategies are forbidden.
948
1048
 
949
1049
  Examples of canonical hard facts:
950
1050
 
951
- - `unrecognized character '<<' in target`
1051
+ - `unrecognized character '<' in target`
952
1052
  - `unrecognized character ':' in signal`
953
1053
  - `unrecognized character 'X' in statement header`
954
- - `expected close tag; got end of input`
1054
+ - `a turn must begin with \`# PLAN0\``
955
1055
  - `expected ')'; got ':'`
956
1056
 
957
1057
  Each malformed statement produces at most one hard error. The first recorded
@@ -986,6 +1086,6 @@ runtime constructs this; the parser provides the fields):
986
1086
  "column": 12,
987
1087
  "source": "parser",
988
1088
  "severity": "error",
989
- "message": "expected close tag `:OPsuffix`; got end of input"
1089
+ "message": "target slot of `## READ0` opened at line 1 but never closed - add `)`"
990
1090
  }
991
1091
  ```