@plurnk/plurnk-contracts 1.4.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 (64) hide show
  1. package/README.md +10 -6
  2. package/SPEC.md +368 -231
  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/MatcherBody.json +1 -1
  8. package/dist/schema/OperationResult.json +3 -0
  9. package/dist/schema/PlurnkStatement.json +57 -12
  10. package/dist/schema/ProposalProjection.json +1 -1
  11. package/dist/schema/ProviderAccounting.json +30 -0
  12. package/dist/schema/ProviderCost.json +6 -15
  13. package/dist/schema/ProviderRequestAccounting.json +17 -0
  14. package/dist/schema/ProviderUsage.json +40 -0
  15. package/dist/schema/RangeExtent.json +47 -0
  16. package/dist/schema/ResourceSelection.json +1 -1
  17. package/dist/schema/TextLineMarker.json +21 -0
  18. package/dist/src/AstBuilder.d.ts.map +1 -1
  19. package/dist/src/AstBuilder.js +123 -22
  20. package/dist/src/AstBuilder.js.map +1 -1
  21. package/dist/src/JsonResult.d.ts +3 -0
  22. package/dist/src/JsonResult.d.ts.map +1 -0
  23. package/dist/src/JsonResult.js +37 -0
  24. package/dist/src/JsonResult.js.map +1 -0
  25. package/dist/src/PlurnkErrorStrategy.d.ts +1 -0
  26. package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
  27. package/dist/src/PlurnkErrorStrategy.js +42 -19
  28. package/dist/src/PlurnkErrorStrategy.js.map +1 -1
  29. package/dist/src/PlurnkParser.d.ts.map +1 -1
  30. package/dist/src/PlurnkParser.js +22 -139
  31. package/dist/src/PlurnkParser.js.map +1 -1
  32. package/dist/src/TagSignal.d.ts +15 -0
  33. package/dist/src/TagSignal.d.ts.map +1 -0
  34. package/dist/src/TagSignal.js +48 -0
  35. package/dist/src/TagSignal.js.map +1 -0
  36. package/dist/src/Validator.d.ts +6 -1
  37. package/dist/src/Validator.d.ts.map +1 -1
  38. package/dist/src/Validator.js +36 -1
  39. package/dist/src/Validator.js.map +1 -1
  40. package/dist/src/generated/plurnkLexer.d.ts +99 -54
  41. package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
  42. package/dist/src/generated/plurnkLexer.js +889 -498
  43. package/dist/src/generated/plurnkLexer.js.map +1 -1
  44. package/dist/src/generated/plurnkParser.d.ts +99 -108
  45. package/dist/src/generated/plurnkParser.d.ts.map +1 -1
  46. package/dist/src/generated/plurnkParser.js +943 -1068
  47. package/dist/src/generated/plurnkParser.js.map +1 -1
  48. package/dist/src/generated/plurnkParserVisitor.d.ts +19 -5
  49. package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
  50. package/dist/src/generated/plurnkParserVisitor.js +16 -4
  51. package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
  52. package/dist/src/index.d.ts +6 -2
  53. package/dist/src/index.d.ts.map +1 -1
  54. package/dist/src/index.js +4 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/types.d.ts +2 -1
  57. package/dist/src/types.d.ts.map +1 -1
  58. package/dist/src/types.generated.d.ts +98 -24
  59. package/dist/src/types.generated.d.ts.map +1 -1
  60. package/dist/src/types.js +4 -1
  61. package/dist/src/types.js.map +1 -1
  62. package/package.json +8 -8
  63. package/plurnk.md +143 -133
  64. package/dist/plurnk.gbnf +0 -394
package/SPEC.md CHANGED
@@ -8,18 +8,26 @@ is the single code API for those contracts.
8
8
 
9
9
  | Surface | Canonical export or artifact |
10
10
  | ------------------------------------------------------------------------------- | --------------------------------------------------- |
11
- | Parser, AST, validators, Problems, results, Notices, text regions | `@plurnk/plurnk-contracts` |
11
+ | Parser, AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
12
12
  | Effective loop policy and its default | `LoopFlags`, `DEFAULT_LOOP_FLAGS` |
13
13
  | Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
14
14
  | Client capability presentation | `ClientDisplayCapabilities` |
15
15
  | JSON Schemas | `@plurnk/plurnk-contracts/schema/*.json` |
16
- | Local-model rail | `@plurnk/plurnk-contracts/plurnk.gbnf` |
16
+ | Generated JSON result rendering | `renderJsonResult` |
17
+ | Local-model rails | `@plurnk/plurnk-contracts/plurnk.{gemma,qwen}.gbnf` |
17
18
  | Model language reference | `plurnk.md` in the package |
18
19
 
19
20
  §contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
20
21
  generated from the schemas; ANTLR is authoritative for accepted model-language
21
22
  syntax; GBNF remains the bounded generation aid described in §1.2.
22
23
 
24
+ §json-result-rendering `renderJsonResult` is the one presentation serializer
25
+ for generated JSON operation results. A top-level array remains one valid,
26
+ compact JSON value but places each item on its own physical line by adding only
27
+ item-boundary newlines; an empty or single-item array and every non-array value
28
+ remain one line. It never rewrites arbitrary stored JSON, whose original lines
29
+ remain source coordinates.
30
+
23
31
  ## §contract-layers 1.1 Contract layers and admission boundary
24
32
 
25
33
  PLURNK uses one contract with deliberately different projections. A tolerant
@@ -30,7 +38,7 @@ semantics valid.
30
38
  ```mermaid
31
39
  flowchart LR
32
40
  canon["Canonical model teaching<br/>plurnk.md"]
33
- rail["Optional raw generation rail<br/>plurnk.gbnf"]
41
+ rail["Optional raw generation rail<br/>Gemma or Qwen template profile"]
34
42
  free["Other admitted input"]
35
43
  syntax["ANTLR lexer + parser<br/>syntax and document tier"]
36
44
  ast["AstBuilder<br/>typed, serializable AST"]
@@ -47,8 +55,8 @@ flowchart LR
47
55
  |--------------------------|-------------------------------------|---------------------------------------------------------------------------------|
48
56
  | Stable current law | `SPEC.md` | Owns invariants and boundaries; forge issues retain history |
49
57
  | Canonical model teaching | `plurnk.md` | Teaches the lean spelling and operational model the model should emit |
50
- | Constrained generation | generated `plurnk.gbnf` | Increases likely ANTLR compliance without reproducing all parser/runtime checks |
51
- | 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 |
52
60
  | Typed admission | `AstBuilder` | Produces JSON-serializable unions and validates deterministic body/path syntax |
53
61
  | Shared wire data | `schema/*.json` | Defines runtime-neutral data shapes projected into generated TypeScript |
54
62
  | Stateful behavior | consuming runtime | Resolves addresses, permissions, selection arithmetic, effects, and lifecycle |
@@ -72,17 +80,43 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
72
80
  | `LoopFlags` | Complete effective `mode`, `auto`, `noWeb`, `noInteraction`, and `noProposals` | Validate/expand persisted partial policy before use |
73
81
  | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
74
82
  | `ProposalProjection` | Identity, review target/body/attrs, effective flags, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
75
- | `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 |
76
87
 
77
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.
78
89
 
79
- §provider-cost `ProviderCost` represents one provider attempt's monetary truth.
80
- An authoritative result preserves the provider's canonical decimal amount and
81
- currency plus its decimal USD equivalent; an estimate carries a decimal USD
82
- amount; free requires an explicit source; unknown requires a reason. Decimal
83
- strings preserve evidence without binary floating-point rewriting. Unknown is
84
- not zero, and no consumer may infer free from absent rates or a legacy numeric
85
- zero.
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.
86
120
 
87
121
  The parser returns ordered statement, error, and text items. It recovers at a
88
122
  trustworthy statement boundary when possible and sets `unparsedTail` when a
@@ -92,110 +126,152 @@ and parse diagnostics are separate contracts.
92
126
  ## 1.2 GBNF Generation Rail
93
127
 
94
128
  §gbnf-rail-purpose ANTLR and AstBuilder define accepted PLURNK input. The generated
95
- `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
96
130
  to make useful, ANTLR-compliant turns more likely without reproducing every
97
131
  parser or semantic validator. Parse compatibility is a design goal balanced
98
132
  against rail size and sampling efficiency, not a language-subset guarantee. A
99
133
  rail-legal operation can therefore produce a parser or AstBuilder error; consumers
100
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.
101
138
 
102
139
 
103
- The shipped raw turn has one shape:
140
+ The rails share one turn shape but begin at their respective sampled-token
141
+ boundaries:
104
142
 
105
143
  ```ebnf
106
- 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
107
149
  ```
108
150
 
109
- §gbnf-turn-shape `channel` is exactly one Gemma Harmony enclosure at byte zero, beginning
110
- `<|channel>thought\n` and ending `<channel|>`. Its body may be empty but cannot
111
- contain another opener or the closer. `sep` is zero through seven whitespace
112
- characters. No channel is legal after the leading one. `tail-0` is unchanged
113
- apart from that removal: zero through fourteen internal statements, separated
114
- only by `sep`, followed by exactly one terminal SEND under the existing terminal
115
- 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.
116
164
 
117
165
  ```mermaid
118
166
  flowchart LR
119
- 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"]
120
169
  split["llama.cpp<br/>reasoning_format: auto"]
121
- reasoning["reasoning_content<br/>channel body"]
122
- 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"]
123
172
  parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
173
+ sampled --> raw
124
174
  raw --> split
125
175
  split --> reasoning
126
176
  split --> content
127
177
  content --> parser
128
178
  ```
129
179
 
130
- §gbnf-reasoning-boundary GBNF applies to the raw sentence on the left, before projection. The two
131
- projected fields are not separate GBNF languages and `content` alone is not
132
- revalidated as though it still contained the required channel. Provider and
133
- core own the projection evidence and rail-verdict boundary; this package owns
134
- only the raw language and the parser/AstBuilder result.
135
-
136
- §plan-body-no-openers On the GBNF rail, PLAN is unsuffixed and its body excludes
137
- the literal `<<`. A single `<` remains legal. This prevents an omitted `:PLAN`
138
- from consuming following operations as intended-goals text; ANTLR remains
139
- 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.
140
201
 
141
202
  ## §canonical-statement 2. Canonical statement form
142
203
 
143
204
  ```text
144
- <<OPsuffix[signal]?(path)?<scope>?:body?:OPsuffix
145
- ```
205
+ # PLANsuffix
206
+ body
146
207
 
147
- §body-fence The two colons fence the body. Everything after the opening colon and
148
- before the matching `:OPsuffix` close is preserved verbatim and is opaque to
149
- operation keywords, slot characters, and nested PLURNK-looking text. An empty
150
- body retains both delimiters: `::OPsuffix`.
208
+ ## OPsuffix [signal]? (path)? <scope>?
209
+ body?
210
+ ```
151
211
 
152
- | Element | Canonical contract |
153
- |-------------|--------------------------------------------------------------------|
154
- | `<<` | Required statement opener |
155
- | `OP` | One protocol operation |
156
- | `suffix` | Optional enclosure suffix, joined directly to `OP` |
157
- | `[signal]` | Optional operation-specific signal |
158
- | `(path)` | Optional syntax slot whose operation contract may require a target |
159
- | `<scope>` | Optional operation-specific numeric scope |
160
- | `:` | Required body opener, even when the body is empty |
161
- | `body` | Operation-specific, character-perfect content |
162
- | `: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 |
163
234
 
164
235
  The following constraints are structural:
165
236
 
166
- - §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.
167
242
  - Each admitted signal, target, and scope slot appears at most once.
168
- - OPEN, FOLD, WORK, FORK, and KILL do not admit a scope slot.
169
- - 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`.
170
245
 
171
246
  §slot-order Canonical producers and the GBNF rail emit signal, then target, then
172
- scope. The tolerant ANTLR ingester accepts any permutation of the slots admitted
173
- by that operation, at most once each. Accepted permutation is not a second
174
- 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.
175
252
 
176
253
  The ingester also accepts several bounded noncanonical forms so it can explain
177
254
  or safely execute understandable input:
178
255
 
179
- | Tolerated input | Canonical or runtime disposition |
180
- |--------------------------------------------------|---------------------------------------------------------------------|
181
- | 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 |
182
259
  | Missing target on a generally targeted operation | AST carries `null`; the runtime rejects when the target is required |
183
- | PLAN modifiers or suffix | Model canon keeps PLAN slotless and unsuffixed |
184
- | KILL annotation body | AST preserves it; model teaching uses an empty body |
185
- | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
186
- | 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 |
187
264
 
188
265
  ## 3. Lexical elements
189
266
 
190
267
  | Element | Accepted shape or role |
191
268
  |-------------|--------------------------------------------------------------------|
192
- | `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC WORK FORK KILL PLAN` |
193
- | `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 |
194
271
  | `[signal]` | Operation-specific tags, identifier, branch, or integer |
195
272
  | `(path)` | Local path or scheme URL target; detailed in §5 |
196
273
  | `<scope>` | One or more signed integers or decimals; detailed in §7 |
197
- | `body` | Opaque text between the body opener and matching close |
198
- | `:OPsuffix` | Adjacent colon plus the exact opening operation and suffix |
274
+ | `body` | Opaque section text before the next same-lane heading or EOF |
199
275
 
200
276
  ## §op-shapes 4. Per-operation semantics
201
277
 
@@ -205,14 +281,15 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
205
281
  | OP | `[signal]` | `(path)` | `<scope>` | `body` |
206
282
  |------|-------------------------------|----------------------------------------------|---------------------------------|--------------------------------|
207
283
  | PLAN | none | none | none | required intended goals |
208
- | FIND | optional filter tags | required target or glob | optional result range | optional matcher |
209
- | READ | optional filter tags | required target or glob | optional text region | optional matcher |
210
- | EDIT | optional apply tags | required file or entry | required for an existing target | literal text |
211
- | COPY | optional apply tags | required source | optional source region | required destination selection |
212
- | MOVE | optional apply tags | required source | optional source region | required destination selection |
213
- | FOLD | optional apply tags | required log selection | none | optional matcher |
214
- | 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 |
215
291
  | EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
292
+ | BARE | optional add log tags | none | none | required prompt |
216
293
  | WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
217
294
  | FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
218
295
  | KILL | optional target-specific code | required target, including a log item | none | empty |
@@ -224,15 +301,19 @@ Directed SEND and KILL delegate any present code to the addressed target's
224
301
  operation contract; a live process may interpret a KILL code as a Unix signal,
225
302
  but that interpretation does not define KILL generally.
226
303
 
227
- §plan-intended-goals **PLAN records intended goals.** The PLAN body is the model's
228
- 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
229
307
  reasoning. Dispatch records it and has no other runtime effect.
230
308
 
231
- OPEN and FOLD are log-curation operations owned by the core log surface. FOLD
232
- selects a set, hides its bodies, and applies any signal tags. OPEN filters by its
233
- signal tags and may omit the target to recall that tagged working set across the
234
- worker log. FOLD requires a target or matcher; OPEN requires a target, matcher,
235
- 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.
236
317
 
237
318
  The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
238
319
  scopes ordered results. EXEC and SEND scope timing. READ, EDIT, COPY, and
@@ -248,20 +329,34 @@ MOVE use one universal text algebra independent of mimetype:
248
329
  two-integer line selections normalize to the same exclusive-end `TextRegion`
249
330
  used by four-coordinate selections. Whole-line replacement deliberately
250
331
  accounts for newline separators; it is an ergonomic projection over exact
251
- 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
252
335
  ingestion tolerance, the runtime accepts three integers as
253
336
  `startLine,startColumn,endLine` and immediately normalizes them to the complete
254
337
  four-coordinate region ending after the final code point of `endLine`.
255
338
  Producers never emit that form. Other arities and decimal text coordinates are
256
339
  runtime 416 failures.
257
340
 
258
- For READ, a body matcher selects files, entries, or log items against their
259
- complete readable content. A non-semantic `<scope>` then projects text from each
260
- selection; it never paginates the match set or limits where the matcher searches.
261
- Without `<scope>`, READ returns each selection's complete readable content.
262
- Semantic READ reserves a leading decimal for an optional similarity threshold;
263
- the remaining one, two, or four integers project text. Without a leading decimal, every
264
- integer belongs to READ projection and selection uses the configured default.
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.
265
360
 
266
361
  Mutation semantics:
267
362
 
@@ -271,13 +366,13 @@ Mutation semantics:
271
366
  - An empty body deletes the selected text.
272
367
  - `<0>` prepends and `<-1>` appends.
273
368
  - `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
274
- - COPY and MOVE parse the body as a destination `ResourceSelection`: a target plus an optional destination scope. 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.
275
370
 
276
371
  ### §operation-observation Per-operation observations
277
372
 
278
373
  | OP | Successful observation |
279
374
  |------|-----------------------------------------------------------------------------------|
280
- | FIND | Catalog rows plus optional structural locators and text match evidence |
375
+ | FIND | Resource catalog groups or exact-target match locations |
281
376
  | READ | Complete or scoped body projections plus optional text match evidence |
282
377
  | EDIT | Status plus a bounded receipt for the effect that landed |
283
378
  | COPY | Source and destination selections plus ordered destination effects |
@@ -286,11 +381,18 @@ Mutation semantics:
286
381
  | FOLD | Status and matched log-item count |
287
382
  | SEND | Status and recipient acknowledgement when applicable |
288
383
  | EXEC | Spawn acknowledgement; output arrives through named stream channels |
384
+ | BARE | The one-shot model response |
289
385
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
290
386
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
291
387
  | KILL | Status of deletion or termination |
292
388
  | PLAN | Status of durable intended-goals logging |
293
389
 
390
+ §find-result-unit For FIND, authored target shape fixes the paginated result
391
+ unit. An exact target with a matcher pages flat match locations; a glob or
392
+ folder target, and every body-less FIND, pages resources. Resolving a glob to
393
+ one resource does not make it exact. The same `<N>`, inclusive `<N,M>`,
394
+ markerless `<1,16>`, and explicit-all `<1,-1>` forms apply to either unit.
395
+
294
396
  §copy-move-observation COPY and MOVE log projections preserve both admitted operand selections,
295
397
  including their independent scopes, whether the result changed state, was a
296
398
  304 no-op, or failed after admission. Operands identify the request; `effects`
@@ -346,7 +448,7 @@ pairs such as `\*` retain both characters for glob interpretation. Encoding
346
448
  escapes backslashes before parentheses, so every target string round-trips.
347
449
  Pathname producers retain the deliberate `%28`/`%29` alias and `%3C` spelling,
348
450
  but must use the lexical layer for identity-bearing query and fragment text
349
- rather than changing their percent-encoded spelling. Newlines and `<<` are
451
+ rather than changing their percent-encoded spelling. Newlines and raw `<` are
350
452
  never target content. Glob metacharacters remain legal path data.
351
453
 
352
454
  §path-glob `PathSyntax` owns exact-path versus path-pattern classification.
@@ -378,8 +480,10 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
378
480
 
379
481
  ## §matcher-prefix-claims 6. Bulk pattern matching
380
482
 
381
- FIND, READ, OPEN, and FOLD accept an optional body matcher. The lexer preserves
382
- the body opaquely; AstBuilder assigns the dialect 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}.
383
487
  A leading prefix claims its dialect. Invalid claimed syntax is a positioned
384
488
  visitor error and never falls back to glob matching.
385
489
 
@@ -405,7 +509,9 @@ statements remain recoverable when their boundaries are trustworthy.
405
509
 
406
510
  - §pattern-body-single-line The GBNF rail permits only single-line matcher
407
511
  bodies. A regex that matches a newline uses the two-character `\n` escape.
408
- ANTLR tolerates multiline matcher bodies during ingestion.
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.
409
515
  - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
410
516
  character. Empty matchers and later colons remain valid; a regex such as
411
517
  `/^:needle/` expresses a pattern beginning with a literal colon.
@@ -413,31 +519,47 @@ statements remain recoverable when their boundaries are trustworthy.
413
519
  ## §scope-slot 7. Scope markers
414
520
 
415
521
  The model-facing slot is `<scope>`; the AST field remains the historical
416
- `lineMarker: { marks: number[] }`. The parser preserves ordered numeric
417
- components, while the operation owner assigns their roles.
418
-
419
- | Operation | Canonical components | Meaning |
420
- |-----------------------|----------------------------------------------|--------------------------------------------------------------|
421
- | FIND | optional threshold, then 0–2 positions | Inclusive positions in a deterministic result order |
422
- | READ | optional threshold, then 0/1/2/4 coordinates | Text projection from every selected file, entry, or log item |
423
- | EDIT | text coordinates | Text replacement, deletion, prepend, or append |
424
- | COPY/MOVE source | text coordinates | Region copied or moved from the selected source |
425
- | COPY/MOVE destination | text coordinates after destination | Region replaced or insertion point at the destination |
426
- | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
427
- | 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 |
428
538
 
429
539
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
430
540
  whole line, two integers are an inclusive whole-line range, and four integers
431
541
  are an exact start-inclusive/end-exclusive region. Mutation scopes additionally
432
- admit `0` as prepend and `-1` as append. A leading decimal on semantic FIND or
433
- READ is a similarity threshold; any remaining integers retain the operation's
434
- ordinary result or text meaning.
542
+ admit `0` as prepend and `-1` as append. A leading decimal on semantic FIND
543
+ is a similarity threshold; any remaining integers select result positions. READ
544
+ does not admit decimal scope components.
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.
435
556
 
436
557
  §scope-marker-forms Canonical producers separate components with commas and no
437
558
  spaces. ANTLR tolerates a dash separator and one space after a comma. Each
438
559
  component greedily consumes an optional leading minus sign, digits, and an
439
560
  optional decimal fraction, so the ingester preserves even noncanonical numeric
440
- shapes for runtime validation.
561
+ shapes for runtime validation. An anchor-bearing text scope uses commas; ANTLR
562
+ tolerates one space after each comma.
441
563
 
442
564
  Apart from the unadvertised three-coordinate text-scope tolerance in
443
565
  {§text-scope-semantics}, the runtime rejects invalid arity, out-of-range or
@@ -448,45 +570,44 @@ does not enforce either condition.
448
570
 
449
571
  ## §suffix-discipline 8. Suffix Discipline
450
572
 
451
- The `:body:` fencing handles the vast majority of grammatical-enclosure
452
- concerns: body content is fully opaque to OP keywords and modifier-like
453
- characters. The suffix is reserved for the residual edge case where
454
- body content literally contains the close-tag pattern `:OPkeyword`.
455
- That happens in two scenarios:
456
-
457
- 1. **Nesting plurnk statements inside a body** (recording a plurnk
458
- transcript, storing examples, etc.). The inner statement's close
459
- `:OP` would prematurely terminate the outer's body.
460
- 2. **Body content contains `:OPkeyword` as literal text** (e.g., a
461
- 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.
462
576
 
463
577
  Suffix rules:
464
578
 
465
- - `suffix` is `[A-Za-z0-9_]*`, concatenated to `OP` with no separator, on both open and close.
466
- - Open `<<OPsuffix` and close `:OPsuffix` must character-match.
467
- - A non-empty suffix on the outer statement ensures its close tag
468
- (`:OPsuffix`) is distinct from any `:OP` substring that may appear in
469
- body content (whether as nested plurnk or as literal text).
470
- - The body of a statement cannot contain its own exact close-tag
471
- literal; choose a suffix that does not collide.
472
- - Empty suffix is the default. Most statements need no suffix.
473
- - Generation-side canon dictates **digit** suffixes (`<<EDIT1 :EDIT1`)
474
- so the shipped GBNF (`dist/plurnk.gbnf`) can enumerate close tags —
475
- the HEREDOC tag match is not context-free. The parser remains
476
- permissive: any matching `[A-Za-z0-9_]*` suffix is valid.
477
-
478
- 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:
479
592
 
480
593
  ```
481
- <<SEND1[400]:
482
- The following is a quoted plurnk operation, preserved verbatim:
483
- <<SEND[400](worker://reviewer):still working:SEND
484
- :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.
485
606
  ```
486
607
 
487
- The inner's `:SEND` close is ordinary body text because the outer close
488
- is `:SEND1`. This rule belongs to the body fence and applies to every
489
- 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.
490
611
 
491
612
  ## 9. SEND Codes
492
613
 
@@ -528,7 +649,7 @@ structural:
528
649
  - §no-idle-102 A **zero-statement turn may not conclude `[102]`** — "continue"
529
650
  with nothing submitted is a spin. The GBNF's `tail-0` exits through
530
651
  a terminal trie without the `[102]` tail, so the idle turn (`PLAN`
531
- straight into `SEND[102]`) is unemittable; one statement restores
652
+ straight into `## SEND0 [102]`) is unemittable; one statement restores
532
653
  the full disposition set. The other four stay legal bare (a zero-op
533
654
  `[202]` is the engine's obligation check). ANTLR stays tolerant
534
655
  (ingest side). A dispatch-emptied turn — ops emitted but failing
@@ -557,61 +678,60 @@ AST. Generated TypeScript targets the `antlr4ng` runtime.
557
678
  stateDiagram-v2
558
679
  [*] --> DEFAULT
559
680
  DEFAULT --> DEFAULT: whitespace or TEXT
560
- DEFAULT --> SLOTS: minted <<OPsuffix
681
+ DEFAULT --> SLOTS: H1 PLANlane or H2 OPlane
561
682
  SLOTS --> SIGNAL: signal opener
562
683
  SIGNAL --> SLOTS: signal close
563
684
  SLOTS --> TARGET: target opener
564
685
  TARGET --> TARGET: balanced literals / target escapes
565
686
  TARGET --> SLOTS: target close at depth zero
566
687
  SLOTS --> SLOTS: scope token
567
- SLOTS --> BODY: body-opening colon
568
- BODY --> DEFAULT: matching close tag
688
+ SLOTS --> BODY: heading line end
689
+ BODY --> DEFAULT: same-lane heading boundary
690
+ BODY --> [*]: end of input
569
691
  ```
570
692
 
571
- SLOTS admits operation-appropriate signal, target, and scope openers in any
572
- order; the parser grammar enforces at-most-once multiplicity. Signal submodes
573
- select tags, integer, or identifier tokens by operation family. TARGET preserves
574
- balanced inner parentheses and recognized target escapes. BODY emits opaque text until the semantic predicate
575
- 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.
576
700
 
577
- TURN is the single exception to opaque-body mode: its opener returns to DEFAULT
578
- so a complete inner turn parses structurally, and a suffix stack recognizes its
579
- matching close. The narrow one-colon empty-body tolerance closes directly from
580
- SLOTS only at a trustworthy statement boundary.
581
-
582
- DEFAULT recognizes minted operation openers and otherwise emits non-whitespace
583
- input as TEXT. A `<<word` whose word is not a minted operation remains TEXT.
584
- Complete native reasoning enclosures are likewise one TEXT token so an operation
585
- 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.
586
705
 
587
706
  RecordingListener captures lexer and parser failures; AstBuilder adds visitor
588
- failures. PlurnkErrorStrategy recovers at statement boundaries where possible.
589
- An unfinished signal, target, body, or TURN leaves the lexer outside DEFAULT and
590
- 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.
591
710
 
592
711
  ## §whitespace-contract 11. Whitespace and interstatement text
593
712
 
594
- | Location | Canonical generation | Tolerant ANTLR ingestion |
595
- |-------------------------|--------------------------------------|-------------------------------------------------------------|
596
- | Between header slots | Adjacent | Spaces, tabs, and newlines are ignored |
597
- | Between `OP` and suffix | Adjacent | Must remain adjacent |
598
- | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
599
- | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline and `<<` are invalid |
600
- | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
601
- | Inside body | Character-perfect | Character-perfect |
602
- | Inside close tag | Colon and `OPsuffix` are adjacent | Must remain adjacent and match the opener |
603
- | 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 |
604
724
 
605
725
  PLURNK never escape-decodes body text: `\n` reaches the owning operation as
606
726
  backslash plus `n`. A matcher or executor may interpret those characters under
607
727
  its own body dialect. Producers that need a physical newline in literal EDIT
608
728
  content emit an actual newline.
609
729
 
610
- `parse` and `parseLog` admit TEXT around their PLAN-anchored operations and
611
- return it as ordered text items without assigning semantics. `parseStatements`
612
- and `parseClient` admit statements and hidden whitespace only. PLURNK defines no
613
- comment syntax; a consumer may treat script-tier text as commentary, but that is
614
- 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.
615
735
 
616
736
  ## §public-api 12. Public API
617
737
 
@@ -620,20 +740,28 @@ and wire types come from generated schemas; the small hand-maintained parser
620
740
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
621
741
  express. Consumers never receive ANTLR parse-tree or token types.
622
742
 
623
- §turn-shape `PlurnkParser.parse` accepts exactly one model turn. PLAN is the
624
- first operation, a disposition-coded SEND is the terminal operation, and PLAN
625
- cannot recur mid-turn. Tolerated TEXT may appear before PLAN, between operations,
626
- and after SEND; it remains an ordered text item. Missing either anchor or placing
627
- 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.
628
756
 
629
757
  §tier-entrypoints Each parser entry point owns one document tier:
630
758
 
631
759
  | Entry point | Accepted document | Result statement type |
632
760
  |--------------------------------|----------------------------------------------------------------|-----------------------|
633
- | `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` |
634
762
  | `PlurnkParser.parseStatements` | Zero or more protocol statements and hidden whitespace | `PlurnkStatement` |
635
- | `PlurnkParser.parseLog` | One or more TURN-wrapped model turns | `PlurnkStatement` |
636
- | `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` |
637
765
 
638
766
  Every entry point returns ordered `statement`, `error`, and, where admitted,
639
767
  `text` items. When present, {§unparsed-tail-boundary} governs the result's item
@@ -654,6 +782,7 @@ following supported consumer values. All other root exports are TypeScript types
654
782
  | `InvalidProblemDetailsError` | Typed failure from `Validator.assertProblemDetails` | {§problem-details} |
655
783
  | `InvalidOperationResultError` | Typed failure from `Validator.assertOperationResult` | {§operation-result} |
656
784
  | `InvalidTextRegionError` | Typed failure from `Validator.assertTextRegion` | {§text-region} |
785
+ | `InvalidRangeExtentError` | Typed failure from `Validator.assertRangeExtent` | {§range-extent} |
657
786
  | `Problems` | RFC 9457 Problem construction | {§problem-details} |
658
787
  | `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
659
788
  | `WORKER_NAME`, `RESERVED_AUTHORITIES` | Authority minting predicate and internal reserved names | {§worker-name} |
@@ -703,7 +832,24 @@ point. A producer supplies all four coordinates or omits the region. It never
703
832
  substitutes UTF-16 offsets, readable-row indices, or partial coordinates.
704
833
  `Validator.assertTextRegion` rejects an end before its start.
705
834
 
706
- ### §entry-read-result 13.2 Client entry reads
835
+ ### §range-extent 13.2 Range extents
836
+
837
+ `RangeExtent` is the compact wire projection of one line or ordered-result
838
+ selection: `{ unit, total, requested: [first,last], returned?: [first,last] }`.
839
+ `requested` preserves the numeric request, including invalid fractional
840
+ evidence on a failed selection; a one-position request therefore repeats its
841
+ endpoint. Successful selection endpoints are integers. `total` is the complete
842
+ available cardinality.
843
+ `returned` names the inclusive positions actually projected and is absent for
844
+ an empty selection or a failed request. Its endpoints are positive, ordered,
845
+ and no greater than `total`.
846
+
847
+ The transparent coordinates make completion and continuation derivable. The
848
+ shape has no separate `complete`, `next`, or all-results instruction. Exact
849
+ text-coordinate selections use {§text-region} instead. `Validator.assertRangeExtent`
850
+ enforces both the schema and the relational endpoint invariants.
851
+
852
+ ### §entry-read-result 13.3 Client entry reads
707
853
 
708
854
  `EntryReadResult` is the exact transport-neutral projection of one entry. It
709
855
  does not expose workspace IDs, storage owner IDs, split scheme/pathname fields,
@@ -719,7 +865,6 @@ scope, or other persistence columns.
719
865
  | `entryId` | Positive durable entry identifier |
720
866
  | `target` | Client selector for the resolved entry, with any channel fragment removed |
721
867
  | `channels` | Every channel for a full read, or exactly the selected channel for a sliced read |
722
- | `tags` | Unique non-empty entry tags |
723
868
 
724
869
  | Channel field | Contract |
725
870
  |-----------------|---------------------------------------------------------------------------------------------------------------|
@@ -736,7 +881,7 @@ offset beyond the current end returns empty content at `contentLength`, not the
736
881
  unbounded requested offset. `Validator.assertEntryReadResult` enforces the
737
882
  schema, this suffix invariant, and Problem status equality.
738
883
 
739
- ### 13.3 Operation results
884
+ ### 13.4 Operation results
740
885
 
741
886
  §operation-result Every public PLURNK operation returns one `OperationResult`:
742
887
 
@@ -750,7 +895,7 @@ fields and Problem Details extension members remain open. A malformed result is
750
895
  an internal producer contract violation; it is not converted into a second
751
896
  model-facing failure envelope.
752
897
 
753
- ### 13.4 Problem Details
898
+ ### 13.5 Problem Details
754
899
 
755
900
  §problem-details `ProblemDetails` requires `type`, `title`, `status`, and `detail`;
756
901
  `instance` is optional until a durable host can attach the occurrence URI.
@@ -780,13 +925,13 @@ Internal invariant violations throw and preserve their cause. An external
780
925
  protocol may require its own error envelope; its adapter maps that envelope to
781
926
  or from the canonical Problem without creating another PLURNK failure contract.
782
927
 
783
- ### 13.5 Notices
928
+ ### 13.6 Notices
784
929
 
785
930
  §notice A `Notice` is a transient, nonterminal observation. It cannot determine durable
786
931
  failure truth, lifecycle, scheduling, or recovery. Sharing a renderer with
787
932
  Problems does not merge their semantics.
788
933
 
789
- ### §client-display-capabilities 13.6 Client display capabilities
934
+ ### §client-display-capabilities 13.7 Client display capabilities
790
935
 
791
936
  `ClientDisplayCapabilities` is the transport-neutral installed-capability
792
937
  projection used by external clients. It is an ordered array of closed,
@@ -824,7 +969,7 @@ class PlurnkParseError extends Error {
824
969
  ```
825
970
 
826
971
  §parser-position Parser source locations are points, not text regions. An AST
827
- statement's `position` identifies the first `<` of its open tag; a diagnostic
972
+ statement's `position` identifies the first `#` of its heading; a diagnostic
828
973
  identifies the offending or recovery point; a text item and `unparsedTail.from`
829
974
  identify the first point at which that item or undefined tail begins. A
830
975
  statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
@@ -850,48 +995,40 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
850
995
  | Source | Boundary |
851
996
  |-------------|----------------------------------------------------------------------------------------|
852
997
  | `"lexer"` | Token-level failure, such as an unrecognized character or malformed `<L>` integer. |
853
- | `"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. |
854
999
  | `"visitor"` | Semantic AST-construction failure, such as an invalid matcher dialect or signal shape. |
855
1000
 
856
1001
  `severity` distinguishes a hard error from a non-fatal advisory. The parser is
857
1002
  the sole and complete owner of syntax-error messaging because it holds the
858
1003
  parse state, lexer mode, and expected-token set that no consumer has. It
859
1004
  produces the final diagnostic message, deduplicated expected-token lists,
860
- turn-shape imperatives (begin with `<<PLAN`, end with a terminal `<<SEND`), and
861
- these targeted diagnostics:
862
-
863
- - §invented-closer-advisory **Invented closer.** When the forgiving parser
864
- swallows a `<<Word…:Word` heredoc whose keyword is a known op confusion
865
- (`<<CLOSE` → did you mean `<<FOLD`), it emits a `warning`-severity near-miss
866
- advisory. On a never-closed body, when the swallowed text carries an
867
- `:ALLCAPS` tag that is not the op's closer, the `unparsedTail` reason names it
868
- (`found \`:COMPARISON_TASK\`, which is body text - the closer echoes the op's
869
- name`) so a cap-cut runaway's recovery turn learns what happened, not just
870
- that something did.
1005
+ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1006
+ `## SEND0 [code]`), and these targeted diagnostics:
1007
+
871
1008
  - §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
872
1009
  `[signal]` slot (executor-ident mode) hits a leading `-` or digit —
873
1010
  mark-shaped `<timeout, poll>` scope content mistyped into the brackets — the
874
1011
  lexer message becomes `timeout/poll ride the \`<scope>\` slot; try
875
- \`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
876
1013
  EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
877
1014
  - §matcher-body-redirect **Matcher body in the slot region.** When the
878
- post-target slot region begins with `$`, `~`, or `@`, the lexer redirects the
879
- unambiguous body matcher into `:body:` instead of returning the generic slot
880
- list. Slash-led regex and XPath are excluded because the same character can
881
- be a forgotten target wrap.
882
- - §plan-body-op-advisory **Operation text swallowed by PLAN.** When an
883
- unsuffixed PLAN body contains op-shaped text (`<<EXEC`…), an advisory reports
884
- that the likely omitted `:PLAN` swallowed the turn's ops (`ops belong after
885
- the plan closes; did you omit \`:PLAN\`?`). A non-empty suffix deliberately
886
- 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.
887
1025
  - §misplaced-target-advisory **Mutation target in the signal slot.** When a
888
1026
  mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
889
1027
  `[signal]` element (a `/` or a dotted extension), the message redirects the
890
- path into `(…)` (`\`<<EDIT\` has no \`(target)\` - that path sits in the
891
- \`[…]\` tag slot; a target goes in \`(…)\`. Try \`EDIT(path):…\``). This
892
- catches the markdown `[label](url)` reading at the parse where the engine
893
- otherwise returns only a bare 400. It is gated on a path-shaped signal so a
894
- 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.
895
1032
 
896
1033
  §error-shape The diagnostic class determines how much guidance the parser may
897
1034
  provide:
@@ -901,20 +1038,20 @@ provide:
901
1038
  | Hard fact | `severity: "error"` | One concise observed fact and violated constraint in PLURNK vocabulary. |
902
1039
  | Targeted hard redirect | `severity: "error"` | One canonical correction only when parser state makes the intended structure unambiguous. |
903
1040
  | Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
904
- | 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. |
905
1042
 
906
- All messages use PLURNK protocol vocabulary: open tag, close tag, signal,
907
- target, scope, line marker, body, statement header, or space between
908
- 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
909
1046
  feature rather than an implementation rule. Generic tutoring, speculative
910
1047
  intent, coordinate restatement, and multiple repair strategies are forbidden.
911
1048
 
912
1049
  Examples of canonical hard facts:
913
1050
 
914
- - `unrecognized character '<<' in target`
1051
+ - `unrecognized character '<' in target`
915
1052
  - `unrecognized character ':' in signal`
916
1053
  - `unrecognized character 'X' in statement header`
917
- - `expected close tag; got end of input`
1054
+ - `a turn must begin with \`# PLAN0\``
918
1055
  - `expected ')'; got ':'`
919
1056
 
920
1057
  Each malformed statement produces at most one hard error. The first recorded
@@ -949,6 +1086,6 @@ runtime constructs this; the parser provides the fields):
949
1086
  "column": 12,
950
1087
  "source": "parser",
951
1088
  "severity": "error",
952
- "message": "expected close tag `:OPsuffix`; got end of input"
1089
+ "message": "target slot of `## READ0` opened at line 1 but never closed - add `)`"
953
1090
  }
954
1091
  ```