@tikoci/centrs 0.1.9-next.23 → 0.1.9-next.27
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 +163 -0
- package/GLOSSARY.txt +23 -1
- package/README.md +30 -18
- package/commands/api/AGENTS.md +21 -3
- package/commands/api/README.md +18 -7
- package/commands/api/examples.md +1 -1
- package/commands/btest/README.md +9 -0
- package/commands/btest/examples.md +7 -0
- package/commands/devices/README.md +72 -3
- package/commands/devices/examples.md +56 -1
- package/commands/execute/README.md +37 -6
- package/commands/execute/examples.md +24 -5
- package/commands/explain/README.md +675 -64
- package/commands/explain/examples.md +199 -42
- package/commands/retrieve/README.md +35 -3
- package/commands/retrieve/examples.md +114 -1
- package/commands/transfer/README.md +33 -4
- package/commands/transfer/examples.md +46 -0
- package/docs/CLI.md +30 -8
- package/docs/CONSTITUTION.md +14 -9
- package/docs/MATRIX.md +37 -22
- package/docs/errors/cdb/encrypt-failed.md +10 -0
- package/docs/errors/cdb/password-required.md +5 -4
- package/docs/errors/cdb/plaintext-backups.md +11 -0
- package/docs/errors/cmr/caller-policy-not-enforced.md +5 -3
- package/docs/errors/input/invalid-routeros-path.md +25 -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 +9 -5
- package/src/api-fanout.ts +2 -0
- package/src/api.ts +46 -39
- package/src/btest.ts +7 -0
- package/src/cli/api.ts +18 -1
- package/src/cli/btest.ts +9 -0
- package/src/cli/devices.ts +76 -8
- package/src/cli/execute.ts +9 -0
- package/src/cli/explain.ts +206 -47
- package/src/cli/retrieve.ts +9 -0
- package/src/cli/terminal.ts +9 -0
- package/src/cli/transfer.ts +10 -1
- package/src/core/envelope.ts +10 -3
- package/src/core/error-catalog.ts +28 -2
- 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/menu-path.ts +89 -0
- package/src/core/routeros-errors.ts +25 -0
- package/src/devices.ts +154 -9
- package/src/execute-cmr.ts +2 -8
- package/src/execute-fanout.ts +2 -0
- package/src/execute.ts +55 -20
- package/src/explain/args.ts +115 -10
- package/src/explain/blocks.ts +2 -2
- package/src/explain/catalog.ts +22 -12
- package/src/explain/group-faults.ts +162 -29
- package/src/explain/operators.ts +2 -1
- package/src/explain/pasted-prompt.ts +198 -0
- package/src/explain/pathresolve.ts +2 -2
- package/src/explain/scope-brace.ts +226 -11
- package/src/explain/segment.ts +138 -3
- package/src/explain/symbols.ts +148 -8
- package/src/explain/values.ts +55 -4
- package/src/explain/verbsplit.ts +2 -2
- package/src/explain/write.ts +2 -2
- 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 +397 -43
- package/src/index.ts +10 -0
- package/src/protocols/adapter.ts +90 -10
- package/src/protocols/btest-session.ts +10 -30
- package/src/protocols/ec-srp5.ts +19 -10
- package/src/protocols/mac-telnet-console.ts +2 -2
- package/src/protocols/mac-telnet.ts +38 -10
- package/src/protocols/native-api.ts +13 -12
- package/src/resolver/cdb.ts +58 -2
- package/src/resolver/index.ts +1 -0
- package/src/resolver/quickchr-provider.ts +5 -0
- package/src/resolver/selection.ts +11 -0
- package/src/retrieve-fanout.ts +2 -0
- 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 +88 -27
- package/src/settings.ts +1 -0
- package/src/terminal.ts +7 -0
- package/src/transfer-fanout.ts +2 -0
- package/src/transfer.ts +117 -27
|
@@ -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).
|
|
@@ -258,6 +257,22 @@ today's path/verb/args split and script-vs-structured gate:
|
|
|
258
257
|
`/interface/wifi/dashboard` and `/system/dashboard` are `inspect` only: the
|
|
259
258
|
trees carry them, the manual publishes no menu entry for them.
|
|
260
259
|
|
|
260
|
+
**Source refresh (accepted 2026-10-30).** The CLI Reference added
|
|
261
|
+
`compare-versions`, a version-comparison widget page with no `**Type:**`
|
|
262
|
+
entry. `llms.txt` lists it as a page and the sitemap serves it as a category
|
|
263
|
+
URL, so it aborted the parser and would have aborted the orphan-dir check.
|
|
264
|
+
It joins `index` as a hand-audited non-command slug, excluded from both
|
|
265
|
+
inventories. The CLI Reference moves from 1,099 pages / 1,106 entries to
|
|
266
|
+
1,109 / 1,116; the inspect pins and the 636-menu union are unchanged. The
|
|
267
|
+
catalog grows from 1,166 to 1,176 paths: 1,005 `both`, 67 `inspect`, 104
|
|
268
|
+
`published`. It adds 10 paths and removes none, with no kind change and no
|
|
269
|
+
provenance loss. The additions are `/app/settings/remove-certificate`, three
|
|
270
|
+
`/system/resource/hardware` commands (`power-cycle`, `power-reset`,
|
|
271
|
+
`reset-overcurrent`), and `/interface/wifi/easymesh` with five children. The
|
|
272
|
+
three `/cmr/client` gate strings drop `!powerpc`. easymesh is published with
|
|
273
|
+
no gate and is in no CHR tree through 7.26beta3, so the unexplained residue
|
|
274
|
+
grows from `/root/terminal` alone to seven paths.
|
|
275
|
+
|
|
261
276
|
There are real unknowns in how far offline parsing can go (expression
|
|
262
277
|
grammar, scope fidelity vs `:parse`, `[]`-nesting corner cases). Grounding
|
|
263
278
|
those unknowns was **phase 0** (#185); its findings are folded in throughout
|
|
@@ -373,9 +388,8 @@ native API's `/execute` **with `as-string=""`** returns the parser text —
|
|
|
373
388
|
including the `(line N column M)` diagnostic on a syntax error — byte-identical
|
|
374
389
|
to what a console transport prints. The opaque `*NN` value seen otherwise is the
|
|
375
390
|
**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`
|
|
391
|
+
"pass `as-string` and interpret `ret`". The execute gate passes
|
|
392
|
+
`as-string` and classifies `ret`; live explain reuses that shared classifier. `/console/inspect`
|
|
379
393
|
(highlight/syntax/completion) works over the native API's `talk` framing too. So
|
|
380
394
|
the line/column diagnostic is available on both live cells — the earlier caveat
|
|
381
395
|
that only a console transport surfaces the parser text is retired.
|
|
@@ -401,10 +415,8 @@ router therefore never reinterprets a previously valid offline invocation.
|
|
|
401
415
|
the offline form with nothing else to analyze. A positional always wins, and
|
|
402
416
|
when fd 0 is redirected anyway the result carries a
|
|
403
417
|
[`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
|
|
418
|
+
than quietly dropping the pipe. `--file -` names stdin explicitly in both forms,
|
|
419
|
+
including live `… | centrs explain edge1 --file -`. The narrower rule is
|
|
408
420
|
deliberate and measured: `runCli` is called in-process by tests, so reading fd 0
|
|
409
421
|
on any path a positional invocation reaches consumes the invoking shell's
|
|
410
422
|
stdin — it fed those bytes to the analyzer and broke 21 unrelated tests once,
|
|
@@ -652,7 +664,7 @@ and its phase is named below.
|
|
|
652
664
|
| `structure.statements[].command` | `path` + `verb`, plus **`args` where the argument list was read** (#202c-1); the ordered token list is `statements[].arguments.tokens` |
|
|
653
665
|
| `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
666
|
| `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`
|
|
667
|
+
| `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
668
|
| `spans` | comment runs and resolved variable classes only; **no value shape or type** |
|
|
657
669
|
| `schema`, `completion` | absent — live evidence, phase 2 |
|
|
658
670
|
|
|
@@ -744,13 +756,10 @@ and its phase is named below.
|
|
|
744
756
|
rejected by RouterOS at `{` and resolve no reference. In an expression the
|
|
745
757
|
remaining `-dns` is scanned as subtraction plus its right operand; inside a
|
|
746
758
|
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.
|
|
759
|
+
- **Evidence follows its producer.** Offline calls emit only `canonicalizer`
|
|
760
|
+
entries, with no device version. Live calls add `live-inspect` entries with
|
|
761
|
+
probe outcomes and available device provenance; existing offline entries
|
|
762
|
+
retain their meaning. See [Live probe foundation](#live-probe-foundation-491).
|
|
754
763
|
- **Value facts are three axes, never one `type` string (#225).** RouterOS
|
|
755
764
|
types values at parse time while `highlight` classes every value byte `none`,
|
|
756
765
|
so type evidence is not a subset of Q12 spans. Each result-local
|
|
@@ -763,7 +772,7 @@ and its phase is named below.
|
|
|
763
772
|
The stable/testing CHR matrix fixes the offline lexicon: `num`, `ip`,
|
|
764
773
|
`ip-prefix`, `ip6`, `ip6-prefix`, `id`, `time`, `array`, `mac`, `bool`, and
|
|
765
774
|
`str` — `op` is deliberately not a member: a deferred `(> …)` abstains
|
|
766
|
-
offline and
|
|
775
|
+
offline and live alike (#288, #225); quoted literals
|
|
767
776
|
hint only `str`, while malformed and out-of-range address-like controls
|
|
768
777
|
abstain even in named attributes. Most vocabulary borrows RouterOS scripting
|
|
769
778
|
type names (with the explicit `macAddr` schema exception below), so a hint
|
|
@@ -797,8 +806,9 @@ and its phase is named below.
|
|
|
797
806
|
1 and identical `:parse` IL — only `:typeof` distinguishes them
|
|
798
807
|
(`test/fixtures/explain/operators.json` → `sweep.runtime`
|
|
799
808
|
`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
|
-
|
|
809
|
+
7.21.5, 7.23.3, 7.24rc4). Offline abstains on every such form, and so does
|
|
810
|
+
the live `observedType`, which never sends `:typeof` (#288, #225). `op` has
|
|
811
|
+
no producer.
|
|
802
812
|
Shape remains non-authoritative: `100000w` is time-shaped but observed as
|
|
803
813
|
`str`. Argument context remains separate: on 7.23.3 and 7.24rc3, both
|
|
804
814
|
`/ip/address address=2.2` and firewall `src-address=2.2` canonicalize it to
|
|
@@ -812,8 +822,8 @@ and its phase is named below.
|
|
|
812
822
|
and does not widen the hint. Thus a hint never validates a value or becomes a
|
|
813
823
|
diagnostic, and never changes `runtimeAcceptance`. Quoting likewise never
|
|
814
824
|
earns a wrong-type diagnostic: commands may cast quoted strings at runtime.
|
|
815
|
-
Offline emits only `shapeHints`;
|
|
816
|
-
existing homes
|
|
825
|
+
Offline emits only `shapeHints`; a live call adds the two live facts into
|
|
826
|
+
their existing homes ([Live value types](#live-value-types-225)).
|
|
817
827
|
|
|
818
828
|
**A located array is read member by member (#225 V1).** Each member of a
|
|
819
829
|
`{…}`/`(…)` literal is its own `values.occurrences[]` row with `kind:
|
|
@@ -1128,8 +1138,9 @@ navigated, so the context stays as unknown as it already was.
|
|
|
1128
1138
|
`empty-group`, `misplaced-separator`, `missing-operand` and
|
|
1129
1139
|
`invalid-at-sign` (#505, #511, #512, below), and the four token faults
|
|
1130
1140
|
`glued-token`, `invalid-variable`, `bad-string-variable` and
|
|
1131
|
-
`invalid-name` (#506, #518, below)
|
|
1132
|
-
|
|
1141
|
+
`invalid-name` (#506, #518, below), and the two unquoted-byte faults
|
|
1142
|
+
`unquoted-reserved-byte` and `unquoted-non-ascii` (#358, #371, below).
|
|
1143
|
+
Nineteen classes the device itself rejects.
|
|
1133
1144
|
- `warning` — `over-depth`, because it is centrs's own resource bound and says
|
|
1134
1145
|
nothing about whether the input is legal; `possible-spaced-argument`
|
|
1135
1146
|
(#350), a spelling RouterOS accepts and marks obsolete; `discarded-close`
|
|
@@ -1326,8 +1337,9 @@ query and brace roles. The `$` inside a string is checked by the escape walk
|
|
|
1326
1337
|
(`src/explain/quoted-string.ts`), and the stray close by the segmenter
|
|
1327
1338
|
(`src/explain/segment.ts` → `discardFrom`). Every rule is a `:parse` answer on
|
|
1328
1339
|
CHR 7.24.5 and 7.23.8, identical on both, and
|
|
1329
|
-
`test/fixtures/explain/token-faults.json` pins
|
|
1330
|
-
|
|
1340
|
+
`test/fixtures/explain/token-faults.json` pins these rows and the unquoted-byte
|
|
1341
|
+
rows below: 292 in all (184 rejections and 108 acceptances, with the
|
|
1342
|
+
acceptances' IL). `test/integration/explain-token-faults.test.ts`
|
|
1331
1343
|
holds the device to that fixture, and `test/unit/explain-token-faults.test.ts`
|
|
1332
1344
|
holds offline to it.
|
|
1333
1345
|
|
|
@@ -1379,11 +1391,59 @@ variable name, so the next `"` does not close the string. Every scanner in
|
|
|
1379
1391
|
`explain` closes it there, so both `$` rules and the glued-token rule abstain
|
|
1380
1392
|
around a string that ends in `$"` (2 of 948 corpus scripts use the form).
|
|
1381
1393
|
`$$`, `$(…)`, `$[…]` and `${…}` outside a string, where the device's byte is
|
|
1382
|
-
not the `$`. A `\` continuation inside a glued token (`"a"\⏎"b"`).
|
|
1383
|
-
word
|
|
1394
|
+
not the `$`. A `\` continuation inside a glued token (`"a"\⏎"b"`). A glued
|
|
1395
|
+
head word (`$v"a"`, `{…}:put 2`). `:local a=b`, which 7.24.5 reads as
|
|
1384
1396
|
a named argument and 7.23.8 as a syntax error. And a name in `/local` or
|
|
1385
1397
|
`:local name=a_b`.
|
|
1386
1398
|
|
|
1399
|
+
### Unquoted bytes (#358, #371)
|
|
1400
|
+
|
|
1401
|
+
A `?`, a backtick and a non-ASCII byte are never part of a bare word, and
|
|
1402
|
+
neither is a second `=` in a menu command's argument. The rules ride the
|
|
1403
|
+
token walk (`src/explain/group-faults.ts`). They were grounded with live
|
|
1404
|
+
explain (`explainLiveEnvelope`: `:parse` and `highlight`) on CHR 7.24.5 and
|
|
1405
|
+
7.23.8, which answered identically. The 147 rows are in
|
|
1406
|
+
`test/fixtures/explain/token-faults.json`.
|
|
1407
|
+
|
|
1408
|
+
| Code | Device rule | Rejected | Accepted |
|
|
1409
|
+
| ---- | ----------- | -------- | -------- |
|
|
1410
|
+
| `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"."?")` |
|
|
1411
|
+
| `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é"}` |
|
|
1412
|
+
|
|
1413
|
+
**Where the device reports, offline reports.** In a menu command's argument
|
|
1414
|
+
name the device names the name's first byte (`/ip/address/add addré=1` and
|
|
1415
|
+
`a?b=1` at the `a`), so the span is the name. A directive (`:put café=1`) and
|
|
1416
|
+
a `$` call (`$f café=1`) report at the byte. A declared name keeps the
|
|
1417
|
+
existing `invalid-name` (`:local café`, `:foreach café`). A query, and a
|
|
1418
|
+
group or array inside one, keeps `=` as an operator but not these bytes
|
|
1419
|
+
(`where (comment=café)` and `in {café}` are rejected at the `é`), and the slash-joined `[/ip/address/find …]` is a query, as
|
|
1420
|
+
`[find …]` is. A leading BOM is a positional fact and is not read as part of
|
|
1421
|
+
the first word. The informational `non-ascii` fact is still published beside
|
|
1422
|
+
the error, so `isPositionalFact("non-ascii")` is unchanged.
|
|
1423
|
+
|
|
1424
|
+
**No property parser takes these bytes.** This is the difference from the
|
|
1425
|
+
glued-token rule. `regexp=a?b` (layer7 and DNS static), ``regexp=a`b``,
|
|
1426
|
+
`public-key=<base64>=`, `secret=abc==`, `password=abc=`,
|
|
1427
|
+
`http-header-field=a:b=c` and the script bodies `on-event=a=b` and
|
|
1428
|
+
`up-script=a=b` are all rejected at the byte.
|
|
1429
|
+
|
|
1430
|
+
**Measured.** Corpus `e12d5d4e58a4`: 8 scripts move to `fail` (7 from `warn`,
|
|
1431
|
+
one from `pass`), and 20 already-failing scripts gain a new code. All 28 are
|
|
1432
|
+
device rejections on every channel. 8 of their first new errors sit on the
|
|
1433
|
+
7.24.5 diagnostic's byte, 20 come after it, and none precedes it. There are no
|
|
1434
|
+
false rejects. On the conformance cases, 10 missed rejections move to `fail`
|
|
1435
|
+
(the `non-ascii-outside-strings` u10–u12 and u14–u16, z90, z92, g18 and e33), with no new
|
|
1436
|
+
false reject.
|
|
1437
|
+
|
|
1438
|
+
**Not covered here.** `:put a=b`, `:local x a=b` and `[:len a=b]`: 7.24.5
|
|
1439
|
+
reads them as a named argument (`bad parameter a`, at the end), and 7.23.8
|
|
1440
|
+
rejects the `=`. A query value that starts with `=` (`where comment==b`,
|
|
1441
|
+
`expected comment value`). A bare `source=` value,
|
|
1442
|
+
which the device rejects at its first byte whatever it holds (`source=abc`), so
|
|
1443
|
+
a byte rule there reports after the device's byte. And `:set` of an undeclared
|
|
1444
|
+
name, which the device rejects at the name's first byte (`:set zzz 2`), so
|
|
1445
|
+
`:set café` is reported at the `é`, after it.
|
|
1446
|
+
|
|
1387
1447
|
### The token stream a rule reads (#316)
|
|
1388
1448
|
|
|
1389
1449
|
`lexArguments` is all-or-nothing by design: its consumer renders a runnable REST
|
|
@@ -1599,15 +1659,34 @@ catalog-named command, with a `:` scripting directive, or with an absolute
|
|
|
1599
1659
|
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
1660
|
`ethernet { :put … }`.
|
|
1601
1661
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1662
|
+
Issue #348 only stopped the failure. It still read `address { … }` as a command
|
|
1663
|
+
`/ip address` whose operand was the brace, so the body was never segmented.
|
|
1664
|
+
That rule was bounded by what a statement can start with, on the theory that
|
|
1665
|
+
`{1;2}` there is an array. The device says otherwise (#351, CHR 7.24.5 and
|
|
1666
|
+
7.23.8): a brace right after a menu word is a statement block whatever the body
|
|
1667
|
+
holds. `/ip { address { 1;2 } }` is `bad command name 1` at the `1`, and
|
|
1668
|
+
`/ip { address { a;b } }` runs `a` as `add`. The old reading failed the OUTER
|
|
1669
|
+
brace at depth three, and passed `print {1;2}` and `print; zzz` inside the body.
|
|
1670
|
+
|
|
1671
|
+
So a relative run inside a menu block now opens a block of its own when the
|
|
1672
|
+
menu it names is known: the enclosing block's path, read outward from the
|
|
1673
|
+
source, plus the run, named by a shipped structure table
|
|
1674
|
+
(`scope-brace.ts` → `menuBlockPath`). The segmenter, the comment mask, the
|
|
1675
|
+
argument lexer and the group-fault walk all ask it. The body is then read like
|
|
1676
|
+
`/ip/address { … }`: `print {1;2}` there fails at its own `{`, the device's
|
|
1677
|
+
byte, and an unresolvable body statement abstains at every depth.
|
|
1678
|
+
|
|
1679
|
+
It is deliberately not widened into a claim of existence. Menu-scope
|
|
1680
|
+
resolution is schema-dependent — `/ip { zzznotamenu { print } }` is a
|
|
1681
|
+
`syntax error` on device — and offline has no schema, so a catalog MISS
|
|
1682
|
+
abstains rather than being decided either way. That is the catalog's own "a
|
|
1683
|
+
hit is decisive, a miss says nothing" contract. Three spellings keep the older
|
|
1684
|
+
reading because the source alone does not name the menu: a block after a
|
|
1685
|
+
navigation statement (`/ip⏎address { … }`), an abbreviation (`addr {`) and a
|
|
1686
|
+
digit-led menu (`6to4 {`, which the resolver does not read as a path). After a
|
|
1687
|
+
navigation, `/ip⏎address { 1;2 }` still fails at the outer brace and
|
|
1688
|
+
`/ip⏎address { print {1;2} }` still passes; the device answers
|
|
1689
|
+
`bad command name 1` and `expected end of command` at the inner brace.
|
|
1611
1690
|
|
|
1612
1691
|
### The `=` is optional in ordinary arguments too (#350)
|
|
1613
1692
|
|
|
@@ -1650,6 +1729,36 @@ device `syntax error` that offline does not report. That is a missed
|
|
|
1650
1729
|
rejection for the device preflight to catch, never a false reject. On the
|
|
1651
1730
|
pinned corpus the widening changed no census.
|
|
1652
1731
|
|
|
1732
|
+
### First-segment script-body abbreviations warn instead of rejecting (#525)
|
|
1733
|
+
|
|
1734
|
+
RouterOS takes any unique prefix of an argument name, including the first
|
|
1735
|
+
segment: `down={ :beep }` lowers to `down-script=` on CHR 7.22.1–7.25, as do
|
|
1736
|
+
bodies opening with one bare command word (`down={ beep }`) or after a comment
|
|
1737
|
+
line. Every word is a prefix of `<word>-script`, so without the command's
|
|
1738
|
+
argument list (schema) offline cannot tell `down=` from `comment=`, which the
|
|
1739
|
+
device rejects. The contract: on a non-root command, outside `where`, an
|
|
1740
|
+
unproven `name={` with a script-like body warns
|
|
1741
|
+
`explain/canonicalizer/possible-script-body-abbreviation` (verdict `warn`, the
|
|
1742
|
+
offline gate lets it through) instead of failing with `invalid-command-brace` —
|
|
1743
|
+
glued or with the brace spaced off the `=` (`down= { :beep }` lowers to
|
|
1744
|
+
`down-script=` on CHR 7.25 exactly like the glued spelling, so the spaced
|
|
1745
|
+
owner warns under the same gates). The brace reads as a body for every
|
|
1746
|
+
reader — a `#` inside is a comment, not `invalid-hash` — while proven slots
|
|
1747
|
+
(`down-script=`, `s=`, `on-…`) stay silent and control scopes (`do={…}`) are
|
|
1748
|
+
never guesses. A bare word followed by `;`-separated members is array
|
|
1749
|
+
members, not commands, so `.proplist={name;comment}` keeps its error alongside
|
|
1750
|
+
the other array-content braces (`{1;2}`, `{"x"}`, `{}`), the positional form,
|
|
1751
|
+
and `where`. A root directive's slots (`:put x={…}`, glued or spaced) and a `where` named
|
|
1752
|
+
comparison (`where gateway={…}`, however wrapped) keep their error too: the
|
|
1753
|
+
slot table and the property parser decide there, so the abbreviation guess
|
|
1754
|
+
never shields them (#554). The integration twin pins both device directions.
|
|
1755
|
+
|
|
1756
|
+
The tradeoff is measured: a verdict sweep of the 948-row pinned corpus
|
|
1757
|
+
population against the pre-batch baseline reports zero verdict or diagnostic
|
|
1758
|
+
changes, so nothing in the corpus pays for the abstention. The one deliberate
|
|
1759
|
+
over-warn — `comment={ :beep }` warns though the device rejects it — has no
|
|
1760
|
+
corpus occurrence.
|
|
1761
|
+
|
|
1653
1762
|
### `'` is not a RouterOS token (#355)
|
|
1654
1763
|
|
|
1655
1764
|
`'` (U+0027) is not a string delimiter and not a legal bare byte. Only `"` opens
|
|
@@ -1986,8 +2095,8 @@ And the grounded complement — asked, and refused:
|
|
|
1986
2095
|
`corpus` by `bun run explain:token-census:readme` and gated against it by
|
|
1987
2096
|
`bun run explain:token-census:readme:check`; the fixture itself is gated
|
|
1988
2097
|
against a fresh corpus run by `bun run explain:token-census:check`. Of
|
|
1989
|
-
1,426,731 analyzed bytes, 1,022,
|
|
1990
|
-
403,
|
|
2098
|
+
1,426,731 analyzed bytes, 1,022,786 are classified (71.69%), the remaining
|
|
2099
|
+
403,945 are `unclassified`. The census emits 241,282 tokens (avg 254.5 per
|
|
1991
2100
|
script). Every byte belongs to exactly one token — sorted by `start`, no
|
|
1992
2101
|
gaps, no overlaps, `join(slice) === input` — and the `class` field is the
|
|
1993
2102
|
vocabulary #264 B5 settled. Each B2 fill should move the classified
|
|
@@ -2468,22 +2577,317 @@ last byte. The `$` sigil and the `[` / `]` of a substitution stay `string` on
|
|
|
2468
2577
|
the same ground — `structure.subcommands` already publishes the resolution, and
|
|
2469
2578
|
its `innerSpan` locates the brackets around it.
|
|
2470
2579
|
|
|
2471
|
-
###
|
|
2472
|
-
|
|
2473
|
-
`
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2580
|
+
### Live probe foundation (#491)
|
|
2581
|
+
|
|
2582
|
+
`centrs explain <router> '<input>'` (or `--quickchr <name>` / `--host <host>`)
|
|
2583
|
+
adds read-only device evidence over REST or native API. Library callers use
|
|
2584
|
+
`explainLiveEnvelope({ input, targetInput, via, ... }, options)` from the root
|
|
2585
|
+
package; the browser-safe `@tikoci/centrs/explain` entry remains offline.
|
|
2586
|
+
Connection/authentication settings use the existing resolver and provider
|
|
2587
|
+
precedence. A pinned unsupported transport is refused. No fan-out or protocol
|
|
2588
|
+
fallback is performed. `AbortSignal` cancels library probes.
|
|
2589
|
+
An explicitly supplied connection flag such as `--via`, `--password` or
|
|
2590
|
+
`--timeout` without `<router>`, `--host` or `--quickchr` is a usage error before
|
|
2591
|
+
input is read. Connection defaults from environment and config remain inert in
|
|
2592
|
+
offline mode.
|
|
2593
|
+
Text output summarizes live observations; `--json` retains full parse and
|
|
2594
|
+
per-byte highlight evidence.
|
|
2595
|
+
|
|
2596
|
+
`data.canonical`, structure, symbols, values and optional tokens retain the
|
|
2597
|
+
offline result. Live observations are additional facts:
|
|
2598
|
+
|
|
2599
|
+
- `data.live.environment` records the observed version, architecture and board.
|
|
2600
|
+
- `data.live.parse` carries the printed parse result, rejection classification
|
|
2601
|
+
and authoritative line/byte-column when reported. Only an escaped
|
|
2602
|
+
`:put [:parse "…"]` wrapper runs; the analyzed input, substitutions and
|
|
2603
|
+
deferred bodies are never executed.
|
|
2604
|
+
- `data.live.highlight` retains raw device classes. Additional `data.spans`
|
|
2605
|
+
cite its evidence and use centrs classes: `dir`, `cmd`, `arg`, `escaped`,
|
|
2606
|
+
comments and variable classes; device state, syntax-meta, error/obsolete and
|
|
2607
|
+
unknown classes map to `unclassified`. Error/obsolete markers produce separate
|
|
2608
|
+
diagnostics. Offline and live spans can overlap; distinguish them by `ev`.
|
|
2609
|
+
- `data.live.arguments[]` contains `path`, `names`, `statementIds` and `ev` for
|
|
2610
|
+
command-level `request=child`. Repeated paths share a probe. Names confirm what
|
|
2611
|
+
the device exposes; they do not rewrite offline arguments, transport eligibility
|
|
2612
|
+
or lexical bindings. The highlight obsolete marker distinguishes `name x`
|
|
2613
|
+
from the two flags `detail without-paging` (example 48).
|
|
2614
|
+
|
|
2615
|
+
Every live evidence entry identifies its probe, observation time, known version
|
|
2616
|
+
and acquisition outcome. Input probes include SHA-256 and normalization;
|
|
2617
|
+
`coverage` identifies the submitted byte range for a usable response, not proof
|
|
2618
|
+
that every byte is syntactically valid. Parse uses original UTF-8 input;
|
|
2619
|
+
highlight uses the existing one-SUB-per-non-ASCII-byte normalization. Its facts
|
|
2620
|
+
therefore describe that explicit variant; both spaces align with `positionMap`.
|
|
2621
|
+
Parse can stop at its first error; highlight `none` remains unclassified.
|
|
2622
|
+
|
|
2623
|
+
Probe outcomes distinguish `ok`, `empty`, `unsupported`, `skipped`, `timeout`,
|
|
2624
|
+
`auth-error`, `transport-error`, `interrupted` and `invalid-response`. A parse
|
|
2625
|
+
rejection is an acquired response (`ok`) plus a diagnostic, not a transport
|
|
2626
|
+
failure. A failed/skipped probe adds a warning diagnostic; acquired offline and
|
|
2627
|
+
live facts remain available. `ok:true` means analysis produced a result, never
|
|
2628
|
+
that all probes succeeded. Resolver/usage failures and transport/authentication/interruption failures before
|
|
2629
|
+
any input probe yields a result remain `ok:false`.
|
|
2630
|
+
`meta.operation.mode` is `live`; no execution-validation verdict is invented:
|
|
2631
|
+
`meta.validation` is `{ enabled: false }`, offline and live, because explain
|
|
2632
|
+
is not a gate (#535). `runtimeAcceptance` always remains `not-proven`.
|
|
2633
|
+
|
|
2634
|
+
Work is bounded by the resolved per-request timeout, 32 distinct command paths,
|
|
2635
|
+
32,767 bytes for highlight and 65,536 bytes for parse. Larger probes are skipped
|
|
2636
|
+
with their reason; no silent prefix is submitted and `input.truncated` stays
|
|
2637
|
+
false. A transport/authentication failure stops later requests. Scripting-path
|
|
2638
|
+
discovery is conservatively skipped before 7.21.4 or on unknown versions. No
|
|
2639
|
+
command-level completion, user-expression evaluation or related
|
|
2640
|
+
configuration-table collection is part of this slice. `--schema` and
|
|
2641
|
+
`--complete` add the probes in the next section, and the value-type probes
|
|
2642
|
+
after it run on every live call.
|
|
2643
|
+
|
|
2644
|
+
Committed controls are
|
|
2645
|
+
[`live-probes.stable.json`](../../test/fixtures/explain/live-probes.stable.json)
|
|
2646
|
+
(7.24.5) and
|
|
2647
|
+
[`live-probes.long-term.json`](../../test/fixtures/explain/live-probes.long-term.json)
|
|
2648
|
+
(7.23.8), both CHR reporting `x86_64`, with package manifests, input hashes,
|
|
2649
|
+
observation times, raw parse/highlight output and projected child names. Each
|
|
2650
|
+
fixture records its capture command. `test/unit/explain-live.test.ts` replays
|
|
2651
|
+
the parse classifier and source-aligned highlighter against these controls;
|
|
2652
|
+
`test/integration/explain-live.test.ts` repeats the probes on a real CHR and
|
|
2653
|
+
checks that the analyzed identity-setting command did not run.
|
|
2654
|
+
|
|
2655
|
+
### Live describe and completion (#492)
|
|
2656
|
+
|
|
2657
|
+
`--schema` and `--complete` work on the statement at the cursor. The cursor
|
|
2658
|
+
defaults to the end of the input. `--cursor <byte>` is a UTF-8 byte offset,
|
|
2659
|
+
which is also the analyzed and device byte offset. Library callers may pass
|
|
2660
|
+
`cursor: { line, character }` (0-based, UTF-16) instead. A cursor inside a
|
|
2661
|
+
multi-byte character moves back to its first byte. A cursor outside the input
|
|
2662
|
+
is [`usage/invalid-cursor`](../../docs/errors/usage/invalid-cursor.md), raised
|
|
2663
|
+
before connecting. `--cursor` or `--full` without a facet is
|
|
2664
|
+
`usage/conflicting-flags`. Offline, both facets still return the live-target
|
|
2665
|
+
tip and enumerate nothing.
|
|
2666
|
+
|
|
2667
|
+
**Probe shapes.** Grounded on CHR 7.12.2, 7.20.8, 7.23.8 and 7.24.5 over REST
|
|
2668
|
+
and native API:
|
|
2669
|
+
|
|
2670
|
+
- `child` names what exists at a path and resolves unique abbreviations
|
|
2671
|
+
(`ip,addr` answers as `address`). It is safe on every build tried.
|
|
2672
|
+
- `syntax` is sent only for an **argument** path (`ip,address,set,address`,
|
|
2673
|
+
`put,message`). That shape answers in about 25 ms on every build, scripting
|
|
2674
|
+
keywords included.
|
|
2675
|
+
- `completion` is sent only with `input=`, the text before the cursor. It is
|
|
2676
|
+
safe at every position tried, including scripting keywords and `do={`
|
|
2677
|
+
bodies on 7.20.8.
|
|
2678
|
+
- Never sent: `syntax` or `completion` with `path=` set to a **command**
|
|
2679
|
+
(`ip,address,set`). On 7.12.2 and 7.20.8 REST stops answering, and the
|
|
2680
|
+
requests queued behind it stall, for about 30 s. Native API closes the
|
|
2681
|
+
connection. 7.23.8 and later answer normally, but the rule is
|
|
2682
|
+
version-independent (`commands/api/AGENTS.md`). restraml's crawlers never
|
|
2683
|
+
meet this, because they send `syntax` to `arg` nodes only.
|
|
2684
|
+
|
|
2685
|
+
Every probe is an `evidence[]` entry with `facet: "schema" | "complete"`, its
|
|
2686
|
+
path, version and outcome. Facts cite it by `ev`. A facet probe that answers
|
|
2687
|
+
`empty` is a result, so it adds no warning diagnostic. Other failed outcomes
|
|
2688
|
+
warn as in the foundation, and a transport or authentication failure stops
|
|
2689
|
+
later probes.
|
|
2690
|
+
|
|
2691
|
+
**`data.schema`** describes one path: the resolved statement, or an absolute
|
|
2692
|
+
slash path as typed (`/ip/addr`). The `child` self row decides `node`:
|
|
2693
|
+
|
|
2694
|
+
- `menu`: `verbs[]` and `menus[]`. Each child is expanded by one more `child`
|
|
2695
|
+
probe into `arguments` (verb) or `verbs`/`menus` (submenu) counts.
|
|
2696
|
+
- `command`: `arguments[]`, each with its argument-level `syntax` rows. A
|
|
2697
|
+
flag's blank row is dropped. For `print`, `proplist` holds the output-field
|
|
2698
|
+
names from `print proplist=` (`source: "print-proplist"`). A singleton
|
|
2699
|
+
settings menu offers none there, so `get value-name=` answers instead
|
|
2700
|
+
(`source: "get-value-name"`; `/ip/dns`, `/system/identity`). These are names
|
|
2701
|
+
a print can show, **not print rows**.
|
|
2702
|
+
- `argument`: its `syntax` rows.
|
|
2703
|
+
- `unknown`: the device reports nothing there, or the statement at the cursor
|
|
2704
|
+
is neither resolved nor an absolute path (`reason`).
|
|
2705
|
+
|
|
2706
|
+
Syntax rows are kept verbatim (`symbol`, RouterOS `symbol-type` as `kind`,
|
|
2707
|
+
`text`, `nested` as `depth`). They are declared help, such as
|
|
2708
|
+
`A.B.C.D (IP address)` or `input | forward | output` beside `string value`.
|
|
2709
|
+
They are not the `schemaType` value fact, which normalizes the same rows per
|
|
2710
|
+
value ([Live value types](#live-value-types-225)).
|
|
2711
|
+
|
|
2712
|
+
**Smart sizing.** By default each list shows up to 64 names, and expansion
|
|
2713
|
+
runs only when the listed children fit in 16 probes (a menu's children, or a
|
|
2714
|
+
command's arguments for their `syntax`). Otherwise names are listed without
|
|
2715
|
+
expansion. `counts` always holds the full sizes, `truncated: true`, `next[]`
|
|
2716
|
+
names up to three narrower inputs, and the envelope carries
|
|
2717
|
+
`tip/explain-schema-truncated`. On CHR 7.24.5, `/ip` (2 verbs, 29 menus) and
|
|
2718
|
+
`/ip/address/print` (21 arguments) truncate; `/ip/address` (12 verbs) and
|
|
2719
|
+
`/ip/firewall/filter` (16) do not. `--full` raises the limits to 1024 names and
|
|
2720
|
+
128 probes. That is the safety ceiling, not unlimited.
|
|
2721
|
+
|
|
2722
|
+
**`data.completion`** reports `cursor`, `at` (original line/character),
|
|
2723
|
+
`replace` (the bytes a candidate replaces, ending at the cursor), `prefix`,
|
|
2724
|
+
`context`, `candidates[]`, `count` and `truncated`. Candidates are capped like
|
|
2725
|
+
names. They are the device's own: one input completion of the text before the
|
|
2726
|
+
cursor, keeping shown, non-syntax rows. The row `offset` sets `replace`. The
|
|
2727
|
+
navigation punctuation (`/`, `:`, `..`) offered everywhere is left out. So a
|
|
2728
|
+
relative path, a block, a `where` clause or an unclosed `[` completes exactly
|
|
2729
|
+
as on the console: `/ip { :put 1; address add comm` → `comment`, and
|
|
2730
|
+
`/ip address⏎` → that menu's verbs. Where the device offers nothing, neither
|
|
2731
|
+
does explain: `do={ add comm` runs at root, so `add` is unknown there.
|
|
2732
|
+
|
|
2733
|
+
The `context` comes from the device highlight of the same text. When the
|
|
2734
|
+
cursor is not at the end, that is one extra highlight probe whose `coverage`
|
|
2735
|
+
says so.
|
|
2736
|
+
|
|
2737
|
+
- `path`: in a `dir`/`cmd` word, after a menu, or at the start of a line.
|
|
2738
|
+
Candidates are `menu` or `verb` by row style. A word the device cannot
|
|
2739
|
+
resolve there is `obj-inactive`, and the token before it gives its role:
|
|
2740
|
+
`/ip/address re` → `remove`, `reset`, and `:lo` → `local`, `lock`, `log`.
|
|
2741
|
+
- `argument-name`: in an `arg` word, or after a command, flag or value.
|
|
2742
|
+
`match: "exact"` marks a complete name. `takesValue` is whether the same
|
|
2743
|
+
answer offers `=` at the cursor: `comment` does, and the flag `detail` does
|
|
2744
|
+
not.
|
|
2745
|
+
- `variable`: a `where` clause, whose property names RouterOS scopes as
|
|
2746
|
+
variables (`where comm` → `comment`).
|
|
2747
|
+
- `value`: after `name=`. Candidates may include configuration state, such as
|
|
2748
|
+
interface names. They are never a closed set:
|
|
2749
|
+
`chain=` offers `forward`, `input` and `output`, but custom chains are legal.
|
|
2750
|
+
For a command argument, `syntax` carries its declared type text, so a
|
|
2751
|
+
free-form `comment=` returns no candidates and `string value`. A `where`
|
|
2752
|
+
value (`disabled=` → `no`, `yes`) has no argument path, so no `syntax` is
|
|
2753
|
+
read.
|
|
2754
|
+
- `unsupported` with a `reason`: strings, the obsolete spaced `name value`
|
|
2755
|
+
spelling, and other tokens the device does not class as a path, name or
|
|
2756
|
+
value.
|
|
2757
|
+
|
|
2758
|
+
Every candidate is an observation of the completion probe its `ev` cites.
|
|
2759
|
+
The declared side is `data.schema`: `child` names and argument `syntax`. So
|
|
2760
|
+
completion carries no second provenance label, and nothing in it is a closed
|
|
2761
|
+
set. `path` names the command at the
|
|
2762
|
+
cursor when it is known. It comes from the offline statement or `[…]`
|
|
2763
|
+
substitution when that resolved, otherwise from the device's `dir`/`cmd` runs.
|
|
2764
|
+
The command is needed only for a value's `syntax`. When it is not visible (a
|
|
2765
|
+
relative path after `;`), the candidates are unaffected and `syntax` is
|
|
2766
|
+
omitted.
|
|
2767
|
+
|
|
2768
|
+
The verdict still judges the input as written, and the two probes agree on
|
|
2769
|
+
it. `/ip/address add comm` is rejected by `:parse` (`expected end of command`
|
|
2770
|
+
at byte 16), and the highlight marks the same byte `error`. So the result
|
|
2771
|
+
fails even while its candidates are published. An assist loop that only wants
|
|
2772
|
+
candidates passes `--fail-on never`.
|
|
2773
|
+
|
|
2774
|
+
Committed controls are
|
|
2775
|
+
[`live-facets.stable.json`](../../test/fixtures/explain/live-facets.stable.json)
|
|
2776
|
+
(7.24.5) and
|
|
2777
|
+
[`live-facets.long-term.json`](../../test/fixtures/explain/live-facets.long-term.json)
|
|
2778
|
+
(7.23.8). They hold every recorded call and its raw answer, plus the projected
|
|
2779
|
+
result. `test/unit/explain-live-facets.test.ts` replays them.
|
|
2780
|
+
`test/integration/explain-live-facets.test.ts` asserts the examples on both
|
|
2781
|
+
transports and regenerates the captures when `CENTRS_EXPLAIN_FACETS_CAPTURE`
|
|
2782
|
+
names an output file.
|
|
2783
|
+
|
|
2784
|
+
Not covered here: MCP (#223), related print rows and topic checks (#493),
|
|
2785
|
+
and per-target caching of describe results (open 3).
|
|
2786
|
+
|
|
2787
|
+
### Live value types (#225)
|
|
2788
|
+
|
|
2789
|
+
Every live call fills the two live homes on `data.values.occurrences[].facts`
|
|
2790
|
+
that offline leaves empty. Neither changes the verdict or
|
|
2791
|
+
`runtimeAcceptance`, and neither becomes a diagnostic. Both probes are
|
|
2792
|
+
`evidence[]` entries with `facet: "types"` and `basis: "derived"`; an `empty`
|
|
2793
|
+
answer is a result, not a warning. They run after `--schema`/`--complete`.
|
|
2794
|
+
|
|
2795
|
+
**`observedType: { value, rendered, ev }`** is the type the device's parser
|
|
2796
|
+
visibly converted a bare literal to. `:parse` prints a string unquoted, so
|
|
2797
|
+
`:local a 123` and `:local b "123"` both render `value=123`. A rendering is
|
|
2798
|
+
therefore evidence only when it CHANGED the literal:
|
|
2799
|
+
|
|
2800
|
+
| Literal | Device rendering | `observedType` |
|
|
2801
|
+
| ------- | ---------------- | -------------- |
|
|
2802
|
+
| `2.2`, `2.2/8` | `2.0.0.2`, `2.0.0.2/8` | `ip`, `ip-prefix` |
|
|
2803
|
+
| `1:0:0::1` | `1::1` | `ip6` |
|
|
2804
|
+
| `0x10`, `0123`, `99999999999999999999` | `16`, `123`, `9223372036854775807` | `num` |
|
|
2805
|
+
| `yes`, `no` | `true`, `false` | `bool` |
|
|
2806
|
+
| `200ms`, `1d`, `1:2`, `10000w` | `00:00:00.200`, `1d00:00:00`, `01:02:00`, `10000w00:00:00` | `time` |
|
|
2807
|
+
| `123`, `1.1.1.1`, `true`, `00:00:02`, `"2.2"` | unchanged | absent |
|
|
2808
|
+
| `100000w`, `abc`, `1e5`, `300.1.1.1` | unchanged | absent |
|
|
2809
|
+
| member `{abc}`, `{-1}` | `$abc`, `(- 1)` | absent |
|
|
2810
|
+
|
|
2811
|
+
The literal is not looked up in the IL of the analyzed input, where a quoted
|
|
2812
|
+
`"x;y=1"` prints exactly like two arguments. Each bare literal is re-parsed
|
|
2813
|
+
alone: `:local vN <lit>` for a scripting operand, `:local vN {<lit>}` for an
|
|
2814
|
+
array member, `;`-joined into one escaped `:put [:parse "…"]` per context.
|
|
2815
|
+
`:put <lit>` converts exactly as `:local` does. Only literals of the bytes
|
|
2816
|
+
`A-Z a-z 0-9 . _ : / -` are sent, so no literal can leave its slot, and a
|
|
2817
|
+
quoted value never qualifies. One hard-error member (`{*1}`) rejects a whole
|
|
2818
|
+
batch, so the two contexts are separate probes and a rejected batch is
|
|
2819
|
+
`invalid-response` with the device's message; its literals abstain. A batch is
|
|
2820
|
+
at most 256 literals, otherwise `skipped`.
|
|
2821
|
+
|
|
2822
|
+
A scripting directive's named argument converts exactly as an operand
|
|
2823
|
+
(`:for i from=0x10` → `from=16`, `:put message=yes` → `message=true`), so it
|
|
2824
|
+
joins the operand batch. A directive's NAME slot is a variable and never
|
|
2825
|
+
converts: `:local yes 2` renders `name=$yes`, `:for yes from=1` renders
|
|
2826
|
+
`counter=$yes` and `:local name=no` renders `name=$no`. So a value the symbol
|
|
2827
|
+
view claims as a name, or a directive's `name=`/`counter=` argument, is never
|
|
2828
|
+
probed.
|
|
2829
|
+
|
|
2830
|
+
Menu-command arguments are never observed. Their own parser converts them
|
|
2831
|
+
and prints a per-argument encoding (`src-address=!1.1.1.1` →
|
|
2832
|
+
`true;1.1.1.1`, `address=2.2` → `2.0.0.2;`), and a quoted value converts only
|
|
2833
|
+
at run time (`address="2.2"` stays `2.2`). That slot is `schemaType`'s. `op`
|
|
2834
|
+
has no producer: `(>[:return 1])` and `(>{"a"=1})` parse identically, and only
|
|
2835
|
+
`:typeof` separates them.
|
|
2836
|
+
|
|
2837
|
+
**`schemaType: { values, list?, declared, ev }`** is the argument's declared
|
|
2838
|
+
type. For each `name=value` of a resolved menu command, the name is resolved
|
|
2839
|
+
against that command's live `child` names, exactly or as a unique prefix
|
|
2840
|
+
(`dist=` → `distance`). One `syntax` probe per distinct argument path
|
|
2841
|
+
(`ip,route,add,distance`) reads the declaration; at most 32 per call, later
|
|
2842
|
+
ones `skipped`. An ambiguous or unknown name sends no probe. `syntax` is never
|
|
2843
|
+
sent to a command path (see [Live describe and completion](#live-describe-and-completion-492)).
|
|
2844
|
+
|
|
2845
|
+
The rows are a small grammar (`[!]Range`, `Address[%Interface][@VRF]`), so
|
|
2846
|
+
`values` lists the LEAF types the declaration mentions, not alternatives of
|
|
2847
|
+
one value. They use the shape-hint names so the two axes compare directly,
|
|
2848
|
+
plus three schema-only terms:
|
|
2849
|
+
|
|
2850
|
+
| Device leaf | Term |
|
|
2851
|
+
| ----------- | ---- |
|
|
2852
|
+
| `(integer number)`, `(decimal number)`, `integer value`, `0..65535` | `num` |
|
|
2853
|
+
| `(time interval)`, `time interval` | `time` |
|
|
2854
|
+
| `(IP address)`, `IPv4 address`, `A.B.C.D` | `ip` |
|
|
2855
|
+
| `IPv6 address` | `ip6` |
|
|
2856
|
+
| `(IP prefix)`, `(IPv6 prefix)` | `ip-prefix`, `ip6-prefix` |
|
|
2857
|
+
| `(IP address range)` | `ip-range` |
|
|
2858
|
+
| `(MAC address)` | `mac` |
|
|
2859
|
+
| `string value…`, `hexadecimal string value…`, `script`, `filename` | `str` |
|
|
2860
|
+
| a listed word set (`input \| forward \| output`, `ether1 \| lo \| ...`) | `enum` |
|
|
2861
|
+
|
|
2862
|
+
`enum` is never a closed set: the device lists the current configuration
|
|
2863
|
+
names (`ether1 | lo`) exactly like keywords, and ends either kind with `...`
|
|
2864
|
+
when the list is long (`ftp | h323 | … | ...`). `list: true` is present when
|
|
2865
|
+
the declaration spells a repetition (`[,Type*]`, `(max 15 times)`); its
|
|
2866
|
+
absence is not a claim — `servers` (`Address[@VRF]`) splits on a comma
|
|
2867
|
+
without saying so. A keyword's help row (`tcp` → `transmission control
|
|
2868
|
+
protocol`) is not a leaf, and a blank placeholder beside a real leaf is
|
|
2869
|
+
skipped. The fact is ABSENT when the declaration is blank (every flag and
|
|
2870
|
+
boolean: `disabled`, `copy-from`) or when any leaf is `see documentation`
|
|
2871
|
+
(`/ipv6/address add address`). `declared` is the device's top-level text,
|
|
2872
|
+
verbatim (`Address[@VRF]`, `Num`).
|
|
2873
|
+
|
|
2874
|
+
The vocabulary also names `bool` and `name` for a second schema source: the
|
|
2875
|
+
CLI Reference's `Type` cells (`bool`, `iface_enum`, `ipAddr`, `macAddr`,
|
|
2876
|
+
`ubit (…)`), which declare the booleans and object references inspect leaves
|
|
2877
|
+
blank or prints as words. A CLI Reference producer would fill the same fact,
|
|
2878
|
+
told apart by its `ev`; delivering those tables is decision 3 (#501).
|
|
2879
|
+
|
|
2880
|
+
Grounded on CHR 7.24.5 and 7.23.8 over REST and native API, identical on both:
|
|
2881
|
+
a 40-command crawl (863 arguments) set the leaf table (571 project, 282 are
|
|
2882
|
+
blank, 7 cite `see documentation` and 3 list no names), and the conversion
|
|
2883
|
+
matrix is recorded raw in
|
|
2884
|
+
[`live-types.stable.json`](../../test/fixtures/explain/live-types.stable.json)
|
|
2885
|
+
and [`live-types.long-term.json`](../../test/fixtures/explain/live-types.long-term.json).
|
|
2886
|
+
`test/unit/explain-live-types.test.ts` replays them and pins the matrix
|
|
2887
|
+
reading; `test/integration/explain-live-types.test.ts` asserts examples 52–53
|
|
2888
|
+
on both transports and that every executed script is a `:parse` wrapper and
|
|
2889
|
+
every `syntax` probe names a discovered argument. It regenerates the captures
|
|
2890
|
+
when `CENTRS_EXPLAIN_TYPES_CAPTURE` names an output file.
|
|
2487
2891
|
|
|
2488
2892
|
## MCP and library surfaces
|
|
2489
2893
|
|
|
@@ -2619,6 +3023,202 @@ a process boundary. A B5 rename is therefore a visible break here rather than a
|
|
|
2619
3023
|
silent one — which is the point of settling the vocabulary before an external
|
|
2620
3024
|
consumer commits to it.
|
|
2621
3025
|
|
|
3026
|
+
## Topic checks and related live reads (#493, design — no implementation)
|
|
3027
|
+
|
|
3028
|
+
This section is the bounded design #493 asks for. It proposes shapes, ownership,
|
|
3029
|
+
and acceptance gates; **no topic check, special-command rule, or contextual
|
|
3030
|
+
read ships with it**. Each check needs its own accepted/rejected CHR controls
|
|
3031
|
+
and committed evidence before an implementation slice lands, and every new
|
|
3032
|
+
RouterOS semantic claim is grounded then — not here. The three examples below
|
|
3033
|
+
are **SYNTHETIC shape illustrations, not device claims**: they show the envelope
|
|
3034
|
+
a check would produce, with invented content, so reviewers can judge the
|
|
3035
|
+
contract without mistaking illustration for evidence.
|
|
3036
|
+
|
|
3037
|
+
**Ownership: one rule table, three consumers.** Fact and check definitions live
|
|
3038
|
+
in one module beside the analyzer (sibling of `src/explain/write.ts`, which is
|
|
3039
|
+
the precedent: an explain-only inference over the shipped structure). The
|
|
3040
|
+
CLI renders it, the library returns it, and the MCP adapter reshapes the same
|
|
3041
|
+
object — no copies of rule logic per surface. Checks evaluate against the
|
|
3042
|
+
**statement tree** (`structure.statements[]` ids plus `parent`/`owner` links),
|
|
3043
|
+
never against byte containment: a finding anchors to a statement id and its
|
|
3044
|
+
span, so a reordered document moves the finding with its statement.
|
|
3045
|
+
|
|
3046
|
+
**Findings reuse the shipped envelope — no new finding object.** A check emits
|
|
3047
|
+
what explain already emits: an `ExplainDiagnostic` (`code`, `severity`,
|
|
3048
|
+
`message`, `span`, `ev`) for the finding, the `ExplainEvidence` entry it cites
|
|
3049
|
+
(`id`, `source`, `probe`, `basis`, `outcome`, `statementIds`) for how it was
|
|
3050
|
+
decided and which statement it anchors to, and a `tip` (`code`, `message`,
|
|
3051
|
+
`fix`) for the next step. Codes follow `explain/<source>/<slug>`, with the
|
|
3052
|
+
check family as the slug prefix (`topic-…`, `special-command-…`). Topic
|
|
3053
|
+
findings are `info`: like the pasted-prompt tip, advice never moves
|
|
3054
|
+
`data.verdict`, which stays the validity answer. A check that should raise the
|
|
3055
|
+
verdict is a separate, explicitly decided change, not a default. Certainty is
|
|
3056
|
+
the cited evidence's `basis` — `heuristic` for a ratified rule over the
|
|
3057
|
+
offline structure (the `containsWrite` precedent), `derived` once it rests on
|
|
3058
|
+
live responses — so how sure a finding is reads from the same field as every
|
|
3059
|
+
other explain fact.
|
|
3060
|
+
|
|
3061
|
+
**What is decided where.** Source-only: structural facts the canonicalizer
|
|
3062
|
+
already proves (unresolved statements, write-shape, symbol bindings). Live
|
|
3063
|
+
schema: what a menu accepts (argument names, enums) — only `--schema` /
|
|
3064
|
+
`--complete` against a named router, never a bundled snapshot (decision 3).
|
|
3065
|
+
Device observations: current state (`print` rows) — only through an explicitly
|
|
3066
|
+
requested contextual read (below). Partial script input is never treated as
|
|
3067
|
+
final device state, and a finding never grants execution permission: advice
|
|
3068
|
+
and the execution gate are separate answers, and a lost read response is
|
|
3069
|
+
reported as a failed read, never as a successful execution.
|
|
3070
|
+
|
|
3071
|
+
**Contextual reads are opt-in and bounded.** The default offline entry stays
|
|
3072
|
+
network-free; naming a router does not authorize reads. A contextual read
|
|
3073
|
+
fires only under an explicit flag (name TBD, e.g. `--with-print`), one bounded
|
|
3074
|
+
`print` per confidently resolved menu, projected and truncated, each reported
|
|
3075
|
+
independently with its read command, time, projection, and truncation — so an
|
|
3076
|
+
agent can distinguish current observed state from the proposed script. The
|
|
3077
|
+
target is the envelope's `meta.target`, not a per-read field. Deduplication is by (statement id, resolved path): two
|
|
3078
|
+
statements in one menu share one read, and a failed read fails alone. The
|
|
3079
|
+
read is a validated centrs `retrieve` on the resolved path — never the user's
|
|
3080
|
+
`[find]`, expression, or analyzed script executed to get context.
|
|
3081
|
+
|
|
3082
|
+
**Secrets follow the existing redaction contract.** Related reads ride the
|
|
3083
|
+
redaction paths from #359: secret-bearing menus and verbose file output are
|
|
3084
|
+
high-risk surfaces, so a contextual read defaults to a non-sensitive
|
|
3085
|
+
projection, names its projection explicitly, and never exports key material
|
|
3086
|
+
into advice artifacts or logs. A check whose finding would need a sensitive
|
|
3087
|
+
field states that need in its definition and is refused without an explicit
|
|
3088
|
+
sensitivity opt-in — no automatic sensitive exports.
|
|
3089
|
+
|
|
3090
|
+
**Shape 1 — a grounded topic finding (synthetic illustration).**
|
|
3091
|
+
Input `SYNTHETIC-1` (illustrative only):
|
|
3092
|
+
`/ip service set telnet disabled=no` — a statement the analyzer resolves with
|
|
3093
|
+
id `s0`, span 0–34. A management-plane topic check (definition owned beside
|
|
3094
|
+
the analyzer, evaluated against `s0`) adds one diagnostic, the evidence entry
|
|
3095
|
+
it cites, and a tip; the ids and the check's `probe` name are invented. A
|
|
3096
|
+
partial envelope — only the fields the finding adds or reads are shown:
|
|
3097
|
+
|
|
3098
|
+
```json
|
|
3099
|
+
{
|
|
3100
|
+
"data": {
|
|
3101
|
+
"verdict": "pass",
|
|
3102
|
+
"diagnostics": [
|
|
3103
|
+
{
|
|
3104
|
+
"code": "explain/canonicalizer/topic-plaintext-management-service",
|
|
3105
|
+
"severity": "info",
|
|
3106
|
+
"message": "This enables telnet, a plaintext management service.",
|
|
3107
|
+
"span": { "start": 0, "end": 34 },
|
|
3108
|
+
"ev": "e10"
|
|
3109
|
+
}
|
|
3110
|
+
],
|
|
3111
|
+
"evidence": [
|
|
3112
|
+
{
|
|
3113
|
+
"id": "e10",
|
|
3114
|
+
"source": "canonicalizer",
|
|
3115
|
+
"probe": "topicPlaintextManagementService",
|
|
3116
|
+
"basis": "heuristic",
|
|
3117
|
+
"outcome": "ok",
|
|
3118
|
+
"statementIds": ["s0"]
|
|
3119
|
+
}
|
|
3120
|
+
]
|
|
3121
|
+
},
|
|
3122
|
+
"tips": [
|
|
3123
|
+
{
|
|
3124
|
+
"code": "tip/explain-topic-plaintext-management-service",
|
|
3125
|
+
"message": "Telnet sends credentials in clear text.",
|
|
3126
|
+
"fix": "Disable the plaintext service or restrict it to a management address list."
|
|
3127
|
+
}
|
|
3128
|
+
]
|
|
3129
|
+
}
|
|
3130
|
+
```
|
|
3131
|
+
|
|
3132
|
+
The verdict stays `pass`; probe outcome and `runtimeAcceptance` are untouched —
|
|
3133
|
+
the finding does not claim the router accepted anything.
|
|
3134
|
+
|
|
3135
|
+
**Shape 2 — a special-command fact (synthetic illustration).** #363 records
|
|
3136
|
+
the real cases (deferred there, not duplicated here); the shape is Shape 1's,
|
|
3137
|
+
with the `special-command-` slug family and a consequence, not a
|
|
3138
|
+
configuration judgement. Illustrative only:
|
|
3139
|
+
|
|
3140
|
+
```json
|
|
3141
|
+
{
|
|
3142
|
+
"code": "explain/canonicalizer/special-command-connection-loss",
|
|
3143
|
+
"severity": "info",
|
|
3144
|
+
"message": "This command can drop the session that runs it.",
|
|
3145
|
+
"span": { "start": 64, "end": 79 },
|
|
3146
|
+
"ev": "e11"
|
|
3147
|
+
}
|
|
3148
|
+
```
|
|
3149
|
+
|
|
3150
|
+
`e11` cites `statementIds: ["s2"]`, and the paired tip's `fix` says to run it
|
|
3151
|
+
from a path that survives the change.
|
|
3152
|
+
|
|
3153
|
+
No isolated device-mode table ships first (the #363 deferral stands); a
|
|
3154
|
+
special-command fact names its consequence and its evidence, nothing more.
|
|
3155
|
+
|
|
3156
|
+
**Shape 3 — an explicitly requested contextual read (synthetic illustration).**
|
|
3157
|
+
Input (illustrative): `/ip address add address=198.51.100.1/32 interface=ether1`
|
|
3158
|
+
with an explicit `--with-print`-style opt-in. The read follows the live
|
|
3159
|
+
argument-name precedent (`data.live.arguments[]` cites a `live-inspect`
|
|
3160
|
+
evidence entry): one evidence entry per read, and the rows in `data.live`
|
|
3161
|
+
citing it, independent of the analysis verdict. A partial `data` object —
|
|
3162
|
+
`evidence` is `data.evidence`, `live` is `data.live`:
|
|
3163
|
+
|
|
3164
|
+
```json
|
|
3165
|
+
{
|
|
3166
|
+
"evidence": [
|
|
3167
|
+
{
|
|
3168
|
+
"id": "e12",
|
|
3169
|
+
"source": "live-read",
|
|
3170
|
+
"probe": "print",
|
|
3171
|
+
"basis": "direct",
|
|
3172
|
+
"outcome": "ok",
|
|
3173
|
+
"version": "SYNTHETIC-version",
|
|
3174
|
+
"observedAt": "SYNTHETIC-timestamp",
|
|
3175
|
+
"statementIds": ["s0"],
|
|
3176
|
+
"path": "/ip/address",
|
|
3177
|
+
"projection": ["address", "interface", "disabled"],
|
|
3178
|
+
"truncation": { "returned": 3, "total": 12 }
|
|
3179
|
+
}
|
|
3180
|
+
],
|
|
3181
|
+
"live": {
|
|
3182
|
+
"reads": [
|
|
3183
|
+
{
|
|
3184
|
+
"ev": "e12",
|
|
3185
|
+
"path": "/ip/address",
|
|
3186
|
+
"statementIds": ["s0"],
|
|
3187
|
+
"rows": [
|
|
3188
|
+
{ "address": "192.0.2.1/24", "interface": "ether1", "disabled": "false" },
|
|
3189
|
+
{ "address": "198.51.100.2/24", "interface": "ether2", "disabled": "false" },
|
|
3190
|
+
{ "address": "203.0.113.1/24", "interface": "bridge", "disabled": "true" }
|
|
3191
|
+
]
|
|
3192
|
+
}
|
|
3193
|
+
]
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
```
|
|
3197
|
+
|
|
3198
|
+
The target is the envelope's existing `meta.target`, not a per-read field.
|
|
3199
|
+
Three additions are proposed, not shipped: the `live-read` evidence source,
|
|
3200
|
+
the `projection` and `truncation` evidence fields, and `data.live.reads[]`.
|
|
3201
|
+
Originating statement and path, time, projection, and truncation are all
|
|
3202
|
+
present, so observed state cannot be mistaken for the proposed script. A
|
|
3203
|
+
failed read reports its `outcome` with `reason`/`errorCode` and fails alone.
|
|
3204
|
+
|
|
3205
|
+
**Acceptance controls and dependencies before implementation slices.**
|
|
3206
|
+
|
|
3207
|
+
- Each check ships with accepted/rejected CHR controls and committed evidence;
|
|
3208
|
+
synthetic shapes above are replaced by measured rows, never promoted as-is.
|
|
3209
|
+
- Controls assert statement-id anchoring (finding moves with its statement),
|
|
3210
|
+
verdict independence (a finding leaves `data.verdict` unchanged), the cited
|
|
3211
|
+
evidence's `basis`, and read independence (one failed read does not fail
|
|
3212
|
+
the analysis or sibling reads).
|
|
3213
|
+
- A check that needs a sensitive field is refused without the sensitivity
|
|
3214
|
+
opt-in; the redaction contract is asserted, not assumed.
|
|
3215
|
+
- Dependencies: published offline readings stay honest (#211, #350, #351,
|
|
3216
|
+
#358, #371); live path/argument/probe facts come from #236; live value
|
|
3217
|
+
types are distinct evidence (#225); the MCP result migration is separate
|
|
3218
|
+
(#223); text stdout/stderr routing is unchanged (#477); device-mode
|
|
3219
|
+
provisioning stays in #95. No new plugin DSL or framework is preselected —
|
|
3220
|
+
existing `warnings`/`tips` and explain diagnostics are reused where they fit.
|
|
3221
|
+
|
|
2622
3222
|
## Non-goals
|
|
2623
3223
|
|
|
2624
3224
|
- No LSP server in centrs — the LSP is a consumer the library must support,
|
|
@@ -2884,14 +3484,22 @@ a missing mandatory value, and a name collision. That was verified rather than
|
|
|
2884
3484
|
assumed — each blocker statement was replayed through `:put [:parse …]` and
|
|
2885
3485
|
`/console/inspect request=highlight` on both versions and **accepted every
|
|
2886
3486
|
time**, with controls (`no-such-attr=`, an unterminated string, a nonexistent
|
|
2887
|
-
menu) correctly rejected
|
|
3487
|
+
menu) correctly rejected by `:parse`. `highlight` is the weaker of the two: it
|
|
3488
|
+
marks only the nonexistent menu with an `error` class, reading the bad
|
|
3489
|
+
attribute as `obj-inactive` and the unterminated string as ordinary tokens, so
|
|
3490
|
+
the controls' rejection rests on `:parse`. So these results carry **no** implication about
|
|
2888
3491
|
whether `explain` can parse the forms, and they do not license claims about the
|
|
2889
3492
|
export styles in general: the captures come from one synthetic configuration on
|
|
2890
3493
|
CHR, which has a single NIC and no switch or wireless hardware, so its defconf
|
|
2891
3494
|
and its available menus differ from any real device's. Treat the provenance
|
|
2892
3495
|
label as **L1-emitted, L2-refuted for these captures on CHR**. Harness and
|
|
2893
|
-
per-document results:
|
|
2894
|
-
|
|
3496
|
+
per-document results: the q203 import-verify lab (history — see #203; its
|
|
3497
|
+
scripts and corpora were never promoted, so the counts above rest on those
|
|
3498
|
+
lab captures). Validity check: `bun run explain:probe:import-blocker-validity`
|
|
3499
|
+
(promoted from the q203 lab for #272; replays the four blockers plus the
|
|
3500
|
+
three rejecting controls through `:parse` and `highlight` on a disposable
|
|
3501
|
+
CHR per version; re-run on 7.23.2 and 7.24rc2 for #568, 6/6 and 7/7 rows
|
|
3502
|
+
agree).
|
|
2895
3503
|
|
|
2896
3504
|
**Non-blocking / deferred (not ratification-gating):** Q5 expression depth, Q7
|
|
2897
3505
|
tokenizer corners, Q9 potential-command taxonomy, Q12 span-vocabulary draft —
|
|
@@ -2988,9 +3596,10 @@ test framework is part of this baseline.
|
|
|
2988
3596
|
|
|
2989
3597
|
### Current and following capabilities
|
|
2990
3598
|
|
|
2991
|
-
- **
|
|
2992
|
-
completion/child/syntax
|
|
2993
|
-
|
|
3599
|
+
- **Done — live probes (#236, phase 2):** highlight + `:parse` (#491),
|
|
3600
|
+
completion/child/syntax (#492) and live value types (#225) over
|
|
3601
|
+
rest-api/native-api, `CHR-passed` on stable and long-term.
|
|
3602
|
+
`runtimeAcceptance` stays `not-proven`.
|
|
2994
3603
|
- **MCP alignment (#223):** the deliberate flat-to-rich response migration,
|
|
2995
3604
|
coordinated with rosetta. Independent of the offline token vocabulary.
|
|
2996
3605
|
- **Facet polish (phase 3):** live sizing, `--full`, completion/schema ergonomics.
|
|
@@ -3061,8 +3670,10 @@ round):
|
|
|
3061
3670
|
|
|
3062
3671
|
Still open (implementation opens — none block ratification):
|
|
3063
3672
|
|
|
3064
|
-
1. Final flag names
|
|
3065
|
-
|
|
3673
|
+
1. ~~Final flag names and smart-sizing thresholds.~~ Decided by #492:
|
|
3674
|
+
`--schema`, `--complete`, `--cursor <byte>` and `--full`; 64 names and 16
|
|
3675
|
+
expansion probes by default, and 1024 / 128 with `--full`
|
|
3676
|
+
([Live describe and completion](#live-describe-and-completion-492)).
|
|
3066
3677
|
2. The mapping table from RouterOS highlight classes to **colors**, and how
|
|
3067
3678
|
unknown/new upstream classes degrade. Phase 0 pinned the raw inputs (Q13's
|
|
3068
3679
|
per-occurrence highlight-class corpus; the observed 19 classes on 7.23.2 plus
|