@tikoci/centrs 0.1.4 → 0.1.6
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/CHANGELOG.md +309 -0
- package/GLOSSARY.txt +40 -1
- package/README.md +8 -4
- package/commands/AGENTS.md +7 -4
- package/commands/api/AGENTS.md +13 -1
- package/commands/api/README.md +37 -8
- package/commands/api/examples.md +31 -1
- package/commands/execute/README.md +39 -10
- package/commands/execute/examples.md +79 -7
- package/commands/explain/README.md +1195 -155
- package/commands/explain/examples.md +99 -3
- package/commands/mcp/README.md +7 -4
- package/commands/mcp/examples.md +2 -2
- package/commands/terminal/examples.md +7 -1
- package/docs/CLI.md +2 -2
- package/docs/CONSTITUTION.md +72 -15
- package/docs/MATRIX.md +89 -52
- package/docs/errors/validation/syntax.md +29 -4
- package/package.json +13 -2
- package/src/api-fanout.ts +17 -4
- package/src/api.ts +254 -67
- package/src/cli/api.ts +1 -1
- package/src/cli/btest.ts +1 -1
- package/src/cli/explain.ts +1 -1
- package/src/cli/terminal.ts +1 -1
- package/src/core/envelope.ts +53 -0
- package/src/core/error-catalog.ts +2 -1
- package/src/core/execute-command.ts +148 -0
- package/src/core/inspect.ts +36 -4
- package/src/core/version.ts +4 -0
- package/src/core/yaml.ts +86 -0
- package/src/execute-fanout.ts +18 -4
- package/src/execute.ts +225 -244
- package/src/explain/arg-tokens.ts +62 -25
- package/src/explain/args.ts +398 -52
- package/src/explain/blocks.ts +54 -11
- package/src/explain/brace-tokens.ts +4 -3
- package/src/explain/catalog.ts +42 -44
- package/src/explain/coordinates.ts +159 -0
- package/src/explain/defects.ts +2 -1
- package/src/explain/escape-tokens.ts +77 -0
- package/src/explain/highlight-projection.ts +308 -0
- package/src/explain/menus.ts +9 -5
- package/src/explain/operator-tokens.ts +13 -7
- package/src/explain/operators.ts +27 -20
- package/src/explain/path-tokens.ts +1 -1
- package/src/explain/pathresolve.ts +358 -113
- package/src/explain/quoted-string.ts +110 -20
- package/src/explain/scope-brace.ts +175 -22
- package/src/explain/segment.ts +429 -21
- package/src/explain/separator.ts +187 -0
- package/src/explain/split-owner.ts +194 -0
- package/src/explain/string-tokens.ts +14 -4
- package/src/explain/symbol-values.ts +209 -173
- package/src/explain/symbols.ts +21 -1
- package/src/explain/transport.ts +17 -0
- package/src/explain/value-tokens.ts +15 -12
- package/src/explain/verbsplit.ts +165 -23
- package/src/explain/write.ts +47 -6
- package/src/explain.ts +522 -72
- package/src/index.ts +19 -2
- package/src/mcp/server.ts +2 -1
- package/src/offline-gate.ts +148 -0
- package/src/retrieve-fanout.ts +1 -1
- package/src/retrieve.ts +1 -59
- package/src/settings.ts +2 -1
- package/src/transfer-fanout.ts +1 -1
- package/src/transfer.ts +1 -1
|
@@ -16,14 +16,23 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
16
16
|
- Mirror RouterOS `execute` semantics. Input is a CLI string; output is
|
|
17
17
|
console-shaped text (or structured records for path+verb writes) wrapped in
|
|
18
18
|
the standard envelope. This includes write-shaped add/set/remove.
|
|
19
|
-
- Validation
|
|
19
|
+
- Validation runs **offline first, then on the device** (see constitution:
|
|
20
|
+
validation; GH#354). Stage 1 is the corpus-gated analyzer over the exact
|
|
21
|
+
command string, with no connection: a syntax fault is rejected with a byte
|
|
22
|
+
span in `error.context.span` and the adapter is never dialed, and
|
|
23
|
+
`meta.validation.stages[]` then shows the device stage `skipped`. Stage 1
|
|
24
|
+
cannot see an unknown attribute — it has no per-menu schema — so stage 2 is
|
|
25
|
+
not optional.
|
|
26
|
+
- Stage 2 is itself two gates (grounded on CHR 7.23.3,
|
|
20
27
|
GH#230): a single `:put [:parse "<cmd>"]` covers both the syntax and the
|
|
21
28
|
unknown-attribute (name-level) gate on **every** transport — `:parse` never
|
|
22
29
|
throws; its return value's printed form (`(evl …)` / `(evl bad parameter …)` /
|
|
23
30
|
`(<%% bad command name …)`) is read from the REST/native `ret` *and* the
|
|
24
31
|
console output. That check is necessary, not sufficient: value-type errors pass
|
|
25
|
-
it, and the `/console/inspect` / server re-validation semantic
|
|
26
|
-
separate.
|
|
32
|
+
it, and the `/console/inspect request=child` / server re-validation semantic
|
|
33
|
+
checks are separate. Command-level `request=completion` is deliberately not
|
|
34
|
+
used: it hangs REST and closes native API on RouterOS 7.12.2 (GH#343), while
|
|
35
|
+
`child` returns the complete argument-name set immediately.
|
|
27
36
|
The outer RouterOS string literal escapes backslash, quote, and dollar so
|
|
28
37
|
validation parses the caller's exact command rather than expanding variables
|
|
29
38
|
in the wrapper.
|
|
@@ -73,13 +82,33 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
73
82
|
terminal width, so each transport handles width differently. **mac-telnet** is
|
|
74
83
|
the only one that negotiates a size: `src/protocols/mac-telnet-console.ts`
|
|
75
84
|
answers the device's terminal-size probe with a deliberately tall/wide screen,
|
|
76
|
-
so captured columns are neither
|
|
77
|
-
*without* a pseudo-tty (`src/protocols/ssh.ts`),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
so captured columns are neither paginated nor wrapped *at the negotiated
|
|
86
|
+
width*. **SSH execute** runs *without* a pseudo-tty (`src/protocols/ssh.ts`),
|
|
87
|
+
so no width is negotiated at all. **REST/native-api** are not consoles, so a
|
|
88
|
+
terminal width never applies (they return structured records, or a string
|
|
89
|
+
`ret` for script-mode). There is therefore no per-call `--width` knob today —
|
|
90
|
+
an explicit width override is deferred until a concrete need appears.
|
|
91
|
+
- **A device-side wrap that no width negotiation reaches (GH#352).** Declaring a
|
|
92
|
+
width is not sufficient, because RouterOS can wrap for reasons that are not
|
|
93
|
+
about width. On 7.23.6 / 7.24.3 / 7.25beta4 a settings menu with exactly one
|
|
94
|
+
property prints its value **one character per line**: `/system/identity/print`
|
|
95
|
+
returns `name: C` / `H` / `R` where 7.24.2 returns `name: CHR`. Measured on
|
|
96
|
+
stock CHR, the wrap is identical over REST `POST /rest/execute`, over
|
|
97
|
+
`ssh user@host "<cmd>"`, and over the mac-telnet console — including the
|
|
98
|
+
mac-telnet reader that has already answered the size probe with 512 columns,
|
|
99
|
+
and the ssh client that negotiates nothing. The console login modifiers
|
|
100
|
+
(`+c512w200h`, `+ct`, `+e`) do not change it, and it is not proportional to
|
|
101
|
+
the value's length. Every multi-property `print` is unaffected on the same
|
|
102
|
+
build, as is `:put [/system/identity/get name]`.
|
|
103
|
+
|
|
104
|
+
Consequence for this command: `data.ret` carries the device's console text
|
|
105
|
+
**verbatim**, wrap included — centrs cannot tell a layout newline from
|
|
106
|
+
content, and inventing a de-wrapper would corrupt legitimate output. Callers
|
|
107
|
+
that need a value rather than a rendering should read it structurally
|
|
108
|
+
(`retrieve`, or `api`) or ask the device for the scalar
|
|
109
|
+
(`:put [<menu>/get <property>]`). The integration examples compare identities
|
|
110
|
+
with `print` column wrapping collapsed for this reason — see
|
|
111
|
+
`collapsePrintWrapping` in `test/integration/chr.ts`.
|
|
83
112
|
- Attribute values pass through **verbatim** — centrs does not guess types or
|
|
84
113
|
coerce them (RouterOS REST tolerates string values; the native API is
|
|
85
114
|
all-strings). Unlike `jo`-style KV builders, there is no per-value
|
|
@@ -73,7 +73,7 @@ arbitrary script expressions. The `$a` variable must survive the validation
|
|
|
73
73
|
wrapper: if that outer string expands it before `:parse`, this example fails even
|
|
74
74
|
though RouterOS accepts the original command.
|
|
75
75
|
|
|
76
|
-
### 5. Syntax reject
|
|
76
|
+
### 5. Syntax reject before the write round-trip
|
|
77
77
|
|
|
78
78
|
Malformed CLI is rejected by the syntax gate before semantic validation or the
|
|
79
79
|
write round-trip.
|
|
@@ -82,9 +82,16 @@ write round-trip.
|
|
|
82
82
|
centrs execute $R '/ip/address/add address="unterminated interface=ether1' --via rest-api --username $U --password $P --yes
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
Envelope: `ok: false`, `error.code=validation/syntax`, `
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
Envelope: `ok: false`, `error.code=validation/syntax`, `meta.via=rest-api`, and
|
|
86
|
+
no address is added.
|
|
87
|
+
|
|
88
|
+
**Which stage rejects it changed in GH#354.** This input is now caught by the
|
|
89
|
+
*offline* stage, so `error.context.validationStage="offline"`,
|
|
90
|
+
`error.cause="explain/canonicalizer/unterminated-string"`,
|
|
91
|
+
`error.context.span={"start":24,"end":54}`, and the device is never contacted —
|
|
92
|
+
see V1–V3 below. It reached `:put [:parse ...]` before that. The code, the
|
|
93
|
+
`meta.via`, and the no-mutation guarantee are unchanged, which is why the
|
|
94
|
+
assertion in `test/integration/execute.test.ts` still reads `validation/syntax`.
|
|
88
95
|
|
|
89
96
|
### 6. Semantic reject that `:parse` accepts
|
|
90
97
|
|
|
@@ -202,14 +209,16 @@ centrs execute $A "/ip/address/remove numbers=$ID" --via native-api --port $API_
|
|
|
202
209
|
Envelope: `ok: true`, `meta.via=native-api`. A subsequent retrieve of
|
|
203
210
|
`/ip/address` does not contain `$ID`.
|
|
204
211
|
|
|
205
|
-
### 15. Native syntax reject
|
|
212
|
+
### 15. Native syntax reject
|
|
206
213
|
|
|
207
214
|
```bash
|
|
208
215
|
centrs execute $A '/ip/address/add address="unterminated interface=ether1' --via native-api --port $API_PORT --username $U --password $P --yes
|
|
209
216
|
```
|
|
210
217
|
|
|
211
|
-
Envelope: `ok: false`, `error.code=validation/syntax`, `
|
|
212
|
-
|
|
218
|
+
Envelope: `ok: false`, `error.code=validation/syntax`, `meta.via=native-api`, and
|
|
219
|
+
no address is added. The native twin of example 5, and it moved the same way:
|
|
220
|
+
the offline stage rejects this input now, so the verdict no longer depends on
|
|
221
|
+
the transport at all.
|
|
213
222
|
|
|
214
223
|
### 16. Native semantic reject after clean parse
|
|
215
224
|
|
|
@@ -285,6 +294,10 @@ Envelope: `ok: true`, `meta.via=mac-telnet`, `data.ret` is the console output an
|
|
|
285
294
|
contains the device identity (cross-checked against REST). `meta.validation.source`
|
|
286
295
|
is `:put [:parse ...] over mac-telnet`.
|
|
287
296
|
|
|
297
|
+
RouterOS 7.23.6/7.24.3/7.25beta4 print a one-property menu's value one
|
|
298
|
+
character per line (GH#352), so the comparison collapses `print` column
|
|
299
|
+
wrapping on both sides rather than asserting the device's layout.
|
|
300
|
+
|
|
288
301
|
### 21. Write (add) over mac-telnet
|
|
289
302
|
|
|
290
303
|
```bash
|
|
@@ -329,6 +342,10 @@ Envelope: `ok: true`, `meta.via=ssh`, `data.ret` is the cleaned console output a
|
|
|
329
342
|
contains the device identity (cross-checked against REST). `meta.validation.source`
|
|
330
343
|
is `:put [:parse ...] over ssh`.
|
|
331
344
|
|
|
345
|
+
RouterOS 7.23.6/7.24.3/7.25beta4 print a one-property menu's value one
|
|
346
|
+
character per line (GH#352), so the comparison collapses `print` column
|
|
347
|
+
wrapping on both sides rather than asserting the device's layout.
|
|
348
|
+
|
|
332
349
|
### S2. Multi-line read returns cleaned, column-aligned output
|
|
333
350
|
|
|
334
351
|
```bash
|
|
@@ -356,6 +373,61 @@ centrs execute 127.0.0.1 '/ip/address/add address=198.51.100.51/32 interface=eth
|
|
|
356
373
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`, `meta.via=ssh`,
|
|
357
374
|
and no address is added — the console `:parse` gate catches `bogus` pre-mutation.
|
|
358
375
|
|
|
376
|
+
## Offline gate (stage 1)
|
|
377
|
+
|
|
378
|
+
Validation runs offline before it runs on the device
|
|
379
|
+
([`docs/CONSTITUTION.md` → Validation is the product](../../docs/CONSTITUTION.md#validation-is-the-product),
|
|
380
|
+
GH#354). These two prove the stage exists and that a stage-1 rejection reaches no
|
|
381
|
+
router. Run by `test/integration/execute.test.ts`.
|
|
382
|
+
|
|
383
|
+
### V1. Offline reject, with the device stage never asked
|
|
384
|
+
|
|
385
|
+
`:put [` is an unclosed bracket. The old local quote preflight counted quotes
|
|
386
|
+
only, so this used to cost a full round trip to be told no; the analyzer rejects
|
|
387
|
+
it with a byte span before anything is dialed.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
centrs execute $R ':put [' --via rest-api --username $U --password $P --json
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Envelope: `ok: false`, `error.code=validation/syntax`,
|
|
394
|
+
`error.context.validationStage="offline"`,
|
|
395
|
+
`error.context.span={"start":5,"end":6}`,
|
|
396
|
+
`error.context.diagnostics[0].code="explain/canonicalizer/unclosed"`,
|
|
397
|
+
`error.position` absent (the offline byte offset is never dressed up as a
|
|
398
|
+
RouterOS-reported one), `meta.validation.source` is the offline analyzer, and
|
|
399
|
+
`meta.validation.stages` is `offline: failed` followed by `device: skipped`.
|
|
400
|
+
|
|
401
|
+
### V2. The same rejection with no reachable router at all
|
|
402
|
+
|
|
403
|
+
The proof that stage 1 opens no connection: point the same command at a port
|
|
404
|
+
nothing is listening on. A gate that dialed first would report a
|
|
405
|
+
`transport/*` error or hang until the timeout.
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
centrs execute 127.0.0.1:1 ':put [' --via rest-api --username $U --password $P --json
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Envelope: identical to V1 — `ok: false`, `error.code=validation/syntax`,
|
|
412
|
+
`error.context.validationStage="offline"` — and **not** a `transport/*` code.
|
|
413
|
+
|
|
414
|
+
### V3. `--validate=false` disables the offline stage too
|
|
415
|
+
|
|
416
|
+
One flag, one meaning. The escape hatch has to reach stage 1, or a caller hitting
|
|
417
|
+
an analyzer defect would have no way past it. RouterOS then rejects the command
|
|
418
|
+
itself on the run.
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
centrs execute $R ':put [' --via rest-api --username $U --password $P --json --validate=false
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Envelope: `ok: false`, `error.code=routeros/request-failed` with RouterOS's own
|
|
425
|
+
`syntax error (line 1 column 7)` in the summary — the **device** rejecting it, not
|
|
426
|
+
centrs — plus `meta.validation.enabled=false`, `meta.validation.result="skipped"`,
|
|
427
|
+
and `meta.validation.stages` listing **both** stages as `skipped`. A gated
|
|
428
|
+
surface always reports every stage, so a consumer reading `stages` never has to
|
|
429
|
+
special-case the disabled shape.
|
|
430
|
+
|
|
359
431
|
## Target selection (fan-out)
|
|
360
432
|
|
|
361
433
|
These exercise the shared target-selection grammar
|