@plurnk/plurnk-contracts 1.10.0 → 1.11.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 (66) hide show
  1. package/README.md +2 -3
  2. package/SPEC.md +149 -75
  3. package/dist/plurnk.gemma.gbnf +4 -3
  4. package/dist/plurnk.qwen.gbnf +4 -3
  5. package/dist/schema/CapabilityDescriptor.json +23 -0
  6. package/dist/schema/CapabilityPolicy.json +18 -0
  7. package/dist/schema/CapabilityProjection.json +16 -0
  8. package/dist/schema/CapabilitySelector.json +24 -0
  9. package/dist/schema/ClientStatement.json +10 -2
  10. package/dist/schema/LoopPolicy.json +13 -0
  11. package/dist/schema/MatcherBody.json +8 -8
  12. package/dist/schema/ParsedPath.json +1 -11
  13. package/dist/schema/PlurnkStatement.json +76 -34
  14. package/dist/schema/ProblemProjection.json +38 -0
  15. package/dist/schema/ProposalProjection.json +2 -2
  16. package/dist/schema/ResourceSelection.json +12 -2
  17. package/dist/src/ApplicationPort.d.ts +9 -40
  18. package/dist/src/ApplicationPort.d.ts.map +1 -1
  19. package/dist/src/AstBuilder.d.ts +1 -7
  20. package/dist/src/AstBuilder.d.ts.map +1 -1
  21. package/dist/src/AstBuilder.js +127 -102
  22. package/dist/src/AstBuilder.js.map +1 -1
  23. package/dist/src/CapabilityAdmission.d.ts +9 -0
  24. package/dist/src/CapabilityAdmission.d.ts.map +1 -0
  25. package/dist/src/CapabilityAdmission.js +96 -0
  26. package/dist/src/CapabilityAdmission.js.map +1 -0
  27. package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
  28. package/dist/src/PlurnkErrorStrategy.js +51 -1
  29. package/dist/src/PlurnkErrorStrategy.js.map +1 -1
  30. package/dist/src/PlurnkParser.d.ts +4 -1
  31. package/dist/src/PlurnkParser.d.ts.map +1 -1
  32. package/dist/src/PlurnkParser.js +86 -21
  33. package/dist/src/PlurnkParser.js.map +1 -1
  34. package/dist/src/Problems.d.ts +6 -1
  35. package/dist/src/Problems.d.ts.map +1 -1
  36. package/dist/src/Problems.js +34 -0
  37. package/dist/src/Problems.js.map +1 -1
  38. package/dist/src/Validator.d.ts +17 -4
  39. package/dist/src/Validator.d.ts.map +1 -1
  40. package/dist/src/Validator.js +65 -10
  41. package/dist/src/Validator.js.map +1 -1
  42. package/dist/src/generated/plurnkLexer.d.ts +52 -40
  43. package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
  44. package/dist/src/generated/plurnkLexer.js +519 -431
  45. package/dist/src/generated/plurnkLexer.js.map +1 -1
  46. package/dist/src/generated/plurnkParser.d.ts +166 -92
  47. package/dist/src/generated/plurnkParser.d.ts.map +1 -1
  48. package/dist/src/generated/plurnkParser.js +1318 -705
  49. package/dist/src/generated/plurnkParser.js.map +1 -1
  50. package/dist/src/generated/plurnkParserVisitor.d.ts +49 -0
  51. package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
  52. package/dist/src/generated/plurnkParserVisitor.js +42 -0
  53. package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
  54. package/dist/src/index.d.ts +4 -4
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +3 -3
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/types.d.ts +3 -2
  59. package/dist/src/types.d.ts.map +1 -1
  60. package/dist/src/types.generated.d.ts +140 -34
  61. package/dist/src/types.generated.d.ts.map +1 -1
  62. package/dist/src/types.js +4 -6
  63. package/dist/src/types.js.map +1 -1
  64. package/package.json +2 -2
  65. package/plurnk.md +58 -44
  66. package/dist/schema/LoopFlags.json +0 -16
package/README.md CHANGED
@@ -49,12 +49,11 @@ The parser entry points deliberately accept different document tiers:
49
49
 
50
50
  | Entry point | Accepted input |
51
51
  |--------------------------------|-------------------------------------------------------|
52
- | `PlurnkParser.parse` | One PLAN-anchored model turn ending in terminal SEND |
52
+ | `PlurnkParser.parse` | One operation-bearing model turn; omitted PLAN/SEND are recovered |
53
53
  | `PlurnkParser.parseStatements` | A strict sequence of protocol statements |
54
- | `PlurnkParser.parseLog` | TURN-wrapped multi-turn script or log input |
54
+ | `PlurnkParser.parseLog` | Strict consecutive PLAN-through-SEND turns |
55
55
  | `PlurnkParser.parseClient` | Protocol statements plus client-only LOOK and BUFF |
56
56
  | `parsePath` | One path or URI using parser-equivalent decomposition |
57
- | `parseResourceSelection` | One COPY/MOVE destination and optional text scope |
58
57
 
59
58
  See SPEC {§turn-shape} and {§tier-entrypoints} for the tier boundaries. All
60
59
  AST, parse-result, schema-derived, and runtime-neutral wire types are exported
package/SPEC.md CHANGED
@@ -9,7 +9,7 @@ is the single code API for those contracts.
9
9
  | Surface | Canonical export or artifact |
10
10
  | ------------------------------------------------------------------------------- | --------------------------------------------------- |
11
11
  | Parser, AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
12
- | Effective loop policy and its default | `LoopFlags`, `DEFAULT_LOOP_FLAGS` |
12
+ | Capability and loop policies with their defaults | `CapabilityPolicy`, `LoopPolicy`, `DEFAULT_CAPABILITY_POLICY`, `DEFAULT_LOOP_POLICY` |
13
13
  | Durable reasoning intent | `ReasoningPolicy`, `REASONING_POLICIES` |
14
14
  | Model route and catalog discovery | `ModelRoute`, `ModelCatalogQuery`, `ModelCatalogPage`, `ModelReadiness` |
15
15
  | Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
@@ -125,15 +125,45 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
125
125
 
126
126
  | Contract | Shape invariant | Runtime responsibility |
127
127
  | ------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
128
- | `LoopFlags` | Complete effective `mode`, `auto`, `noWeb`, `noInteraction`, and `noProposals` | Validate/expand persisted partial policy before use |
128
+ | `CapabilityDescriptor` | One routed operation demand with its operation, access class, resource/runtime/tool coordinates, and declared traits | Derive every demand before dispatch |
129
+ | `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service, workspace, Worker, and loop layers |
130
+ | `CapabilityProjection` | Exact service, workspace, immutable Worker bound, mutable Worker, and normalized effective policies | Expose the resolver's Worker-level cascade to clients without claiming one layer is effective authority |
131
+ | `LoopPolicy` | Complete capability attenuation plus one `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
129
132
  | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
130
- | `ProposalProjection` | Identity, `{ scheme, authority, pathname }` review target, body/attrs, effective flags, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
133
+ | `ProposalProjection` | Identity, `{ scheme, authority, pathname }` review target, body/attrs, effective policy, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
131
134
  | `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
132
135
  | `ProviderCost` | Exact charged, estimated, or unknown monetary evidence | Normalize one monetary disposition for each physical provider request |
133
136
  | `ProviderRequestAccounting` | Usage and cost evidence for one physical provider request | Preserve request order across retries, failover, success, and failure |
134
137
  | `ProviderAccounting` | Ordered requests plus deterministic usage and exact-USD projections | Derive loop, protocol, telemetry, and client reporting without a second authority |
135
138
 
136
- `DEFAULT_LOOP_FLAGS` is the contracts-owned effective default value. A consumer may persist a partial object as an implementation detail, but it never exposes or acts on that partial representation as though it were the complete contract.
139
+ §capability-policy-matching A selector is an exact conjunction: every field it
140
+ declares must equal the descriptor, while every selected `trait` must occur in
141
+ the descriptor's trait set. `deny` wins within a layer. When `only` is present, at
142
+ least one selector must match. An empty policy admits everything and an empty
143
+ `only` list admits nothing.
144
+
145
+ §capability-policy-cascade Capability layers are purely subtractive and
146
+ order-independent: a descriptor is admitted only when every layer admits it.
147
+ No Worker or loop can restore service, workspace, or parent authority. A
148
+ composed operation is admitted only when every routed demand survives. These
149
+ descriptors govern routed external authority, not every grammar statement:
150
+ log/program control such as PLAN, OPEN, FOLD, log KILL, and targetless SEND
151
+ creates no capability demand. A known interactive runtime is represented by
152
+ access class `interact`; scheme and runtime manifests contribute traits rather
153
+ than hidden policy behavior.
154
+
155
+ §capability-policy-projection A `CapabilityProjection` reports every durable
156
+ Worker-level layer and their normalized intersection. The `worker` field is the
157
+ only client-mutable layer; `effective` is the authority a new unattenuated loop
158
+ would receive. A client never derives effective authority from the mutable
159
+ layer alone. Per-loop attenuation remains an immutable input to that loop and
160
+ is therefore absent from this durable Worker projection.
161
+
162
+ §loop-policy `DEFAULT_CAPABILITY_POLICY` and `DEFAULT_LOOP_POLICY` are the
163
+ contracts-owned complete defaults. A loop policy is immutable after creation;
164
+ its `capabilities` field only narrows broader authority and its `proposals`
165
+ field chooses one unambiguous downstream settlement posture. Capability
166
+ admission precedes effect classification and proposal settlement.
137
167
 
138
168
  §reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
139
169
  medium | high`. The schema owns this shared wire vocabulary. Providers own the
@@ -305,6 +335,7 @@ under {§plan-value}.
305
335
  | `delimiter` | Heading lane, joined directly to PLAN or OP |
306
336
  | `[signal]` | Optional operation-specific signal, preceded by one space |
307
337
  | `(path)` | Optional target slot, preceded by one space |
338
+ | `{metadata}` | Optional repeatable scheme-metadata modifier after a target |
308
339
  | `<scope>` | Optional numeric scope, preceded by one space |
309
340
  | `<!-- … -->` | Optional trailing operation annotation, preceded by one space |
310
341
  | line ending | Ends the single-line heading |
@@ -318,21 +349,25 @@ The following constraints are structural:
318
349
  ordinary body text.
319
350
  - PLAN is the only H1 operation and every non-PLAN operation is H2.
320
351
  - A header occupies one physical line.
321
- - Each admitted signal, target, and scope slot appears at most once.
352
+ - Each admitted signal, target, and scope slot appears at most once. Metadata
353
+ blocks may repeat only immediately after the target.
354
+ - §plan-slotless PLAN accepts no signal, target, metadata, or scope modifier;
355
+ observed modifiers are a bounded hard error naming only the rejected slots.
322
356
  - An annotation follows every present modifier and appears at most once.
323
357
  - BARE, WORK, FORK, and KILL do not admit a scope slot.
324
358
  - An ingested delimiter is `[A-Za-z0-9_]*`; canonical teaching and the GBNF use `0`.
325
359
 
326
360
  §slot-order Canonical producers and the GBNF rail emit signal, then target, then
327
- scope, then annotation, with one ASCII space before every present slot. Slot delimiters make
361
+ metadata, then scope, then annotation, with one ASCII space before every present modifier. Slot delimiters make
328
362
  their boundaries unambiguous, so the tolerant ANTLR ingester accepts zero or
329
363
  more horizontal whitespace characters before each slot and any permutation of
330
- the slots admitted by that operation, at most once each. Accepted spacing and
331
- permutation are not second canonical spellings.
364
+ the signal, target-with-metadata, and scope admitted by that operation, at most
365
+ once each. Metadata remains attached immediately after its target. Accepted
366
+ spacing and permutation are not second canonical spellings.
332
367
 
333
368
  §heading-inline-body Text that follows the last slot on a heading line — after
334
369
  horizontal whitespace, beginning with a character that cannot open a slot (not `[`,
335
- `(`, or `<`) — is the first body line: `## EXEC0 [crm] (crm_query) {"soql": "…"}` and
370
+ `(`, or `<`, nor `{` after a target) — is the first body line: `## EXEC0 [crm] (crm_query) SELECT Id FROM Case` and
336
371
  `## FIND0 (src/**) /createCoder/i` parse as their canonical two-line forms. Nothing is
337
372
  lost and the stored statement is canonical; the spelling is tolerated and announced:
338
373
  one warning-severity advisory follows the statement, naming the heading and the rule
@@ -348,14 +383,24 @@ authorization, status, or body. An empty comment normalizes to the empty string.
348
383
  Text containing a newline or lacking the closing `-->` is not an annotation;
349
384
  `<!--` elsewhere remains ordinary body text.
350
385
 
386
+ §scheme-metadata-modifier A target may be followed by zero or more
387
+ single-line `{metadata}` blocks. AstBuilder preserves each block's exact inner
388
+ text and order as the statement's `metadata: string[] | null`; nested braces
389
+ remain balanced content. The blocks are not part of the target: braces inside
390
+ `(path)` remain ordinary path and glob syntax, including `{PLAN,READ}`. The
391
+ language assigns metadata no meaning. A runtime admits it only for a scheme
392
+ that declares the capability, and that scheme exclusively owns interpretation,
393
+ validation, and authorization. An unfinished block or a newline before its
394
+ closing brace is a structural failure.
395
+
351
396
  The ingester also accepts several bounded noncanonical forms so it can explain
352
397
  or safely execute understandable input:
353
398
 
354
399
  | Tolerated input | Canonical or runtime disposition |
355
400
  |---------------------------------------------------|---------------------------------------------------------------------|
356
- | Reordered admitted slots | Producers retain signal → target → scope order |
401
+ | Reordered admitted slots | Producers retain signal → target → metadata → scope order |
357
402
  | Missing target on a generally targeted operation | AST carries `null`; the runtime rejects when the target is required |
358
- | PLAN modifiers or a non-`0` lane | Model canon keeps PLAN slotless and uses lane `0` |
403
+ | A non-`0` PLAN lane | Model canon uses lane `0` |
359
404
  | KILL annotation body | AST preserves it; model teaching leaves the KILL section empty |
360
405
  | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
361
406
  | Empty content where semantics require a body | The empty section normalizes null; the operation owner rejects it |
@@ -368,6 +413,7 @@ or safely execute understandable input:
368
413
  | `delimiter` | `[A-Za-z0-9_]*`, adjacent to PLAN or OP |
369
414
  | `[signal]` | Operation-specific tags, identifier, branch, or integer |
370
415
  | `(path)` | Local path or scheme URL target; detailed in §5 |
416
+ | `{metadata}` | Repeatable opaque scheme modifier attached after a target |
371
417
  | `<scope>` | One or more signed integers or decimals; detailed in §7 |
372
418
  | annotation | Optional trailing `<!-- … -->` descriptive text |
373
419
  | `body` | Opaque section text before the next same-lane heading or EOF |
@@ -383,14 +429,14 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
383
429
  | FIND | optional add log tags | required target or glob | optional result range | optional matcher |
384
430
  | READ | optional add log tags | required target | optional text region | empty |
385
431
  | EDIT | optional add log tags | required file or entry | required for an existing target | literal text |
386
- | COPY | optional add log tags | required source | optional source region | required destination selection |
387
- | MOVE | optional add log tags | required source | optional source region | required destination selection |
432
+ | COPY | optional add log tags | required source and destination | optional region after each path | empty |
433
+ | MOVE | optional add log tags | required source and destination | optional region after each path | empty |
388
434
  | FOLD | optional filter/change tags | optional log selection | optional log-body line scope | optional matcher |
389
435
  | OPEN | optional filter/change tags | optional log selection | optional log-body line scope | optional matcher |
390
436
  | EXEC | optional executor | optional local working path | optional timeout, poll | optional executor input |
391
437
  | BARE | optional add log tags | none | none | required prompt |
392
- | WORK | optional Git branch | required fresh `worker://name` | none | required prompt |
393
- | FORK | optional Git branch | required context-inheriting `worker://name` | none | required prompt |
438
+ | WORK | optional Git branch (refused unless the service enables branch delegation) | required fresh `worker://name` | none | required prompt |
439
+ | FORK | optional Git branch (refused unless the service enables branch delegation) | required context-inheriting `worker://name` | none | required prompt |
394
440
  | KILL | optional target-specific code | required target, including a log item | none | empty |
395
441
  | SEND | optional target-specific code | optional recipient | optional timeout, poll | message; terminal is nonempty |
396
442
 
@@ -400,10 +446,10 @@ Directed SEND and KILL delegate any present code to the addressed target's
400
446
  operation contract; a live process may interpret a KILL code as a Unix signal,
401
447
  but that interpretation does not define KILL generally.
402
448
 
403
- §plan-value **PLAN carries one complete Plurnk Plan.** Its entries are the
404
- model's current working-memory inventory: durable findings are `memory`, finished
405
- actions are `completed`, open inquiries are `pending`, and active priorities are
406
- `in_progress`. Admission parses the JSON body — one JSON array document in any whitespace layout, including the {§json-result-rendering} spread the log projects — strips unknown
449
+ §plan-value **PLAN carries one installment of the model's running work journal.**
450
+ Its entries record newly made working-memory items: durable findings and decisions
451
+ are `memory`, finished actions are `completed`, open work is `pending`, and active
452
+ priorities are `in_progress`. Admission parses the JSON body — one JSON array document in any whitespace layout, including the {§json-result-rendering} spread the log projects — strips unknown
407
453
  entry keys (the model-facing Plan carries no priority: struck 2026-08-24 so the
408
454
  log echoes only the canonical shape, starving stale-field habits), and validates
409
455
  the canonical bare array: every entry has string `content` and `status` in
@@ -411,10 +457,11 @@ the canonical bare array: every entry has string `content` and `status` in
411
457
  malformed-JSON, or otherwise invalid body becomes one `in_progress`
412
458
  entry whose content is the exact authored body; admission performs no partial
413
459
  repair or list inference. An empty body becomes the planless `[]`
414
- value. Each PLAN completely replaces the current Plan; it never expresses a
415
- delta. The exact `turnOps` source remains forensic program evidence, while the
416
- normalized array is the sole semantic value used by AST, persistence, durable
417
- log bodies, and model-packet materialization. PLAN is public log content—not
460
+ value. Each PLAN is the complete semantic value of that journal installment;
461
+ prior installments remain ordinary curatable log items. The exact `turnOps`
462
+ source remains forensic program evidence, while the normalized array is the sole
463
+ semantic value used by AST, persistence, durable log bodies, and model-packet
464
+ materialization. PLAN is public log content—not
418
465
  provider reasoning—and Plurnk initially mints no `_meta` values. Dispatch records
419
466
  the canonical value and has no other runtime effect.
420
467
 
@@ -432,7 +479,7 @@ commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
432
479
 
433
480
  §exec-tag-signal EXEC's bracket slot names its runtime. A bracket whose first character is
434
481
  a sign is a tag signal instead — a runtime never starts with `+` or `-` — so
435
- `## EXEC0 [+fetch] (.)` and `## EXEC0 [crm] [+schema] (crm_describe)` admit: the tags
482
+ `## EXEC0 [+fetch]` and `## EXEC0 [crm] [+schema] (crm_describe)` admit: the tags
436
483
  classify the log row exactly as on FIND or READ, the runtime is the explicit one or the
437
484
  default shell, and each slot appears at most once.
438
485
 
@@ -498,7 +545,7 @@ Mutation semantics:
498
545
  - An empty body deletes the selected text.
499
546
  - `<0>` prepends and `<-1>` appends.
500
547
  - `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
501
- - §destination-scope-boundary COPY and MOVE parse the body as a destination `ResourceSelection`: a target plus an optional destination scope. A destination scope is final body content; a scope-shaped suffix followed by residue before the section boundary is rejected rather than reinterpreted as target data. Scope-shaped text elsewhere remains target data, and a URL requiring the reserved terminal spelling percent-encodes its angle brackets. Header target, fragment, and scope independently select the source resource, channel, and region.
548
+ - §transfer-resource-selections COPY and MOVE require two singular `ResourceSelection` operands on the heading, source first and destination second, and admit no body. Each operand consists of `(path)`, any following `{metadata}`, and an optional following `<scope>`; modifiers bind only to the immediately preceding path. The two operands independently select their resource, channel, scheme metadata, and text region.
502
549
 
503
550
  ### §operation-observation Per-operation observations
504
551
 
@@ -549,7 +596,6 @@ and path globs share the slot; content matchers belong in the body.
549
596
  | `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
550
597
  | Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
551
598
  | `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
552
- | Trailing `{key: value}` | Removed before URL parsing and preserved as ordered `headers` pairs | Addressed scheme interprets request metadata |
553
599
  | `?query` | Preserved as ordered `UrlPath.query`; `null` = absent, `""` = `?` | Participates in scheme resource identity |
554
600
 
555
601
  AstBuilder recognizes a URL with the case-insensitive prefix
@@ -591,15 +637,6 @@ never target content. Glob metacharacters remain legal path data.
591
637
 
592
638
  Matching and folder-scope semantics remain runtime concerns.
593
639
 
594
- §path-request-metadata A scheme URL may append one or more
595
- `{key: value}` request-metadata blocks. AstBuilder removes the blocks before
596
- WHATWG decomposition and preserves them as ordered pairs so order and duplicate
597
- names survive. Local paths retain braces as ordinary path text. Scheme handlers,
598
- not the language parser, define the meaning and authorization of the metadata.
599
- The admitted AST retains exact values for execution; malformed-metadata visitor
600
- diagnostics identify only the structural fault and source position, never quote
601
- metadata contents or a native URL parser's input-bearing diagnostic.
602
-
603
640
  §worker-name The exported `WORKER_NAME` contract governs names minted for URI
604
641
  authority slots: a lowercase DNS label matching
605
642
  `[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. `RESERVED_AUTHORITIES` contains the
@@ -611,9 +648,9 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
611
648
 
612
649
  ## §matcher-prefix-claims 6. Bulk pattern matching
613
650
 
614
- FIND, OPEN, FOLD, and authored READ accept an optional body matcher. The lexer
615
- preserves the body opaquely; AstBuilder assigns the dialect from its leading
616
- characters, then normalizes matcher-bearing READ to FIND under
651
+ FIND, authored READ, OPEN, FOLD, LOOK, and BUFF accept an optional body matcher.
652
+ The lexer preserves the body opaquely; AstBuilder assigns the dialect from its
653
+ leading characters, then normalizes matcher-bearing READ to FIND under
617
654
  {§read-find-normalization}.
618
655
  A leading prefix claims its dialect. Invalid claimed syntax is a positioned
619
656
  visitor error and never falls back to glob matching.
@@ -624,13 +661,17 @@ visitor error and never falls back to glob matching.
624
661
  | `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
625
662
  | `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
626
663
  | `~` | Semantic | `~phrase` | Any text after the prefix | Embedding index |
627
- | `@` | Graph | `@symbol`, `@<symbol`, or `@>symbol` | Direction and symbol preserved | Symbol index |
628
- | none | Glob | Shell glob or literal text | Raw string | Mimetype projection |
664
+ | `&` | Graph | `&symbol`, `&<symbol`, or `&>symbol` | Exact shape validation | Symbol index |
665
+ | none | Glob | Shell glob or literal text | Single-line raw string | Mimetype projection |
629
666
 
630
667
  XPath is classified before regex because its prefix is two slashes. Regex
631
668
  splitting respects escapes and character classes; `\/` represents a literal
632
669
  slash. The AST stores regex `pattern` and `flags`, not a compiled object.
633
- Semantic and graph matchers require no parse step. Scope carries semantic
670
+ Semantic matchers require no parse step. Graph admission validates its direction
671
+ and non-whitespace symbol before runtime. Every other leading character remains
672
+ in the fallback glob/literal dialect; `@(...)` is therefore an extglob group and
673
+ bare `@text` remains literal matcher text. Rendered READ coordinates are
674
+ structural output rows, not a reserved matcher prefix. Scope carries semantic
634
675
  threshold and result-range information rather than changing the matcher body.
635
676
 
636
677
  AstBuilder validation is compile-only and never evaluates a document. Matcher
@@ -638,11 +679,10 @@ evaluation belongs to the runtime's selected mimetype, embedding, or symbol
638
679
  implementation. A matcher admission error is local to its statement; later
639
680
  statements remain recoverable when their boundaries are trustworthy.
640
681
 
641
- - §pattern-body-single-line The GBNF rail permits only single-line matcher
642
- bodies. A regex that matches a newline uses the two-character `\n` escape.
643
- ANTLR preserves the complete section body; the same-lane heading boundary
644
- keeps every following statement independently parseable without a matcher-
645
- specific implicit close rule.
682
+ - §pattern-body-single-line Every matcher body is one physical line. AstBuilder
683
+ rejects multiline bodies before dialect classification, while GBNF excludes
684
+ line terminators. A regex that matches a newline uses the two-character `\n`
685
+ escape. Non-matcher operation bodies remain multiline.
646
686
  - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
647
687
  character. Empty matchers and later colons remain valid; a regex such as
648
688
  `/^:needle/` expresses a pattern beginning with a literal colon.
@@ -665,7 +705,7 @@ The operation column names the canonical AST operation after
665
705
  | COPY/MOVE source | 0/1/2/4 text coordinates | Region copied or moved from the selected source |
666
706
  | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
667
707
  | OPEN / FOLD | 0/1/2 body-relative line coordinates | Whole log body when absent; one physical line or inclusive range when present |
668
- | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in seconds |
708
+ | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
669
709
  | Terminal SEND `[202]` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence |
670
710
 
671
711
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
@@ -775,7 +815,7 @@ disposition. The shape rules ARE structural:
775
815
  non-disposition code, targeted or pathless, empty body allowed.
776
816
  - §terminal-body-nonempty The GBNF rail requires a non-empty terminal SEND body — a constrained
777
817
  turn cannot end empty-handed. ANTLR remains tolerant during ingestion.
778
- - §park-202-only The **park** rides `[202]` only: `<T>` (wait up to T seconds),
818
+ - §park-202-only The **park** rides `[202]` only: `<T>` (wait up to T minutes),
779
819
  `<T,P>` (adds a poll cadence, mirroring EXEC's slot), `<-1>`
780
820
  (indefinite; the join's own liveness bounds it). See §7 for the
781
821
  GBNF-strict / ANTLR-tolerant split.
@@ -817,6 +857,9 @@ stateDiagram-v2
817
857
  SLOTS --> TARGET: target opener
818
858
  TARGET --> TARGET: balanced literals / target escapes
819
859
  TARGET --> SLOTS: target close at depth zero
860
+ SLOTS --> METADATA: metadata opener after target
861
+ METADATA --> METADATA: balanced inner braces
862
+ METADATA --> SLOTS: metadata close at depth zero
820
863
  SLOTS --> SLOTS: scope token
821
864
  SLOTS --> SLOTS: trailing annotation
822
865
  SLOTS --> BODY: heading line end
@@ -826,8 +869,9 @@ stateDiagram-v2
826
869
 
827
870
  The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
828
871
  PLAN or H2 minted operation carrying that exact lane. SLOTS admits
829
- operation-appropriate signal, target, and scope openers in any order, followed
830
- by an optional annotation; the parser grammar enforces at-most-once multiplicity.
872
+ operation-appropriate signal, target-with-metadata, and scope openers in any
873
+ order, followed by an optional annotation; the parser grammar enforces
874
+ at-most-once slot multiplicity and keeps repeatable metadata attached to its target.
831
875
  Signal submodes select tags, integer,
832
876
  or identifier tokens by operation family. TARGET preserves balanced inner
833
877
  parentheses and recognized target escapes. BODY emits opaque text until a
@@ -840,7 +884,7 @@ provider reasoning cannot become the turn anchor.
840
884
 
841
885
  RecordingListener captures lexer and parser failures; AstBuilder adds visitor
842
886
  failures. PlurnkErrorStrategy recovers at structural heading boundaries where
843
- possible. EOF is a valid body boundary. An unfinished signal or target produces
887
+ possible. EOF is a valid body boundary. An unfinished signal, target, or metadata block produces
844
888
  `unparsedTail`; no later input is trustworthy.
845
889
 
846
890
  ## §whitespace-contract 11. Whitespace and interstatement text
@@ -849,9 +893,10 @@ possible. EOF is a valid body boundary. An unfinished signal or target produces
849
893
  |-----------------------------|---------------------------------------|-----------------------------------------------------------|
850
894
  | Heading marker | `# PLAN0` or `## OP0` at column zero | The initial PLAN may directly follow leading TEXT; subsequent headings retain exact depth and column |
851
895
  | Between OP and delimiter | Adjacent | Must remain adjacent |
852
- | Before each header slot | One ASCII space | Zero or more horizontal whitespace characters |
896
+ | Before each modifier | One ASCII space | Zero or more horizontal whitespace characters |
853
897
  | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
854
898
  | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline is invalid |
899
+ | Inside scheme metadata | Scheme-defined single-line content | Balanced braces tolerated; newline is invalid |
855
900
  | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
856
901
  | Before annotation | One ASCII space | Zero or more horizontal whitespace characters |
857
902
  | Inside annotation | One-line prose padded by one space | Any single-line text through the first closing `-->` |
@@ -878,25 +923,33 @@ and wire types come from generated schemas; the small hand-maintained parser
878
923
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
879
924
  express. Consumers never receive ANTLR parse-tree or token types.
880
925
 
881
- §turn-shape `PlurnkParser.parse` accepts exactly one model turn. H1 PLAN is the
882
- first operation, a disposition-coded H2 SEND is the terminal operation, and PLAN
883
- cannot recur mid-turn. Tolerated TEXT may appear only before PLAN; after PLAN,
884
- nonstructural text is section body content. Missing either anchor or placing a
885
- same-lane operation after the terminal SEND is an error.
926
+ §turn-shape `PlurnkParser.parse` accepts exactly one model turn containing at
927
+ least one parsed source operation. Canonical generation begins with H1 PLAN,
928
+ ends with a disposition-coded H2 SEND, and never repeats PLAN mid-turn. When no
929
+ valid leading PLAN was parsed, the parser prepends an empty PLAN; when no valid
930
+ terminal SEND was parsed, it appends a bodyless `SEND [102]`. Either default
931
+ carries {§parser-position} `UNKNOWN_POSITION` and one exact hard diagnostic
932
+ stating the observed boundary failure and applied default. The source text
933
+ remains unchanged.
934
+ An authored terminal SEND still ends the source turn: a same-lane operation or
935
+ other hard error after it is outside the trustworthy boundary. Tolerated TEXT
936
+ may appear only before the first operation; after that point, nonstructural text
937
+ is section body content. `parseLog` remains strict canonical PLAN-through-SEND
938
+ input, and GBNF remains strict canonical generation.
886
939
 
887
940
  §document-fence `PlurnkParser.parse` additionally admits one outer Markdown code
888
941
  fence whose opening line is exactly ```` ```plurnk ```` and whose closing line,
889
942
  when present, is ```` ``` ````. The fence encloses the complete
890
- PLAN-through-SEND turn and projects neither text nor body content into the AST.
891
- Its opener commits the document to either that closer or EOF immediately after
892
- the complete turn. This is document framing, not another statement grammar, and
943
+ model turn and projects neither text nor body content into the AST. Its opener
944
+ commits the document to either that closer or EOF immediately after the turn.
945
+ This is document framing, not another statement grammar, and
893
946
  no other parser tier admits it. GBNF continues to shape the paired form.
894
947
 
895
948
  §tier-entrypoints Each parser entry point owns one document tier:
896
949
 
897
950
  | Entry point | Accepted document | Result statement type |
898
951
  |--------------------------------|----------------------------------------------------------------|-----------------------|
899
- | `PlurnkParser.parse` | One PLAN turn: bare with optional TEXT, or outer `plurnk` fence ending at its closer or EOF | `PlurnkStatement` |
952
+ | `PlurnkParser.parse` | One operation-bearing model turn, bare with optional TEXT or inside one outer `plurnk` fence; omitted PLAN/SEND recover to defaults | `PlurnkStatement` |
900
953
  | `PlurnkParser.parseStatements` | Zero or more protocol statements and hidden whitespace | `PlurnkStatement` |
901
954
  | `PlurnkParser.parseLog` | One or more consecutive same-lane PLAN-anchored turns | `PlurnkStatement` |
902
955
  | `PlurnkParser.parseClient` | H2 protocol statements plus read-shaped LOOK/BUFF commands | `ClientStatement` |
@@ -913,15 +966,16 @@ following supported consumer values. All other root exports are TypeScript types
913
966
  |---------------------------------------|---------------------------------------------------------------------|---------------------------------------------|
914
967
  | `PlurnkParser` | Four document-tier entry points listed above | {§parser-architecture}, {§tier-entrypoints} |
915
968
  | `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
916
- | `parsePath`, `parseResourceSelection` | Parser-equivalent target and COPY/MOVE destination admission | {§path-syntax}, {§tier-entrypoints} |
969
+ | `parsePath` | Parser-equivalent target admission | {§path-syntax}, {§tier-entrypoints} |
917
970
  | `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
918
971
  | `Validator` | Validation and assertion against the owning JSON Schemas | {§wire-entrypoint} |
919
972
  | `InvalidNoticeError` | Typed failure from `Validator.assertNotice` | {§notice} |
920
973
  | `InvalidProblemDetailsError` | Typed failure from `Validator.assertProblemDetails` | {§problem-details} |
974
+ | `InvalidProblemProjectionError` | Typed failure from `Validator.assertProblemProjection` | {§problem-projection} |
921
975
  | `InvalidOperationResultError` | Typed failure from `Validator.assertOperationResult` | {§operation-result} |
922
976
  | `InvalidTextRegionError` | Typed failure from `Validator.assertTextRegion` | {§text-region} |
923
977
  | `InvalidRangeExtentError` | Typed failure from `Validator.assertRangeExtent` | {§range-extent} |
924
- | `Problems` | RFC 9457 Problem construction | {§problem-details} |
978
+ | `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
925
979
  | `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
926
980
  | `WORKER_NAME`, `RESERVED_AUTHORITIES` | Authority minting predicate and internal reserved names | {§worker-name} |
927
981
  | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
@@ -931,7 +985,7 @@ than alternate consumer entry points:
931
985
 
932
986
  | Internal component | Boundary |
933
987
  |--------------------------------------------|---------------------------------------------------------------------------------------------------------------|
934
- | `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser`, `parsePath`, and `parseResourceSelection` own its API |
988
+ | `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser` and `parsePath` own its API |
935
989
  | `PlurnkErrorStrategy`, `RecordingListener` | Assemble parser recovery and diagnostics around `antlr4ng`; consumers receive `PlurnkParseError` values |
936
990
  | `Jsonplurnk` test helper | Independently checks the Core-owned {§jsonplurnk} renderer corpus; it is neither shipped code nor a root API |
937
991
 
@@ -1063,6 +1117,20 @@ Internal invariant violations throw and preserve their cause. An external
1063
1117
  protocol may require its own error envelope; its adapter maps that envelope to
1064
1118
  or from the canonical Problem without creating another PLURNK failure contract.
1065
1119
 
1120
+ §problem-projection `ProblemProjection` is the sole compact model-packet view of
1121
+ an exact `ProblemDetails`. `Problems.project(problem, context)` validates both
1122
+ representations and rejects a status that contradicts the enclosing row.
1123
+
1124
+ | Projection member | Contract |
1125
+ |-------------------|----------|
1126
+ | `type`, `detail` | Always retained; together they identify the stable class and occurrence-specific cause |
1127
+ | `stage`, `recovery`, `retryable`, extensions | Retained only when present and not exactly duplicated by the enclosing row |
1128
+ | `title`, `status`, `instance` | Forbidden; the exact Problem owns stable title and occurrence identity while the enclosing packet row already owns model-facing status and address |
1129
+
1130
+ Projection never mutates or replaces the exact Problem. Durable storage,
1131
+ external protocols, clients, and forensic artifacts continue to receive the
1132
+ complete RFC 9457 object.
1133
+
1066
1134
  ### 13.6 Notices
1067
1135
 
1068
1136
  §notice A `Notice` is a transient, nonterminal observation. It cannot determine durable
@@ -1214,9 +1282,12 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
1214
1282
  `severity` distinguishes a hard error from a non-fatal advisory. The parser is
1215
1283
  the sole and complete owner of syntax-error messaging because it holds the
1216
1284
  parse state, lexer mode, and expected-token set that no consumer has. It
1217
- produces the final diagnostic message, deduplicated expected-token lists,
1218
- turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1219
- `## SEND0 [code]`), and these targeted diagnostics:
1285
+ produces the final diagnostic message, deduplicated expected-token lists, and
1286
+ turn-shape diagnostics. No valid leading PLAN yields ``No valid leading PLAN
1287
+ was parsed; an empty `# PLAN0` was used.``; no valid terminal SEND yields ``No
1288
+ valid terminal SEND was parsed; `## SEND0 [102]` was used.``; source with no
1289
+ parsed operation yields `no valid Plurnk operation was found.` Targeted
1290
+ diagnostics are:
1220
1291
 
1221
1292
  - §signal-scope-redirect **EXEC scope in the signal slot.** When EXEC's
1222
1293
  `[signal]` slot (executor-ident mode) hits a leading `-` or digit —
@@ -1225,17 +1296,21 @@ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1225
1296
  `## EXEC0 <-1,300>`” instead of a raw `unrecognized character`. The redirect is
1226
1297
  EXEC-scoped because its signal mode is exclusive; SEND/KILL are untouched.
1227
1298
  - §matcher-body-redirect **Matcher body in the slot region.** When the
1228
- post-target header region begins with `$`, `~`, or `@` with no whitespace
1299
+ post-target modifier region begins with `$`, `~`, or `@` with no whitespace
1229
1300
  before it, the lexer redirects the unambiguous matcher to body content below the
1230
1301
  OP heading instead of returning the generic slot list (after whitespace it is
1231
1302
  already the inline body, {§heading-inline-body}). Slash-led regex and XPath are
1232
1303
  excluded because `/` can be target data.
1304
+ - §bare-target-redirect **A `(target)` on BARE.** BARE takes no `(path)`; a model that
1305
+ writes its prompt, or the prompt's address, into a parenthesized slot (`## BARE0
1306
+ (What day is it?)`, `## BARE0 (prompt:///1/1)`) is told that the prompt is the body
1307
+ line beneath the heading, with the heading's own opener, instead of the generic
1308
+ slot list. Two operator sessions on 2026-08-26 produced exactly these shapes.
1233
1309
  - §combined-anchor-line-redirect **Combined anchor and line number in a scope.**
1234
1310
  A text-coordinate scope containing `@hash:L` or `@hash L` is one bounded hard
1235
1311
  error: `a scope position accepts one line coordinate; use the \`@hash\` anchor
1236
1312
  without its displayed line number`. A malformed header scope is consumed as
1237
- one token, while a COPY/MOVE destination selection fails at its visitor
1238
- boundary; neither produces a punctuation cascade.
1313
+ one token at either COPY/MOVE operand; neither produces a punctuation cascade.
1239
1314
  - §misplaced-target-advisory **Mutation target in the signal slot.** When a
1240
1315
  mutating op (EDIT/COPY/MOVE) parses with a null `(target)` and a path-shaped
1241
1316
  `[signal]` element (a `/` or a dotted extension), the message redirects the
@@ -1247,11 +1322,10 @@ turn-shape imperatives (begin with `# PLAN0`, end with a terminal
1247
1322
  body is solely an HTML comment (`<!-- … -->`) can never carry a matcher: it is
1248
1323
  the annotation the model put on the line below the heading. The builder takes
1249
1324
  the comment as the annotation when the heading has none, builds the operation
1250
- with no body, and raises one warning-severity advisory naming what happened
1251
- and the correct form (`## READ0 (…) <…> <!-- -->`); the parser places the
1252
- advisory right after its statement and the service delivers it as a
1253
- `parse_advisory` notice with its position. A body with any other content is a
1254
- matcher, as before.
1325
+ with no body, and raises one warning-severity advisory stating that observed
1326
+ normalization; the parser places the advisory right after its statement and
1327
+ the service delivers it as a `parse_advisory` notice with its position. A body
1328
+ with any other content is a matcher, as before.
1255
1329
 
1256
1330
  §error-shape The diagnostic class determines how much guidance the parser may
1257
1331
  provide:
@@ -55,8 +55,8 @@ plan ::= "# PLAN0" annotation-slot? "\n" section-body-ne "\n"
55
55
  find ::= "## FIND0" add-tags-slot? target-slot line-slot? annotation-slot? "\n" | "## FIND0" add-tags-slot? target-slot line-slot? annotation-slot? "\n" pattern-body-ne "\n"
56
56
  read ::= "## READ0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" | "## READ0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
57
57
  edit ::= "## EDIT0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" | "## EDIT0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
58
- copy ::= "## COPY0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
59
- move ::= "## MOVE0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
58
+ copy ::= "## COPY0" add-tags-slot? target-slot text-line-slot? target-slot text-line-slot? annotation-slot? "\n"
59
+ move ::= "## MOVE0" add-tags-slot? target-slot text-line-slot? target-slot text-line-slot? annotation-slot? "\n"
60
60
  open ::= "## OPEN0" log-selection text-line-slot? annotation-slot? "\n" | "## OPEN0" log-selection text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
61
61
  fold ::= "## FOLD0" log-selection text-line-slot? annotation-slot? "\n" | "## FOLD0" log-selection text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
62
62
  exec ::= "## EXEC0" exec-slot? target-slot? line-slot? annotation-slot? "\n" | "## EXEC0" exec-slot? target-slot? line-slot? annotation-slot? "\n" section-body-ne "\n"
@@ -122,7 +122,8 @@ root-gemma ::= channel sep framed-turn
122
122
  add-tags-slot ::= " " add-tags
123
123
  curation-tags-slot ::= " " curation-tags
124
124
  log-target-slot ::= " (log:" target-atom+ ")"
125
- target-slot ::= " " target
125
+ target-slot ::= " " target metadata-slot*
126
+ metadata-slot ::= " {" [^\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\n\r{}]* "}"
126
127
  line-slot ::= " " line
127
128
  text-line-slot ::= " " text-line
128
129
  exec-slot ::= " " exec-sig
@@ -55,8 +55,8 @@ plan ::= "# PLAN0" annotation-slot? "\n" section-body-ne "\n"
55
55
  find ::= "## FIND0" add-tags-slot? target-slot line-slot? annotation-slot? "\n" | "## FIND0" add-tags-slot? target-slot line-slot? annotation-slot? "\n" pattern-body-ne "\n"
56
56
  read ::= "## READ0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" | "## READ0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
57
57
  edit ::= "## EDIT0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" | "## EDIT0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
58
- copy ::= "## COPY0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
59
- move ::= "## MOVE0" add-tags-slot? target-slot text-line-slot? annotation-slot? "\n" section-body-ne "\n"
58
+ copy ::= "## COPY0" add-tags-slot? target-slot text-line-slot? target-slot text-line-slot? annotation-slot? "\n"
59
+ move ::= "## MOVE0" add-tags-slot? target-slot text-line-slot? target-slot text-line-slot? annotation-slot? "\n"
60
60
  open ::= "## OPEN0" log-selection text-line-slot? annotation-slot? "\n" | "## OPEN0" log-selection text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
61
61
  fold ::= "## FOLD0" log-selection text-line-slot? annotation-slot? "\n" | "## FOLD0" log-selection text-line-slot? annotation-slot? "\n" pattern-body-ne "\n"
62
62
  exec ::= "## EXEC0" exec-slot? target-slot? line-slot? annotation-slot? "\n" | "## EXEC0" exec-slot? target-slot? line-slot? annotation-slot? "\n" section-body-ne "\n"
@@ -111,7 +111,8 @@ root-qwen-response ::= "<think>\n" root-qwen
111
111
  add-tags-slot ::= " " add-tags
112
112
  curation-tags-slot ::= " " curation-tags
113
113
  log-target-slot ::= " (log:" target-atom+ ")"
114
- target-slot ::= " " target
114
+ target-slot ::= " " target metadata-slot*
115
+ metadata-slot ::= " {" [^\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\n\r{}]* "}"
115
116
  line-slot ::= " " line
116
117
  text-line-slot ::= " " text-line
117
118
  exec-slot ::= " " exec-sig
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.xyz/v0/CapabilityDescriptor.json",
4
+ "title": "CapabilityDescriptor",
5
+ "description": "One fully routed operation demand evaluated by the capability-policy cascade.",
6
+ "type": "object",
7
+ "required": ["operation", "access", "traits"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "operation": {
11
+ "enum": ["FIND", "READ", "EDIT", "COPY", "MOVE", "SEND", "EXEC", "BARE", "WORK", "FORK", "KILL"]
12
+ },
13
+ "scheme": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*$" },
14
+ "runtime": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*$" },
15
+ "tool": { "type": "string", "minLength": 1 },
16
+ "access": { "enum": ["observe", "mutate", "execute", "interact", "control"] },
17
+ "traits": {
18
+ "type": "array",
19
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
20
+ "uniqueItems": true
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.xyz/v0/CapabilityPolicy.json",
4
+ "title": "CapabilityPolicy",
5
+ "description": "One purely subtractive capability-policy layer. Deny wins; when only is present, a demand must match at least one selector.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "only": {
10
+ "type": "array",
11
+ "items": { "$ref": "https://schemas.plurnk.xyz/v0/CapabilitySelector.json" }
12
+ },
13
+ "deny": {
14
+ "type": "array",
15
+ "items": { "$ref": "https://schemas.plurnk.xyz/v0/CapabilitySelector.json" }
16
+ }
17
+ }
18
+ }