@plurnk/plurnk-contracts 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +9 -5
  2. package/SPEC.md +331 -233
  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 +3 -11
  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 +80 -23
  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,151 @@ 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
-
114
- The shipped raw turn has one shape:
139
+ The rails share one turn shape but begin at their respective sampled-token
140
+ boundaries:
115
141
 
116
142
  ```ebnf
117
- root-turn ::= channel sep plan sep tail-0
143
+ root-gemma ::= channel sep framed-turn
144
+ root-qwen ::= think-body think-close sep framed-turn
145
+ root-qwen-response ::= think-open root-qwen
146
+ framed-turn ::= turn | fence-open turn fence-close
147
+ turn ::= plan sep tail-0
118
148
  ```
119
149
 
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.
150
+ §gbnf-turn-shape The `gemma` transport root samples one complete
151
+ `<|channel>thought\n <channel|>` enclosure. A Qwen-style chat template has
152
+ already supplied `<think>\n` when the `qwen` transport root begins, so that root
153
+ samples the body and required `</think>` closer. Each generated artifact declares
154
+ an `@plurnk-response-root`; for `qwen`, that root composes the template opener
155
+ back onto the sampled text so the complete pre-projection response can be graded.
156
+ Either body may be empty and cannot contain its profile's opener or closer.
157
+ `sep` is zero through seven whitespace characters. The projected PLURNK content
158
+ is either bare or enclosed once in a paired `plurnk` Markdown fence; the turn
159
+ begins with `# PLAN0`, and every following operation is a same-lane `## OP0`
160
+ section.
161
+ `tail-0` admits zero through fourteen internal operations followed by exactly
162
+ one terminal SEND under the existing terminal-eligibility rules.
127
163
 
128
164
  ```mermaid
129
165
  flowchart LR
130
- raw["Raw constrained decode<br/>channel · sep · PLAN · sep · tail-0"]
166
+ sampled["Constrained sampled text<br/>profile reasoning bytes · sep · optional fence · PLAN0 turn"]
167
+ raw["Pre-projection response<br/>one complete reasoning envelope · PLURNK turn"]
131
168
  split["llama.cpp<br/>reasoning_format: auto"]
132
- reasoning["reasoning_content<br/>channel body"]
133
- content["content<br/>PLAN through terminal SEND"]
169
+ reasoning["reasoning_content<br/>envelope body"]
170
+ content["content<br/>bare or fenced PLAN through terminal SEND"]
134
171
  parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
172
+ sampled --> raw
135
173
  raw --> split
136
174
  split --> reasoning
137
175
  split --> content
138
176
  content --> parser
139
177
  ```
140
178
 
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.
179
+ §gbnf-reasoning-boundary GBNF applies from sampled token zero before response
180
+ projection. The declared response root composes any template-provided prefix for
181
+ independent validation of the pre-projection evidence. The two projected fields
182
+ are not separate GBNF languages, and `content` alone is not revalidated as though
183
+ it still contained the required reasoning envelope. Provider and core own the
184
+ projection evidence and rail-verdict boundary; this package owns the sampled and
185
+ response roots plus the parser/AstBuilder result.
186
+
187
+ §rail-heading-boundaries On the GBNF rail, PLAN and every operation use lane `0`.
188
+ Every reserved PLAN or operation heading stem is structural, regardless of the
189
+ suffix a model attempts next, so a non-`0` pseudo-heading cannot be swallowed as
190
+ literal body text. Rail bodies therefore cannot quote reserved headings from any
191
+ lane. This makes both the canonical suffix and section boundary structurally
192
+ available during constrained generation; ANTLR remains the wider language and
193
+ accepts intentional alternate-lane literals during ingestion.
194
+
195
+ §gbnf-curation-shaping The rail admits OPEN/FOLD curation terms, a canonical
196
+ `log:` target, and a matcher independently. It accepts any ordered mixture of
197
+ unsigned, `+`, and `-` terms without proving that the combination selects a log
198
+ item; ANTLR and AstBuilder own that condition, while runtime owns wider ingested
199
+ target resolution.
151
200
 
152
201
  ## §canonical-statement 2. Canonical statement form
153
202
 
154
203
  ```text
155
- <<OPsuffix[signal]?(path)?<scope>?:body?:OPsuffix
204
+ # PLANsuffix
205
+ body
206
+
207
+ ## OPsuffix [signal]? (path)? <scope>?
208
+ body?
156
209
  ```
157
210
 
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`.
162
-
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 |
211
+ §section-boundary A statement is one Markdown section. PLAN alone uses a level-one
212
+ heading; every other operation uses a level-two heading. Its body is the
213
+ character-perfect section content before the next structural heading or EOF.
214
+ Canonical adjacent sections have one blank separator line. That final separator
215
+ line is syntax and is not body content; any additional preceding blank lines
216
+ remain body content. The tolerant ingester also admits a structural heading on
217
+ the immediately following line.
218
+
219
+ §empty-section An empty section has no body lines between its heading and the
220
+ section separator (or EOF) and normalizes to a null body.
221
+
222
+ | Element | Canonical contract |
223
+ |--------------|---------------------------------------------------------------------------|
224
+ | `# PLAN` | Required level-one turn anchor |
225
+ | `## OP` | Level-two protocol operation |
226
+ | `suffix` | Heading lane, joined directly to PLAN or OP |
227
+ | `[signal]` | Optional operation-specific signal, preceded by one space |
228
+ | `(path)` | Optional target slot, preceded by one space |
229
+ | `<scope>` | Optional numeric scope, preceded by one space |
230
+ | line ending | Ends the single-line heading |
231
+ | `body` | Zero or more characters of operation-specific, character-perfect content |
232
+ | blank line | Canonical section separator; excluded from the preceding body |
174
233
 
175
234
  The following constraints are structural:
176
235
 
177
- - §close-tag-match The close `:OPsuffix` must character-match the opening operation and suffix.
236
+ - §lane-match PLAN establishes one lane for the turn. A heading is structural
237
+ only when its suffix character-matches that lane; a different suffix remains
238
+ ordinary body text.
239
+ - PLAN is the only H1 operation and every non-PLAN operation is H2.
240
+ - A header occupies one physical line.
178
241
  - 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.
242
+ - OPEN, FOLD, BARE, WORK, FORK, and KILL do not admit a scope slot.
243
+ - An ingested suffix is `[A-Za-z0-9_]*`; canonical teaching and the GBNF use `0`.
181
244
 
182
245
  §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.
246
+ scope, with one ASCII space before every present slot. Slot delimiters make
247
+ their boundaries unambiguous, so the tolerant ANTLR ingester accepts zero or
248
+ more horizontal whitespace characters before each slot and any permutation of
249
+ the slots admitted by that operation, at most once each. Accepted spacing and
250
+ permutation are not second canonical spellings.
186
251
 
187
252
  The ingester also accepts several bounded noncanonical forms so it can explain
188
253
  or safely execute understandable input:
189
254
 
190
- | Tolerated input | Canonical or runtime disposition |
191
- |--------------------------------------------------|---------------------------------------------------------------------|
192
- | Reordered admitted slots | Producers retain signal → target → scope order |
255
+ | Tolerated input | Canonical or runtime disposition |
256
+ |---------------------------------------------------|---------------------------------------------------------------------|
257
+ | Reordered admitted slots | Producers retain signal → target → scope order |
193
258
  | 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 |
259
+ | PLAN modifiers or a non-`0` lane | Model canon keeps PLAN slotless and uses lane `0` |
260
+ | KILL annotation body | AST preserves it; model teaching leaves the KILL section empty |
261
+ | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
262
+ | Empty content where semantics require a body | The empty section normalizes null; the operation owner rejects it |
198
263
 
199
264
  ## 3. Lexical elements
200
265
 
201
266
  | Element | Accepted shape or role |
202
267
  |-------------|--------------------------------------------------------------------|
203
- | `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC WORK FORK KILL PLAN` |
204
- | `suffix` | `[A-Za-z0-9_]*`, adjacent to `OP` |
268
+ | `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC BARE WORK FORK KILL PLAN` |
269
+ | `suffix` | `[A-Za-z0-9_]*`, adjacent to PLAN or OP |
205
270
  | `[signal]` | Operation-specific tags, identifier, branch, or integer |
206
271
  | `(path)` | Local path or scheme URL target; detailed in §5 |
207
272
  | `<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 |
273
+ | `body` | Opaque section text before the next same-lane heading or EOF |
210
274
 
211
275
  ## §op-shapes 4. Per-operation semantics
212
276
 
@@ -216,14 +280,15 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
216
280
  | OP | `[signal]` | `(path)` | `<scope>` | `body` |
217
281
  |------|-------------------------------|----------------------------------------------|---------------------------------|--------------------------------|
218
282
  | 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 |
283
+ | FIND | optional add log tags | required target or glob | optional result range | optional matcher |
284
+ | READ | optional add log tags | required target | optional text region | empty |
285
+ | EDIT | optional add log tags | required file or entry | required for an existing target | literal text |
286
+ | COPY | optional add log tags | required source | optional source region | required destination selection |
287
+ | MOVE | optional add log tags | required source | optional source region | required destination selection |
288
+ | FOLD | optional filter/change tags | optional log selection | none | optional matcher |
289
+ | OPEN | optional filter/change tags | optional log selection | none | optional matcher |
226
290
  | EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
291
+ | BARE | optional add log tags | none | none | required prompt |
227
292
  | WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
228
293
  | FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
229
294
  | KILL | optional target-specific code | required target, including a log item | none | empty |
@@ -235,15 +300,19 @@ Directed SEND and KILL delegate any present code to the addressed target's
235
300
  operation contract; a live process may interpret a KILL code as a Unix signal,
236
301
  but that interpretation does not define KILL generally.
237
302
 
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
303
+ §plan-intended-goals **PLAN records working-state deltas.** Its concise body adds
304
+ new material conclusions or unresolved questions and the current turn's priorities;
305
+ it does not restate settled context. PLAN is public, durable log content—not provider
240
306
  reasoning. Dispatch records it and has no other runtime effect.
241
307
 
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.
308
+ §log-tag-signal FIND, READ, EDIT, COPY, MOVE, and BARE canonically express additions
309
+ as `+tag`. Because those operations have no tag-selection semantics, ANTLR also
310
+ tolerates unsigned `tag` as an equivalent addition; `-tag` is invalid. Core
311
+ strips any `+`; the signal neither filters nor modifies resources. OPEN and
312
+ FOLD treat every unsigned `tag` as an ALL-tags selector, then add each `+tag`
313
+ and remove each `-tag` from the selected log items. Signed terms never select,
314
+ so either a target, matcher, or unsigned tag is required. Adding and removing
315
+ the same tag conflicts. Neither curation operation exposes a positional scope.
247
316
 
248
317
  The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
249
318
  scopes ordered results. EXEC and SEND scope timing. READ, EDIT, COPY, and
@@ -259,19 +328,34 @@ MOVE use one universal text algebra independent of mimetype:
259
328
  two-integer line selections normalize to the same exclusive-end `TextRegion`
260
329
  used by four-coordinate selections. Whole-line replacement deliberately
261
330
  accounts for newline separators; it is an ergonomic projection over exact
262
- replacement, not a different mimetype navigation mode. As an unadvertised
331
+ replacement, not a different mimetype navigation mode. An end bound beyond
332
+ the available content clamps to the final addressable endpoint; the start bound
333
+ must resolve. As an unadvertised
263
334
  ingestion tolerance, the runtime accepts three integers as
264
335
  `startLine,startColumn,endLine` and immediately normalizes them to the complete
265
336
  four-coordinate region ending after the final code point of `endLine`.
266
337
  Producers never emit that form. Other arities and decimal text coordinates are
267
338
  runtime 416 failures.
268
339
 
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.
340
+ §bare-statement **BARE requests one isolated model inference.** Its required
341
+ body is the complete prompt. It admits only optional additive log tags: no
342
+ target, scope, persistent worker identity, or output-language statement shape
343
+ is represented in the AST. Runtime provider selection, batching, accounting,
344
+ and observation timing belong to the consuming service.
345
+
346
+ §read-find-normalization An authored READ with a nonempty matcher body or a
347
+ target path classified as a glob normalizes during AST construction to one
348
+ ordinary FIND statement. Target, signals, scope, and matcher are preserved;
349
+ FIND's result pagination and projection contract then applies. The canonical
350
+ AST retains no parallel matcher-READ mode, and the runtime performs no READ
351
+ fan-out.
352
+
353
+ §read-exact-target After normalization, READ targets one exact resource (a
354
+ local path or scheme URL, with optional `#channel` fragment or
355
+ `{header: value}` metadata) and has no matcher body. A `<scope>` on READ selects
356
+ a text region from that exact target. Without a scope, READ defaults to
357
+ `<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope components are
358
+ invalid on READ.
275
359
 
276
360
  Mutation semantics:
277
361
 
@@ -281,13 +365,13 @@ Mutation semantics:
281
365
  - An empty body deletes the selected text.
282
366
  - `<0>` prepends and `<-1>` appends.
283
367
  - `<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.
368
+ - §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
369
 
286
370
  ### §operation-observation Per-operation observations
287
371
 
288
372
  | OP | Successful observation |
289
373
  |------|-----------------------------------------------------------------------------------|
290
- | FIND | Resource catalog rows or exact-target match locations |
374
+ | FIND | Resource catalog groups or exact-target match locations |
291
375
  | READ | Complete or scoped body projections plus optional text match evidence |
292
376
  | EDIT | Status plus a bounded receipt for the effect that landed |
293
377
  | COPY | Source and destination selections plus ordered destination effects |
@@ -296,6 +380,7 @@ Mutation semantics:
296
380
  | FOLD | Status and matched log-item count |
297
381
  | SEND | Status and recipient acknowledgement when applicable |
298
382
  | EXEC | Spawn acknowledgement; output arrives through named stream channels |
383
+ | BARE | The one-shot model response |
299
384
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
300
385
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
301
386
  | KILL | Status of deletion or termination |
@@ -315,7 +400,6 @@ each landed textual create or update carries the same bounded receipt used by
315
400
  EDIT. Whole-channel transfers remain bodyless structural effects; runtime
316
401
  owners reject binary markers rather than treating a text field as a byte lane.
317
402
 
318
-
319
403
  Every operation returns the runtime-neutral `OperationResult` defined by
320
404
  {§operation-result}. Its `status` belongs to the result envelope and is not a
321
405
  SEND signal. Durable operation observations are projected into a later packet;
@@ -362,7 +446,7 @@ pairs such as `\*` retain both characters for glob interpretation. Encoding
362
446
  escapes backslashes before parentheses, so every target string round-trips.
363
447
  Pathname producers retain the deliberate `%28`/`%29` alias and `%3C` spelling,
364
448
  but must use the lexical layer for identity-bearing query and fragment text
365
- rather than changing their percent-encoded spelling. Newlines and `<<` are
449
+ rather than changing their percent-encoded spelling. Newlines and raw `<` are
366
450
  never target content. Glob metacharacters remain legal path data.
367
451
 
368
452
  §path-glob `PathSyntax` owns exact-path versus path-pattern classification.
@@ -394,9 +478,10 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
394
478
 
395
479
  ## §matcher-prefix-claims 6. Bulk pattern matching
396
480
 
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.
481
+ FIND, OPEN, FOLD, and authored READ accept an optional body matcher. The lexer
482
+ preserves the body opaquely; AstBuilder assigns the dialect from its leading
483
+ characters, then normalizes matcher-bearing READ to FIND under
484
+ {§read-find-normalization}.
400
485
  A leading prefix claims its dialect. Invalid claimed syntax is a positioned
401
486
  visitor error and never falls back to glob matching.
402
487
 
@@ -422,9 +507,9 @@ statements remain recoverable when their boundaries are trustworthy.
422
507
 
423
508
  - §pattern-body-single-line The GBNF rail permits only single-line matcher
424
509
  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.
510
+ ANTLR preserves the complete section body; the same-lane heading boundary
511
+ keeps every following statement independently parseable without a matcher-
512
+ specific implicit close rule.
428
513
  - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
429
514
  character. Empty matchers and later colons remain valid; a regex such as
430
515
  `/^:needle/` expresses a pattern beginning with a literal colon.
@@ -432,18 +517,22 @@ statements remain recoverable when their boundaries are trustworthy.
432
517
  ## §scope-slot 7. Scope markers
433
518
 
434
519
  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 |
520
+ `lineMarker`. Numeric scopes preserve ordered components in `LineMarker`;
521
+ text-coordinate operations use `TextLineMarker`, whose line positions may also
522
+ carry rendered anchors. The operation owner assigns every component's role.
523
+
524
+ The operation column names the canonical AST operation after
525
+ read-find-normalization}.
526
+
527
+ | Operation | Canonical components | Meaning |
528
+ |-----------------------|----------------------------------------|----------------------------------------------------------------------------|
529
+ | FIND | optional threshold, then 0–2 positions | Inclusive resource or exact-target location positions ({§find-result-unit}; defaults to `<1,16>`) |
530
+ | READ / client LOOK | 0/1/2/4 text coordinates | Text projection from one exact selected file, entry, or log item |
531
+ | EDIT | 0/1/2/4 text coordinates | Text replacement, deletion, prepend, or append |
532
+ | COPY/MOVE source | 0/1/2/4 text coordinates | Region copied or moved from the selected source |
533
+ | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
534
+ | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
535
+ | Terminal SEND `[202]` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence |
447
536
 
448
537
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
449
538
  whole line, two integers are an inclusive whole-line range, and four integers
@@ -452,11 +541,23 @@ admit `0` as prepend and `-1` as append. A leading decimal on semantic FIND
452
541
  is a similarity threshold; any remaining integers select result positions. READ
453
542
  does not admit decimal scope components.
454
543
 
544
+ §text-line-anchor-syntax A text coordinate admits a case-sensitive line anchor
545
+ spelled `@` followed by exactly five Base62 characters (`0-9A-Za-z`) wherever
546
+ its `L`, `SL`, or `EL` position denotes a line. Columns, prepend `0`, and append
547
+ `-1` remain numeric. Exact READ, EDIT, COPY/MOVE source and destination, and
548
+ client LOOK preserve these positions in `TextLineMarker`; core resolves them
549
+ against the addressed current text before operation-specific numeric scope
550
+ semantics run. A matcher-bearing or path-glob READ normalizes to FIND, whose
551
+ result positions remain numeric and reject anchors. Numeric text scopes remain
552
+ canonical and fully supported. Parser acceptance does not imply model-facing
553
+ recommendation.
554
+
455
555
  §scope-marker-forms Canonical producers separate components with commas and no
456
556
  spaces. ANTLR tolerates a dash separator and one space after a comma. Each
457
557
  component greedily consumes an optional leading minus sign, digits, and an
458
558
  optional decimal fraction, so the ingester preserves even noncanonical numeric
459
- shapes for runtime validation.
559
+ shapes for runtime validation. An anchor-bearing text scope uses commas; ANTLR
560
+ tolerates one space after each comma.
460
561
 
461
562
  Apart from the unadvertised three-coordinate text-scope tolerance in
462
563
  {§text-scope-semantics}, the runtime rejects invalid arity, out-of-range or
@@ -467,45 +568,44 @@ does not enforce either condition.
467
568
 
468
569
  ## §suffix-discipline 8. Suffix Discipline
469
570
 
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).
571
+ The suffix is a turn-wide heading lane. A heading carrying the active lane
572
+ is structural; an otherwise valid PLURNK heading carrying another lane is body
573
+ text. The lane therefore makes literal or nested PLURNK unambiguous.
481
574
 
482
575
  Suffix rules:
483
576
 
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:
498
-
499
- ```
500
- <<SEND1[400]:
501
- The following is a quoted plurnk operation, preserved verbatim:
502
- <<SEND[400](worker://reviewer):still working:SEND
503
- :SEND1
577
+ - `suffix` is `[A-Za-z0-9_]*`, concatenated to PLAN or OP with no separator.
578
+ - The H1 PLAN establishes the lane; every real H2 operation heading in that
579
+ turn has the exact same suffix.
580
+ - An empty suffix is accepted only by ANTLR ingestion. Canonical teaching and
581
+ the generated rail use `0` on PLAN and every operation.
582
+ - A body may contain any heading whose suffix differs from the active lane.
583
+ - To carry a nested turn written with lane `0`, choose another suffix for the
584
+ outer turn and repeat it on every outer heading.
585
+ - The GBNF deliberately emits only lane `0`. It cannot emit body content that
586
+ contains a same-lane structural heading; unconstrained producers use another
587
+ outer lane when that representation is required.
588
+
589
+ Example — a lane-0 turn stored inside a lane-2 EDIT body:
590
+
591
+ ```plurnk
592
+ # PLAN2
593
+ Store the quoted turn.
594
+
595
+ ## EDIT2 (worker:///quoted.plurnk)
596
+ # PLAN0
597
+ Answer from memory.
598
+
599
+ ## SEND0 [200]
600
+ Paris.
601
+
602
+ ## SEND2 [200]
603
+ Stored the quoted turn.
504
604
  ```
505
605
 
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.
606
+ The lane-0 headings are ordinary EDIT body text because the outer turn's
607
+ structural lane is `2`. This rule belongs to section framing and applies to
608
+ every operation, not to EDIT semantics.
509
609
 
510
610
  ## 9. SEND Codes
511
611
 
@@ -547,7 +647,7 @@ structural:
547
647
  - §no-idle-102 A **zero-statement turn may not conclude `[102]`** — "continue"
548
648
  with nothing submitted is a spin. The GBNF's `tail-0` exits through
549
649
  a terminal trie without the `[102]` tail, so the idle turn (`PLAN`
550
- straight into `SEND[102]`) is unemittable; one statement restores
650
+ straight into `## SEND0 [102]`) is unemittable; one statement restores
551
651
  the full disposition set. The other four stay legal bare (a zero-op
552
652
  `[202]` is the engine's obligation check). ANTLR stays tolerant
553
653
  (ingest side). A dispatch-emptied turn — ops emitted but failing
@@ -576,61 +676,60 @@ AST. Generated TypeScript targets the `antlr4ng` runtime.
576
676
  stateDiagram-v2
577
677
  [*] --> DEFAULT
578
678
  DEFAULT --> DEFAULT: whitespace or TEXT
579
- DEFAULT --> SLOTS: minted <<OPsuffix
679
+ DEFAULT --> SLOTS: H1 PLANlane or H2 OPlane
580
680
  SLOTS --> SIGNAL: signal opener
581
681
  SIGNAL --> SLOTS: signal close
582
682
  SLOTS --> TARGET: target opener
583
683
  TARGET --> TARGET: balanced literals / target escapes
584
684
  TARGET --> SLOTS: target close at depth zero
585
685
  SLOTS --> SLOTS: scope token
586
- SLOTS --> BODY: body-opening colon
587
- BODY --> DEFAULT: matching close tag
686
+ SLOTS --> BODY: heading line end
687
+ BODY --> DEFAULT: same-lane heading boundary
688
+ BODY --> [*]: end of input
588
689
  ```
589
690
 
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.
691
+ The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
692
+ PLAN or H2 minted operation carrying that exact lane. SLOTS admits
693
+ operation-appropriate signal, target, and scope openers in any order; the parser
694
+ grammar enforces at-most-once multiplicity. Signal submodes select tags, integer,
695
+ or identifier tokens by operation family. TARGET preserves balanced inner
696
+ parentheses and recognized target escapes. BODY emits opaque text until a
697
+ same-lane heading boundary or EOF.
595
698
 
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.
699
+ A differently suffixed heading stays BODY text. Multi-turn logs are plain
700
+ sequences of independently lane-anchored PLAN turns. Complete native reasoning
701
+ enclosures before PLAN remain one TEXT token so an operation drafted inside
702
+ provider reasoning cannot become the turn anchor.
605
703
 
606
704
  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.
705
+ failures. PlurnkErrorStrategy recovers at structural heading boundaries where
706
+ possible. EOF is a valid body boundary. An unfinished signal or target produces
707
+ `unparsedTail`; no later input is trustworthy.
610
708
 
611
709
  ## §whitespace-contract 11. Whitespace and interstatement text
612
710
 
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 |
711
+ | Location | Canonical generation | Tolerant ANTLR ingestion |
712
+ |-----------------------------|---------------------------------------|-----------------------------------------------------------|
713
+ | Heading marker | `# PLAN0` or `## OP0` at column zero | The initial PLAN may directly follow leading TEXT; subsequent headings retain exact depth and column |
714
+ | Between OP and suffix | Adjacent | Must remain adjacent |
715
+ | Before each header slot | One ASCII space | Zero or more horizontal whitespace characters |
716
+ | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
717
+ | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline is invalid |
718
+ | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
719
+ | Inside body | Character-perfect | Character-perfect |
720
+ | Between canonical sections | One empty separator line | A directly following structural heading is also admitted |
721
+ | Before the first PLAN | Nothing | Whitespace or TEXT may surface as preamble items without requiring a separator before PLAN |
623
722
 
624
723
  PLURNK never escape-decodes body text: `\n` reaches the owning operation as
625
724
  backslash plus `n`. A matcher or executor may interpret those characters under
626
725
  its own body dialect. Producers that need a physical newline in literal EDIT
627
726
  content emit an actual newline.
628
727
 
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.
728
+ `parse` admits TEXT before its PLAN, including without an intervening line
729
+ break, and returns it as ordered text items without assigning semantics. Once
730
+ a heading begins, all nonstructural text belongs to that section body.
731
+ `parseStatements` and `parseClient` admit H2 statements;
732
+ `parseLog` admits consecutive H1 PLAN turns. PLURNK defines no comment syntax.
634
733
 
635
734
  ## §public-api 12. Public API
636
735
 
@@ -639,20 +738,28 @@ and wire types come from generated schemas; the small hand-maintained parser
639
738
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
640
739
  express. Consumers never receive ANTLR parse-tree or token types.
641
740
 
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.
741
+ §turn-shape `PlurnkParser.parse` accepts exactly one model turn. H1 PLAN is the
742
+ first operation, a disposition-coded H2 SEND is the terminal operation, and PLAN
743
+ cannot recur mid-turn. Tolerated TEXT may appear only before PLAN; after PLAN,
744
+ nonstructural text is section body content. Missing either anchor or placing a
745
+ same-lane operation after the terminal SEND is an error.
746
+
747
+ §document-fence `PlurnkParser.parse` additionally admits one outer Markdown code
748
+ fence whose opening line is exactly ```` ```plurnk ```` and whose closing line,
749
+ when present, is ```` ``` ````. The fence encloses the complete
750
+ PLAN-through-SEND turn and projects neither text nor body content into the AST.
751
+ Its opener commits the document to either that closer or EOF immediately after
752
+ the complete turn. This is document framing, not another statement grammar, and
753
+ no other parser tier admits it. GBNF continues to shape the paired form.
647
754
 
648
755
  §tier-entrypoints Each parser entry point owns one document tier:
649
756
 
650
757
  | Entry point | Accepted document | Result statement type |
651
758
  |--------------------------------|----------------------------------------------------------------|-----------------------|
652
- | `PlurnkParser.parse` | One PLAN-anchored model turn with optional TEXT | `PlurnkStatement` |
759
+ | `PlurnkParser.parse` | One PLAN turn: bare with optional TEXT, or outer `plurnk` fence ending at its closer or EOF | `PlurnkStatement` |
653
760
  | `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` |
761
+ | `PlurnkParser.parseLog` | One or more consecutive same-lane PLAN-anchored turns | `PlurnkStatement` |
762
+ | `PlurnkParser.parseClient` | H2 protocol statements plus read-shaped LOOK/BUFF commands | `ClientStatement` |
656
763
 
657
764
  Every entry point returns ordered `statement`, `error`, and, where admitted,
658
765
  `text` items. When present, {§unparsed-tail-boundary} governs the result's item
@@ -756,7 +863,6 @@ scope, or other persistence columns.
756
863
  | `entryId` | Positive durable entry identifier |
757
864
  | `target` | Client selector for the resolved entry, with any channel fragment removed |
758
865
  | `channels` | Every channel for a full read, or exactly the selected channel for a sliced read |
759
- | `tags` | Unique non-empty entry tags |
760
866
 
761
867
  | Channel field | Contract |
762
868
  |-----------------|---------------------------------------------------------------------------------------------------------------|
@@ -764,7 +870,7 @@ scope, or other persistence columns.
764
870
  | `contentOffset` | Actual Unicode-code-point offset of `content`; zero for a full read and capped at `contentLength` |
765
871
  | `contentLength` | Unicode-code-point length of the complete stored channel |
766
872
  | `mimetype` | Stored channel mimetype |
767
- | `tokens` | Stored token count for the complete channel |
873
+ | `weight` | Stored model-independent curation weight for the complete channel |
768
874
  | `state` | `static`, `active`, `closed`, or `errored` |
769
875
 
770
876
  For every returned channel,
@@ -861,7 +967,7 @@ class PlurnkParseError extends Error {
861
967
  ```
862
968
 
863
969
  §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
970
+ statement's `position` identifies the first `#` of its heading; a diagnostic
865
971
  identifies the offending or recovery point; a text item and `unparsedTail.from`
866
972
  identify the first point at which that item or undefined tail begins. A
867
973
  statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
@@ -887,48 +993,40 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
887
993
  | Source | Boundary |
888
994
  |-------------|----------------------------------------------------------------------------------------|
889
995
  | `"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. |
996
+ | `"parser"` | Structural failure, such as the wrong heading depth or slot order. |
891
997
  | `"visitor"` | Semantic AST-construction failure, such as an invalid matcher dialect or signal shape. |
892
998
 
893
999
  `severity` distinguishes a hard error from a non-fatal advisory. The parser is
894
1000
  the sole and complete owner of syntax-error messaging because it holds the
895
1001
  parse state, lexer mode, and expected-token set that no consumer has. It
896
1002
  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.
1003
+ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1004
+ `## SEND0 [code]`), and these targeted diagnostics:
1005
+
908
1006
  - §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
909
1007
  `[signal]` slot (executor-ident mode) hits a leading `-` or digit —
910
1008
  mark-shaped `<timeout, poll>` scope content mistyped into the brackets — the
911
- lexer message becomes `timeout/poll ride the \`<scope>\` slot; try
912
- \`EXEC<-1,300>\`` instead of a raw `unrecognized character`. The redirect is
1009
+ lexer message becomes timeout/poll ride the `<scope>` slot; try
1010
+ `## EXEC0 <-1,300>`” instead of a raw `unrecognized character`. The redirect is
913
1011
  EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
914
1012
  - §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.
1013
+ post-target header region begins with `$`, `~`, or `@`, the lexer redirects
1014
+ the unambiguous matcher to the first body line instead of returning the
1015
+ generic slot list. Slash-led regex and XPath are excluded because `/` can be
1016
+ target data.
1017
+ - §combined-anchor-line-redirect **Combined anchor and line number in a scope.**
1018
+ A text-coordinate scope containing `@hash:L` or `@hash L` is one bounded hard
1019
+ error: `a scope position accepts one line coordinate; use the \`@hash\` anchor
1020
+ without its displayed line number`. A malformed header scope is consumed as
1021
+ one token, while a COPY/MOVE destination selection fails at its visitor
1022
+ boundary; neither produces a punctuation cascade.
924
1023
  - §misplaced-target-advisory **Mutation target in the signal slot.** When a
925
1024
  mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
926
1025
  `[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.
1026
+ path into `(…)` (`\`## EDIT0\` has no \`(target)\` - that path sits in the
1027
+ \`[…]\` tag slot; a target goes in \`(…)\`. Try \`## EDIT0 (path)\``). It is
1028
+ gated on a path-shaped signal so a genuine additive-tag signal is not mis-steered
1029
+ toward a path it lacks.
932
1030
 
933
1031
  §error-shape The diagnostic class determines how much guidance the parser may
934
1032
  provide:
@@ -938,20 +1036,20 @@ provide:
938
1036
  | Hard fact | `severity: "error"` | One concise observed fact and violated constraint in PLURNK vocabulary. |
939
1037
  | Targeted hard redirect | `severity: "error"` | One canonical correction only when parser state makes the intended structure unambiguous. |
940
1038
  | 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. |
1039
+ | Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
942
1040
 
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
1041
+ All messages use PLURNK protocol vocabulary: heading, lane, signal, target,
1042
+ scope, line marker, body, section boundary, or space between slots. They never
1043
+ expose ANTLR rule or token names. They refer to a slot or
946
1044
  feature rather than an implementation rule. Generic tutoring, speculative
947
1045
  intent, coordinate restatement, and multiple repair strategies are forbidden.
948
1046
 
949
1047
  Examples of canonical hard facts:
950
1048
 
951
- - `unrecognized character '<<' in target`
1049
+ - `unrecognized character '<' in target`
952
1050
  - `unrecognized character ':' in signal`
953
1051
  - `unrecognized character 'X' in statement header`
954
- - `expected close tag; got end of input`
1052
+ - `a turn must begin with \`# PLAN0\``
955
1053
  - `expected ')'; got ':'`
956
1054
 
957
1055
  Each malformed statement produces at most one hard error. The first recorded
@@ -986,6 +1084,6 @@ runtime constructs this; the parser provides the fields):
986
1084
  "column": 12,
987
1085
  "source": "parser",
988
1086
  "severity": "error",
989
- "message": "expected close tag `:OPsuffix`; got end of input"
1087
+ "message": "target slot of `## READ0` opened at line 1 but never closed - add `)`"
990
1088
  }
991
1089
  ```