@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
|
@@ -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
|
|
@@ -862,7 +888,14 @@ centrs explain '[admin@MikroTik] > /ip address print' --json
|
|
|
862
888
|
`data.verdict` is `fail` with `explain/canonicalizer/invalid-at-sign` at the
|
|
863
889
|
`@`. The device answers `syntax error (line 1 column 7)`, the same byte. This
|
|
864
890
|
is a pasted console prompt. `:put a@b` and `comment=a@b` pass, because a `@` is
|
|
865
|
-
a value byte in an argument.
|
|
891
|
+
a value byte in an argument. The envelope also carries
|
|
892
|
+
`tip/explain-pasted-prompt` offering the stripped `/ip address print` as
|
|
893
|
+
repair text (never applied). A `/menu>` prompt such as
|
|
894
|
+
`/interface/lte> monitor lte1` keeps a `warn` abstention with the same tip
|
|
895
|
+
shape, offering `monitor lte1` typed at `/interface/lte`. The manual's
|
|
896
|
+
in-menu prompt `[admin@MikroTik] /ip/address> add …` fails the same way and
|
|
897
|
+
offers `add …` typed at `/ip/address`; a HotLock `>>` is stripped with the
|
|
898
|
+
prompt.
|
|
866
899
|
|
|
867
900
|
### 41. A named comparison brace inside a `[find where …]` is rejected
|
|
868
901
|
|
|
@@ -959,3 +992,127 @@ The CHR controls in `test/integration/explain-structure.test.ts` parse the
|
|
|
959
992
|
scope examples and compare stored-script creation, explicit `run`, and an
|
|
960
993
|
immediate `:do` body. Those are test operations on disposable CHR; explain
|
|
961
994
|
itself never executes the analyzed input.
|
|
995
|
+
|
|
996
|
+
### 48. Safe argument discovery distinguishes spaced arguments from flags
|
|
997
|
+
|
|
998
|
+
```bash
|
|
999
|
+
centrs explain $R '/interface set ether1 name x' -u $U -p $P --json
|
|
1000
|
+
centrs explain $R '/interface print detail without-paging' -u $U -p $P --json
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
`data.live.arguments[]` cites command-level child evidence and statement `s0`.
|
|
1004
|
+
The first result includes `name`, the second includes `detail`. Both parse;
|
|
1005
|
+
only `name x` gets the device's obsolete-syntax warning. An unknown argument
|
|
1006
|
+
control (`/interface set ether1 definitely-unknown-491=x`) carries the shared
|
|
1007
|
+
parse classifier's rejection. No command-level completion is requested.
|
|
1008
|
+
|
|
1009
|
+
The integration controls also explain a write-shaped identity change and a
|
|
1010
|
+
substitution without executing either, verify identity before/after, exercise
|
|
1011
|
+
CLI stdin and quickchr resolution, and skip oversized highlight without
|
|
1012
|
+
submitting a prefix. Parse/hash/normalization evidence preserves the Unicode
|
|
1013
|
+
byte coordinates. Run on long-term as well as stable before changing these
|
|
1014
|
+
claims. Sanitized captures can be regenerated with `CENTRS_EXPLAIN_CAPTURE`
|
|
1015
|
+
pointing at an ignored output file when running the integration suite.
|
|
1016
|
+
|
|
1017
|
+
### 49. `?`, a backtick and a second `=` need quotes
|
|
1018
|
+
|
|
1019
|
+
```bash
|
|
1020
|
+
centrs explain '/tool/fetch url=http://x/a?b=c' --json
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
`data.verdict` is `fail` with `explain/canonicalizer/unquoted-reserved-byte`
|
|
1024
|
+
at the `?`. The device answers `expected end of command (line 1 column 27)`,
|
|
1025
|
+
the same byte. A backtick is rejected the same way, in a value, a
|
|
1026
|
+
positional, a `where`, a `find`, an array and an expression. A second `=` in
|
|
1027
|
+
a menu command's argument is rejected at that `=`. So `name=a=b` and a bare
|
|
1028
|
+
base64 key ending in `=` both fail. The value's property parser never takes
|
|
1029
|
+
these bytes: `regexp=a?b` and `secret=abc==` are rejected too. In a menu
|
|
1030
|
+
command's argument name, the device reports from the name's first byte
|
|
1031
|
+
(`/ip/address/add a?b=1` at the `a`). `url="http://x/a?b=c"`,
|
|
1032
|
+
`where comment=a=b`, `[find comment=a=b]`, `{a=b}` and `# a?b` pass.
|
|
1033
|
+
`:put a=b` abstains: 7.24.5 reads it as a named argument and 7.23.8 rejects
|
|
1034
|
+
the `=`.
|
|
1035
|
+
|
|
1036
|
+
### 50. A non-ASCII character outside a string is rejected
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
centrs explain ':put $café' --json
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
`data.verdict` is `fail` with `explain/canonicalizer/unquoted-non-ascii` over
|
|
1043
|
+
the `é`. The device answers `expected end of command (line 1 column 10)`, at
|
|
1044
|
+
the `é`'s first byte. RouterOS reads only ASCII outside a string. A bare
|
|
1045
|
+
value (`name=router-café`, `name=🛰`), a reference, a path (`/systemé`), a
|
|
1046
|
+
verb, an argument name, a `where` value, an array and an expression are all
|
|
1047
|
+
rejected. So are pasted non-breaking spaces, curly quotes and ellipses. A
|
|
1048
|
+
declared name (`:local café`) keeps its `invalid-name` error. Strings and
|
|
1049
|
+
comments carry any byte: `:put "café"`, `:local "café" 1`, `$"café"`,
|
|
1050
|
+
`name="router-🚀"`, `# café` and a multi-line string pass. The informational
|
|
1051
|
+
`non-ascii` fact is still published beside the error, and a leading BOM is
|
|
1052
|
+
not read as part of the first word.
|
|
1053
|
+
|
|
1054
|
+
### 51. A brace after a submenu opens a block at any depth
|
|
1055
|
+
|
|
1056
|
+
```bash
|
|
1057
|
+
centrs explain '/ip { address { print {1;2} } }' --json
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
`data.verdict` is `fail` with `explain/canonicalizer/invalid-command-brace` at
|
|
1061
|
+
the inner `{`. The device answers `expected end of command (line 1 column 23)`,
|
|
1062
|
+
the same byte. `address {` inside `/ip { … }` is a block over `/ip/address`,
|
|
1063
|
+
so `data.structure.statements` reads `/ip`, `/ip/address` and
|
|
1064
|
+
`/ip/address print`, and the `{1;2}` is the operand of `print`.
|
|
1065
|
+
|
|
1066
|
+
The device reads a brace after a menu word as statements whatever the body
|
|
1067
|
+
holds, at any depth. A body offline cannot resolve abstains with
|
|
1068
|
+
`unresolved-statement` instead of failing as an array:
|
|
1069
|
+
`/ip { address { 1;2 } }` (the device says `bad command name 1`) and
|
|
1070
|
+
`/ip { address { zzznotamenu { print } } }` (`syntax error`) both give `warn`,
|
|
1071
|
+
as `/ip/address { 1;2 }` and `/ip { zzznotamenu { print } }` do.
|
|
1072
|
+
`/ip { address { print } }` and `/ip { firewall { filter { print } } }` pass.
|
|
1073
|
+
The menu must be known from the source alone, so a block after a navigation
|
|
1074
|
+
statement (`/ip⏎address { … }`), an abbreviation (`addr {`) and a digit-led
|
|
1075
|
+
menu (`6to4 {`) keep the older reading.
|
|
1076
|
+
|
|
1077
|
+
### 52. A live call observes a type only where the device converts a literal
|
|
1078
|
+
|
|
1079
|
+
```bash
|
|
1080
|
+
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
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
This is example 26's input against a target. The bare `2.2` gains
|
|
1084
|
+
`facts.observedType: { value: "ip", rendered: "2.0.0.2" }`, `200ms` gains
|
|
1085
|
+
`time` (`00:00:00.200`) and `yes` gains `bool` (`true`). Of the array's
|
|
1086
|
+
members, `0x10` is `num` (`16`) and `2.2` is `ip`, while `1` renders unchanged
|
|
1087
|
+
and gets nothing. The quoted `"2.2"` and the id `*1` also get nothing, because
|
|
1088
|
+
an unchanged rendering proves no type: `:parse` prints a string unquoted. The
|
|
1089
|
+
`shapeHints` are untouched, and no record has `schemaType`, because no value
|
|
1090
|
+
here is a menu command's argument.
|
|
1091
|
+
|
|
1092
|
+
A scripting directive's named argument is an operand too:
|
|
1093
|
+
`:for i from=0x10 to=20 do={:put $i}` gives `from` the fact `num` (`16`), and
|
|
1094
|
+
`to=20` renders unchanged and gets nothing. A NAME slot is a variable, never a
|
|
1095
|
+
value: in `:local yes 2` and `:local name=no value=yes`, the names `yes` and
|
|
1096
|
+
`no` get nothing (the device renders `name=$yes`, `name=$no`), while
|
|
1097
|
+
`value=yes` is `bool`.
|
|
1098
|
+
|
|
1099
|
+
Each fact cites one `:parse` evidence entry per context (operands, members)
|
|
1100
|
+
with `facet: "types"` and `basis: "derived"`. The probe re-parses each literal
|
|
1101
|
+
alone, as `:local vN <lit>` or `:local vN {<lit>}`; the analyzed input and
|
|
1102
|
+
the literals are never evaluated, and no `:typeof` is sent.
|
|
1103
|
+
|
|
1104
|
+
### 53. A live call normalizes each argument's declared type
|
|
1105
|
+
|
|
1106
|
+
```bash
|
|
1107
|
+
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
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
Each `name=value` gains `facts.schemaType` from one argument-level `syntax`
|
|
1111
|
+
probe. `servers` is `{ values: ["ip", "ip6", "enum"], declared:
|
|
1112
|
+
"Address[@VRF]" }`, and `gateway` is `ip`, `ip6`, `enum`. `dist` resolves as
|
|
1113
|
+
the unique prefix of `distance` and is `num`. `comment` is `str`. `chain` is
|
|
1114
|
+
`enum` plus `str`, since custom chains are legal. `dst-port` is `num` with
|
|
1115
|
+
`list: true`. `disabled` gets nothing, because the device declares a boolean
|
|
1116
|
+
with a blank row. No menu argument gets `observedType`: its own parser
|
|
1117
|
+
converts it, which is the schema's slot. The text output prints
|
|
1118
|
+
`observed=ip(2.0.0.2)` and `schema=ip|ip6|enum` on the value rows.
|
|
@@ -87,12 +87,32 @@ 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.
|
|
95
98
|
|
|
99
|
+
A read only ever dispatches a `print`/`get`, on every transport and even with
|
|
100
|
+
`--validate=false`: that flag skips command validation, not the read/write
|
|
101
|
+
split (#586). Each path segment must be a RouterOS menu name — letters,
|
|
102
|
+
digits and hyphens (case folds), the grammar of every dir and command in the
|
|
103
|
+
7.9–7.26 tree — or the read fails with `input/invalid-routeros-path` before
|
|
104
|
+
resolution. Before this check, a `#` or `?` cut the appended `/print` off the
|
|
105
|
+
REST URL, so `retrieve '/system/reboot#' --no-validate --attribute uptime`
|
|
106
|
+
posted `/rest/system/reboot` and rebooted CHR 7.24.5. A well-formed command
|
|
107
|
+
path is safe: RouterOS answers `POST /rest/system/reboot/print` (and a `GET` of
|
|
108
|
+
a command) with "no such command", and native API answers
|
|
109
|
+
`/system/reboot/print` the same way (CHR 7.24.5). Under the REST adapter, a
|
|
110
|
+
second guard refuses any request path containing `?`, `#`, `\`, a control
|
|
111
|
+
character, or a `.`/`..` segment (including the percent-encoded `%2e%2e` and
|
|
112
|
+
`.%2E` forms, which the URL parser also collapses) for every command. RouterOS
|
|
113
|
+
does not decode percent-escapes in REST paths, so nothing else changes the
|
|
114
|
+
target.
|
|
115
|
+
|
|
96
116
|
SNMP validation is separate from RouterOS `/console/inspect`: OIDs and MIB
|
|
97
117
|
names must resolve through the MIB cache before any SNMP request is sent.
|
|
98
118
|
|
|
@@ -147,6 +167,14 @@ handling.
|
|
|
147
167
|
}
|
|
148
168
|
```
|
|
149
169
|
|
|
170
|
+
Projection rule: the bare value is about the attribute *count*, not the flag
|
|
171
|
+
spelling. On a singleton, exactly one projected attribute — `--attribute x`
|
|
172
|
+
or `--attributes x` alike — returns the bare value (`data` is the scalar,
|
|
173
|
+
not an object); two or more return an object with those keys. On a list, any
|
|
174
|
+
projection returns an array of objects, even a single attribute. Unprojected
|
|
175
|
+
reads return the full object (singleton) or the row array (list). (Examples
|
|
176
|
+
5–6, 20, N5–N6 pin every cell of this table.)
|
|
177
|
+
|
|
150
178
|
`--max-bytes` / `--max-rows` truncation populates
|
|
151
179
|
`meta.truncated = { returned, total, totalBytes }` and keeps `ok: true` with a
|
|
152
180
|
warning entry — it is not an error. (Earlier text said a byte overflow returns an
|
|
@@ -513,7 +541,11 @@ stays the single-target envelope; a selector flag or a second positional target
|
|
|
513
541
|
switches to fan-out. `--quickchr <name>` (the named-live-provider) behaves like a
|
|
514
542
|
lone positional: one flag stays single-target, repeating it fans out, and mixing
|
|
515
543
|
it with positionals or CDB selectors is `usage/conflicting-flags` (constitution:
|
|
516
|
-
resolution providers).
|
|
544
|
+
resolution providers). When several CDB records share one `<router>` (the
|
|
545
|
+
same address saved under two users), `--match user=<name>` (or
|
|
546
|
+
`target=<addr>`, or a record type) picks one, as on `devices show`; it is
|
|
547
|
+
single-target only (see
|
|
548
|
+
[devices → Ambiguity](../devices/README.md#ambiguity), #583). The `--host` **flag** with no `<router>` positional
|
|
517
549
|
(`retrieve --host http://192.0.2.10:8080 /system/resource`) is a single target
|
|
518
550
|
too: no CDB lookup, so auth comes from flags, env or config. `CENTRS_HOST` never
|
|
519
551
|
stands in for `<router>`.
|
|
@@ -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.
|
|
@@ -129,6 +146,43 @@ centrs retrieve $R /not/a/real/path --username $U --password $P --json
|
|
|
129
146
|
centrs retrieve $R /system/resource --attribute bogus --username $U --password $P --json
|
|
130
147
|
```
|
|
131
148
|
|
|
149
|
+
## Read paths dispatch only reads (#586)
|
|
150
|
+
|
|
151
|
+
A read sends only a `print`/`get`, even with `--validate=false`. Covered by
|
|
152
|
+
the CHR test `test/integration/read-path-guard.test.ts` (RP1–RP2, over REST
|
|
153
|
+
and native-api, for `retrieve` and `api`; the router's uptime must never go
|
|
154
|
+
backwards) and the offline grammar and REST-guard tests in
|
|
155
|
+
`test/unit/menu-path.test.ts` (RP3).
|
|
156
|
+
|
|
157
|
+
### RP1. A `#` or `?` in the path is refused before dispatch
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
centrs retrieve $R '/system/reboot#' --no-validate --attribute uptime --username $U --password $P --json
|
|
161
|
+
centrs api $R '/system/reboot?' --validate=false --attribute uptime --username $U --password $P --json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Envelope: `ok: false`, `error.code=input/invalid-routeros-path`,
|
|
165
|
+
`error.context.invalidSegments` names the bad segment, and nothing is sent.
|
|
166
|
+
Before #586 the first line posted `/rest/system/reboot` and rebooted the CHR.
|
|
167
|
+
|
|
168
|
+
### RP2. A well-formed command path is RouterOS's refusal, not a dispatch
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
centrs retrieve $R /system/reboot --no-validate --username $U --password $P --json
|
|
172
|
+
centrs retrieve $R /system/reset-configuration --no-validate --attribute uptime --username $U --password $P --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Envelope: `ok: false` with a `routeros/*` code. RouterOS answers
|
|
176
|
+
`<command>/print` and a `GET` of a command with "no such command", so the
|
|
177
|
+
appended `/print` is the guarantee once the path cannot cut it off.
|
|
178
|
+
|
|
179
|
+
### RP3. Menu-name grammar and the REST URL guard
|
|
180
|
+
|
|
181
|
+
Path segments are letters, digits and hyphens (case folds:
|
|
182
|
+
`/IP/Address` is accepted). Under every REST caller, a request path with
|
|
183
|
+
`?`, `#`, `\`, a control character or a `.`/`..` segment (including
|
|
184
|
+
`%2e%2e` and `.%2E`) fails `input/invalid-routeros-path` before `fetch`.
|
|
185
|
+
|
|
132
186
|
## Transport / error contract
|
|
133
187
|
|
|
134
188
|
### 12. Bad credentials
|
|
@@ -362,6 +416,10 @@ centrs retrieve $A /interface --attributes name,type --via native-api --port $AP
|
|
|
362
416
|
|
|
363
417
|
`data` is an array of objects each containing only `name` and `type`.
|
|
364
418
|
|
|
419
|
+
A single attribute (`--attributes name`) keeps the array shape here too: an
|
|
420
|
+
array of single-key objects. On a singleton, one item via either flag returns
|
|
421
|
+
the bare value (N5) — the rule is the count, not the flag.
|
|
422
|
+
|
|
365
423
|
### N7. `--all-attributes` (native `print detail`)
|
|
366
424
|
|
|
367
425
|
```bash
|
|
@@ -380,6 +438,14 @@ centrs retrieve $A /system/resource --list-attributes --via native-api --port $A
|
|
|
380
438
|
centrs retrieve $A /not/a/real/path --via native-api --port $API_PORT --username $U --password $P
|
|
381
439
|
```
|
|
382
440
|
|
|
441
|
+
`/system/routerboard` on CHR earns the targeted remediation (no routerboard
|
|
442
|
+
outside MikroTik hardware; read `/system/license` for the license ID
|
|
443
|
+
instead), same as example 10.
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
centrs retrieve $A /system/routerboard --via native-api --port $API_PORT --username $U --password $P
|
|
447
|
+
```
|
|
448
|
+
|
|
383
449
|
### N10. Unknown attribute → `validation/unknown-attribute`
|
|
384
450
|
|
|
385
451
|
```bash
|
|
@@ -792,3 +858,50 @@ centrs retrieve $R /interface --until name=missing --wait 500ms --username $U --
|
|
|
792
858
|
One envelope goes to stdout, with `stopReason: "deadline-elapsed"`; exit 1.
|
|
793
859
|
No intermediate sample frames are printed. Unit anchors cover slow validation,
|
|
794
860
|
unfinished reads, cancellation and narrow retry classification.
|
|
861
|
+
|
|
862
|
+
## Record selection (`--match`, #583)
|
|
863
|
+
|
|
864
|
+
Covered by the named tests `MA1`–`MA3` in `test/integration/cdb-match.test.ts`
|
|
865
|
+
on disposable CHR; MA4 is the offline contract in the `MA4.` tests of
|
|
866
|
+
`test/unit/cdb-match.test.ts`, which drive `retrieve`/`api` through the CLI. `$CDB` holds
|
|
867
|
+
two `ipAdmin` records for the same target `$R`: user `$U` with a deliberately
|
|
868
|
+
wrong password, and a `read`-group user `reader` with its real password.
|
|
869
|
+
The same selector works on `execute`, `api`, `transfer`, `terminal` and
|
|
870
|
+
`btest client`.
|
|
871
|
+
|
|
872
|
+
### MA1. Two records for one target are ambiguous
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
centrs retrieve $R /system/identity --cdb-file $CDB --json
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
Envelope: `ok: false`, `error.code=identity/ambiguous`, `context.matches`
|
|
879
|
+
lists both records, and the remediation names `--match user=<name>`.
|
|
880
|
+
|
|
881
|
+
### MA2. `--match user=` picks the record and its credentials
|
|
882
|
+
|
|
883
|
+
```bash
|
|
884
|
+
centrs retrieve $R /system/identity --cdb-file $CDB --match user=reader --json
|
|
885
|
+
centrs execute $R /system/identity/print --cdb-file $CDB --match user=reader --json
|
|
886
|
+
centrs api $R system/identity --cdb-file $CDB --match user=reader --json
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
Each is `ok: true` with `meta.target.recordIndex=1`. With
|
|
890
|
+
`--match user=$U` instead, each fails `transport/auth-failed`: the decoy's
|
|
891
|
+
wrong password went on the wire, so the selection is real.
|
|
892
|
+
|
|
893
|
+
### MA3. A selector that matches nothing
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
centrs retrieve $R /system/identity --cdb-file $CDB --match user=nobody --json
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
Envelope: `ok: false`, `error.code=identity/no-match`.
|
|
900
|
+
|
|
901
|
+
### MA4. `--match` is single-target and never ignored
|
|
902
|
+
|
|
903
|
+
With a fan-out selection (`--group`, `--where`, `--all`, several
|
|
904
|
+
positionals) or `--quickchr`, `--match` is `usage/conflicting-flags`. When no
|
|
905
|
+
CDB record is in play (no CDB file, an unregistered literal host, a
|
|
906
|
+
`__default__`-only fallback) it is `identity/no-match`, never a silent
|
|
907
|
+
literal-host read.
|
|
@@ -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`):
|
|
@@ -118,6 +118,31 @@ past 60 KB) rather than quietly switching to sftp. `scp`, `fetch`, and `ftp` are
|
|
|
118
118
|
explicit-only; `ftp` additionally requires `ALLOW_UNSAFE_PROTOCOLS=ftp` because
|
|
119
119
|
it is cleartext.
|
|
120
120
|
|
|
121
|
+
The method resolves `--via` → `CENTRS_TRANSFER_VIA` → the CDB record's `via=`
|
|
122
|
+
preference → config → auto ([constitution: settings](../../docs/CONSTITUTION.md)).
|
|
123
|
+
A record's `via=` names a *transport* (shared with `execute`), so transfer maps
|
|
124
|
+
it to a method (#591):
|
|
125
|
+
|
|
126
|
+
| CDB / `--via` transport name | Transfer method |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `ssh` | `sftp` |
|
|
129
|
+
| `rest-api` (also `--via rest`) | `rest` |
|
|
130
|
+
| `native-api` (also `--via native`) | `native` |
|
|
131
|
+
| `mac-telnet`, `romon`, `winbox-terminal`, `snmp`, `mndp`, `btest` | none — `settings/invalid-via` |
|
|
132
|
+
|
|
133
|
+
`--via ssh` (and `CENTRS_TRANSFER_VIA=ssh`) is accepted as an alias of
|
|
134
|
+
`sftp`. A record whose `via=` cannot carry files fails `settings/invalid-via`
|
|
135
|
+
naming the record, rather than being ignored; pass `--via sftp|rest|native`
|
|
136
|
+
for that transfer. A record `via=` holds a transport id: method names
|
|
137
|
+
(`sftp`, `rest`, `ftp`) and unknown values are rejected when the record is
|
|
138
|
+
read (`cdb/invalid-option` warning) and leave the method on auto. A record
|
|
139
|
+
preference is a setting, not an auto hop: a `via=rest-api` record uploading
|
|
140
|
+
more than 60 KB fails `transport/unsupported-operation` like a pinned
|
|
141
|
+
`--via rest` does, and the remediation names the record. The endpoint and
|
|
142
|
+
credentials still come from the same record; the mapping never selects a
|
|
143
|
+
different one. A record's `port=` travels with a `--via` pin, so if the
|
|
144
|
+
record's port belongs to another service, point `--port` at the pinned one.
|
|
145
|
+
|
|
121
146
|
**File contents are bytes on `rest`/`native`.** RouterOS carries `/file`
|
|
122
147
|
contents as raw bytes on both wires — REST puts bytes 0x80–0xff unescaped in the
|
|
123
148
|
JSON string, so the body is not UTF-8 — and stores the UTF-8 encoding of every
|
|
@@ -302,6 +327,10 @@ gate as in `retrieve` / `execute`; RouterOS's own menu errors (missing remote
|
|
|
302
327
|
file, permission denied) map to `routeros/*`, and SFTP/SCP/fetch errors map to
|
|
303
328
|
`transport/*` and `auth/*`.
|
|
304
329
|
|
|
330
|
+
`meta.validation` reports that existence probe as one `device` stage. `list`
|
|
331
|
+
runs no probe, so it is not a gate: its `meta.validation` is
|
|
332
|
+
`{ enabled: false }`, on success and on failure (#535).
|
|
333
|
+
|
|
305
334
|
A failure **after** target resolution keeps what resolution found: the error
|
|
306
335
|
envelope carries the resolved `meta.target` (host, port, identity, sources),
|
|
307
336
|
`meta.via`, the setting sources in `meta.settings`, and `meta.operation`
|
|
@@ -371,9 +400,9 @@ done rule. The first coded pass targets `rest`/`native` (no SSH dependency);
|
|
|
371
400
|
(random URL token, bound to the interface facing the router) and drives the
|
|
372
401
|
router with `/tool/fetch` over `rest`/`native`. **upload**: serve the local
|
|
373
402
|
file, `fetch url=http://<advertise>/<token> dst-path=<remote>` → router GETs.
|
|
374
|
-
**download
|
|
375
|
-
|
|
376
|
-
|
|
403
|
+
**download** stays on chunked `/file/read`: router PUTs fail with `only
|
|
404
|
+
[s]ftp modes support upload` (#548), and HTTP `http-data` caps at 60000
|
|
405
|
+
bytes, so the PUT half cannot carry files. Open questions: advertise-host
|
|
377
406
|
auto-detection behind NAT, HTTP-vs-HTTPS + `check-certificate`, and whether to
|
|
378
407
|
add fetch's `user`/`password` on top of the URL token.
|
|
379
408
|
- **ssh** — `sftp` is **built** (`src/protocols/sftp.ts`, host OpenSSH `sftp`
|