@tikoci/centrs 0.1.9-next.23 → 0.1.9-next.24
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 +106 -0
- package/GLOSSARY.txt +14 -0
- package/README.md +2 -2
- package/commands/api/AGENTS.md +18 -1
- package/commands/api/README.md +10 -6
- package/commands/api/examples.md +1 -1
- package/commands/execute/README.md +26 -6
- package/commands/execute/examples.md +24 -5
- package/commands/explain/README.md +452 -61
- package/commands/explain/examples.md +191 -41
- package/commands/retrieve/README.md +13 -2
- package/commands/retrieve/examples.md +30 -1
- package/commands/transfer/README.md +8 -4
- package/docs/CLI.md +19 -5
- package/docs/CONSTITUTION.md +14 -9
- package/docs/MATRIX.md +34 -21
- package/docs/errors/cmr/caller-policy-not-enforced.md +5 -3
- package/docs/errors/routeros/permission-denied.md +24 -0
- package/docs/errors/transport/edge-auth-failed.md +22 -0
- package/docs/errors/transport/write-outcome-unknown.md +3 -0
- package/docs/errors/usage/invalid-cursor.md +20 -0
- package/docs/errors/validation/unknown-path.md +9 -0
- package/package.json +2 -2
- package/src/api.ts +34 -39
- package/src/cli/explain.ts +206 -47
- package/src/core/envelope.ts +10 -3
- package/src/core/error-catalog.ts +15 -0
- package/src/core/execute-command.ts +53 -0
- package/src/core/fanout.ts +12 -2
- package/src/core/inspect.ts +4 -5
- package/src/core/routeros-errors.ts +25 -0
- package/src/execute-cmr.ts +2 -8
- package/src/execute.ts +48 -20
- package/src/explain/args.ts +115 -10
- package/src/explain/group-faults.ts +162 -29
- package/src/explain/operators.ts +2 -1
- package/src/explain/scope-brace.ts +226 -11
- package/src/explain/segment.ts +136 -1
- package/src/explain/symbols.ts +146 -6
- package/src/explain/values.ts +55 -4
- package/src/explain-live-facets.ts +739 -0
- package/src/explain-live-types.ts +490 -0
- package/src/explain-live.ts +593 -0
- package/src/explain.ts +395 -43
- package/src/index.ts +6 -0
- package/src/protocols/adapter.ts +88 -10
- package/src/protocols/mac-telnet.ts +38 -10
- package/src/protocols/native-api.ts +13 -12
- package/src/retrieve-follow.ts +2 -1
- package/src/retrieve-sample.ts +2 -1
- package/src/retrieve-wait.ts +10 -6
- package/src/retrieve.ts +77 -27
- package/src/transfer.ts +38 -13
|
@@ -10,12 +10,11 @@ validation): the cheap, safe knowledge tier in front of the runners
|
|
|
10
10
|
(`execute` / `api` / `retrieve`).
|
|
11
11
|
|
|
12
12
|
Status: offline baseline `verified`; live probes over `rest-api` and `native-api`
|
|
13
|
-
`
|
|
13
|
+
`CHR-passed`. **`centrs explain '<input>'` runs without a router or CDB**, and
|
|
14
|
+
`centrs explain <router> '<input>'` adds read-only device evidence.
|
|
14
15
|
[`docs/MATRIX.md`](../../docs/MATRIX.md#offline-analysis) tracks these separately;
|
|
15
16
|
[#90](https://github.com/tikoci/centrs/issues/90) indexes the remaining tasks.
|
|
16
17
|
The spec is ratified on the [phase-0 grounding lab](#phase-0-ratification-185).
|
|
17
|
-
The current finish line is [Offline baseline acceptance](#offline-baseline-acceptance),
|
|
18
|
-
not completion of the live protocol cells.
|
|
19
18
|
Load-bearing rules — envelope, errors, settings precedence, identity,
|
|
20
19
|
validation, protocol selection — live in
|
|
21
20
|
[`docs/CONSTITUTION.md`](../../docs/CONSTITUTION.md).
|
|
@@ -373,9 +372,8 @@ native API's `/execute` **with `as-string=""`** returns the parser text —
|
|
|
373
372
|
including the `(line N column M)` diagnostic on a syntax error — byte-identical
|
|
374
373
|
to what a console transport prints. The opaque `*NN` value seen otherwise is the
|
|
375
374
|
**no-`as-string` job handle**, not a `:parse` limitation; the readout rule is
|
|
376
|
-
"pass `as-string` and interpret `ret`". The
|
|
377
|
-
`as-string`
|
|
378
|
-
readout rather than copy the omission. `/console/inspect`
|
|
375
|
+
"pass `as-string` and interpret `ret`". The execute gate passes
|
|
376
|
+
`as-string` and classifies `ret`; live explain reuses that shared classifier. `/console/inspect`
|
|
379
377
|
(highlight/syntax/completion) works over the native API's `talk` framing too. So
|
|
380
378
|
the line/column diagnostic is available on both live cells — the earlier caveat
|
|
381
379
|
that only a console transport surfaces the parser text is retired.
|
|
@@ -401,10 +399,8 @@ router therefore never reinterprets a previously valid offline invocation.
|
|
|
401
399
|
the offline form with nothing else to analyze. A positional always wins, and
|
|
402
400
|
when fd 0 is redirected anyway the result carries a
|
|
403
401
|
[`usage/stdin-ignored`](../../docs/errors/usage/stdin-ignored.md) warning rather
|
|
404
|
-
than quietly dropping the pipe. `--file -` names stdin explicitly
|
|
405
|
-
|
|
406
|
-
(`… | centrs explain edge1 --file -`) is parsed and then refused with
|
|
407
|
-
`usage/not-implemented` until phase 2. The narrower rule is
|
|
402
|
+
than quietly dropping the pipe. `--file -` names stdin explicitly in both forms,
|
|
403
|
+
including live `… | centrs explain edge1 --file -`. The narrower rule is
|
|
408
404
|
deliberate and measured: `runCli` is called in-process by tests, so reading fd 0
|
|
409
405
|
on any path a positional invocation reaches consumes the invoking shell's
|
|
410
406
|
stdin — it fed those bytes to the analyzer and broke 21 unrelated tests once,
|
|
@@ -652,7 +648,7 @@ and its phase is named below.
|
|
|
652
648
|
| `structure.statements[].command` | `path` + `verb`, plus **`args` where the argument list was read** (#202c-1); the ordered token list is `statements[].arguments.tokens` |
|
|
653
649
|
| `structure.statements[].transport` | complete for commands: `api-candidate`, `execute`, or fail-closed `unknown`, with an equivalent `centrs` invocation and opt-in `curl` for tested REST mappings (#202c-2). The `basis` names the evidence for the route it chose or refused |
|
|
654
650
|
| `symbols.occurrences[]` | Q13 name/span/class plus semantic `role`, result-local `bindingIds`, sigil spelling, and an abstention note where needed; **plus** (#239 S2) `valueId` on declarations/assignments with a literal RHS in the same statement, and `reachingValueIds` + `reachingUnknown` on references (flow-sensitive, branch → set, loop/non-literal → `unknown`, no RHS evaluation) |
|
|
655
|
-
| `values.occurrences[]` | result-local value id/span plus three separate fact homes: offline `shapeHints`; live `observedType` and `schemaType`
|
|
651
|
+
| `values.occurrences[]` | result-local value id/span plus three separate fact homes: offline `shapeHints`; live `observedType` and `schemaType` are added only by a live call ([Live value types](#live-value-types-225)). `kind: "element"` rows are array members, carrying `parent` and, for a keyed member, `name` |
|
|
656
652
|
| `spans` | comment runs and resolved variable classes only; **no value shape or type** |
|
|
657
653
|
| `schema`, `completion` | absent — live evidence, phase 2 |
|
|
658
654
|
|
|
@@ -744,13 +740,10 @@ and its phase is named below.
|
|
|
744
740
|
rejected by RouterOS at `{` and resolve no reference. In an expression the
|
|
745
741
|
remaining `-dns` is scanned as subtraction plus its right operand; inside a
|
|
746
742
|
string it is literal text.
|
|
747
|
-
- **Evidence
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
typed to what offline produces rather than pre-declaring variants nothing can
|
|
752
|
-
emit, so phase 2 widens the union and adds the stamp — an addition, not a
|
|
753
|
-
change to what a caller reads today.
|
|
743
|
+
- **Evidence follows its producer.** Offline calls emit only `canonicalizer`
|
|
744
|
+
entries, with no device version. Live calls add `live-inspect` entries with
|
|
745
|
+
probe outcomes and available device provenance; existing offline entries
|
|
746
|
+
retain their meaning. See [Live probe foundation](#live-probe-foundation-491).
|
|
754
747
|
- **Value facts are three axes, never one `type` string (#225).** RouterOS
|
|
755
748
|
types values at parse time while `highlight` classes every value byte `none`,
|
|
756
749
|
so type evidence is not a subset of Q12 spans. Each result-local
|
|
@@ -763,7 +756,7 @@ and its phase is named below.
|
|
|
763
756
|
The stable/testing CHR matrix fixes the offline lexicon: `num`, `ip`,
|
|
764
757
|
`ip-prefix`, `ip6`, `ip6-prefix`, `id`, `time`, `array`, `mac`, `bool`, and
|
|
765
758
|
`str` — `op` is deliberately not a member: a deferred `(> …)` abstains
|
|
766
|
-
offline and
|
|
759
|
+
offline and live alike (#288, #225); quoted literals
|
|
767
760
|
hint only `str`, while malformed and out-of-range address-like controls
|
|
768
761
|
abstain even in named attributes. Most vocabulary borrows RouterOS scripting
|
|
769
762
|
type names (with the explicit `macAddr` schema exception below), so a hint
|
|
@@ -797,8 +790,9 @@ and its phase is named below.
|
|
|
797
790
|
1 and identical `:parse` IL — only `:typeof` distinguishes them
|
|
798
791
|
(`test/fixtures/explain/operators.json` → `sweep.runtime`
|
|
799
792
|
`typeof-deferred`/`deferred-array-typeof` and `sweep.opAxis` `defer-*` on
|
|
800
|
-
7.21.5, 7.23.3, 7.24rc4). Offline abstains on every such form
|
|
801
|
-
|
|
793
|
+
7.21.5, 7.23.3, 7.24rc4). Offline abstains on every such form, and so does
|
|
794
|
+
the live `observedType`, which never sends `:typeof` (#288, #225). `op` has
|
|
795
|
+
no producer.
|
|
802
796
|
Shape remains non-authoritative: `100000w` is time-shaped but observed as
|
|
803
797
|
`str`. Argument context remains separate: on 7.23.3 and 7.24rc3, both
|
|
804
798
|
`/ip/address address=2.2` and firewall `src-address=2.2` canonicalize it to
|
|
@@ -812,8 +806,8 @@ and its phase is named below.
|
|
|
812
806
|
and does not widen the hint. Thus a hint never validates a value or becomes a
|
|
813
807
|
diagnostic, and never changes `runtimeAcceptance`. Quoting likewise never
|
|
814
808
|
earns a wrong-type diagnostic: commands may cast quoted strings at runtime.
|
|
815
|
-
Offline emits only `shapeHints`;
|
|
816
|
-
existing homes
|
|
809
|
+
Offline emits only `shapeHints`; a live call adds the two live facts into
|
|
810
|
+
their existing homes ([Live value types](#live-value-types-225)).
|
|
817
811
|
|
|
818
812
|
**A located array is read member by member (#225 V1).** Each member of a
|
|
819
813
|
`{…}`/`(…)` literal is its own `values.occurrences[]` row with `kind:
|
|
@@ -1128,8 +1122,9 @@ navigated, so the context stays as unknown as it already was.
|
|
|
1128
1122
|
`empty-group`, `misplaced-separator`, `missing-operand` and
|
|
1129
1123
|
`invalid-at-sign` (#505, #511, #512, below), and the four token faults
|
|
1130
1124
|
`glued-token`, `invalid-variable`, `bad-string-variable` and
|
|
1131
|
-
`invalid-name` (#506, #518, below)
|
|
1132
|
-
|
|
1125
|
+
`invalid-name` (#506, #518, below), and the two unquoted-byte faults
|
|
1126
|
+
`unquoted-reserved-byte` and `unquoted-non-ascii` (#358, #371, below).
|
|
1127
|
+
Nineteen classes the device itself rejects.
|
|
1133
1128
|
- `warning` — `over-depth`, because it is centrs's own resource bound and says
|
|
1134
1129
|
nothing about whether the input is legal; `possible-spaced-argument`
|
|
1135
1130
|
(#350), a spelling RouterOS accepts and marks obsolete; `discarded-close`
|
|
@@ -1326,8 +1321,9 @@ query and brace roles. The `$` inside a string is checked by the escape walk
|
|
|
1326
1321
|
(`src/explain/quoted-string.ts`), and the stray close by the segmenter
|
|
1327
1322
|
(`src/explain/segment.ts` → `discardFrom`). Every rule is a `:parse` answer on
|
|
1328
1323
|
CHR 7.24.5 and 7.23.8, identical on both, and
|
|
1329
|
-
`test/fixtures/explain/token-faults.json` pins
|
|
1330
|
-
|
|
1324
|
+
`test/fixtures/explain/token-faults.json` pins these rows and the unquoted-byte
|
|
1325
|
+
rows below: 292 in all (184 rejections and 108 acceptances, with the
|
|
1326
|
+
acceptances' IL). `test/integration/explain-token-faults.test.ts`
|
|
1331
1327
|
holds the device to that fixture, and `test/unit/explain-token-faults.test.ts`
|
|
1332
1328
|
holds offline to it.
|
|
1333
1329
|
|
|
@@ -1379,11 +1375,59 @@ variable name, so the next `"` does not close the string. Every scanner in
|
|
|
1379
1375
|
`explain` closes it there, so both `$` rules and the glued-token rule abstain
|
|
1380
1376
|
around a string that ends in `$"` (2 of 948 corpus scripts use the form).
|
|
1381
1377
|
`$$`, `$(…)`, `$[…]` and `${…}` outside a string, where the device's byte is
|
|
1382
|
-
not the `$`. A `\` continuation inside a glued token (`"a"\⏎"b"`).
|
|
1383
|
-
word
|
|
1378
|
+
not the `$`. A `\` continuation inside a glued token (`"a"\⏎"b"`). A glued
|
|
1379
|
+
head word (`$v"a"`, `{…}:put 2`). `:local a=b`, which 7.24.5 reads as
|
|
1384
1380
|
a named argument and 7.23.8 as a syntax error. And a name in `/local` or
|
|
1385
1381
|
`:local name=a_b`.
|
|
1386
1382
|
|
|
1383
|
+
### Unquoted bytes (#358, #371)
|
|
1384
|
+
|
|
1385
|
+
A `?`, a backtick and a non-ASCII byte are never part of a bare word, and
|
|
1386
|
+
neither is a second `=` in a menu command's argument. The rules ride the
|
|
1387
|
+
token walk (`src/explain/group-faults.ts`). They were grounded with live
|
|
1388
|
+
explain (`explainLiveEnvelope`: `:parse` and `highlight`) on CHR 7.24.5 and
|
|
1389
|
+
7.23.8, which answered identically. The 147 rows are in
|
|
1390
|
+
`test/fixtures/explain/token-faults.json`.
|
|
1391
|
+
|
|
1392
|
+
| Code | Device rule | Rejected | Accepted |
|
|
1393
|
+
| ---- | ----------- | -------- | -------- |
|
|
1394
|
+
| `unquoted-reserved-byte` | `?` and a backtick, at the byte, outside a string or comment. A second `=` in a menu command's argument, at that `=` | `name=a?b`, ``name=a`b``, `name=?b`, `:put a?b`, `[:len a?b]`, `where comment=a?b`, `[find comment=a?b]`, `where (comment=a?b)`, `where comment in {a?b}`, `{a?b}`, `(a?b)`, `/ip/address/print?`, `name=a=b`, `name==b`, `comment=x==`, `url=http://x/a?b=c`, `regexp=^a?b`, `public-key=…=`, `secret=abc==`, `on-event=a=b` | `name="a?b"`, ``name="a`b"``, `name="a=b"`, `where comment=a=b`, `[find comment=a=b]`, `[/ip/address/find comment=a=b]`, `where (comment=a=b)`, `where comment in {"a?b"}`, `{a=b}`, `{"a?"=1}`, `# a?b`, `("a"."?")` |
|
|
1395
|
+
| `unquoted-non-ascii` | A non-ASCII byte outside a string or comment, at its first byte; the span is the whole run | `:put café`, `$café`, `name=router-café`, `name=🛰`, `/systemé/identity`, `printé`, `namé=x`, `where comment=café`, `{café}`, `(café)`, `"x"é`, a non-breaking space, `“x”`, `address=…` | `:put "café"`, `:local "café" 1`, `$"café"`, `"a$café"` (`$caf` then `é`), `name="router-🚀"`, `# café`, `do={:put "café"}` |
|
|
1396
|
+
|
|
1397
|
+
**Where the device reports, offline reports.** In a menu command's argument
|
|
1398
|
+
name the device names the name's first byte (`/ip/address/add addré=1` and
|
|
1399
|
+
`a?b=1` at the `a`), so the span is the name. A directive (`:put café=1`) and
|
|
1400
|
+
a `$` call (`$f café=1`) report at the byte. A declared name keeps the
|
|
1401
|
+
existing `invalid-name` (`:local café`, `:foreach café`). A query, and a
|
|
1402
|
+
group or array inside one, keeps `=` as an operator but not these bytes
|
|
1403
|
+
(`where (comment=café)` and `in {café}` are rejected at the `é`), and the slash-joined `[/ip/address/find …]` is a query, as
|
|
1404
|
+
`[find …]` is. A leading BOM is a positional fact and is not read as part of
|
|
1405
|
+
the first word. The informational `non-ascii` fact is still published beside
|
|
1406
|
+
the error, so `isPositionalFact("non-ascii")` is unchanged.
|
|
1407
|
+
|
|
1408
|
+
**No property parser takes these bytes.** This is the difference from the
|
|
1409
|
+
glued-token rule. `regexp=a?b` (layer7 and DNS static), ``regexp=a`b``,
|
|
1410
|
+
`public-key=<base64>=`, `secret=abc==`, `password=abc=`,
|
|
1411
|
+
`http-header-field=a:b=c` and the script bodies `on-event=a=b` and
|
|
1412
|
+
`up-script=a=b` are all rejected at the byte.
|
|
1413
|
+
|
|
1414
|
+
**Measured.** Corpus `e12d5d4e58a4`: 8 scripts move to `fail` (7 from `warn`,
|
|
1415
|
+
one from `pass`), and 20 already-failing scripts gain a new code. All 28 are
|
|
1416
|
+
device rejections on every channel. 8 of their first new errors sit on the
|
|
1417
|
+
7.24.5 diagnostic's byte, 20 come after it, and none precedes it. There are no
|
|
1418
|
+
false rejects. On the conformance cases, 10 missed rejections move to `fail`
|
|
1419
|
+
(the `non-ascii-outside-strings` u10–u12 and u14–u16, z90, z92, g18 and e33), with no new
|
|
1420
|
+
false reject.
|
|
1421
|
+
|
|
1422
|
+
**Not covered here.** `:put a=b`, `:local x a=b` and `[:len a=b]`: 7.24.5
|
|
1423
|
+
reads them as a named argument (`bad parameter a`, at the end), and 7.23.8
|
|
1424
|
+
rejects the `=`. A query value that starts with `=` (`where comment==b`,
|
|
1425
|
+
`expected comment value`). A bare `source=` value,
|
|
1426
|
+
which the device rejects at its first byte whatever it holds (`source=abc`), so
|
|
1427
|
+
a byte rule there reports after the device's byte. And `:set` of an undeclared
|
|
1428
|
+
name, which the device rejects at the name's first byte (`:set zzz 2`), so
|
|
1429
|
+
`:set café` is reported at the `é`, after it.
|
|
1430
|
+
|
|
1387
1431
|
### The token stream a rule reads (#316)
|
|
1388
1432
|
|
|
1389
1433
|
`lexArguments` is all-or-nothing by design: its consumer renders a runnable REST
|
|
@@ -1599,15 +1643,34 @@ catalog-named command, with a `:` scripting directive, or with an absolute
|
|
|
1599
1643
|
command — corpus script `forum/amm0/topic-184067-routeros-lsp-for-better-syntax-checking-command-completion-in-editors-like-vscod/post-0014-snippet-02.rsc` is the directive case,
|
|
1600
1644
|
`ethernet { :put … }`.
|
|
1601
1645
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1646
|
+
Issue #348 only stopped the failure. It still read `address { … }` as a command
|
|
1647
|
+
`/ip address` whose operand was the brace, so the body was never segmented.
|
|
1648
|
+
That rule was bounded by what a statement can start with, on the theory that
|
|
1649
|
+
`{1;2}` there is an array. The device says otherwise (#351, CHR 7.24.5 and
|
|
1650
|
+
7.23.8): a brace right after a menu word is a statement block whatever the body
|
|
1651
|
+
holds. `/ip { address { 1;2 } }` is `bad command name 1` at the `1`, and
|
|
1652
|
+
`/ip { address { a;b } }` runs `a` as `add`. The old reading failed the OUTER
|
|
1653
|
+
brace at depth three, and passed `print {1;2}` and `print; zzz` inside the body.
|
|
1654
|
+
|
|
1655
|
+
So a relative run inside a menu block now opens a block of its own when the
|
|
1656
|
+
menu it names is known: the enclosing block's path, read outward from the
|
|
1657
|
+
source, plus the run, named by a shipped structure table
|
|
1658
|
+
(`scope-brace.ts` → `menuBlockPath`). The segmenter, the comment mask, the
|
|
1659
|
+
argument lexer and the group-fault walk all ask it. The body is then read like
|
|
1660
|
+
`/ip/address { … }`: `print {1;2}` there fails at its own `{`, the device's
|
|
1661
|
+
byte, and an unresolvable body statement abstains at every depth.
|
|
1662
|
+
|
|
1663
|
+
It is deliberately not widened into a claim of existence. Menu-scope
|
|
1664
|
+
resolution is schema-dependent — `/ip { zzznotamenu { print } }` is a
|
|
1665
|
+
`syntax error` on device — and offline has no schema, so a catalog MISS
|
|
1666
|
+
abstains rather than being decided either way. That is the catalog's own "a
|
|
1667
|
+
hit is decisive, a miss says nothing" contract. Three spellings keep the older
|
|
1668
|
+
reading because the source alone does not name the menu: a block after a
|
|
1669
|
+
navigation statement (`/ip⏎address { … }`), an abbreviation (`addr {`) and a
|
|
1670
|
+
digit-led menu (`6to4 {`, which the resolver does not read as a path). After a
|
|
1671
|
+
navigation, `/ip⏎address { 1;2 }` still fails at the outer brace and
|
|
1672
|
+
`/ip⏎address { print {1;2} }` still passes; the device answers
|
|
1673
|
+
`bad command name 1` and `expected end of command` at the inner brace.
|
|
1611
1674
|
|
|
1612
1675
|
### The `=` is optional in ordinary arguments too (#350)
|
|
1613
1676
|
|
|
@@ -1650,6 +1713,36 @@ device `syntax error` that offline does not report. That is a missed
|
|
|
1650
1713
|
rejection for the device preflight to catch, never a false reject. On the
|
|
1651
1714
|
pinned corpus the widening changed no census.
|
|
1652
1715
|
|
|
1716
|
+
### First-segment script-body abbreviations warn instead of rejecting (#525)
|
|
1717
|
+
|
|
1718
|
+
RouterOS takes any unique prefix of an argument name, including the first
|
|
1719
|
+
segment: `down={ :beep }` lowers to `down-script=` on CHR 7.22.1–7.25, as do
|
|
1720
|
+
bodies opening with one bare command word (`down={ beep }`) or after a comment
|
|
1721
|
+
line. Every word is a prefix of `<word>-script`, so without the command's
|
|
1722
|
+
argument list (schema) offline cannot tell `down=` from `comment=`, which the
|
|
1723
|
+
device rejects. The contract: on a non-root command, outside `where`, an
|
|
1724
|
+
unproven `name={` with a script-like body warns
|
|
1725
|
+
`explain/canonicalizer/possible-script-body-abbreviation` (verdict `warn`, the
|
|
1726
|
+
offline gate lets it through) instead of failing with `invalid-command-brace` —
|
|
1727
|
+
glued or with the brace spaced off the `=` (`down= { :beep }` lowers to
|
|
1728
|
+
`down-script=` on CHR 7.25 exactly like the glued spelling, so the spaced
|
|
1729
|
+
owner warns under the same gates). The brace reads as a body for every
|
|
1730
|
+
reader — a `#` inside is a comment, not `invalid-hash` — while proven slots
|
|
1731
|
+
(`down-script=`, `s=`, `on-…`) stay silent and control scopes (`do={…}`) are
|
|
1732
|
+
never guesses. A bare word followed by `;`-separated members is array
|
|
1733
|
+
members, not commands, so `.proplist={name;comment}` keeps its error alongside
|
|
1734
|
+
the other array-content braces (`{1;2}`, `{"x"}`, `{}`), the positional form,
|
|
1735
|
+
and `where`. A root directive's slots (`:put x={…}`, glued or spaced) and a `where` named
|
|
1736
|
+
comparison (`where gateway={…}`, however wrapped) keep their error too: the
|
|
1737
|
+
slot table and the property parser decide there, so the abbreviation guess
|
|
1738
|
+
never shields them (#554). The integration twin pins both device directions.
|
|
1739
|
+
|
|
1740
|
+
The tradeoff is measured: a verdict sweep of the 948-row pinned corpus
|
|
1741
|
+
population against the pre-batch baseline reports zero verdict or diagnostic
|
|
1742
|
+
changes, so nothing in the corpus pays for the abstention. The one deliberate
|
|
1743
|
+
over-warn — `comment={ :beep }` warns though the device rejects it — has no
|
|
1744
|
+
corpus occurrence.
|
|
1745
|
+
|
|
1653
1746
|
### `'` is not a RouterOS token (#355)
|
|
1654
1747
|
|
|
1655
1748
|
`'` (U+0027) is not a string delimiter and not a legal bare byte. Only `"` opens
|
|
@@ -1986,8 +2079,8 @@ And the grounded complement — asked, and refused:
|
|
|
1986
2079
|
`corpus` by `bun run explain:token-census:readme` and gated against it by
|
|
1987
2080
|
`bun run explain:token-census:readme:check`; the fixture itself is gated
|
|
1988
2081
|
against a fresh corpus run by `bun run explain:token-census:check`. Of
|
|
1989
|
-
1,426,731 analyzed bytes, 1,022,
|
|
1990
|
-
403,
|
|
2082
|
+
1,426,731 analyzed bytes, 1,022,786 are classified (71.69%), the remaining
|
|
2083
|
+
403,945 are `unclassified`. The census emits 241,282 tokens (avg 254.5 per
|
|
1991
2084
|
script). Every byte belongs to exactly one token — sorted by `start`, no
|
|
1992
2085
|
gaps, no overlaps, `join(slice) === input` — and the `class` field is the
|
|
1993
2086
|
vocabulary #264 B5 settled. Each B2 fill should move the classified
|
|
@@ -2468,22 +2561,317 @@ last byte. The `$` sigil and the `[` / `]` of a substitution stay `string` on
|
|
|
2468
2561
|
the same ground — `structure.subcommands` already publishes the resolution, and
|
|
2469
2562
|
its `innerSpan` locates the brackets around it.
|
|
2470
2563
|
|
|
2471
|
-
###
|
|
2472
|
-
|
|
2473
|
-
`
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2564
|
+
### Live probe foundation (#491)
|
|
2565
|
+
|
|
2566
|
+
`centrs explain <router> '<input>'` (or `--quickchr <name>` / `--host <host>`)
|
|
2567
|
+
adds read-only device evidence over REST or native API. Library callers use
|
|
2568
|
+
`explainLiveEnvelope({ input, targetInput, via, ... }, options)` from the root
|
|
2569
|
+
package; the browser-safe `@tikoci/centrs/explain` entry remains offline.
|
|
2570
|
+
Connection/authentication settings use the existing resolver and provider
|
|
2571
|
+
precedence. A pinned unsupported transport is refused. No fan-out or protocol
|
|
2572
|
+
fallback is performed. `AbortSignal` cancels library probes.
|
|
2573
|
+
An explicitly supplied connection flag such as `--via`, `--password` or
|
|
2574
|
+
`--timeout` without `<router>`, `--host` or `--quickchr` is a usage error before
|
|
2575
|
+
input is read. Connection defaults from environment and config remain inert in
|
|
2576
|
+
offline mode.
|
|
2577
|
+
Text output summarizes live observations; `--json` retains full parse and
|
|
2578
|
+
per-byte highlight evidence.
|
|
2579
|
+
|
|
2580
|
+
`data.canonical`, structure, symbols, values and optional tokens retain the
|
|
2581
|
+
offline result. Live observations are additional facts:
|
|
2582
|
+
|
|
2583
|
+
- `data.live.environment` records the observed version, architecture and board.
|
|
2584
|
+
- `data.live.parse` carries the printed parse result, rejection classification
|
|
2585
|
+
and authoritative line/byte-column when reported. Only an escaped
|
|
2586
|
+
`:put [:parse "…"]` wrapper runs; the analyzed input, substitutions and
|
|
2587
|
+
deferred bodies are never executed.
|
|
2588
|
+
- `data.live.highlight` retains raw device classes. Additional `data.spans`
|
|
2589
|
+
cite its evidence and use centrs classes: `dir`, `cmd`, `arg`, `escaped`,
|
|
2590
|
+
comments and variable classes; device state, syntax-meta, error/obsolete and
|
|
2591
|
+
unknown classes map to `unclassified`. Error/obsolete markers produce separate
|
|
2592
|
+
diagnostics. Offline and live spans can overlap; distinguish them by `ev`.
|
|
2593
|
+
- `data.live.arguments[]` contains `path`, `names`, `statementIds` and `ev` for
|
|
2594
|
+
command-level `request=child`. Repeated paths share a probe. Names confirm what
|
|
2595
|
+
the device exposes; they do not rewrite offline arguments, transport eligibility
|
|
2596
|
+
or lexical bindings. The highlight obsolete marker distinguishes `name x`
|
|
2597
|
+
from the two flags `detail without-paging` (example 48).
|
|
2598
|
+
|
|
2599
|
+
Every live evidence entry identifies its probe, observation time, known version
|
|
2600
|
+
and acquisition outcome. Input probes include SHA-256 and normalization;
|
|
2601
|
+
`coverage` identifies the submitted byte range for a usable response, not proof
|
|
2602
|
+
that every byte is syntactically valid. Parse uses original UTF-8 input;
|
|
2603
|
+
highlight uses the existing one-SUB-per-non-ASCII-byte normalization. Its facts
|
|
2604
|
+
therefore describe that explicit variant; both spaces align with `positionMap`.
|
|
2605
|
+
Parse can stop at its first error; highlight `none` remains unclassified.
|
|
2606
|
+
|
|
2607
|
+
Probe outcomes distinguish `ok`, `empty`, `unsupported`, `skipped`, `timeout`,
|
|
2608
|
+
`auth-error`, `transport-error`, `interrupted` and `invalid-response`. A parse
|
|
2609
|
+
rejection is an acquired response (`ok`) plus a diagnostic, not a transport
|
|
2610
|
+
failure. A failed/skipped probe adds a warning diagnostic; acquired offline and
|
|
2611
|
+
live facts remain available. `ok:true` means analysis produced a result, never
|
|
2612
|
+
that all probes succeeded. Resolver/usage failures and transport/authentication/interruption failures before
|
|
2613
|
+
any input probe yields a result remain `ok:false`.
|
|
2614
|
+
`meta.operation.mode` is `live`; no execution-validation verdict is invented:
|
|
2615
|
+
`meta.validation` is `{ enabled: false }`, offline and live, because explain
|
|
2616
|
+
is not a gate (#535). `runtimeAcceptance` always remains `not-proven`.
|
|
2617
|
+
|
|
2618
|
+
Work is bounded by the resolved per-request timeout, 32 distinct command paths,
|
|
2619
|
+
32,767 bytes for highlight and 65,536 bytes for parse. Larger probes are skipped
|
|
2620
|
+
with their reason; no silent prefix is submitted and `input.truncated` stays
|
|
2621
|
+
false. A transport/authentication failure stops later requests. Scripting-path
|
|
2622
|
+
discovery is conservatively skipped before 7.21.4 or on unknown versions. No
|
|
2623
|
+
command-level completion, user-expression evaluation or related
|
|
2624
|
+
configuration-table collection is part of this slice. `--schema` and
|
|
2625
|
+
`--complete` add the probes in the next section, and the value-type probes
|
|
2626
|
+
after it run on every live call.
|
|
2627
|
+
|
|
2628
|
+
Committed controls are
|
|
2629
|
+
[`live-probes.stable.json`](../../test/fixtures/explain/live-probes.stable.json)
|
|
2630
|
+
(7.24.5) and
|
|
2631
|
+
[`live-probes.long-term.json`](../../test/fixtures/explain/live-probes.long-term.json)
|
|
2632
|
+
(7.23.8), both CHR reporting `x86_64`, with package manifests, input hashes,
|
|
2633
|
+
observation times, raw parse/highlight output and projected child names. Each
|
|
2634
|
+
fixture records its capture command. `test/unit/explain-live.test.ts` replays
|
|
2635
|
+
the parse classifier and source-aligned highlighter against these controls;
|
|
2636
|
+
`test/integration/explain-live.test.ts` repeats the probes on a real CHR and
|
|
2637
|
+
checks that the analyzed identity-setting command did not run.
|
|
2638
|
+
|
|
2639
|
+
### Live describe and completion (#492)
|
|
2640
|
+
|
|
2641
|
+
`--schema` and `--complete` work on the statement at the cursor. The cursor
|
|
2642
|
+
defaults to the end of the input. `--cursor <byte>` is a UTF-8 byte offset,
|
|
2643
|
+
which is also the analyzed and device byte offset. Library callers may pass
|
|
2644
|
+
`cursor: { line, character }` (0-based, UTF-16) instead. A cursor inside a
|
|
2645
|
+
multi-byte character moves back to its first byte. A cursor outside the input
|
|
2646
|
+
is [`usage/invalid-cursor`](../../docs/errors/usage/invalid-cursor.md), raised
|
|
2647
|
+
before connecting. `--cursor` or `--full` without a facet is
|
|
2648
|
+
`usage/conflicting-flags`. Offline, both facets still return the live-target
|
|
2649
|
+
tip and enumerate nothing.
|
|
2650
|
+
|
|
2651
|
+
**Probe shapes.** Grounded on CHR 7.12.2, 7.20.8, 7.23.8 and 7.24.5 over REST
|
|
2652
|
+
and native API:
|
|
2653
|
+
|
|
2654
|
+
- `child` names what exists at a path and resolves unique abbreviations
|
|
2655
|
+
(`ip,addr` answers as `address`). It is safe on every build tried.
|
|
2656
|
+
- `syntax` is sent only for an **argument** path (`ip,address,set,address`,
|
|
2657
|
+
`put,message`). That shape answers in about 25 ms on every build, scripting
|
|
2658
|
+
keywords included.
|
|
2659
|
+
- `completion` is sent only with `input=`, the text before the cursor. It is
|
|
2660
|
+
safe at every position tried, including scripting keywords and `do={`
|
|
2661
|
+
bodies on 7.20.8.
|
|
2662
|
+
- Never sent: `syntax` or `completion` with `path=` set to a **command**
|
|
2663
|
+
(`ip,address,set`). On 7.12.2 and 7.20.8 REST stops answering, and the
|
|
2664
|
+
requests queued behind it stall, for about 30 s. Native API closes the
|
|
2665
|
+
connection. 7.23.8 and later answer normally, but the rule is
|
|
2666
|
+
version-independent (`commands/api/AGENTS.md`). restraml's crawlers never
|
|
2667
|
+
meet this, because they send `syntax` to `arg` nodes only.
|
|
2668
|
+
|
|
2669
|
+
Every probe is an `evidence[]` entry with `facet: "schema" | "complete"`, its
|
|
2670
|
+
path, version and outcome. Facts cite it by `ev`. A facet probe that answers
|
|
2671
|
+
`empty` is a result, so it adds no warning diagnostic. Other failed outcomes
|
|
2672
|
+
warn as in the foundation, and a transport or authentication failure stops
|
|
2673
|
+
later probes.
|
|
2674
|
+
|
|
2675
|
+
**`data.schema`** describes one path: the resolved statement, or an absolute
|
|
2676
|
+
slash path as typed (`/ip/addr`). The `child` self row decides `node`:
|
|
2677
|
+
|
|
2678
|
+
- `menu`: `verbs[]` and `menus[]`. Each child is expanded by one more `child`
|
|
2679
|
+
probe into `arguments` (verb) or `verbs`/`menus` (submenu) counts.
|
|
2680
|
+
- `command`: `arguments[]`, each with its argument-level `syntax` rows. A
|
|
2681
|
+
flag's blank row is dropped. For `print`, `proplist` holds the output-field
|
|
2682
|
+
names from `print proplist=` (`source: "print-proplist"`). A singleton
|
|
2683
|
+
settings menu offers none there, so `get value-name=` answers instead
|
|
2684
|
+
(`source: "get-value-name"`; `/ip/dns`, `/system/identity`). These are names
|
|
2685
|
+
a print can show, **not print rows**.
|
|
2686
|
+
- `argument`: its `syntax` rows.
|
|
2687
|
+
- `unknown`: the device reports nothing there, or the statement at the cursor
|
|
2688
|
+
is neither resolved nor an absolute path (`reason`).
|
|
2689
|
+
|
|
2690
|
+
Syntax rows are kept verbatim (`symbol`, RouterOS `symbol-type` as `kind`,
|
|
2691
|
+
`text`, `nested` as `depth`). They are declared help, such as
|
|
2692
|
+
`A.B.C.D (IP address)` or `input | forward | output` beside `string value`.
|
|
2693
|
+
They are not the `schemaType` value fact, which normalizes the same rows per
|
|
2694
|
+
value ([Live value types](#live-value-types-225)).
|
|
2695
|
+
|
|
2696
|
+
**Smart sizing.** By default each list shows up to 64 names, and expansion
|
|
2697
|
+
runs only when the listed children fit in 16 probes (a menu's children, or a
|
|
2698
|
+
command's arguments for their `syntax`). Otherwise names are listed without
|
|
2699
|
+
expansion. `counts` always holds the full sizes, `truncated: true`, `next[]`
|
|
2700
|
+
names up to three narrower inputs, and the envelope carries
|
|
2701
|
+
`tip/explain-schema-truncated`. On CHR 7.24.5, `/ip` (2 verbs, 29 menus) and
|
|
2702
|
+
`/ip/address/print` (21 arguments) truncate; `/ip/address` (12 verbs) and
|
|
2703
|
+
`/ip/firewall/filter` (16) do not. `--full` raises the limits to 1024 names and
|
|
2704
|
+
128 probes. That is the safety ceiling, not unlimited.
|
|
2705
|
+
|
|
2706
|
+
**`data.completion`** reports `cursor`, `at` (original line/character),
|
|
2707
|
+
`replace` (the bytes a candidate replaces, ending at the cursor), `prefix`,
|
|
2708
|
+
`context`, `candidates[]`, `count` and `truncated`. Candidates are capped like
|
|
2709
|
+
names. They are the device's own: one input completion of the text before the
|
|
2710
|
+
cursor, keeping shown, non-syntax rows. The row `offset` sets `replace`. The
|
|
2711
|
+
navigation punctuation (`/`, `:`, `..`) offered everywhere is left out. So a
|
|
2712
|
+
relative path, a block, a `where` clause or an unclosed `[` completes exactly
|
|
2713
|
+
as on the console: `/ip { :put 1; address add comm` → `comment`, and
|
|
2714
|
+
`/ip address⏎` → that menu's verbs. Where the device offers nothing, neither
|
|
2715
|
+
does explain: `do={ add comm` runs at root, so `add` is unknown there.
|
|
2716
|
+
|
|
2717
|
+
The `context` comes from the device highlight of the same text. When the
|
|
2718
|
+
cursor is not at the end, that is one extra highlight probe whose `coverage`
|
|
2719
|
+
says so.
|
|
2720
|
+
|
|
2721
|
+
- `path`: in a `dir`/`cmd` word, after a menu, or at the start of a line.
|
|
2722
|
+
Candidates are `menu` or `verb` by row style. A word the device cannot
|
|
2723
|
+
resolve there is `obj-inactive`, and the token before it gives its role:
|
|
2724
|
+
`/ip/address re` → `remove`, `reset`, and `:lo` → `local`, `lock`, `log`.
|
|
2725
|
+
- `argument-name`: in an `arg` word, or after a command, flag or value.
|
|
2726
|
+
`match: "exact"` marks a complete name. `takesValue` is whether the same
|
|
2727
|
+
answer offers `=` at the cursor: `comment` does, and the flag `detail` does
|
|
2728
|
+
not.
|
|
2729
|
+
- `variable`: a `where` clause, whose property names RouterOS scopes as
|
|
2730
|
+
variables (`where comm` → `comment`).
|
|
2731
|
+
- `value`: after `name=`. Candidates may include configuration state, such as
|
|
2732
|
+
interface names. They are never a closed set:
|
|
2733
|
+
`chain=` offers `forward`, `input` and `output`, but custom chains are legal.
|
|
2734
|
+
For a command argument, `syntax` carries its declared type text, so a
|
|
2735
|
+
free-form `comment=` returns no candidates and `string value`. A `where`
|
|
2736
|
+
value (`disabled=` → `no`, `yes`) has no argument path, so no `syntax` is
|
|
2737
|
+
read.
|
|
2738
|
+
- `unsupported` with a `reason`: strings, the obsolete spaced `name value`
|
|
2739
|
+
spelling, and other tokens the device does not class as a path, name or
|
|
2740
|
+
value.
|
|
2741
|
+
|
|
2742
|
+
Every candidate is an observation of the completion probe its `ev` cites.
|
|
2743
|
+
The declared side is `data.schema`: `child` names and argument `syntax`. So
|
|
2744
|
+
completion carries no second provenance label, and nothing in it is a closed
|
|
2745
|
+
set. `path` names the command at the
|
|
2746
|
+
cursor when it is known. It comes from the offline statement or `[…]`
|
|
2747
|
+
substitution when that resolved, otherwise from the device's `dir`/`cmd` runs.
|
|
2748
|
+
The command is needed only for a value's `syntax`. When it is not visible (a
|
|
2749
|
+
relative path after `;`), the candidates are unaffected and `syntax` is
|
|
2750
|
+
omitted.
|
|
2751
|
+
|
|
2752
|
+
The verdict still judges the input as written, and the two probes agree on
|
|
2753
|
+
it. `/ip/address add comm` is rejected by `:parse` (`expected end of command`
|
|
2754
|
+
at byte 16), and the highlight marks the same byte `error`. So the result
|
|
2755
|
+
fails even while its candidates are published. An assist loop that only wants
|
|
2756
|
+
candidates passes `--fail-on never`.
|
|
2757
|
+
|
|
2758
|
+
Committed controls are
|
|
2759
|
+
[`live-facets.stable.json`](../../test/fixtures/explain/live-facets.stable.json)
|
|
2760
|
+
(7.24.5) and
|
|
2761
|
+
[`live-facets.long-term.json`](../../test/fixtures/explain/live-facets.long-term.json)
|
|
2762
|
+
(7.23.8). They hold every recorded call and its raw answer, plus the projected
|
|
2763
|
+
result. `test/unit/explain-live-facets.test.ts` replays them.
|
|
2764
|
+
`test/integration/explain-live-facets.test.ts` asserts the examples on both
|
|
2765
|
+
transports and regenerates the captures when `CENTRS_EXPLAIN_FACETS_CAPTURE`
|
|
2766
|
+
names an output file.
|
|
2767
|
+
|
|
2768
|
+
Not covered here: MCP (#223), related print rows and topic checks (#493),
|
|
2769
|
+
and per-target caching of describe results (open 3).
|
|
2770
|
+
|
|
2771
|
+
### Live value types (#225)
|
|
2772
|
+
|
|
2773
|
+
Every live call fills the two live homes on `data.values.occurrences[].facts`
|
|
2774
|
+
that offline leaves empty. Neither changes the verdict or
|
|
2775
|
+
`runtimeAcceptance`, and neither becomes a diagnostic. Both probes are
|
|
2776
|
+
`evidence[]` entries with `facet: "types"` and `basis: "derived"`; an `empty`
|
|
2777
|
+
answer is a result, not a warning. They run after `--schema`/`--complete`.
|
|
2778
|
+
|
|
2779
|
+
**`observedType: { value, rendered, ev }`** is the type the device's parser
|
|
2780
|
+
visibly converted a bare literal to. `:parse` prints a string unquoted, so
|
|
2781
|
+
`:local a 123` and `:local b "123"` both render `value=123`. A rendering is
|
|
2782
|
+
therefore evidence only when it CHANGED the literal:
|
|
2783
|
+
|
|
2784
|
+
| Literal | Device rendering | `observedType` |
|
|
2785
|
+
| ------- | ---------------- | -------------- |
|
|
2786
|
+
| `2.2`, `2.2/8` | `2.0.0.2`, `2.0.0.2/8` | `ip`, `ip-prefix` |
|
|
2787
|
+
| `1:0:0::1` | `1::1` | `ip6` |
|
|
2788
|
+
| `0x10`, `0123`, `99999999999999999999` | `16`, `123`, `9223372036854775807` | `num` |
|
|
2789
|
+
| `yes`, `no` | `true`, `false` | `bool` |
|
|
2790
|
+
| `200ms`, `1d`, `1:2`, `10000w` | `00:00:00.200`, `1d00:00:00`, `01:02:00`, `10000w00:00:00` | `time` |
|
|
2791
|
+
| `123`, `1.1.1.1`, `true`, `00:00:02`, `"2.2"` | unchanged | absent |
|
|
2792
|
+
| `100000w`, `abc`, `1e5`, `300.1.1.1` | unchanged | absent |
|
|
2793
|
+
| member `{abc}`, `{-1}` | `$abc`, `(- 1)` | absent |
|
|
2794
|
+
|
|
2795
|
+
The literal is not looked up in the IL of the analyzed input, where a quoted
|
|
2796
|
+
`"x;y=1"` prints exactly like two arguments. Each bare literal is re-parsed
|
|
2797
|
+
alone: `:local vN <lit>` for a scripting operand, `:local vN {<lit>}` for an
|
|
2798
|
+
array member, `;`-joined into one escaped `:put [:parse "…"]` per context.
|
|
2799
|
+
`:put <lit>` converts exactly as `:local` does. Only literals of the bytes
|
|
2800
|
+
`A-Z a-z 0-9 . _ : / -` are sent, so no literal can leave its slot, and a
|
|
2801
|
+
quoted value never qualifies. One hard-error member (`{*1}`) rejects a whole
|
|
2802
|
+
batch, so the two contexts are separate probes and a rejected batch is
|
|
2803
|
+
`invalid-response` with the device's message; its literals abstain. A batch is
|
|
2804
|
+
at most 256 literals, otherwise `skipped`.
|
|
2805
|
+
|
|
2806
|
+
A scripting directive's named argument converts exactly as an operand
|
|
2807
|
+
(`:for i from=0x10` → `from=16`, `:put message=yes` → `message=true`), so it
|
|
2808
|
+
joins the operand batch. A directive's NAME slot is a variable and never
|
|
2809
|
+
converts: `:local yes 2` renders `name=$yes`, `:for yes from=1` renders
|
|
2810
|
+
`counter=$yes` and `:local name=no` renders `name=$no`. So a value the symbol
|
|
2811
|
+
view claims as a name, or a directive's `name=`/`counter=` argument, is never
|
|
2812
|
+
probed.
|
|
2813
|
+
|
|
2814
|
+
Menu-command arguments are never observed. Their own parser converts them
|
|
2815
|
+
and prints a per-argument encoding (`src-address=!1.1.1.1` →
|
|
2816
|
+
`true;1.1.1.1`, `address=2.2` → `2.0.0.2;`), and a quoted value converts only
|
|
2817
|
+
at run time (`address="2.2"` stays `2.2`). That slot is `schemaType`'s. `op`
|
|
2818
|
+
has no producer: `(>[:return 1])` and `(>{"a"=1})` parse identically, and only
|
|
2819
|
+
`:typeof` separates them.
|
|
2820
|
+
|
|
2821
|
+
**`schemaType: { values, list?, declared, ev }`** is the argument's declared
|
|
2822
|
+
type. For each `name=value` of a resolved menu command, the name is resolved
|
|
2823
|
+
against that command's live `child` names, exactly or as a unique prefix
|
|
2824
|
+
(`dist=` → `distance`). One `syntax` probe per distinct argument path
|
|
2825
|
+
(`ip,route,add,distance`) reads the declaration; at most 32 per call, later
|
|
2826
|
+
ones `skipped`. An ambiguous or unknown name sends no probe. `syntax` is never
|
|
2827
|
+
sent to a command path (see [Live describe and completion](#live-describe-and-completion-492)).
|
|
2828
|
+
|
|
2829
|
+
The rows are a small grammar (`[!]Range`, `Address[%Interface][@VRF]`), so
|
|
2830
|
+
`values` lists the LEAF types the declaration mentions, not alternatives of
|
|
2831
|
+
one value. They use the shape-hint names so the two axes compare directly,
|
|
2832
|
+
plus three schema-only terms:
|
|
2833
|
+
|
|
2834
|
+
| Device leaf | Term |
|
|
2835
|
+
| ----------- | ---- |
|
|
2836
|
+
| `(integer number)`, `(decimal number)`, `integer value`, `0..65535` | `num` |
|
|
2837
|
+
| `(time interval)`, `time interval` | `time` |
|
|
2838
|
+
| `(IP address)`, `IPv4 address`, `A.B.C.D` | `ip` |
|
|
2839
|
+
| `IPv6 address` | `ip6` |
|
|
2840
|
+
| `(IP prefix)`, `(IPv6 prefix)` | `ip-prefix`, `ip6-prefix` |
|
|
2841
|
+
| `(IP address range)` | `ip-range` |
|
|
2842
|
+
| `(MAC address)` | `mac` |
|
|
2843
|
+
| `string value…`, `hexadecimal string value…`, `script`, `filename` | `str` |
|
|
2844
|
+
| a listed word set (`input \| forward \| output`, `ether1 \| lo \| ...`) | `enum` |
|
|
2845
|
+
|
|
2846
|
+
`enum` is never a closed set: the device lists the current configuration
|
|
2847
|
+
names (`ether1 | lo`) exactly like keywords, and ends either kind with `...`
|
|
2848
|
+
when the list is long (`ftp | h323 | … | ...`). `list: true` is present when
|
|
2849
|
+
the declaration spells a repetition (`[,Type*]`, `(max 15 times)`); its
|
|
2850
|
+
absence is not a claim — `servers` (`Address[@VRF]`) splits on a comma
|
|
2851
|
+
without saying so. A keyword's help row (`tcp` → `transmission control
|
|
2852
|
+
protocol`) is not a leaf, and a blank placeholder beside a real leaf is
|
|
2853
|
+
skipped. The fact is ABSENT when the declaration is blank (every flag and
|
|
2854
|
+
boolean: `disabled`, `copy-from`) or when any leaf is `see documentation`
|
|
2855
|
+
(`/ipv6/address add address`). `declared` is the device's top-level text,
|
|
2856
|
+
verbatim (`Address[@VRF]`, `Num`).
|
|
2857
|
+
|
|
2858
|
+
The vocabulary also names `bool` and `name` for a second schema source: the
|
|
2859
|
+
CLI Reference's `Type` cells (`bool`, `iface_enum`, `ipAddr`, `macAddr`,
|
|
2860
|
+
`ubit (…)`), which declare the booleans and object references inspect leaves
|
|
2861
|
+
blank or prints as words. A CLI Reference producer would fill the same fact,
|
|
2862
|
+
told apart by its `ev`; delivering those tables is decision 3 (#501).
|
|
2863
|
+
|
|
2864
|
+
Grounded on CHR 7.24.5 and 7.23.8 over REST and native API, identical on both:
|
|
2865
|
+
a 40-command crawl (863 arguments) set the leaf table (571 project, 282 are
|
|
2866
|
+
blank, 7 cite `see documentation` and 3 list no names), and the conversion
|
|
2867
|
+
matrix is recorded raw in
|
|
2868
|
+
[`live-types.stable.json`](../../test/fixtures/explain/live-types.stable.json)
|
|
2869
|
+
and [`live-types.long-term.json`](../../test/fixtures/explain/live-types.long-term.json).
|
|
2870
|
+
`test/unit/explain-live-types.test.ts` replays them and pins the matrix
|
|
2871
|
+
reading; `test/integration/explain-live-types.test.ts` asserts examples 52–53
|
|
2872
|
+
on both transports and that every executed script is a `:parse` wrapper and
|
|
2873
|
+
every `syntax` probe names a discovered argument. It regenerates the captures
|
|
2874
|
+
when `CENTRS_EXPLAIN_TYPES_CAPTURE` names an output file.
|
|
2487
2875
|
|
|
2488
2876
|
## MCP and library surfaces
|
|
2489
2877
|
|
|
@@ -2988,9 +3376,10 @@ test framework is part of this baseline.
|
|
|
2988
3376
|
|
|
2989
3377
|
### Current and following capabilities
|
|
2990
3378
|
|
|
2991
|
-
- **
|
|
2992
|
-
completion/child/syntax
|
|
2993
|
-
|
|
3379
|
+
- **Done — live probes (#236, phase 2):** highlight + `:parse` (#491),
|
|
3380
|
+
completion/child/syntax (#492) and live value types (#225) over
|
|
3381
|
+
rest-api/native-api, `CHR-passed` on stable and long-term.
|
|
3382
|
+
`runtimeAcceptance` stays `not-proven`.
|
|
2994
3383
|
- **MCP alignment (#223):** the deliberate flat-to-rich response migration,
|
|
2995
3384
|
coordinated with rosetta. Independent of the offline token vocabulary.
|
|
2996
3385
|
- **Facet polish (phase 3):** live sizing, `--full`, completion/schema ergonomics.
|
|
@@ -3061,8 +3450,10 @@ round):
|
|
|
3061
3450
|
|
|
3062
3451
|
Still open (implementation opens — none block ratification):
|
|
3063
3452
|
|
|
3064
|
-
1. Final flag names
|
|
3065
|
-
|
|
3453
|
+
1. ~~Final flag names and smart-sizing thresholds.~~ Decided by #492:
|
|
3454
|
+
`--schema`, `--complete`, `--cursor <byte>` and `--full`; 64 names and 16
|
|
3455
|
+
expansion probes by default, and 1024 / 128 with `--full`
|
|
3456
|
+
([Live describe and completion](#live-describe-and-completion-492)).
|
|
3066
3457
|
2. The mapping table from RouterOS highlight classes to **colors**, and how
|
|
3067
3458
|
unknown/new upstream classes degrade. Phase 0 pinned the raw inputs (Q13's
|
|
3068
3459
|
per-occurrence highlight-class corpus; the observed 19 classes on 7.23.2 plus
|