@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
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# explain — examples
|
|
2
2
|
|
|
3
|
-
Each numbered example is an executable spec.
|
|
4
|
-
|
|
5
|
-
`test/integration/explain.test.ts
|
|
6
|
-
`test/integration/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
The live examples are still the **target**:
|
|
11
|
-
those cells are `designed`, and flag and field names track the ratified surface
|
|
12
|
-
in `README.md`.
|
|
3
|
+
Each numbered example is an executable spec. Offline examples run under the
|
|
4
|
+
named unit/fixture suites. Live foundation examples 11, 12, 16, 24 and 48 run
|
|
5
|
+
under `test/integration/explain-live.test.ts`, and describe/completion examples
|
|
6
|
+
7–10, 13–15 and 19 under `test/integration/explain-live-facets.test.ts`, on REST
|
|
7
|
+
and native API with a CHR booted by `@tikoci/quickchr`. Live types 52–53 run
|
|
8
|
+
under `test/integration/explain-live-types.test.ts`. All of them pass on stable
|
|
9
|
+
7.24.5 and long-term 7.23.8, so both protocol cells are `CHR-passed` (#236).
|
|
13
10
|
|
|
14
11
|
A letter-suffixed example (`1b`, `4b`, `18b`) is the **counterpart** of the number it
|
|
15
12
|
follows: the same question with the other answer, added where implementing the
|
|
@@ -151,10 +148,10 @@ renders no curl.
|
|
|
151
148
|
centrs explain $R /ip/address -u $U -p $P --schema --json
|
|
152
149
|
```
|
|
153
150
|
|
|
154
|
-
`data.schema.verbs` lists the verbs
|
|
155
|
-
`remove`, `print`, …)
|
|
156
|
-
|
|
157
|
-
CHR's RouterOS version.
|
|
151
|
+
`data.schema.node` is `menu`, and `data.schema.verbs` lists the verbs at
|
|
152
|
+
`/ip/address` (`add`, `set`, `remove`, `print`, …). Each carries its argument
|
|
153
|
+
count from one more `child` probe rather than the full argument expansion.
|
|
154
|
+
Facts cite `live-inspect` evidence stamped with the CHR's RouterOS version.
|
|
158
155
|
|
|
159
156
|
### 8. Describe a verb: its arguments
|
|
160
157
|
|
|
@@ -162,8 +159,9 @@ CHR's RouterOS version.
|
|
|
162
159
|
centrs explain $R /ip/address/set -u $U -p $P --schema --json
|
|
163
160
|
```
|
|
164
161
|
|
|
165
|
-
`data.schema` enumerates the settable arguments of `set` (`address`,
|
|
166
|
-
`interface`, `disabled`, …)
|
|
162
|
+
`data.schema.arguments` enumerates the settable arguments of `set` (`address`,
|
|
163
|
+
`interface`, `disabled`, …). Each has its argument-level `syntax` rows where the
|
|
164
|
+
device reports them (`address`: `A.B.C.D (IP address)`).
|
|
167
165
|
|
|
168
166
|
### 9. Describe print: the proplist special case
|
|
169
167
|
|
|
@@ -171,10 +169,12 @@ centrs explain $R /ip/address/set -u $U -p $P --schema --json
|
|
|
171
169
|
centrs explain $R /ip/address/print -u $U -p $P --schema --json
|
|
172
170
|
```
|
|
173
171
|
|
|
174
|
-
`data.schema` returns the `.proplist` value set — what the output
|
|
175
|
-
contain —
|
|
172
|
+
`data.schema.proplist` returns the `.proplist` value set — what the output
|
|
173
|
+
*can* contain — besides `print`'s own arguments. The grounded probe is
|
|
176
174
|
`completion input="/ip/address/print proplist="` (dot-free console spelling),
|
|
177
|
-
filtered to rows with `show=true`;
|
|
175
|
+
filtered to rows with `show=true`; it cites that live evidence. A singleton
|
|
176
|
+
settings menu (`/ip/dns/print`) offers no proplist names, so
|
|
177
|
+
`get value-name=` answers with `source: "get-value-name"`.
|
|
178
178
|
|
|
179
179
|
### 10. Smart sizing truncates with counts, `--full` lifts it
|
|
180
180
|
|
|
@@ -182,9 +182,11 @@ filtered to rows with `show=true`; each returned fact cites that live evidence.
|
|
|
182
182
|
centrs explain $R /ip -u $U -p $P --schema --json
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
The `/ip` subtree is large
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
The `/ip` subtree is large (31 children, past the 16-probe default):
|
|
186
|
+
`data.schema.truncated` is `true`, the names are listed with `counts` but not
|
|
187
|
+
expanded, and `tip/explain-schema-truncated` names the narrower inputs in
|
|
188
|
+
`data.schema.next` or `--full`. With `--full`, every submenu carries its verb
|
|
189
|
+
and menu counts.
|
|
188
190
|
|
|
189
191
|
### 11. Spans and the error byte (centrs vocabulary)
|
|
190
192
|
|
|
@@ -192,9 +194,21 @@ narrower query (or `--full`) that expands it.
|
|
|
192
194
|
centrs explain $R '/ip/address add address=bogus interface=' -u $U -p $P --json
|
|
193
195
|
```
|
|
194
196
|
|
|
195
|
-
`data.spans`
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
`data.spans` includes device-derived spans in the **centrs** vocabulary,
|
|
198
|
+
citing `live-inspect` evidence. On CHR 7.24.5, this original example produces
|
|
199
|
+
no parse rejection or highlight error: `address=bogus` is not value-validated,
|
|
200
|
+
and the trailing empty argument is omitted from the parse output. Keep it as
|
|
201
|
+
a probe-limit control, not a promised syntax error.
|
|
202
|
+
|
|
203
|
+
A confirmed hard-error control is:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
centrs explain $R '/ip/route add dst-address=10.9.0.0/16 blackhole=yes' -u $U -p $P --json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
On CHR 7.24.5, highlight reports byte 47 and parse reports line 1 column 48.
|
|
210
|
+
The test asserts that source-located diagnostic separately from runtime
|
|
211
|
+
acceptance; the original input remains a control on both transports.
|
|
198
212
|
|
|
199
213
|
### 12. Structure via :parse
|
|
200
214
|
|
|
@@ -213,8 +227,14 @@ here over rest-api; example 24 asserts the same readout over native API using
|
|
|
213
227
|
centrs explain $R '/ip/address add comm' -u $U -p $P --complete --json
|
|
214
228
|
```
|
|
215
229
|
|
|
216
|
-
`data.completion`
|
|
217
|
-
partial
|
|
230
|
+
`data.completion` is `context: "argument-name"` with `comment` as a
|
|
231
|
+
`partial` candidate replacing bytes 16–20. No canonicalizer diagnostic calls
|
|
232
|
+
`comm` unknown. The device still rejects the input as written: `:parse` and
|
|
233
|
+
the highlight `error` both name byte 16. So pass `--fail-on never` when only
|
|
234
|
+
the candidates matter. `--cursor 20` gives the same answer inside a longer
|
|
235
|
+
input. The same candidate comes back for a relative path after `;`
|
|
236
|
+
(`/ip { :put 1; address add comm`), and `where comm` completes `comment` as a
|
|
237
|
+
`variable`.
|
|
218
238
|
|
|
219
239
|
### 14. Completion: complete argument name, no value yet
|
|
220
240
|
|
|
@@ -222,9 +242,10 @@ partial word `comm` — a valid partial, no unknown-word diagnostic.
|
|
|
222
242
|
centrs explain $R '/ip/address add comment' -u $U -p $P --complete --json
|
|
223
243
|
```
|
|
224
244
|
|
|
225
|
-
The candidate set marks `comment` as
|
|
226
|
-
|
|
227
|
-
an unknown word.
|
|
245
|
+
The candidate set marks `comment` as `match: "exact"` with `takesValue: true`,
|
|
246
|
+
because the device offers `=` at the cursor. That is distinct from example 13's
|
|
247
|
+
partial match and from an unknown word. The flag `/interface print detail` is
|
|
248
|
+
exact with `takesValue: false`.
|
|
228
249
|
|
|
229
250
|
### 15. Completion: value position
|
|
230
251
|
|
|
@@ -233,9 +254,11 @@ centrs explain $R '/ip/firewall/filter add chain=' -u $U -p $P --complete --json
|
|
|
233
254
|
```
|
|
234
255
|
|
|
235
256
|
With the input ending in `=`, candidates are **values** for `chain`
|
|
236
|
-
(`forward`, `input`, `output` —
|
|
237
|
-
a free-form argument like `comment=`, the same probe yields
|
|
238
|
-
|
|
257
|
+
(`forward`, `input`, `output` — observed from the completion probe, never a closed set). For
|
|
258
|
+
a free-form argument like `comment=`, the same probe yields no candidates and
|
|
259
|
+
`data.completion.syntax` carries `string value`. An unclosed substitution
|
|
260
|
+
(`:local x [/ip/address add interface=`) still completes, from the device's
|
|
261
|
+
own path runs.
|
|
239
262
|
|
|
240
263
|
### 16. Runtime acceptance is never claimed
|
|
241
264
|
|
|
@@ -343,9 +366,9 @@ curated read verb; before the command axis this document abstained.
|
|
|
343
366
|
centrs explain $R '/ip/route' -u $U -p $P --json
|
|
344
367
|
```
|
|
345
368
|
|
|
346
|
-
|
|
347
|
-
`
|
|
348
|
-
has no runnable transport rendering. The evidence entry is version-stamped and
|
|
369
|
+
The highlight classes every byte `dir`, and with `--schema` the `child` self
|
|
370
|
+
row says `menu`; the first statement is `resolution: "resolved"`,
|
|
371
|
+
`kind: "menu"`, and has no runnable transport rendering. The evidence entry is version-stamped and
|
|
349
372
|
the probe never executes the path. Since #207 the offline reading (example 18b)
|
|
350
373
|
already says the same thing from the generated table, so what the live probe
|
|
351
374
|
adds here is **provenance and a version stamp**, not the answer — and it is the
|
|
@@ -452,7 +475,8 @@ full MAC spelling carry `["array"]`, `["time"]`, `["id"]`, and `["mac"]`.
|
|
|
452
475
|
is `str` while the CLI Reference declares `mac-address` as `macAddr`. Each hint
|
|
453
476
|
fact cites canonicalizer evidence with
|
|
454
477
|
`basis: "heuristic"`; no record has `observedType` or `schemaType`, because
|
|
455
|
-
offline analysis has neither live parser output nor an argument schema
|
|
478
|
+
offline analysis has neither live parser output nor an argument schema
|
|
479
|
+
(examples 52 and 53 are the live counterparts). A hint
|
|
456
480
|
is not a diagnostic and does not change `data.verdict` or
|
|
457
481
|
`runtimeAcceptance: "not-proven"`.
|
|
458
482
|
|
|
@@ -768,10 +792,12 @@ RouterOS accepts any unique prefix of an argument name. An abbreviation that
|
|
|
768
792
|
can only stand for a script-valued name keeps its body in either spelling:
|
|
769
793
|
`sour { :beep }`, `down-scr { :beep }` and `o={ :beep }` lower to `source=`,
|
|
770
794
|
`down-script=` and `on-event=` on CHR 7.24.5 and 7.23.8. Abbreviating the first
|
|
771
|
-
segment (`down={ :beep }` for `down-script`) is accepted on the device too,
|
|
772
|
-
offline
|
|
773
|
-
`<word>-script`, so telling it from
|
|
774
|
-
argument list.
|
|
795
|
+
segment (`down={ :beep }` for `down-script`) is accepted on the device too, and
|
|
796
|
+
offline warns `possible-script-body-abbreviation` there instead of rejecting
|
|
797
|
+
(#525): every word is a prefix of some `<word>-script`, so telling it from
|
|
798
|
+
`comment={ :beep }` needs the command's argument list, and the device decides.
|
|
799
|
+
The tradeoff is deliberate — `comment={ :beep }`, which the device rejects,
|
|
800
|
+
carries the same warning — and `data.verdict` stays `warn`, never `pass`.
|
|
775
801
|
|
|
776
802
|
A brace straight after a command is a device error, and offline now reports it.
|
|
777
803
|
`/system/script add { :beep }`, `/ip/route/print {1;2}` and
|
|
@@ -959,3 +985,127 @@ The CHR controls in `test/integration/explain-structure.test.ts` parse the
|
|
|
959
985
|
scope examples and compare stored-script creation, explicit `run`, and an
|
|
960
986
|
immediate `:do` body. Those are test operations on disposable CHR; explain
|
|
961
987
|
itself never executes the analyzed input.
|
|
988
|
+
|
|
989
|
+
### 48. Safe argument discovery distinguishes spaced arguments from flags
|
|
990
|
+
|
|
991
|
+
```bash
|
|
992
|
+
centrs explain $R '/interface set ether1 name x' -u $U -p $P --json
|
|
993
|
+
centrs explain $R '/interface print detail without-paging' -u $U -p $P --json
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
`data.live.arguments[]` cites command-level child evidence and statement `s0`.
|
|
997
|
+
The first result includes `name`, the second includes `detail`. Both parse;
|
|
998
|
+
only `name x` gets the device's obsolete-syntax warning. An unknown argument
|
|
999
|
+
control (`/interface set ether1 definitely-unknown-491=x`) carries the shared
|
|
1000
|
+
parse classifier's rejection. No command-level completion is requested.
|
|
1001
|
+
|
|
1002
|
+
The integration controls also explain a write-shaped identity change and a
|
|
1003
|
+
substitution without executing either, verify identity before/after, exercise
|
|
1004
|
+
CLI stdin and quickchr resolution, and skip oversized highlight without
|
|
1005
|
+
submitting a prefix. Parse/hash/normalization evidence preserves the Unicode
|
|
1006
|
+
byte coordinates. Run on long-term as well as stable before changing these
|
|
1007
|
+
claims. Sanitized captures can be regenerated with `CENTRS_EXPLAIN_CAPTURE`
|
|
1008
|
+
pointing at an ignored output file when running the integration suite.
|
|
1009
|
+
|
|
1010
|
+
### 49. `?`, a backtick and a second `=` need quotes
|
|
1011
|
+
|
|
1012
|
+
```bash
|
|
1013
|
+
centrs explain '/tool/fetch url=http://x/a?b=c' --json
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
`data.verdict` is `fail` with `explain/canonicalizer/unquoted-reserved-byte`
|
|
1017
|
+
at the `?`. The device answers `expected end of command (line 1 column 27)`,
|
|
1018
|
+
the same byte. A backtick is rejected the same way, in a value, a
|
|
1019
|
+
positional, a `where`, a `find`, an array and an expression. A second `=` in
|
|
1020
|
+
a menu command's argument is rejected at that `=`. So `name=a=b` and a bare
|
|
1021
|
+
base64 key ending in `=` both fail. The value's property parser never takes
|
|
1022
|
+
these bytes: `regexp=a?b` and `secret=abc==` are rejected too. In a menu
|
|
1023
|
+
command's argument name, the device reports from the name's first byte
|
|
1024
|
+
(`/ip/address/add a?b=1` at the `a`). `url="http://x/a?b=c"`,
|
|
1025
|
+
`where comment=a=b`, `[find comment=a=b]`, `{a=b}` and `# a?b` pass.
|
|
1026
|
+
`:put a=b` abstains: 7.24.5 reads it as a named argument and 7.23.8 rejects
|
|
1027
|
+
the `=`.
|
|
1028
|
+
|
|
1029
|
+
### 50. A non-ASCII character outside a string is rejected
|
|
1030
|
+
|
|
1031
|
+
```bash
|
|
1032
|
+
centrs explain ':put $café' --json
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
`data.verdict` is `fail` with `explain/canonicalizer/unquoted-non-ascii` over
|
|
1036
|
+
the `é`. The device answers `expected end of command (line 1 column 10)`, at
|
|
1037
|
+
the `é`'s first byte. RouterOS reads only ASCII outside a string. A bare
|
|
1038
|
+
value (`name=router-café`, `name=🛰`), a reference, a path (`/systemé`), a
|
|
1039
|
+
verb, an argument name, a `where` value, an array and an expression are all
|
|
1040
|
+
rejected. So are pasted non-breaking spaces, curly quotes and ellipses. A
|
|
1041
|
+
declared name (`:local café`) keeps its `invalid-name` error. Strings and
|
|
1042
|
+
comments carry any byte: `:put "café"`, `:local "café" 1`, `$"café"`,
|
|
1043
|
+
`name="router-🚀"`, `# café` and a multi-line string pass. The informational
|
|
1044
|
+
`non-ascii` fact is still published beside the error, and a leading BOM is
|
|
1045
|
+
not read as part of the first word.
|
|
1046
|
+
|
|
1047
|
+
### 51. A brace after a submenu opens a block at any depth
|
|
1048
|
+
|
|
1049
|
+
```bash
|
|
1050
|
+
centrs explain '/ip { address { print {1;2} } }' --json
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
`data.verdict` is `fail` with `explain/canonicalizer/invalid-command-brace` at
|
|
1054
|
+
the inner `{`. The device answers `expected end of command (line 1 column 23)`,
|
|
1055
|
+
the same byte. `address {` inside `/ip { … }` is a block over `/ip/address`,
|
|
1056
|
+
so `data.structure.statements` reads `/ip`, `/ip/address` and
|
|
1057
|
+
`/ip/address print`, and the `{1;2}` is the operand of `print`.
|
|
1058
|
+
|
|
1059
|
+
The device reads a brace after a menu word as statements whatever the body
|
|
1060
|
+
holds, at any depth. A body offline cannot resolve abstains with
|
|
1061
|
+
`unresolved-statement` instead of failing as an array:
|
|
1062
|
+
`/ip { address { 1;2 } }` (the device says `bad command name 1`) and
|
|
1063
|
+
`/ip { address { zzznotamenu { print } } }` (`syntax error`) both give `warn`,
|
|
1064
|
+
as `/ip/address { 1;2 }` and `/ip { zzznotamenu { print } }` do.
|
|
1065
|
+
`/ip { address { print } }` and `/ip { firewall { filter { print } } }` pass.
|
|
1066
|
+
The menu must be known from the source alone, so a block after a navigation
|
|
1067
|
+
statement (`/ip⏎address { … }`), an abbreviation (`addr {`) and a digit-led
|
|
1068
|
+
menu (`6to4 {`) keep the older reading.
|
|
1069
|
+
|
|
1070
|
+
### 52. A live call observes a type only where the device converts a literal
|
|
1071
|
+
|
|
1072
|
+
```bash
|
|
1073
|
+
centrs explain $R ':local x 2.2; :set x "2.2"; :local z (1,0x10,2.2); :local t 200ms; :local i *1; :local y yes' -u $U -p $P --json
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
This is example 26's input against a target. The bare `2.2` gains
|
|
1077
|
+
`facts.observedType: { value: "ip", rendered: "2.0.0.2" }`, `200ms` gains
|
|
1078
|
+
`time` (`00:00:00.200`) and `yes` gains `bool` (`true`). Of the array's
|
|
1079
|
+
members, `0x10` is `num` (`16`) and `2.2` is `ip`, while `1` renders unchanged
|
|
1080
|
+
and gets nothing. The quoted `"2.2"` and the id `*1` also get nothing, because
|
|
1081
|
+
an unchanged rendering proves no type: `:parse` prints a string unquoted. The
|
|
1082
|
+
`shapeHints` are untouched, and no record has `schemaType`, because no value
|
|
1083
|
+
here is a menu command's argument.
|
|
1084
|
+
|
|
1085
|
+
A scripting directive's named argument is an operand too:
|
|
1086
|
+
`:for i from=0x10 to=20 do={:put $i}` gives `from` the fact `num` (`16`), and
|
|
1087
|
+
`to=20` renders unchanged and gets nothing. A NAME slot is a variable, never a
|
|
1088
|
+
value: in `:local yes 2` and `:local name=no value=yes`, the names `yes` and
|
|
1089
|
+
`no` get nothing (the device renders `name=$yes`, `name=$no`), while
|
|
1090
|
+
`value=yes` is `bool`.
|
|
1091
|
+
|
|
1092
|
+
Each fact cites one `:parse` evidence entry per context (operands, members)
|
|
1093
|
+
with `facet: "types"` and `basis: "derived"`. The probe re-parses each literal
|
|
1094
|
+
alone, as `:local vN <lit>` or `:local vN {<lit>}`; the analyzed input and
|
|
1095
|
+
the literals are never evaluated, and no `:typeof` is sent.
|
|
1096
|
+
|
|
1097
|
+
### 53. A live call normalizes each argument's declared type
|
|
1098
|
+
|
|
1099
|
+
```bash
|
|
1100
|
+
centrs explain $R '/ip/dns/set servers=1.1.1.1,8.8.8.8; /ip/route/add gateway=2.2 dist=2 comment=abc; /ip/firewall/filter/add chain=input dst-port=80,443 protocol=tcp disabled=yes' -u $U -p $P --json
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
Each `name=value` gains `facts.schemaType` from one argument-level `syntax`
|
|
1104
|
+
probe. `servers` is `{ values: ["ip", "ip6", "enum"], declared:
|
|
1105
|
+
"Address[@VRF]" }`, and `gateway` is `ip`, `ip6`, `enum`. `dist` resolves as
|
|
1106
|
+
the unique prefix of `distance` and is `num`. `comment` is `str`. `chain` is
|
|
1107
|
+
`enum` plus `str`, since custom chains are legal. `dst-port` is `num` with
|
|
1108
|
+
`list: true`. `disabled` gets nothing, because the device declares a boolean
|
|
1109
|
+
with a blank row. No menu argument gets `observedType`: its own parser
|
|
1110
|
+
converts it, which is the schema's slot. The text output prints
|
|
1111
|
+
`observed=ip(2.0.0.2)` and `schema=ip|ip6|enum` on the value rows.
|
|
@@ -87,8 +87,11 @@ alternatives in the error envelope.
|
|
|
87
87
|
`failed` when the gate refuses: `validation/*`, or `--query` on a singleton.
|
|
88
88
|
It is absent when a `transport/*`, `auth/*`, `routeros/*` or `internal/*` error
|
|
89
89
|
stopped inspect from answering; the device stage then says `not reached`
|
|
90
|
-
(examples 12 and 16b; 16c is the `passed` case).
|
|
91
|
-
|
|
90
|
+
(examples 12 and 16b; 16c is the `passed` case). The same holds for a
|
|
91
|
+
`wait/*` error from a `--wait` whose gate never settled, and for
|
|
92
|
+
`--sample` / `--follow` cancelled before the gate ran. That last case has no
|
|
93
|
+
error code, so its reason is `not reached: interrupted` (#537). With
|
|
94
|
+
`--validate=false` it is `{enabled: false, source: "disabled"}`.
|
|
92
95
|
|
|
93
96
|
If `--all-attributes` is combined with `--attribute(s)`, fail with
|
|
94
97
|
`usage/conflicting-flags` *before* hitting the network.
|
|
@@ -147,6 +150,14 @@ handling.
|
|
|
147
150
|
}
|
|
148
151
|
```
|
|
149
152
|
|
|
153
|
+
Projection rule: the bare value is about the attribute *count*, not the flag
|
|
154
|
+
spelling. On a singleton, exactly one projected attribute — `--attribute x`
|
|
155
|
+
or `--attributes x` alike — returns the bare value (`data` is the scalar,
|
|
156
|
+
not an object); two or more return an object with those keys. On a list, any
|
|
157
|
+
projection returns an array of objects, even a single attribute. Unprojected
|
|
158
|
+
reads return the full object (singleton) or the row array (list). (Examples
|
|
159
|
+
5–6, 20, N5–N6 pin every cell of this table.)
|
|
160
|
+
|
|
150
161
|
`--max-bytes` / `--max-rows` truncation populates
|
|
151
162
|
`meta.truncated = { returned, total, totalBytes }` and keeps `ok: true` with a
|
|
152
163
|
warning entry — it is not an error. (Earlier text said a byte overflow returns an
|
|
@@ -22,7 +22,8 @@ centrs retrieve $R /system/resource --username $U --password $P --json
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Envelope: `ok: true`, `data` is an object, `meta.via=rest-api`,
|
|
25
|
-
`meta.validation.source=/console/inspect
|
|
25
|
+
`meta.validation.source=/console/inspect`, and `meta.validation.stages` is the
|
|
26
|
+
one `device: passed` stage (#535).
|
|
26
27
|
|
|
27
28
|
### 2. /system/identity
|
|
28
29
|
|
|
@@ -70,6 +71,10 @@ centrs retrieve $R /interface --attributes name,type --username $U --password $P
|
|
|
70
71
|
|
|
71
72
|
Envelope: `data` is an array of objects each containing only `name` and `type`.
|
|
72
73
|
|
|
74
|
+
A single attribute (`--attributes name`) keeps the array shape: an array of
|
|
75
|
+
single-key objects. On a singleton, one item via either flag returns the bare
|
|
76
|
+
value (example 5) — the rule is the count, not the flag.
|
|
77
|
+
|
|
73
78
|
### 7. --all-attributes (RouterOS details=true)
|
|
74
79
|
|
|
75
80
|
```bash
|
|
@@ -121,6 +126,18 @@ available.
|
|
|
121
126
|
centrs retrieve $R /not/a/real/path --username $U --password $P --json
|
|
122
127
|
```
|
|
123
128
|
|
|
129
|
+
`/system/routerboard` earns a targeted remediation instead of the generic
|
|
130
|
+
one: the menu exists only on MikroTik hardware, so its absence on CHR and
|
|
131
|
+
x86 systems is expected rather than a typo, and the remediation points at
|
|
132
|
+
`/system/license` for the device's license ID (`system-id` on CHR,
|
|
133
|
+
`software-id` on x86 — a projected attribute would fail on one platform or
|
|
134
|
+
the other, so the example reads the whole object).
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
centrs retrieve $R /system/routerboard --username $U --password $P --json
|
|
138
|
+
centrs retrieve $R /system/license --username $U --password $P --json
|
|
139
|
+
```
|
|
140
|
+
|
|
124
141
|
### 11. Unknown attribute
|
|
125
142
|
|
|
126
143
|
`validation/unknown-attribute` with suggestions.
|
|
@@ -362,6 +379,10 @@ centrs retrieve $A /interface --attributes name,type --via native-api --port $AP
|
|
|
362
379
|
|
|
363
380
|
`data` is an array of objects each containing only `name` and `type`.
|
|
364
381
|
|
|
382
|
+
A single attribute (`--attributes name`) keeps the array shape here too: an
|
|
383
|
+
array of single-key objects. On a singleton, one item via either flag returns
|
|
384
|
+
the bare value (N5) — the rule is the count, not the flag.
|
|
385
|
+
|
|
365
386
|
### N7. `--all-attributes` (native `print detail`)
|
|
366
387
|
|
|
367
388
|
```bash
|
|
@@ -380,6 +401,14 @@ centrs retrieve $A /system/resource --list-attributes --via native-api --port $A
|
|
|
380
401
|
centrs retrieve $A /not/a/real/path --via native-api --port $API_PORT --username $U --password $P
|
|
381
402
|
```
|
|
382
403
|
|
|
404
|
+
`/system/routerboard` on CHR earns the targeted remediation (no routerboard
|
|
405
|
+
outside MikroTik hardware; read `/system/license` for the license ID
|
|
406
|
+
instead), same as example 10.
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
centrs retrieve $A /system/routerboard --via native-api --port $API_PORT --username $U --password $P
|
|
410
|
+
```
|
|
411
|
+
|
|
383
412
|
### N10. Unknown attribute → `validation/unknown-attribute`
|
|
384
413
|
|
|
385
414
|
```bash
|
|
@@ -96,7 +96,7 @@ size-aware. Grounding: [Files](https://help.mikrotik.com/docs/spaces/ROS/pages/2
|
|
|
96
96
|
| `rest` / `native` | rest-api / native-api | small via `/file/get … contents` (**≤ 60 KB**); **large via chunked `/file/read offset chunk-size≤32768`** | **`/file/set contents=` ≤ 60 KB only** — no chunked write |
|
|
97
97
|
| `sftp` (default secure) | ssh | any size | any size |
|
|
98
98
|
| `scp` | ssh | any size | any size |
|
|
99
|
-
| `fetch` *(deferred)* | rest-api/native-api + inbound HTTP | router
|
|
99
|
+
| `fetch` *(deferred, upload-only)* | rest-api/native-api + inbound HTTP | chunked `/file/read` (router PUT fails: `only [s]ftp modes support upload`, #548) | router GETs from centrs (`url=http://centrs/… dst-path=…`) |
|
|
100
100
|
| `ftp` *(gated)* | ftp | any size | any size |
|
|
101
101
|
|
|
102
102
|
Auto-selection (no `--via`):
|
|
@@ -302,6 +302,10 @@ gate as in `retrieve` / `execute`; RouterOS's own menu errors (missing remote
|
|
|
302
302
|
file, permission denied) map to `routeros/*`, and SFTP/SCP/fetch errors map to
|
|
303
303
|
`transport/*` and `auth/*`.
|
|
304
304
|
|
|
305
|
+
`meta.validation` reports that existence probe as one `device` stage. `list`
|
|
306
|
+
runs no probe, so it is not a gate: its `meta.validation` is
|
|
307
|
+
`{ enabled: false }`, on success and on failure (#535).
|
|
308
|
+
|
|
305
309
|
A failure **after** target resolution keeps what resolution found: the error
|
|
306
310
|
envelope carries the resolved `meta.target` (host, port, identity, sources),
|
|
307
311
|
`meta.via`, the setting sources in `meta.settings`, and `meta.operation`
|
|
@@ -371,9 +375,9 @@ done rule. The first coded pass targets `rest`/`native` (no SSH dependency);
|
|
|
371
375
|
(random URL token, bound to the interface facing the router) and drives the
|
|
372
376
|
router with `/tool/fetch` over `rest`/`native`. **upload**: serve the local
|
|
373
377
|
file, `fetch url=http://<advertise>/<token> dst-path=<remote>` → router GETs.
|
|
374
|
-
**download
|
|
375
|
-
|
|
376
|
-
|
|
378
|
+
**download** stays on chunked `/file/read`: router PUTs fail with `only
|
|
379
|
+
[s]ftp modes support upload` (#548), and HTTP `http-data` caps at 60000
|
|
380
|
+
bytes, so the PUT half cannot carry files. Open questions: advertise-host
|
|
377
381
|
auto-detection behind NAT, HTTP-vs-HTTPS + `check-certificate`, and whether to
|
|
378
382
|
add fetch's `user`/`password` on top of the URL token.
|
|
379
383
|
- **ssh** — `sftp` is **built** (`src/protocols/sftp.ts`, host OpenSSH `sftp`
|
package/docs/CLI.md
CHANGED
|
@@ -15,7 +15,7 @@ implemented flags live in each command's `commands/<name>/README.md`.
|
|
|
15
15
|
| [`api`](#api) | Structured RouterOS API passthrough (gh-api style) over REST or native API. |
|
|
16
16
|
| [`transfer`](#transfer) | Copy files to/from a RouterOS device and manage device files (rest/native/sftp). |
|
|
17
17
|
| [`terminal`](#terminal) | Open an interactive RouterOS console (ssh or mac-telnet). |
|
|
18
|
-
| [`explain`](#explain) | Analyze
|
|
18
|
+
| [`explain`](#explain) | Analyze RouterOS input offline or add read-only device evidence from one router. |
|
|
19
19
|
| [`devices`](#devices) | Inspect and mutate the CDB-backed device registry. `devices` is the only command that writes the CDB. |
|
|
20
20
|
| [`discover`](#discover) | Discover RouterOS neighbors over MNDP and optionally save them into the CDB. |
|
|
21
21
|
| [`btest`](#btest) | Run MikroTik's bandwidth test as a client or server (peer measurement). |
|
|
@@ -220,18 +220,32 @@ Usage: centrs terminal <router> [flags] | centrs terminal --host <host> [flags]
|
|
|
220
220
|
|
|
221
221
|
## explain
|
|
222
222
|
|
|
223
|
-
Analyze
|
|
223
|
+
Analyze RouterOS input offline or add read-only device evidence from one router.
|
|
224
224
|
|
|
225
225
|
```text
|
|
226
|
-
Usage: centrs explain '<input>' [flags]
|
|
226
|
+
Usage: centrs explain [<router>] '<input>' [flags]
|
|
227
227
|
```
|
|
228
228
|
|
|
229
229
|
| Flag | Value | Description |
|
|
230
230
|
| ---- | ----- | ----------- |
|
|
231
|
+
| `--via` | <rest-api\|native-api> | Pin the transport; no silent downgrade. Default rest-api. |
|
|
232
|
+
| `--host` | <host\|url> | Host or base URL to dial. With no `<router>` it is the target (no CDB lookup); with one it overrides the resolved host. |
|
|
233
|
+
| `--port` | `<port>` | Override the resolved management port. |
|
|
234
|
+
| `--username / --user / -u` | `<name>` | RouterOS username. Falls back to `CENTRS_USERNAME`. |
|
|
235
|
+
| `--password` | `<secret>` | RouterOS password. Falls back to `CENTRS_PASSWORD`. |
|
|
236
|
+
| `--insecure` | | Accept a self-signed `api-ssl`/REST TLS cert. Default verifies. |
|
|
237
|
+
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
238
|
+
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
239
|
+
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
240
|
+
| `--timeout` | `<duration>` | Per-request timeout (for REST, max 60s). |
|
|
241
|
+
| `-p` | `<secret>` | Alias for --password. |
|
|
242
|
+
| `--quickchr` | `<name>` | Resolve one running quickchr VM by name. |
|
|
231
243
|
| `--file` | `<path>` | Read the input from a file instead of the positional (`-` reads stdin). Piped stdin is used automatically when no input positional is given. |
|
|
232
244
|
| `--fail-on` | <error\|warning\|never> | Exit 2 when the verdict reaches this severity. Default `error`; an ambiguous or unknown statement is a warning, never an error. |
|
|
233
|
-
| `--complete` | |
|
|
234
|
-
| `--schema` | |
|
|
245
|
+
| `--complete` | | Candidates at the cursor: menus and verbs, argument names, or values. Live evidence — offline emits a tip and enumerates nothing. |
|
|
246
|
+
| `--schema` | | Describe the path at the cursor: a menu's verbs and submenus, a command's arguments, `print`'s proplist. Live evidence — offline emits a tip and enumerates nothing. |
|
|
247
|
+
| `--cursor` | `<byte>` | UTF-8 byte offset for --complete/--schema. Default: the end of the input. |
|
|
248
|
+
| `--full` | | Lift --complete/--schema sizing to its safety ceiling (1024 names, 128 expansion probes). |
|
|
235
249
|
| `--curl` | | Render a ready-to-edit REST curl for statements covered by a runtime-tested mapping rule. |
|
|
236
250
|
| `--tokens` | | Emit the total, gapless token partition behind `data.tokens[]` (every byte not claimed by an analyzer is `unclassified`). |
|
|
237
251
|
| `--format` | <text\|json\|yaml> | Output format for the CLI response. |
|
package/docs/CONSTITUTION.md
CHANGED
|
@@ -73,15 +73,20 @@ gate passed keeps `passed`. When something stopped the gate before it gave a
|
|
|
73
73
|
verdict (a `transport/*` or `auth/*` failure, a device error that is not a
|
|
74
74
|
gate answer, or a refusal before the gate ran), `result` and the
|
|
75
75
|
`syntax`/`semantic` summaries are absent. Each stage the gate did not reach is
|
|
76
|
-
then `skipped` with `reason: "not reached: <error.code>"` (GH#435).
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
then `skipped` with `reason: "not reached: <error.code>"` (GH#435).
|
|
77
|
+
|
|
78
|
+
The flat `syntax` / `semantic` fields summarize `stages[]`, and each is present
|
|
79
|
+
**only when a stage gave that verdict** (#535). Absent means no stage was
|
|
80
|
+
asked, never "passed" and never "failed". So `--validate=false` carries
|
|
81
|
+
neither. A script has no `semantic` field, because it has no names to check,
|
|
82
|
+
and `api`'s structured requests have no `syntax` field, because they run no
|
|
83
|
+
`:parse`. On a rejection, `syntax: true` with `semantic: false` is a
|
|
84
|
+
name-level refusal (an unknown attribute in a structured command). A `:parse`
|
|
85
|
+
or offline refusal is `syntax: false` with no `semantic`. Every gated success
|
|
86
|
+
reports its `stages[]`, retrieve and transfer included. `explain` is not a
|
|
87
|
+
gate, because it never runs the input: its `meta.validation` is
|
|
88
|
+
`{ enabled: false }` with no `result`, offline and live, and its verdict is
|
|
89
|
+
`data.verdict`.
|
|
85
90
|
|
|
86
91
|
`--validate=false` disables **both** stages — one flag, one meaning. A bypass
|
|
87
92
|
that covered only the device stage would leave a caller stranded behind a
|
package/docs/MATRIX.md
CHANGED
|
@@ -40,7 +40,7 @@ their command-specific evidence contracts.
|
|
|
40
40
|
| devices | — | — | — | — | — | — | — | — |
|
|
41
41
|
| discover | — | — | — | — | — | `CHR-passed` | — | — |
|
|
42
42
|
| check | `designed` | `designed` | `designed` | `designed` | `not-started` | `not-started` | `not-started` | `not-started` |
|
|
43
|
-
| explain | `
|
|
43
|
+
| explain | `CHR-passed` | `CHR-passed` | — | — | — | — | — | — |
|
|
44
44
|
| settings | — | — | — | — | — | — | — | — |
|
|
45
45
|
|
|
46
46
|
## Status pointers
|
|
@@ -101,10 +101,14 @@ and implementation sequencing in the linked issues.
|
|
|
101
101
|
`winbox-terminal` stay `not-started`. Its per-host fan-out is the machinery the
|
|
102
102
|
IP-scan discovery (#149) will iterate; the L2-default timing evidence (#136) is
|
|
103
103
|
recorded in the README before the cells advance past `designed`.
|
|
104
|
-
- `explain`'s two `
|
|
105
|
-
(
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
- `explain`'s two `CHR-passed` protocol cells are the whole live capability
|
|
105
|
+
(#236). `explain <router> '<input>'` collects read-only `:parse`, highlight
|
|
106
|
+
and child argument-name evidence (#491), and `--schema`/`--complete` describe
|
|
107
|
+
paths and complete at a cursor (#492). Live value types (#225) fill
|
|
108
|
+
`observedType` from visible `:parse` conversions and `schemaType` from
|
|
109
|
+
argument `syntax`. Examples 7–16, 19, 24, 48 and 52–53 pass on REST and
|
|
110
|
+
native API in `test/integration/explain-live*.test.ts`, on stable 7.24.5 and
|
|
111
|
+
long-term 7.23.8. `runtimeAcceptance` stays `not-proven`. Offline analysis
|
|
108
112
|
has its own capability entry below. The generated structure tables
|
|
109
113
|
`src/explain/menus.ts` and `src/explain/catalog.ts` keep analysis offline;
|
|
110
114
|
their ownership and regeneration contracts live in `commands/explain/README.md`.
|
|
@@ -182,16 +186,25 @@ Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
|
|
|
182
186
|
|
|
183
187
|
## Current priority
|
|
184
188
|
|
|
189
|
+
Live `explain` (#236) is `CHR-passed` on REST and native API: the #491
|
|
190
|
+
foundation, #492's describe and completion facets, and #225's live value types,
|
|
191
|
+
with every applicable example green on stable and long-term CHR. Issue #90
|
|
192
|
+
owns the remaining sequencing, including #507 context grounding.
|
|
193
|
+
|
|
185
194
|
**First: `explain`** (maintainer decision 2026-10-16 on
|
|
186
195
|
[#90](https://github.com/tikoci/centrs/issues/90)). In order: make the
|
|
187
|
-
published offline readings honest (#350, #211), give the result a
|
|
188
|
-
tree, then the live probe foundation (#491
|
|
189
|
-
|
|
196
|
+
published offline readings honest (#350, #211; complete), give the result a
|
|
197
|
+
statement tree (#497; complete), then the live probe foundation (#491;
|
|
198
|
+
complete). Those probes grounded #351, #358 and #371 (complete), #492
|
|
199
|
+
added the describe/completion facets, and #225 the live value types. The
|
|
200
|
+
closeout of #236 promoted both live cells (complete). The
|
|
190
201
|
plan, and what is deliberately out of this run, is the 2026-10-16 plan
|
|
191
202
|
comment on #90. It is step 3 of the validation track below.
|
|
192
203
|
|
|
193
|
-
**The CMR track is paused
|
|
194
|
-
|
|
204
|
+
**The CMR track is paused** (maintainer decision 2026-10-16; held for the
|
|
205
|
+
7.26beta caller-policy fix — #527 now carries direct 7.26beta3 evidence that a
|
|
206
|
+
restricted caller is refused, with the first refusing release still
|
|
207
|
+
unbracketed). It was first from the 2026-10-12 decision on
|
|
195
208
|
[#441](https://github.com/tikoci/centrs/issues/441). Done: `execute --cmr`
|
|
196
209
|
(#450), which runs one validated command on CMR clients through their
|
|
197
210
|
controller. The contract is in
|
|
@@ -222,11 +235,11 @@ order agreed on [#236](https://github.com/tikoci/centrs/issues/236):
|
|
|
222
235
|
2. **Done** — front the `execute` / `api` device preflight with the offline
|
|
223
236
|
analyzer (#354). The gate is now two-stage; the contract is in
|
|
224
237
|
[`docs/CONSTITUTION.md` → Validation is the product](CONSTITUTION.md#validation-is-the-product).
|
|
225
|
-
3. **
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
[
|
|
238
|
+
3. **Done** — **live `explain` (#236)**, as stage two of a gate that now exists.
|
|
239
|
+
The contract is [`commands/explain/README.md`](../commands/explain/README.md)
|
|
240
|
+
and the live examples in
|
|
241
|
+
[`commands/explain/examples.md`](../commands/explain/examples.md);
|
|
242
|
+
[#90](https://github.com/tikoci/centrs/issues/90) indexes what follows.
|
|
230
243
|
|
|
231
244
|
The offline baseline stays `verified`: #264 closed the measured token vocabulary
|
|
232
245
|
and #322 closed the final bounded-execution gap, including the public depth-axis
|
|
@@ -235,17 +248,17 @@ guard.
|
|
|
235
248
|
The live pass is the device-backed cross-check on the offline result, not a
|
|
236
249
|
replacement for it. It adds read-only `/console/inspect` and `:parse` evidence,
|
|
237
250
|
the describe/completion facets, and the live `observedType`/`schemaType`
|
|
238
|
-
producers
|
|
251
|
+
producers (#225). REST and native-api cells advance only when their
|
|
239
252
|
applicable examples pass on CHR via `bun run test:integration`.
|
|
240
253
|
|
|
241
254
|
Open offline follow-ups remain explicit but do not reopen the verified baseline.
|
|
242
255
|
Issue #211 made an unlisted absolute bare path abstain (rule F, R14); rule
|
|
243
256
|
P's closed-world reading is still not adopted (#372). #272 owns
|
|
244
|
-
non-authoritative `.scratch/` citation cleanup. #350 (the broad
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
non-authoritative `.scratch/` citation cleanup. Issues #350 (the broad
|
|
258
|
+
optional `=`), #351 (a relative submenu brace read as a command operand)
|
|
259
|
+
and #355 (`'` is not a RouterOS token) are complete. A submenu block after a navigation statement
|
|
260
|
+
(`/ip⏎address { … }`) keeps the pre-#351 reading; see
|
|
261
|
+
`commands/explain/README.md`. #336, #342 and #498 are complete: the
|
|
249
262
|
corpus is pinned to schema 5, with one recorded session per release channel
|
|
250
263
|
(long-term 7.23.8, stable 7.24.5, testing 7.25rc2, development 7.26beta1) read
|
|
251
264
|
through `scripts/corpus-db.ts`. The highlight slice is cut from those sessions
|