newt-interaction 0.8.3

Renderer-neutral interaction protocol model for Newt Markup (epic #1803, slice A2)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "InteractionDefinition",
  "description": "The immutable semantic model of one interaction.\n\nIdentity is a [`DefinitionId`] — a `ContentId` over the canonical encoding of this whole record, which is also its **exact form digest**.",
  "type": "object",
  "required": [
    "controls",
    "features",
    "kind",
    "markdown",
    "revision",
    "schema"
  ],
  "properties": {
    "controls": {
      "description": "The controls, in presentation order.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/Control"
      }
    },
    "features": {
      "description": "What a surface must be able to do to present this faithfully, each demand marked required or optional.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/FeatureDemand"
      }
    },
    "kind": {
      "description": "What kind of interaction this is.",
      "allOf": [
        {
          "$ref": "#/definitions/InteractionKind"
        }
      ]
    },
    "markdown": {
      "description": "The readable body — canonical Markdown, the fallback every surface can render (ADR law 1). Held as text; parsing belongs to `newt_core::markup`, not to the protocol layer.",
      "type": "string"
    },
    "note": {
      "description": "A subordinate line beneath the body — a control hint, a danger warning. Readable content like `markdown`, kept separate because surfaces place it differently and the legacy prompt already distinguishes the two.\n\nOmitted when absent, so a definition without one is byte-identical to one written before this field existed.",
      "type": [
        "string",
        "null"
      ]
    },
    "revision": {
      "description": "Which revision of this definition's authoring lineage this is.",
      "type": "integer",
      "format": "uint64",
      "minimum": 0.0
    },
    "schema": {
      "description": "Versioned type tag; see [`DEFINITION_SCHEMA_V1`].",
      "allOf": [
        {
          "$ref": "#/definitions/DefinitionTag"
        }
      ]
    }
  },
  "additionalProperties": false,
  "definitions": {
    "ChoiceOption": {
      "description": "One mutually-exclusive option of a choice control.\n\nThe option carries the SemanticRole, not the control: a permission question has no single meaning — each of its options does. `allow once` means Allow, `deny (default)` means Deny, and they are options of one question rather than two questions.",
      "type": "object",
      "required": [
        "id",
        "label",
        "role"
      ],
      "properties": {
        "aliases": {
          "description": "Hidden inputs that also select this option — `n`/`N` for a deny. Never rendered, and never able to shadow another option's canonical id or key: matching is canonical-first, and ambiguity denies.\n\nOmitted when empty, for the same reason as `key`.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "id": {
          "description": "Stable within the control; the definition commits to it.",
          "allOf": [
            {
              "$ref": "#/definitions/OptionId"
            }
          ]
        },
        "key": {
          "description": "The accelerator a keyboard surface offers for this option — `a`, `d`. Presentation, but NOT only presentation: the terminal authorizes on it, so it is part of what the definition means and cannot live in the view.\n\nOmitted when empty, so an option that offers no accelerator is byte-identical to one written before this field existed.",
          "type": "string"
        },
        "label": {
          "description": "Human-readable label. Labels never confer authority (ADR law 2).",
          "type": "string"
        },
        "role": {
          "description": "What picking this option means.",
          "allOf": [
            {
              "$ref": "#/definitions/SemanticRole"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "Control": {
      "description": "One control: a stable id, what it means, and how it takes input.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "label",
        "requirement"
      ],
      "properties": {
        "id": {
          "description": "Stable within the definition; the definition commits to it.",
          "allOf": [
            {
              "$ref": "#/definitions/ControlId"
            }
          ]
        },
        "kind": {
          "description": "How it accepts input, and — for a choice — what it offers.",
          "allOf": [
            {
              "$ref": "#/definitions/ControlKind"
            }
          ]
        },
        "label": {
          "description": "Human-readable label. Labels never confer authority (ADR law 2).",
          "type": "string"
        },
        "requirement": {
          "description": "Whether a response must include this control.",
          "allOf": [
            {
              "$ref": "#/definitions/Requirement"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "ControlId": {
      "type": "string",
      "minLength": 1,
      "pattern": "^[A-Za-z0-9_-]+$"
    },
    "ControlKind": {
      "description": "How a control accepts input.\n\n`Choice` carries its options inline because they belong to it: one field, N mutually-exclusive answers. Modelling each option as its own control would make [`Requirement`] incoherent — under \"the response must include this control\", answering an allow would require also answering the deny.\n\n# A kind is a MEANING; an affordance is not a kind\n\nThe line this vocabulary is drawn on, because the HTML input catalog it mirrors does not draw it and copying that list variant-for-variant would have produced twenty-two.\n\nA kind earns its place when it changes what a well-formed ANSWER is — `#c0ffee` is a color and not a date, and no surface may accept it as one. It does not earn a place merely by suggesting a different widget: a slider and a typed number accept the same answers, so [`Range`](Self::Range) is separate from [`Number`](Self::Number) only because a bounded-and-stepped value space IS different (its bounds are mandatory), not because one draws as a slider.\n\nWhat this excludes, deliberately:\n\n- `button`, `submit`, `reset`, `image` — form ACTIONS. A definition's actions are its [`SemanticRole`]s; a submit button that carried its own meaning would be a second authority vocabulary. - `hidden` — not a control at all. A value the operator cannot see is not a thing they answered, and putting one here would let a surface claim an answer nobody gave. - `radio`, `checkbox`, `password`, `select` — already spelled [`Choice`](Self::Choice), [`Toggle`](Self::Toggle), [`Secret`](Self::Secret) and [`Choice`](Self::Choice) again. `<select>` and a radio group are one meaning with two presentations.\n\n# Every kind here is answerable by typing\n\nNone of these mint a [`SurfaceFeature`] demand (see `downgrade::intrinsic_demands`), and that is deliberate rather than unfinished. A date answered by typing `2026-09-01` is a satisfied date, so a surface with no calendar widget has NOT failed to present the question — it presented it plainly. Only [`Secret`](Self::Secret) demands a feature, because it changes what may be DISCLOSED rather than how something is drawn. Minting a demand per kind would make plain and headless surfaces refuse questions they can answer perfectly well, which is law 5 pointed at the wrong axis.",
      "oneOf": [
        {
          "description": "Pick exactly one of the options offered.",
          "type": "object",
          "required": [
            "choice"
          ],
          "properties": {
            "choice": {
              "type": "object",
              "required": [
                "options"
              ],
              "properties": {
                "options": {
                  "description": "The options, in presentation order.",
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/ChoiceOption"
                  }
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        {
          "description": "Free text.\n\n**Deliberately still a unit variant.** HTML spells five flavours of text — `email`, `url`, `tel`, `search`, plain — and carrying one here as `Text { format }` would re-encode the most common kind in the vocabulary: a unit variant is the dag-cbor string `\"text\"`, a struct variant is a map. That breaks `tests/data/interaction-vectors.json`, whose frozen canonical bytes are DECODED rather than rebuilt, and the external-consumer corpus answering them — the compatibility fixtures CLAUDE.md names as what a rewrite is written against.\n\nThe flavours differ, in a terminal, only in what counts as valid. That is not worth re-addressing every definition ever written. If one earns its own kind later it can be ADDED as a new variant, which breaks nothing — the way `Number`, `Temporal`, `Color` and `Path` were.",
          "type": "string",
          "enum": [
            "text"
          ]
        },
        {
          "description": "A boolean.",
          "type": "string",
          "enum": [
            "toggle"
          ]
        },
        {
          "description": "A secret: never persisted in markup or logs (ADR D1).",
          "type": "string",
          "enum": [
            "secret"
          ]
        },
        {
          "description": "A whole number, optionally bounded and stepped.\n\n**Integers, and that is a decision.** A binary float cannot be canonically encoded without deciding what to do about `NaN` and `-0.0`, and this vocabulary is content-addressed — two encodings of one value would be two identities. Every number newt asks for today (a round cap, a port, a timeout, a percentage) is integral. A fractional control is a later, separate decision, and it will carry its digits as a decimal STRING rather than reopening this one.",
          "type": "object",
          "required": [
            "number"
          ],
          "properties": {
            "number": {
              "type": "object",
              "properties": {
                "max": {
                  "description": "Largest acceptable value, inclusive.",
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64"
                },
                "min": {
                  "description": "Smallest acceptable value, inclusive.",
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64"
                },
                "step": {
                  "description": "The grid the value must sit on, counted from `min` (or from zero when unbounded below). `None` accepts any integer in range.",
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64"
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        {
          "description": "A number on a bounded, stepped scale — HTML's `range`.\n\nDistinct from [`Number`](Self::Number) because its bounds are MANDATORY: a slider without ends is not a slider, and a surface that draws one needs both to draw anything at all.",
          "type": "object",
          "required": [
            "range"
          ],
          "properties": {
            "range": {
              "type": "object",
              "required": [
                "max",
                "min",
                "step"
              ],
              "properties": {
                "max": {
                  "description": "Largest acceptable value, inclusive.",
                  "type": "integer",
                  "format": "int64"
                },
                "min": {
                  "description": "Smallest acceptable value, inclusive.",
                  "type": "integer",
                  "format": "int64"
                },
                "step": {
                  "description": "The grid the value must sit on, counted from `min`.",
                  "type": "integer",
                  "format": "int64"
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        {
          "description": "A point in time at the given precision.",
          "type": "object",
          "required": [
            "temporal"
          ],
          "properties": {
            "temporal": {
              "type": "object",
              "required": [
                "precision"
              ],
              "properties": {
                "precision": {
                  "description": "How much of a moment is being asked for.",
                  "allOf": [
                    {
                      "$ref": "#/definitions/TemporalPrecision"
                    }
                  ]
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        {
          "description": "An sRGB color, written `#rrggbb`.",
          "type": "string",
          "enum": [
            "color"
          ]
        },
        {
          "description": "A filesystem path.\n\nThe protocol says what is being NAMED, never whether it exists: an existence check is the host's business, is racy by nature, and would make a definition's validity depend on the machine reading it.",
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "object",
              "required": [
                "kind"
              ],
              "properties": {
                "accept": {
                  "description": "Extensions or globs a surface may filter by — HTML's `accept`. Advisory: a surface that ignores it still collects a valid answer.",
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "kind": {
                  "description": "Whether the path names a file or a directory.",
                  "allOf": [
                    {
                      "$ref": "#/definitions/PathKind"
                    }
                  ]
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "DefinitionTag": {
      "type": "string",
      "const": "newt.interaction.definition/v1"
    },
    "FeatureDemand": {
      "description": "A definition's demand for one surface feature, and how hard a demand it is.",
      "type": "object",
      "required": [
        "feature",
        "requirement"
      ],
      "properties": {
        "feature": {
          "description": "Which capability.",
          "allOf": [
            {
              "$ref": "#/definitions/SurfaceFeature"
            }
          ]
        },
        "requirement": {
          "description": "Required, or merely preferred.",
          "allOf": [
            {
              "$ref": "#/definitions/Requirement"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "InteractionKind": {
      "description": "What kind of interaction this is. The kind is semantic, never a renderer choice: `modal` is a view decision (ADR C1), not a kind.",
      "oneOf": [
        {
          "description": "Pick one of a fixed, displayed set — today's `Question<A>`.\n\nNot a two-option yes/no: that is [`InteractionKind::Confirm`].",
          "type": "string",
          "enum": [
            "choice"
          ]
        },
        {
          "description": "Free text.",
          "type": "string",
          "enum": [
            "prompt"
          ]
        },
        {
          "description": "A yes/no decision — proceed or not.\n\nCarried either by a single toggle, or by one choice control offering exactly two options where one grants and the other refuses or backs out. The distinction from [`InteractionKind::Choice`] is the ROLES the options carry, never how many there are.",
          "type": "string",
          "enum": [
            "confirm"
          ]
        },
        {
          "description": "A multi-field form.",
          "type": "string",
          "enum": [
            "form"
          ]
        },
        {
          "description": "A notice that carries no controls and expects no response.",
          "type": "string",
          "enum": [
            "notice"
          ]
        }
      ]
    },
    "OptionId": {
      "type": "string",
      "minLength": 1,
      "pattern": "^[A-Za-z0-9_-]+$"
    },
    "PathKind": {
      "description": "What a [`ControlKind::Path`] control points at.",
      "oneOf": [
        {
          "description": "A single file.",
          "type": "string",
          "enum": [
            "file"
          ]
        },
        {
          "description": "A directory.",
          "type": "string",
          "enum": [
            "directory"
          ]
        }
      ]
    },
    "Requirement": {
      "description": "Whether something is REQUIRED to present or answer an interaction faithfully, or merely preferred.\n\nThis is the axis ADR law 5 turns on: *unknown REQUIRED behavior fails closed; unknown OPTIONAL behavior degrades visibly.* Without it a consumer that cannot satisfy a demand has only one move for both cases, and whichever it picks is wrong half the time — silently dropping a mandatory secret field, or refusing a document that merely wanted a diagram.\n\nOne vocabulary, deliberately: controls and surface features both use it, so a wire consumer learns the distinction once. On a control it means \"the response must answer this FIELD\" — which is only coherent because a choice is one field with many options, not many fields.",
      "oneOf": [
        {
          "description": "Must be satisfiable. If it is not, the interaction is `Unsupported` and no answer may be guessed.",
          "type": "string",
          "enum": [
            "required"
          ]
        },
        {
          "description": "Preferred. If it is not satisfiable, the interaction proceeds and the shortfall is reported visibly.",
          "type": "string",
          "enum": [
            "optional"
          ]
        }
      ]
    },
    "SemanticRole": {
      "description": "What a control MEANS, independent of how any surface draws it.",
      "oneOf": [
        {
          "description": "Grants the thing being asked about.",
          "type": "string",
          "enum": [
            "allow"
          ]
        },
        {
          "description": "Refuses it. The fail-closed default for an absent answer.",
          "type": "string",
          "enum": [
            "deny"
          ]
        },
        {
          "description": "Backs out without deciding.",
          "type": "string",
          "enum": [
            "cancel"
          ]
        },
        {
          "description": "Ends the session.",
          "type": "string",
          "enum": [
            "exit"
          ]
        },
        {
          "description": "Supplies a value rather than a decision.",
          "type": "string",
          "enum": [
            "value"
          ]
        }
      ]
    },
    "SurfaceFeature": {
      "type": "string",
      "minLength": 1
    },
    "TemporalPrecision": {
      "description": "How much of a point in time a [`ControlKind::Temporal`] control asks for.\n\nHTML's `date`, `time`, `datetime-local`, `month` and `week` — five input types that differ only in precision, so they are five configurations of one kind rather than five kinds.",
      "oneOf": [
        {
          "description": "A calendar date: `YYYY-MM-DD`.",
          "type": "string",
          "enum": [
            "date"
          ]
        },
        {
          "description": "A wall-clock time: `HH:MM` or `HH:MM:SS`.",
          "type": "string",
          "enum": [
            "time"
          ]
        },
        {
          "description": "A local date and time: `YYYY-MM-DDTHH:MM[:SS]`.",
          "type": "string",
          "enum": [
            "date-time"
          ]
        },
        {
          "description": "A calendar month: `YYYY-MM`.",
          "type": "string",
          "enum": [
            "month"
          ]
        },
        {
          "description": "An ISO week: `YYYY-Www`.",
          "type": "string",
          "enum": [
            "week"
          ]
        }
      ]
    }
  }
}