@plurnk/plurnk-contracts 1.4.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.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/SPEC.md +954 -0
- package/bin/plurnk-contracts.js +43 -0
- package/dist/plurnk.gbnf +394 -0
- package/dist/schema/ClientDisplayCapabilities.json +54 -0
- package/dist/schema/ClientStatement.json +68 -0
- package/dist/schema/EntryReadResult.json +101 -0
- package/dist/schema/LineMarker.json +16 -0
- package/dist/schema/LoopFlags.json +16 -0
- package/dist/schema/MatcherBody.json +84 -0
- package/dist/schema/Notice.json +67 -0
- package/dist/schema/OperationResult.json +44 -0
- package/dist/schema/ParsedPath.json +64 -0
- package/dist/schema/PlurnkStatement.json +264 -0
- package/dist/schema/Position.json +20 -0
- package/dist/schema/ProblemDetails.json +51 -0
- package/dist/schema/ProposalDisposition.json +26 -0
- package/dist/schema/ProposalProjection.json +44 -0
- package/dist/schema/ProviderCost.json +69 -0
- package/dist/schema/ResourceSelection.json +20 -0
- package/dist/schema/SendBody.json +13 -0
- package/dist/schema/TextRegion.json +31 -0
- package/dist/src/AstBuilder.d.ts +24 -0
- package/dist/src/AstBuilder.d.ts.map +1 -0
- package/dist/src/AstBuilder.js +609 -0
- package/dist/src/AstBuilder.js.map +1 -0
- package/dist/src/PathSyntax.d.ts +9 -0
- package/dist/src/PathSyntax.d.ts.map +1 -0
- package/dist/src/PathSyntax.js +36 -0
- package/dist/src/PathSyntax.js.map +1 -0
- package/dist/src/PlurnkErrorStrategy.d.ts +10 -0
- package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -0
- package/dist/src/PlurnkErrorStrategy.js +147 -0
- package/dist/src/PlurnkErrorStrategy.js.map +1 -0
- package/dist/src/PlurnkParseError.d.ts +17 -0
- package/dist/src/PlurnkParseError.d.ts.map +1 -0
- package/dist/src/PlurnkParseError.js +24 -0
- package/dist/src/PlurnkParseError.js.map +1 -0
- package/dist/src/PlurnkParser.d.ts +9 -0
- package/dist/src/PlurnkParser.d.ts.map +1 -0
- package/dist/src/PlurnkParser.js +396 -0
- package/dist/src/PlurnkParser.js.map +1 -0
- package/dist/src/Problems.d.ts +8 -0
- package/dist/src/Problems.d.ts.map +1 -0
- package/dist/src/Problems.js +23 -0
- package/dist/src/Problems.js.map +1 -0
- package/dist/src/RecordingListener.d.ts +9 -0
- package/dist/src/RecordingListener.d.ts.map +1 -0
- package/dist/src/RecordingListener.js +19 -0
- package/dist/src/RecordingListener.js.map +1 -0
- package/dist/src/Validator.d.ts +49 -0
- package/dist/src/Validator.d.ts.map +1 -0
- package/dist/src/Validator.js +211 -0
- package/dist/src/Validator.js.map +1 -0
- package/dist/src/generated/plurnkLexer.d.ts +129 -0
- package/dist/src/generated/plurnkLexer.d.ts.map +1 -0
- package/dist/src/generated/plurnkLexer.js +808 -0
- package/dist/src/generated/plurnkLexer.js.map +1 -0
- package/dist/src/generated/plurnkParser.d.ts +525 -0
- package/dist/src/generated/plurnkParser.d.ts.map +1 -0
- package/dist/src/generated/plurnkParser.js +3896 -0
- package/dist/src/generated/plurnkParser.js.map +1 -0
- package/dist/src/generated/plurnkParserVisitor.d.ts +298 -0
- package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -0
- package/dist/src/generated/plurnkParserVisitor.js +257 -0
- package/dist/src/generated/plurnkParserVisitor.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +29 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.generated.d.ts +450 -0
- package/dist/src/types.generated.d.ts.map +1 -0
- package/dist/src/types.generated.js +4 -0
- package/dist/src/types.generated.js.map +1 -0
- package/dist/src/types.js +23 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +87 -0
- package/plurnk.md +220 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
# PLURNK Contracts Specification
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
§contract-authority This package is the single authority for PLURNK's language, schemas, generated
|
|
6
|
+
types, parser, model rail, and runtime-neutral wire envelopes. Its package root
|
|
7
|
+
is the single code API for those contracts.
|
|
8
|
+
|
|
9
|
+
| Surface | Canonical export or artifact |
|
|
10
|
+
| ------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
11
|
+
| Parser, AST, validators, Problems, results, Notices, text regions | `@plurnk/plurnk-contracts` |
|
|
12
|
+
| Effective loop policy and its default | `LoopFlags`, `DEFAULT_LOOP_FLAGS` |
|
|
13
|
+
| Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
|
|
14
|
+
| Client capability presentation | `ClientDisplayCapabilities` |
|
|
15
|
+
| JSON Schemas | `@plurnk/plurnk-contracts/schema/*.json` |
|
|
16
|
+
| Local-model rail | `@plurnk/plurnk-contracts/plurnk.gbnf` |
|
|
17
|
+
| Model language reference | `plurnk.md` in the package |
|
|
18
|
+
|
|
19
|
+
§contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
|
|
20
|
+
generated from the schemas; ANTLR is authoritative for accepted model-language
|
|
21
|
+
syntax; GBNF remains the bounded generation aid described in §1.2.
|
|
22
|
+
|
|
23
|
+
## §contract-layers 1.1 Contract layers and admission boundary
|
|
24
|
+
|
|
25
|
+
PLURNK uses one contract with deliberately different projections. A tolerant
|
|
26
|
+
ingester accepting a spelling does not make that spelling canonical model
|
|
27
|
+
teaching, and a generation rail admitting a sentence does not make its runtime
|
|
28
|
+
semantics valid.
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
flowchart LR
|
|
32
|
+
canon["Canonical model teaching<br/>plurnk.md"]
|
|
33
|
+
rail["Optional raw generation rail<br/>plurnk.gbnf"]
|
|
34
|
+
free["Other admitted input"]
|
|
35
|
+
syntax["ANTLR lexer + parser<br/>syntax and document tier"]
|
|
36
|
+
ast["AstBuilder<br/>typed, serializable AST"]
|
|
37
|
+
runtime["Runtime owners<br/>stateful semantics and effects"]
|
|
38
|
+
canon --> rail
|
|
39
|
+
rail --> syntax
|
|
40
|
+
canon --> free
|
|
41
|
+
free --> syntax
|
|
42
|
+
syntax --> ast
|
|
43
|
+
ast --> runtime
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Layer | Owner or artifact | Contract |
|
|
47
|
+
|--------------------------|-------------------------------------|---------------------------------------------------------------------------------|
|
|
48
|
+
| Stable current law | `SPEC.md` | Owns invariants and boundaries; forge issues retain history |
|
|
49
|
+
| 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 |
|
|
52
|
+
| Typed admission | `AstBuilder` | Produces JSON-serializable unions and validates deterministic body/path syntax |
|
|
53
|
+
| Shared wire data | `schema/*.json` | Defines runtime-neutral data shapes projected into generated TypeScript |
|
|
54
|
+
| Stateful behavior | consuming runtime | Resolves addresses, permissions, selection arithmetic, effects, and lifecycle |
|
|
55
|
+
|
|
56
|
+
ANTLR owns statement structure, suffix matching, slot multiplicity, accepted
|
|
57
|
+
slot permutations, scope-number syntax, and interstatement text recognition.
|
|
58
|
+
AstBuilder owns URL decomposition and deterministic matcher validation through
|
|
59
|
+
WHATWG `URL`, ECMAScript `RegExp`, XPath 1.0, and RFC 9535 JSONPath parsers.
|
|
60
|
+
|
|
61
|
+
The runtime owner decides facts that require state or operation-specific
|
|
62
|
+
meaning, including registered scheme resolution, target existence, tag
|
|
63
|
+
selection, text-region bounds, result ordering, semantic similarity, mutation
|
|
64
|
+
effects, executor behavior, and numeric operation-code semantics.
|
|
65
|
+
|
|
66
|
+
### §contract-proposal-projection Loop policy and stopped-world projection
|
|
67
|
+
|
|
68
|
+
The schemas own the runtime-neutral shapes; core owns their stateful values.
|
|
69
|
+
|
|
70
|
+
| Contract | Shape invariant | Runtime responsibility |
|
|
71
|
+
| ------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
72
|
+
| `LoopFlags` | Complete effective `mode`, `auto`, `noWeb`, `noInteraction`, and `noProposals` | Validate/expand persisted partial policy before use |
|
|
73
|
+
| `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
|
|
74
|
+
| `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 |
|
|
76
|
+
|
|
77
|
+
`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
|
+
|
|
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.
|
|
86
|
+
|
|
87
|
+
The parser returns ordered statement, error, and text items. It recovers at a
|
|
88
|
+
trustworthy statement boundary when possible and sets `unparsedTail` when a
|
|
89
|
+
boundary-destroying failure makes later input undefined. SEND operation codes
|
|
90
|
+
and parse diagnostics are separate contracts.
|
|
91
|
+
|
|
92
|
+
## 1.2 GBNF Generation Rail
|
|
93
|
+
|
|
94
|
+
§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
|
|
96
|
+
to make useful, ANTLR-compliant turns more likely without reproducing every
|
|
97
|
+
parser or semantic validator. Parse compatibility is a design goal balanced
|
|
98
|
+
against rail size and sampling efficiency, not a language-subset guarantee. A
|
|
99
|
+
rail-legal operation can therefore produce a parser or AstBuilder error; consumers
|
|
100
|
+
apply their ordinary admission and bounded-operation recovery contract.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
The shipped raw turn has one shape:
|
|
104
|
+
|
|
105
|
+
```ebnf
|
|
106
|
+
root-turn ::= channel sep plan sep tail-0
|
|
107
|
+
```
|
|
108
|
+
|
|
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.
|
|
116
|
+
|
|
117
|
+
```mermaid
|
|
118
|
+
flowchart LR
|
|
119
|
+
raw["Raw constrained decode<br/>channel · sep · PLAN · sep · tail-0"]
|
|
120
|
+
split["llama.cpp<br/>reasoning_format: auto"]
|
|
121
|
+
reasoning["reasoning_content<br/>channel body"]
|
|
122
|
+
content["content<br/>PLAN through terminal SEND"]
|
|
123
|
+
parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
|
|
124
|
+
raw --> split
|
|
125
|
+
split --> reasoning
|
|
126
|
+
split --> content
|
|
127
|
+
content --> parser
|
|
128
|
+
```
|
|
129
|
+
|
|
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.
|
|
140
|
+
|
|
141
|
+
## §canonical-statement 2. Canonical statement form
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
<<OPsuffix[signal]?(path)?<scope>?:body?:OPsuffix
|
|
145
|
+
```
|
|
146
|
+
|
|
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`.
|
|
151
|
+
|
|
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 |
|
|
163
|
+
|
|
164
|
+
The following constraints are structural:
|
|
165
|
+
|
|
166
|
+
- §close-tag-match The close `:OPsuffix` must character-match the opening operation and suffix.
|
|
167
|
+
- 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.
|
|
170
|
+
|
|
171
|
+
§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.
|
|
175
|
+
|
|
176
|
+
The ingester also accepts several bounded noncanonical forms so it can explain
|
|
177
|
+
or safely execute understandable input:
|
|
178
|
+
|
|
179
|
+
| Tolerated input | Canonical or runtime disposition |
|
|
180
|
+
|--------------------------------------------------|---------------------------------------------------------------------|
|
|
181
|
+
| Reordered admitted slots | Producers retain signal → target → scope order |
|
|
182
|
+
| 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 |
|
|
187
|
+
|
|
188
|
+
## 3. Lexical elements
|
|
189
|
+
|
|
190
|
+
| Element | Accepted shape or role |
|
|
191
|
+
|-------------|--------------------------------------------------------------------|
|
|
192
|
+
| `OP` | `FIND READ EDIT COPY MOVE OPEN FOLD SEND EXEC WORK FORK KILL PLAN` |
|
|
193
|
+
| `suffix` | `[A-Za-z0-9_]*`, adjacent to `OP` |
|
|
194
|
+
| `[signal]` | Operation-specific tags, identifier, branch, or integer |
|
|
195
|
+
| `(path)` | Local path or scheme URL target; detailed in §5 |
|
|
196
|
+
| `<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 |
|
|
199
|
+
|
|
200
|
+
## §op-shapes 4. Per-operation semantics
|
|
201
|
+
|
|
202
|
+
The model-facing forms below are the canonical projection. Parser tolerance is
|
|
203
|
+
governed by {§canonical-statement}; runtime conditions remain explicit below.
|
|
204
|
+
|
|
205
|
+
| OP | `[signal]` | `(path)` | `<scope>` | `body` |
|
|
206
|
+
|------|-------------------------------|----------------------------------------------|---------------------------------|--------------------------------|
|
|
207
|
+
| 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 |
|
|
215
|
+
| EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
|
|
216
|
+
| WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
|
|
217
|
+
| FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
|
|
218
|
+
| KILL | optional target-specific code | required target, including a log item | none | empty |
|
|
219
|
+
| SEND | optional target-specific code | optional recipient | optional timeout, poll | message; terminal is nonempty |
|
|
220
|
+
|
|
221
|
+
§operation-code-polymorphism SEND and KILL share a numeric wire slot, not one universal numeric vocabulary.
|
|
222
|
+
For pathless terminal SEND, the code is the loop disposition defined in §9.
|
|
223
|
+
Directed SEND and KILL delegate any present code to the addressed target's
|
|
224
|
+
operation contract; a live process may interpret a KILL code as a Unix signal,
|
|
225
|
+
but that interpretation does not define KILL generally.
|
|
226
|
+
|
|
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
|
|
229
|
+
reasoning. Dispatch records it and has no other runtime effect.
|
|
230
|
+
|
|
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.
|
|
236
|
+
|
|
237
|
+
The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
|
|
238
|
+
scopes ordered results. EXEC and SEND scope timing. READ, EDIT, COPY, and
|
|
239
|
+
MOVE use one universal text algebra independent of mimetype:
|
|
240
|
+
|
|
241
|
+
| Arity | Surface meaning | Endpoint rule |
|
|
242
|
+
|---------------|---------------------------------------------------------------------|------------------------------------------------------|
|
|
243
|
+
| one integer | One whole physical line, or the documented `0`/`-1` mutation anchor | Exactly one ordinal line |
|
|
244
|
+
| two integers | Whole physical lines `firstLine..lastLine` | Both lines are included |
|
|
245
|
+
| four integers | Exact `startLine,startColumn,endLine,endColumn` region | Start included, end excluded; equality is zero-width |
|
|
246
|
+
|
|
247
|
+
§text-scope-semantics Exact regions use 1-based lines and Unicode code-point columns. One- and
|
|
248
|
+
two-integer line selections normalize to the same exclusive-end `TextRegion`
|
|
249
|
+
used by four-coordinate selections. Whole-line replacement deliberately
|
|
250
|
+
accounts for newline separators; it is an ergonomic projection over exact
|
|
251
|
+
replacement, not a different mimetype navigation mode. As an unadvertised
|
|
252
|
+
ingestion tolerance, the runtime accepts three integers as
|
|
253
|
+
`startLine,startColumn,endLine` and immediately normalizes them to the complete
|
|
254
|
+
four-coordinate region ending after the final code point of `endLine`.
|
|
255
|
+
Producers never emit that form. Other arities and decimal text coordinates are
|
|
256
|
+
runtime 416 failures.
|
|
257
|
+
|
|
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.
|
|
265
|
+
|
|
266
|
+
Mutation semantics:
|
|
267
|
+
|
|
268
|
+
- No scope and a target address that does not yet exist creates a file or entry from the body. This is the only unscoped EDIT.
|
|
269
|
+
- §unscoped-edit-create-only No scope and an existing target is refused. Replacing existing content requires a precise text scope or `<1,-1>`; core owns the existence check.
|
|
270
|
+
- `<N>` replaces whole line `N`; `<N,M>` replaces inclusive whole lines `N` through `M`.
|
|
271
|
+
- An empty body deletes the selected text.
|
|
272
|
+
- `<0>` prepends and `<-1>` appends.
|
|
273
|
+
- `<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.
|
|
275
|
+
|
|
276
|
+
### §operation-observation Per-operation observations
|
|
277
|
+
|
|
278
|
+
| OP | Successful observation |
|
|
279
|
+
|------|-----------------------------------------------------------------------------------|
|
|
280
|
+
| FIND | Catalog rows plus optional structural locators and text match evidence |
|
|
281
|
+
| READ | Complete or scoped body projections plus optional text match evidence |
|
|
282
|
+
| EDIT | Status plus a bounded receipt for the effect that landed |
|
|
283
|
+
| COPY | Source and destination selections plus ordered destination effects |
|
|
284
|
+
| MOVE | Source and destination selections plus ordered destination and source effects |
|
|
285
|
+
| OPEN | Status and matched log-item count |
|
|
286
|
+
| FOLD | Status and matched log-item count |
|
|
287
|
+
| SEND | Status and recipient acknowledgement when applicable |
|
|
288
|
+
| EXEC | Spawn acknowledgement; output arrives through named stream channels |
|
|
289
|
+
| WORK | Spawn acknowledgement; the deliverable arrives through the log |
|
|
290
|
+
| FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
|
|
291
|
+
| KILL | Status of deletion or termination |
|
|
292
|
+
| PLAN | Status of durable intended-goals logging |
|
|
293
|
+
|
|
294
|
+
§copy-move-observation COPY and MOVE log projections preserve both admitted operand selections,
|
|
295
|
+
including their independent scopes, whether the result changed state, was a
|
|
296
|
+
304 no-op, or failed after admission. Operands identify the request; `effects`
|
|
297
|
+
describe only mutations that landed. If either operand uses textual scope,
|
|
298
|
+
each landed textual create or update carries the same bounded receipt used by
|
|
299
|
+
EDIT. Whole-channel transfers remain bodyless structural effects; runtime
|
|
300
|
+
owners reject binary markers rather than treating a text field as a byte lane.
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
Every operation returns the runtime-neutral `OperationResult` defined by
|
|
304
|
+
{§operation-result}. Its `status` belongs to the result envelope and is not a
|
|
305
|
+
SEND signal. Durable operation observations are projected into a later packet;
|
|
306
|
+
retrieval never returns inline within the emitting turn.
|
|
307
|
+
|
|
308
|
+
## §path-syntax 5. Target and path grammar
|
|
309
|
+
|
|
310
|
+
The target slot contains either a local path or a scheme URL. Exact addresses
|
|
311
|
+
and path globs share the slot; content matchers belong in the body.
|
|
312
|
+
|
|
313
|
+
| Form | Typed admission | Runtime meaning |
|
|
314
|
+
|-------------------------|---------------------------------------------------------------------|------------------------------------------------------|
|
|
315
|
+
| Bare path | `LocalPath { kind: "local", raw }` | Resolves through the runtime's file surface |
|
|
316
|
+
| `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
|
|
317
|
+
| Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
|
|
318
|
+
| `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
|
|
319
|
+
| Trailing `{key: value}` | Removed before URL parsing and preserved as ordered `headers` pairs | Addressed scheme interprets request metadata |
|
|
320
|
+
| `?query` | Preserved as ordered `UrlPath.query`; `null` = absent, `""` = `?` | Participates in scheme resource identity |
|
|
321
|
+
|
|
322
|
+
AstBuilder recognizes a URL with the case-insensitive prefix
|
|
323
|
+
`[a-z][a-z0-9+.-]*://`, passes it through WHATWG `URL`, and surfaces malformed
|
|
324
|
+
URL structure as a visitor error. A target without that prefix remains a raw
|
|
325
|
+
local path. Grammar acceptance does not register a scheme; the runtime scheme
|
|
326
|
+
catalogue and packet-time scheme teaching remain dynamically owned elsewhere.
|
|
327
|
+
|
|
328
|
+
§path-query The serialized query component is the lossless representation.
|
|
329
|
+
Ordering, duplicate names, encoded spelling, and the distinction between an
|
|
330
|
+
absent query and an explicit empty query survive parsing. Consumers that need
|
|
331
|
+
key/value access may construct `URLSearchParams`; the shared AST does not replace
|
|
332
|
+
URI identity with a grouped object projection.
|
|
333
|
+
|
|
334
|
+
§path-parentheses An unescaped depth-zero `)` closes the target slot. The
|
|
335
|
+
tolerant lexer preserves balanced unescaped parentheses, while canonical
|
|
336
|
+
producers use one lossless Plurnk lexical layer before target interpretation:
|
|
337
|
+
|
|
338
|
+
| Target-slot spelling | Interpreted character |
|
|
339
|
+
| -------------------- | --------------------- |
|
|
340
|
+
| `\\` | `\` |
|
|
341
|
+
| `\(` | `(` |
|
|
342
|
+
| `\)` | `)` |
|
|
343
|
+
|
|
344
|
+
Decoding consumes only those three pairs in one left-to-right pass; unknown
|
|
345
|
+
pairs such as `\*` retain both characters for glob interpretation. Encoding
|
|
346
|
+
escapes backslashes before parentheses, so every target string round-trips.
|
|
347
|
+
Pathname producers retain the deliberate `%28`/`%29` alias and `%3C` spelling,
|
|
348
|
+
but must use the lexical layer for identity-bearing query and fragment text
|
|
349
|
+
rather than changing their percent-encoded spelling. Newlines and `<<` are
|
|
350
|
+
never target content. Glob metacharacters remain legal path data.
|
|
351
|
+
|
|
352
|
+
§path-glob `PathSyntax` owns exact-path versus path-pattern classification.
|
|
353
|
+
|
|
354
|
+
| Method | Contract |
|
|
355
|
+
| -------------------------------- | ------------------------------------------------------------------------ |
|
|
356
|
+
| `hasGlob(pathname)` | Recognizes `*`, `?`, character-class, brace, extglob, and escape syntax |
|
|
357
|
+
| `globMagicIndex(pathname)` | First such position, solely for conservative candidate-prefix selection |
|
|
358
|
+
|
|
359
|
+
Matching and folder-scope semantics remain runtime concerns.
|
|
360
|
+
|
|
361
|
+
§path-request-metadata A scheme URL may append one or more
|
|
362
|
+
`{key: value}` request-metadata blocks. AstBuilder removes the blocks before
|
|
363
|
+
WHATWG decomposition and preserves them as ordered pairs so order and duplicate
|
|
364
|
+
names survive. Local paths retain braces as ordinary path text. Scheme handlers,
|
|
365
|
+
not the language parser, define the meaning and authorization of the metadata.
|
|
366
|
+
The admitted AST retains exact values for execution; malformed-metadata visitor
|
|
367
|
+
diagnostics identify only the structural fault and source position, never quote
|
|
368
|
+
metadata contents or a native URL parser's input-bearing diagnostic.
|
|
369
|
+
|
|
370
|
+
§worker-name The exported `WORKER_NAME` contract governs names minted for URI
|
|
371
|
+
authority slots: a lowercase DNS label matching
|
|
372
|
+
`[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. `RESERVED_AUTHORITIES` contains the
|
|
373
|
+
authority-shaped internal worker names `commons` and `plurnk`, which are
|
|
374
|
+
unavailable for minting. `~` is the sole current-worker sigil and falls outside
|
|
375
|
+
the mintable alphabet; every matching unreserved value, including `self`, is an
|
|
376
|
+
ordinary literal worker name. This is a minting and registry invariant, not an
|
|
377
|
+
ingestion restriction: the parser decomposes arbitrary URL authorities.
|
|
378
|
+
|
|
379
|
+
## §matcher-prefix-claims 6. Bulk pattern matching
|
|
380
|
+
|
|
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.
|
|
383
|
+
A leading prefix claims its dialect. Invalid claimed syntax is a positioned
|
|
384
|
+
visitor error and never falls back to glob matching.
|
|
385
|
+
|
|
386
|
+
| Prefix | Dialect | Canonical body | Typed admission | Runtime owner |
|
|
387
|
+
|-----------|----------|--------------------------------------|-----------------------------------|---------------------|
|
|
388
|
+
| `//` | XPath | `//selector` | XPath 1.0 `xpath.parse()` | Mimetype projection |
|
|
389
|
+
| `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
|
|
390
|
+
| `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
|
|
391
|
+
| `~` | Semantic | `~phrase` | Any text after the prefix | Embedding index |
|
|
392
|
+
| `@` | Graph | `@symbol`, `@<symbol`, or `@>symbol` | Direction and symbol preserved | Symbol index |
|
|
393
|
+
| none | Glob | Shell glob or literal text | Raw string | Mimetype projection |
|
|
394
|
+
|
|
395
|
+
XPath is classified before regex because its prefix is two slashes. Regex
|
|
396
|
+
splitting respects escapes and character classes; `\/` represents a literal
|
|
397
|
+
slash. The AST stores regex `pattern` and `flags`, not a compiled object.
|
|
398
|
+
Semantic and graph matchers require no parse step. Scope carries semantic
|
|
399
|
+
threshold and result-range information rather than changing the matcher body.
|
|
400
|
+
|
|
401
|
+
AstBuilder validation is compile-only and never evaluates a document. Matcher
|
|
402
|
+
evaluation belongs to the runtime's selected mimetype, embedding, or symbol
|
|
403
|
+
implementation. A matcher admission error is local to its statement; later
|
|
404
|
+
statements remain recoverable when their boundaries are trustworthy.
|
|
405
|
+
|
|
406
|
+
- §pattern-body-single-line The GBNF rail permits only single-line matcher
|
|
407
|
+
bodies. A regex that matches a newline uses the two-character `\n` escape.
|
|
408
|
+
ANTLR tolerates multiline matcher bodies during ingestion.
|
|
409
|
+
- §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
|
|
410
|
+
character. Empty matchers and later colons remain valid; a regex such as
|
|
411
|
+
`/^:needle/` expresses a pattern beginning with a literal colon.
|
|
412
|
+
|
|
413
|
+
## §scope-slot 7. Scope markers
|
|
414
|
+
|
|
415
|
+
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 |
|
|
428
|
+
|
|
429
|
+
Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
|
|
430
|
+
whole line, two integers are an inclusive whole-line range, and four integers
|
|
431
|
+
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.
|
|
435
|
+
|
|
436
|
+
§scope-marker-forms Canonical producers separate components with commas and no
|
|
437
|
+
spaces. ANTLR tolerates a dash separator and one space after a comma. Each
|
|
438
|
+
component greedily consumes an optional leading minus sign, digits, and an
|
|
439
|
+
optional decimal fraction, so the ingester preserves even noncanonical numeric
|
|
440
|
+
shapes for runtime validation.
|
|
441
|
+
|
|
442
|
+
Apart from the unadvertised three-coordinate text-scope tolerance in
|
|
443
|
+
{§text-scope-semantics}, the runtime rejects invalid arity, out-of-range or
|
|
444
|
+
inverted positions, and decimal text coordinates rather than rounding or
|
|
445
|
+
reinterpreting them. FIND owns a deterministic result order so the same
|
|
446
|
+
inclusive range selects the same positions from unchanged state. The parser
|
|
447
|
+
does not enforce either condition.
|
|
448
|
+
|
|
449
|
+
## §suffix-discipline 8. Suffix Discipline
|
|
450
|
+
|
|
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).
|
|
462
|
+
|
|
463
|
+
Suffix rules:
|
|
464
|
+
|
|
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:
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
<<SEND1[400]:
|
|
482
|
+
The following is a quoted plurnk operation, preserved verbatim:
|
|
483
|
+
<<SEND[400](worker://reviewer):still working:SEND
|
|
484
|
+
:SEND1
|
|
485
|
+
```
|
|
486
|
+
|
|
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.
|
|
490
|
+
|
|
491
|
+
## 9. SEND Codes
|
|
492
|
+
|
|
493
|
+
Pathless terminal SEND disposition codes align with HTTP semantics so that model training
|
|
494
|
+
transfers directly:
|
|
495
|
+
|
|
496
|
+
| Class | Terminal meaning | Disposition used by the model |
|
|
497
|
+
|-------|-----------------------------------------------------------------|-------------------------------|
|
|
498
|
+
| `1xx` | Continue after submitted operations | `102 Processing` |
|
|
499
|
+
| `2xx` | Conclude successfully or wait on live obligations | `200 OK`, `202 Accepted` |
|
|
500
|
+
| `3xx` | Ask the interactive user a stop-the-world multiple choice | `300 Multiple Choices` |
|
|
501
|
+
| `4xx` | Abandon the loop after a model-side inability | `499` |
|
|
502
|
+
| `5xx` | Runtime or infrastructure failure; never a model terminal claim | none |
|
|
503
|
+
|
|
504
|
+
### §waitpid-dispositions The terminal contract (waitpid)
|
|
505
|
+
|
|
506
|
+
The model signals one intention per turn — **continue (102)**, **done
|
|
507
|
+
(200)**, **wait (202)**, or **give up (499)**, plus the operator-facing
|
|
508
|
+
**question (300)** — and the engine verifies the claim against the
|
|
509
|
+
loop's live obligations (spawned children, open streams, pending
|
|
510
|
+
retrievals); the grammar polices *shape* only. The shape rules ARE
|
|
511
|
+
structural:
|
|
512
|
+
|
|
513
|
+
- §send-mid-reservation The five disposition codes `{102, 200, 202, 300, 499}` lex as a
|
|
514
|
+
distinct `DISPOSITION` token, making a disposition-coded SEND
|
|
515
|
+
**structurally terminal**: a statement after it is a parse error
|
|
516
|
+
(the mid-termination rule), and the GBNF reserves the five from
|
|
517
|
+
mid-position SENDs (`status-mid` is their complement over `DDD`).
|
|
518
|
+
This keeps the grammar's last-SEND model and the dispatcher's
|
|
519
|
+
first-disposition model coincident.
|
|
520
|
+
- A **mid** SEND (before the terminal) is comms: statusless, or any
|
|
521
|
+
non-disposition code, targeted or pathless, empty body allowed.
|
|
522
|
+
- §terminal-body-nonempty The GBNF rail requires a non-empty terminal SEND body — a constrained
|
|
523
|
+
turn cannot end empty-handed. ANTLR remains tolerant during ingestion.
|
|
524
|
+
- §park-202-only The **park** rides `[202]` only: `<T>` (wait up to T seconds),
|
|
525
|
+
`<T,P>` (adds a poll cadence, mirroring EXEC's slot), `<-1>`
|
|
526
|
+
(indefinite; the join's own liveness bounds it). See §7 for the
|
|
527
|
+
GBNF-strict / ANTLR-tolerant split.
|
|
528
|
+
- §no-idle-102 A **zero-statement turn may not conclude `[102]`** — "continue"
|
|
529
|
+
with nothing submitted is a spin. The GBNF's `tail-0` exits through
|
|
530
|
+
a terminal trie without the `[102]` tail, so the idle turn (`PLAN`
|
|
531
|
+
straight into `SEND[102]`) is unemittable; one statement restores
|
|
532
|
+
the full disposition set. The other four stay legal bare (a zero-op
|
|
533
|
+
`[202]` is the engine's obligation check). ANTLR stays tolerant
|
|
534
|
+
(ingest side). A dispatch-emptied turn — ops emitted but failing
|
|
535
|
+
downstream validation — survives the rail by nature; the engine's
|
|
536
|
+
idle-turn 409 backstops that class.
|
|
537
|
+
|
|
538
|
+
SEND with no `(path)` broadcasts to the default control channel — the
|
|
539
|
+
turn's disposition. SEND with `(path)` directs the message at a
|
|
540
|
+
specific recipient URI (a worker, a stream, a peer).
|
|
541
|
+
|
|
542
|
+
### §send-body SEND body projection
|
|
543
|
+
|
|
544
|
+
SEND body syntax is opaque. AstBuilder preserves the exact `raw` string and
|
|
545
|
+
also exposes a best-effort `json` value when `JSON.parse` succeeds; invalid JSON
|
|
546
|
+
leaves `json: null` without invalidating an otherwise legal SEND. Plain text and
|
|
547
|
+
JSON are both messages, not implicitly stored resources, and the language
|
|
548
|
+
defines no synthetic scheme or READ-back convention for them.
|
|
549
|
+
|
|
550
|
+
## §parser-architecture 10. Parser architecture
|
|
551
|
+
|
|
552
|
+
`plurnkLexer.g4` owns tokens and modes; `plurnkParser.g4` owns document tiers
|
|
553
|
+
and statement composition; AstBuilder projects parse-tree leaves into the public
|
|
554
|
+
AST. Generated TypeScript targets the `antlr4ng` runtime.
|
|
555
|
+
|
|
556
|
+
```mermaid
|
|
557
|
+
stateDiagram-v2
|
|
558
|
+
[*] --> DEFAULT
|
|
559
|
+
DEFAULT --> DEFAULT: whitespace or TEXT
|
|
560
|
+
DEFAULT --> SLOTS: minted <<OPsuffix
|
|
561
|
+
SLOTS --> SIGNAL: signal opener
|
|
562
|
+
SIGNAL --> SLOTS: signal close
|
|
563
|
+
SLOTS --> TARGET: target opener
|
|
564
|
+
TARGET --> TARGET: balanced literals / target escapes
|
|
565
|
+
TARGET --> SLOTS: target close at depth zero
|
|
566
|
+
SLOTS --> SLOTS: scope token
|
|
567
|
+
SLOTS --> BODY: body-opening colon
|
|
568
|
+
BODY --> DEFAULT: matching close tag
|
|
569
|
+
```
|
|
570
|
+
|
|
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.
|
|
576
|
+
|
|
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.
|
|
586
|
+
|
|
587
|
+
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.
|
|
591
|
+
|
|
592
|
+
## §whitespace-contract 11. Whitespace and interstatement text
|
|
593
|
+
|
|
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 |
|
|
604
|
+
|
|
605
|
+
PLURNK never escape-decodes body text: `\n` reaches the owning operation as
|
|
606
|
+
backslash plus `n`. A matcher or executor may interpret those characters under
|
|
607
|
+
its own body dialect. Producers that need a physical newline in literal EDIT
|
|
608
|
+
content emit an actual newline.
|
|
609
|
+
|
|
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.
|
|
615
|
+
|
|
616
|
+
## §public-api 12. Public API
|
|
617
|
+
|
|
618
|
+
The package root is the single JavaScript and TypeScript entry point. Shared AST
|
|
619
|
+
and wire types come from generated schemas; the small hand-maintained parser
|
|
620
|
+
types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
|
|
621
|
+
express. Consumers never receive ANTLR parse-tree or token types.
|
|
622
|
+
|
|
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.
|
|
628
|
+
|
|
629
|
+
§tier-entrypoints Each parser entry point owns one document tier:
|
|
630
|
+
|
|
631
|
+
| Entry point | Accepted document | Result statement type |
|
|
632
|
+
|--------------------------------|----------------------------------------------------------------|-----------------------|
|
|
633
|
+
| `PlurnkParser.parse` | One PLAN-anchored model turn with optional TEXT | `PlurnkStatement` |
|
|
634
|
+
| `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` |
|
|
637
|
+
|
|
638
|
+
Every entry point returns ordered `statement`, `error`, and, where admitted,
|
|
639
|
+
`text` items. When present, {§unparsed-tail-boundary} governs the result's item
|
|
640
|
+
extent. The statement `op` field discriminates the generated per-operation
|
|
641
|
+
union.
|
|
642
|
+
|
|
643
|
+
§root-value-api The package-root runtime namespace is closed and consists of the
|
|
644
|
+
following supported consumer values. All other root exports are TypeScript types.
|
|
645
|
+
|
|
646
|
+
| Root value(s) | Consumer contract | Exact owner |
|
|
647
|
+
|---------------------------------------|---------------------------------------------------------------------|---------------------------------------------|
|
|
648
|
+
| `PlurnkParser` | Four document-tier entry points listed above | {§parser-architecture}, {§tier-entrypoints} |
|
|
649
|
+
| `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
|
|
650
|
+
| `parsePath`, `parseResourceSelection` | Parser-equivalent target and COPY/MOVE destination admission | {§path-syntax}, {§tier-entrypoints} |
|
|
651
|
+
| `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
|
|
652
|
+
| `Validator` | Validation and assertion against the owning JSON Schemas | {§wire-entrypoint} |
|
|
653
|
+
| `InvalidNoticeError` | Typed failure from `Validator.assertNotice` | {§notice} |
|
|
654
|
+
| `InvalidProblemDetailsError` | Typed failure from `Validator.assertProblemDetails` | {§problem-details} |
|
|
655
|
+
| `InvalidOperationResultError` | Typed failure from `Validator.assertOperationResult` | {§operation-result} |
|
|
656
|
+
| `InvalidTextRegionError` | Typed failure from `Validator.assertTextRegion` | {§text-region} |
|
|
657
|
+
| `Problems` | RFC 9457 Problem construction | {§problem-details} |
|
|
658
|
+
| `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
|
|
659
|
+
| `WORKER_NAME`, `RESERVED_AUTHORITIES` | Authority minting predicate and internal reserved names | {§worker-name} |
|
|
660
|
+
| `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
|
|
661
|
+
|
|
662
|
+
§parser-construction-boundary Parser construction components are internal rather
|
|
663
|
+
than alternate consumer entry points:
|
|
664
|
+
|
|
665
|
+
| Internal component | Boundary |
|
|
666
|
+
|--------------------------------------------|---------------------------------------------------------------------------------------------------------------|
|
|
667
|
+
| `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser`, `parsePath`, and `parseResourceSelection` own its API |
|
|
668
|
+
| `PlurnkErrorStrategy`, `RecordingListener` | Assemble parser recovery and diagnostics around `antlr4ng`; consumers receive `PlurnkParseError` values |
|
|
669
|
+
| `Jsonplurnk` test helper | Independently checks the Core-owned {§jsonplurnk} renderer corpus; it is neither shipped code nor a root API |
|
|
670
|
+
|
|
671
|
+
### CLI
|
|
672
|
+
|
|
673
|
+
```text
|
|
674
|
+
plurnk-contracts [file] parse a file, or standard input when omitted
|
|
675
|
+
plurnk-contracts --help show usage
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
The CLI prints the parse result as JSON. It exits `0` when no error item or
|
|
679
|
+
`unparsedTail` exists and `1` otherwise.
|
|
680
|
+
|
|
681
|
+
## 13. Runtime-neutral wire contracts
|
|
682
|
+
|
|
683
|
+
§wire-entrypoint The package root exports generated wire types, `Problems`, and
|
|
684
|
+
`Validator` alongside the parser and AST. Their owning JSON Schemas are published
|
|
685
|
+
through `@plurnk/plurnk-contracts/schema/*.json`, not re-exported as root values.
|
|
686
|
+
|
|
687
|
+
### §text-region 13.1 Text regions
|
|
688
|
+
|
|
689
|
+
`TextRegion` identifies one contiguous region of textual content:
|
|
690
|
+
|
|
691
|
+
| Required field | Coordinate |
|
|
692
|
+
|----------------|-----------------------------------------------------------|
|
|
693
|
+
| `startLine` | Line containing the included start |
|
|
694
|
+
| `startColumn` | Unicode code-point column of the included start |
|
|
695
|
+
| `endLine` | Line containing the excluded end |
|
|
696
|
+
| `endColumn` | Unicode code-point column immediately after the selection |
|
|
697
|
+
|
|
698
|
+
Lines and columns are positive safe integers and 1-based. Columns count Unicode
|
|
699
|
+
code points. LF, CRLF, and CR are line separators; CRLF is one indivisible
|
|
700
|
+
separator, and separator code units are not column positions. The end is
|
|
701
|
+
exclusive; equal start and end coordinates identify a zero-length insertion
|
|
702
|
+
point. A producer supplies all four coordinates or omits the region. It never
|
|
703
|
+
substitutes UTF-16 offsets, readable-row indices, or partial coordinates.
|
|
704
|
+
`Validator.assertTextRegion` rejects an end before its start.
|
|
705
|
+
|
|
706
|
+
### §entry-read-result 13.2 Client entry reads
|
|
707
|
+
|
|
708
|
+
`EntryReadResult` is the exact transport-neutral projection of one entry. It
|
|
709
|
+
does not expose workspace IDs, storage owner IDs, split scheme/pathname fields,
|
|
710
|
+
scope, or other persistence columns.
|
|
711
|
+
|
|
712
|
+
| Outcome | Exact shape |
|
|
713
|
+
|---------|------------------------------------------------------------------|
|
|
714
|
+
| Success | `{ status: 200, entry: ClientEntry }` |
|
|
715
|
+
| Failure | `{ status: 400..599, problem: ProblemDetails, entry: null }` |
|
|
716
|
+
|
|
717
|
+
| `ClientEntry` field | Contract |
|
|
718
|
+
|---------------------|----------------------------------------------------------------------------------------------|
|
|
719
|
+
| `entryId` | Positive durable entry identifier |
|
|
720
|
+
| `target` | Client selector for the resolved entry, with any channel fragment removed |
|
|
721
|
+
| `channels` | Every channel for a full read, or exactly the selected channel for a sliced read |
|
|
722
|
+
| `tags` | Unique non-empty entry tags |
|
|
723
|
+
|
|
724
|
+
| Channel field | Contract |
|
|
725
|
+
|-----------------|---------------------------------------------------------------------------------------------------------------|
|
|
726
|
+
| `content` | Full content, or the suffix beginning at `contentOffset` |
|
|
727
|
+
| `contentOffset` | Actual Unicode-code-point offset of `content`; zero for a full read and capped at `contentLength` |
|
|
728
|
+
| `contentLength` | Unicode-code-point length of the complete stored channel |
|
|
729
|
+
| `mimetype` | Stored channel mimetype |
|
|
730
|
+
| `tokens` | Stored token count for the complete channel |
|
|
731
|
+
| `state` | `static`, `active`, `closed`, or `errored` |
|
|
732
|
+
|
|
733
|
+
For every returned channel,
|
|
734
|
+
`contentOffset + codePointLength(content) === contentLength`. Therefore an
|
|
735
|
+
offset beyond the current end returns empty content at `contentLength`, not the
|
|
736
|
+
unbounded requested offset. `Validator.assertEntryReadResult` enforces the
|
|
737
|
+
schema, this suffix invariant, and Problem status equality.
|
|
738
|
+
|
|
739
|
+
### 13.3 Operation results
|
|
740
|
+
|
|
741
|
+
§operation-result Every public PLURNK operation returns one `OperationResult`:
|
|
742
|
+
|
|
743
|
+
| Status | Required shape |
|
|
744
|
+
|---------|-------------------------------------------|
|
|
745
|
+
| 100–399 | `problem` is forbidden |
|
|
746
|
+
| 400–599 | One RFC 9457 `problem` object is required |
|
|
747
|
+
|
|
748
|
+
The legacy top-level `error` field is forbidden. Producer-specific success
|
|
749
|
+
fields and Problem Details extension members remain open. A malformed result is
|
|
750
|
+
an internal producer contract violation; it is not converted into a second
|
|
751
|
+
model-facing failure envelope.
|
|
752
|
+
|
|
753
|
+
### 13.4 Problem Details
|
|
754
|
+
|
|
755
|
+
§problem-details `ProblemDetails` requires `type`, `title`, `status`, and `detail`;
|
|
756
|
+
`instance` is optional until a durable host can attach the occurrence URI.
|
|
757
|
+
|
|
758
|
+
| Field | Contract |
|
|
759
|
+
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
|
760
|
+
| `type` | Stable absolute URI for the problem class |
|
|
761
|
+
| `title` | Stable summary with no occurrence data or instruction |
|
|
762
|
+
| `status` | Equals the containing operation status |
|
|
763
|
+
| `detail` | Tersely states the failed subject, observed fact, and violated constraint at the layer that knows the cause |
|
|
764
|
+
| `instance` | Durable URI for this occurrence |
|
|
765
|
+
| `stage` | Stable failed stage, only when neighboring stages imply different recovery |
|
|
766
|
+
| `recovery` | One generally valid next action; omitted when the producer cannot know |
|
|
767
|
+
| `retryable` | `true` only when the producer recommends automatically retrying the identical request; otherwise false or unknown/omitted as applicable |
|
|
768
|
+
| extensions | Factual producer-known operands or constraints |
|
|
769
|
+
|
|
770
|
+
`detail` is failure truth; `recovery` is not a second explanation. Producers do
|
|
771
|
+
not infer motives, blame the model, restate status, or expose an implementation
|
|
772
|
+
accident as the cause. General syntax and workflow teaching remain in the model
|
|
773
|
+
packet rather than being repeated in every Problem.
|
|
774
|
+
|
|
775
|
+
`Problems.create(owner, code, status, detail, extensions?, options?)` derives a
|
|
776
|
+
stable title from `code` unless an established type supplies
|
|
777
|
+
`options.title`. Occurrence-specific text never belongs in `title`.
|
|
778
|
+
|
|
779
|
+
Internal invariant violations throw and preserve their cause. An external
|
|
780
|
+
protocol may require its own error envelope; its adapter maps that envelope to
|
|
781
|
+
or from the canonical Problem without creating another PLURNK failure contract.
|
|
782
|
+
|
|
783
|
+
### 13.5 Notices
|
|
784
|
+
|
|
785
|
+
§notice A `Notice` is a transient, nonterminal observation. It cannot determine durable
|
|
786
|
+
failure truth, lifecycle, scheduling, or recovery. Sharing a renderer with
|
|
787
|
+
Problems does not merge their semantics.
|
|
788
|
+
|
|
789
|
+
### §client-display-capabilities 13.6 Client display capabilities
|
|
790
|
+
|
|
791
|
+
`ClientDisplayCapabilities` is the transport-neutral installed-capability
|
|
792
|
+
projection used by external clients. It is an ordered array of closed,
|
|
793
|
+
discriminated values:
|
|
794
|
+
|
|
795
|
+
| `kind` | Identity field | `display` |
|
|
796
|
+
|--------|----------------|-----------|
|
|
797
|
+
| `scheme` | Non-empty `scheme` URI-family name | `CapabilityDisplay` |
|
|
798
|
+
| `mimetype` | Non-empty `mimetype` media type | `CapabilityDisplay` |
|
|
799
|
+
|
|
800
|
+
`CapabilityDisplay` is a closed object whose optional `glyph` is a non-empty,
|
|
801
|
+
opaque string. Capability frameworks own and validate their intrinsic
|
|
802
|
+
declarations; Core owns composition of the installed families; interface
|
|
803
|
+
modules expose this exact shape; clients own rendering, font support, theme,
|
|
804
|
+
and identity fallback when `glyph` is absent. Empty framework sentinels are
|
|
805
|
+
normalized to absence at composition. Display metadata is client state, never
|
|
806
|
+
model-language syntax or model packet teaching.
|
|
807
|
+
|
|
808
|
+
## 14. Parse diagnostics
|
|
809
|
+
|
|
810
|
+
§parse-diagnostics `PlurnkParseError` is a JSON-serializable Error subclass.
|
|
811
|
+
Its `message` contains only the parser-owned diagnostic; position, source, and
|
|
812
|
+
severity remain separate facts.
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
type ErrorSource = "lexer" | "parser" | "visitor";
|
|
816
|
+
type Severity = "error" | "warning";
|
|
817
|
+
|
|
818
|
+
class PlurnkParseError extends Error {
|
|
819
|
+
readonly line: number;
|
|
820
|
+
readonly column: number;
|
|
821
|
+
readonly source: ErrorSource;
|
|
822
|
+
readonly severity: Severity;
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
§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
|
|
828
|
+
identifies the offending or recovery point; a text item and `unparsedTail.from`
|
|
829
|
+
identify the first point at which that item or undefined tail begins. A
|
|
830
|
+
statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
|
|
831
|
+
the unknown sentinel; its dispatch origin remains a separate fact.
|
|
832
|
+
|
|
833
|
+
| Representation | Line | Column | Absence or extent |
|
|
834
|
+
|-------------------------------------|--------------------------|------------------------------------------|--------------------------------------------------------|
|
|
835
|
+
| Parser/AST `Position` | 1-based | 0-based Unicode code points | `{ line: 0, column: 0 }` is the sole unknown sentinel |
|
|
836
|
+
| Notice `content-offset` | 1-based | 0-based Unicode code points | Omit or set `position` to null when unknown |
|
|
837
|
+
| Contracts `TextRegion` | 1-based | 1-based Unicode code points | Start included, end excluded; equality is zero-width |
|
|
838
|
+
| SARIF 2.1.0 line/column region | 1-based | 1-based, declared by `columnKind` | End excluded; equality is zero-width |
|
|
839
|
+
|
|
840
|
+
Parser columns count code points, not UTF-16 code units or grapheme clusters.
|
|
841
|
+
LF and CRLF delimit source lines; a lone CR occupies one column. A point may sit
|
|
842
|
+
immediately after the final code point and carries no implicit character extent.
|
|
843
|
+
Consumers preserve parser points unchanged inside PLURNK. A SARIF adapter that
|
|
844
|
+
preserves one must add one to `column`, declare `columnKind` as
|
|
845
|
+
`"unicodeCodePoints"`, and emit equal start/end coordinates rather than
|
|
846
|
+
inventing an extent. `TextRegion` already uses SARIF's base and exclusive-end algebra but
|
|
847
|
+
remains a distinct contracts-owned representation. See [SARIF 2.1.0 §§3.14.26–27
|
|
848
|
+
and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
|
|
849
|
+
|
|
850
|
+
| Source | Boundary |
|
|
851
|
+
|-------------|----------------------------------------------------------------------------------------|
|
|
852
|
+
| `"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. |
|
|
854
|
+
| `"visitor"` | Semantic AST-construction failure, such as an invalid matcher dialect or signal shape. |
|
|
855
|
+
|
|
856
|
+
`severity` distinguishes a hard error from a non-fatal advisory. The parser is
|
|
857
|
+
the sole and complete owner of syntax-error messaging because it holds the
|
|
858
|
+
parse state, lexer mode, and expected-token set that no consumer has. It
|
|
859
|
+
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.
|
|
871
|
+
- §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
|
|
872
|
+
`[signal]` slot (executor-ident mode) hits a leading `-` or digit —
|
|
873
|
+
mark-shaped `<timeout, poll>` scope content mistyped into the brackets — the
|
|
874
|
+
lexer message becomes `timeout/poll ride the \`<scope>\` slot; try
|
|
875
|
+
\`EXEC<-1,300>\`` instead of a raw `unrecognized character`. The redirect is
|
|
876
|
+
EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
|
|
877
|
+
- §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.
|
|
887
|
+
- §misplaced-target-advisory **Mutation target in the signal slot.** When a
|
|
888
|
+
mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
|
|
889
|
+
`[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.
|
|
895
|
+
|
|
896
|
+
§error-shape The diagnostic class determines how much guidance the parser may
|
|
897
|
+
provide:
|
|
898
|
+
|
|
899
|
+
| Class | Surface | Message contract |
|
|
900
|
+
|------------------------|-----------------------|-------------------------------------------------------------------------------------------|
|
|
901
|
+
| Hard fact | `severity: "error"` | One concise observed fact and violated constraint in PLURNK vocabulary. |
|
|
902
|
+
| Targeted hard redirect | `severity: "error"` | One canonical correction only when parser state makes the intended structure unambiguous. |
|
|
903
|
+
| 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. |
|
|
905
|
+
|
|
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
|
|
909
|
+
feature rather than an implementation rule. Generic tutoring, speculative
|
|
910
|
+
intent, coordinate restatement, and multiple repair strategies are forbidden.
|
|
911
|
+
|
|
912
|
+
Examples of canonical hard facts:
|
|
913
|
+
|
|
914
|
+
- `unrecognized character '<<' in target`
|
|
915
|
+
- `unrecognized character ':' in signal`
|
|
916
|
+
- `unrecognized character 'X' in statement header`
|
|
917
|
+
- `expected close tag; got end of input`
|
|
918
|
+
- `expected ')'; got ':'`
|
|
919
|
+
|
|
920
|
+
Each malformed statement produces at most one hard error. The first recorded
|
|
921
|
+
hard lexer or parser error within its source range wins; later failures in that
|
|
922
|
+
same range are consumed rather than projected as a cascade. A visitor failure
|
|
923
|
+
surfaces when syntax admitted the statement but AST construction did not.
|
|
924
|
+
Independent malformed statements each retain one hard error. Advisories remain
|
|
925
|
+
separate because they do not represent failed admission.
|
|
926
|
+
|
|
927
|
+
§unparsed-tail-boundary When the lexer cannot determine where a malformed
|
|
928
|
+
statement ends, the result's `unparsedTail` marks the position from which
|
|
929
|
+
parsing gave up. `ParseResult.items` contains only facts that begin strictly
|
|
930
|
+
before that point; recovered contexts and diagnostics at or beyond it are not
|
|
931
|
+
public results. The tail is one separate boundary fact, not an additional
|
|
932
|
+
malformed-statement diagnostic. Consumers must treat anything from that point
|
|
933
|
+
onward as undefined and must never dispatch a recovered statement from it.
|
|
934
|
+
|
|
935
|
+
| Consumer duty | Contract |
|
|
936
|
+
|--------------------|----------------------------------------------------------------------------------------------------------------|
|
|
937
|
+
| Diagnostic text | Project `message` verbatim; do not strip prefixes, restate coordinates, or synthesize generic syntax recovery. |
|
|
938
|
+
| Structured context | Preserve `line`, `column`, `source`, and `severity` as separate fields. |
|
|
939
|
+
| Runtime recovery | Attach only a separately owned fact, such as Core knowing that bounded sibling operations were retained. |
|
|
940
|
+
| Durable projection | Map bounded hard errors to failed operation results; warnings may become Notices with `level: "warn"`. |
|
|
941
|
+
| Presentation | Normalize or bound the diagnostic only when the surface requires it, without changing its meaning. |
|
|
942
|
+
|
|
943
|
+
Serialization convention for transmission to the model (the agent
|
|
944
|
+
runtime constructs this; the parser provides the fields):
|
|
945
|
+
|
|
946
|
+
```json
|
|
947
|
+
{
|
|
948
|
+
"line": 1,
|
|
949
|
+
"column": 12,
|
|
950
|
+
"source": "parser",
|
|
951
|
+
"severity": "error",
|
|
952
|
+
"message": "expected close tag `:OPsuffix`; got end of input"
|
|
953
|
+
}
|
|
954
|
+
```
|