@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
|
@@ -569,3 +569,49 @@ list succeeds over sftp: `meta.via=sftp`, `meta.target.source.kind=provider`,
|
|
|
569
569
|
handed over) carried as a provider-sourced `sshKey`. Otherwise the envelope is
|
|
570
570
|
`ok: false` with `error.code=quickchr/unsupported-via` and exit code 1 — a
|
|
571
571
|
typed gate, **never** a password prompt and never a silent fallback to REST.
|
|
572
|
+
|
|
573
|
+
## CDB record `via=` (#591)
|
|
574
|
+
|
|
575
|
+
A record's `via=` names a transport, shared with `execute`; transfer maps it
|
|
576
|
+
to a method (`ssh`→`sftp`, `rest-api`→`rest`, `native-api`→`native`). `$CDB`
|
|
577
|
+
holds one record for `127.0.0.1` whose comment is
|
|
578
|
+
`via=ssh port=$SSH_PORT ssh-key=$KEY insecure=true`; the key is imported as
|
|
579
|
+
in the sftp section. C1–C2 are covered by
|
|
580
|
+
`test/integration/transfer-cdb-via.test.ts`; C3–C4 are offline error
|
|
581
|
+
contracts covered by `test/unit/transfer.test.ts`
|
|
582
|
+
(`resolveTransferRequest takes the CDB record's via=`).
|
|
583
|
+
|
|
584
|
+
### C1. A `via=ssh` record runs execute over ssh and transfer over sftp
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
centrs execute 127.0.0.1 --cdb-file $CDB /system/identity/print --json
|
|
588
|
+
centrs transfer 127.0.0.1 --cdb-file $CDB upload $SRC centrs-591.txt --json
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Both `ok: true` with `meta.via="ssh"`, and no `--via` on either. The
|
|
592
|
+
transfer's `meta.settings.via` is `{kind: "comment-kv", key: "record:0:via"}`.
|
|
593
|
+
|
|
594
|
+
### C2. Download from the same record, with and without `--via sftp`
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
centrs transfer 127.0.0.1 --cdb-file $CDB download centrs-591.txt $TMP/c2-auto.txt --json
|
|
598
|
+
centrs transfer 127.0.0.1 --cdb-file $CDB --via sftp download centrs-591.txt $TMP/c2-sftp.txt --json
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Both `ok: true`, `meta.via="ssh"`; each file is byte-identical to `$SRC`.
|
|
602
|
+
`--via ssh` is accepted as the same method as `--via sftp`.
|
|
603
|
+
|
|
604
|
+
### C3. A record transport that cannot carry files
|
|
605
|
+
|
|
606
|
+
A record with `via=mac-telnet` (or `romon`, `snmp`, `winbox-terminal`,
|
|
607
|
+
`mndp`, `btest`) and no `--via`: `ok: false`, `error.code=settings/invalid-via`,
|
|
608
|
+
`error.context.source="record:N:via"`, and the remediation suggests
|
|
609
|
+
`--via sftp`, `--via rest` or `--via native`. An explicit `--via` wins over
|
|
610
|
+
the record and the transfer proceeds.
|
|
611
|
+
|
|
612
|
+
### C4. A `via=rest-api` record does not auto-hop a large upload
|
|
613
|
+
|
|
614
|
+
A `via=rest-api` record uploading more than 60 KB with no `--via`:
|
|
615
|
+
`ok: false`, `error.code=transport/unsupported-operation`, nothing sent. The
|
|
616
|
+
remediation names `record:N:via` and suggests `--via sftp`, matching a
|
|
617
|
+
pinned `--via rest`.
|
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). |
|
|
@@ -64,6 +64,7 @@ Usage: centrs retrieve <target> <routeros-path> [flags] | centrs retrieve <targe
|
|
|
64
64
|
| `--max-results` | `<bytes>` | Fail instead of printing output larger than the given byte budget. |
|
|
65
65
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
66
66
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
67
|
+
| `--match` | <user=name\|target=addr\|record-type> | Pick one of several CDB records saved for `<router>` (as `devices show --match`); single target only. |
|
|
67
68
|
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
68
69
|
| `--validate / --no-validate` | | Enable or disable inspect-backed preflight validation. |
|
|
69
70
|
| `--verbose` | | Show resolved setting sources in text output, on stderr. |
|
|
@@ -97,6 +98,7 @@ Usage: centrs execute <target> <command> [flags] | centrs execute <target...> --
|
|
|
97
98
|
| `--insecure` | | Disable SSH host-key verification (`--via ssh`: accepts changed/impersonated keys, not just new) or accept a self-signed `api-ssl` TLS cert. Default verifies. |
|
|
98
99
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
99
100
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
101
|
+
| `--match` | <user=name\|target=addr\|record-type> | Pick one of several CDB records saved for `<router>` (as `devices show --match`); single target only. |
|
|
100
102
|
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
101
103
|
| `--timeout` | `<duration>` | Per-request timeout (for REST, max 60s). |
|
|
102
104
|
| `--validate / --no-validate` | | Run RouterOS :parse and /console/inspect validation before execution (default true; `--validate=false` also accepted). |
|
|
@@ -145,6 +147,7 @@ Usage: centrs api <router> <endpoint> [flags] | centrs api --host <host|url> <en
|
|
|
145
147
|
| `--insecure` | | Accept a self-signed `api-ssl`/REST TLS cert. Default verifies. |
|
|
146
148
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
147
149
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
150
|
+
| `--match` | <user=name\|target=addr\|record-type> | Pick one of several CDB records saved for `<router>` (as `devices show --match`); single target only. |
|
|
148
151
|
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
149
152
|
| `--timeout` | `<duration>` | Per-request timeout (for REST, max 60s). |
|
|
150
153
|
| `--validate / --no-validate` | | Run `/console/inspect` validation before the request (default true; `--validate=false` also accepted). |
|
|
@@ -162,7 +165,7 @@ Usage: centrs transfer <router>|--host <host> upload <local> [remote] | download
|
|
|
162
165
|
|
|
163
166
|
| Flag | Value | Description |
|
|
164
167
|
| ---- | ----- | ----------- |
|
|
165
|
-
| `--via` | `<method>` | Pin the method: rest, native, or sftp (large transfers); scp/fetch/ftp are not built yet. Auto picks the cheapest by size/direction. |
|
|
168
|
+
| `--via` | `<method>` | Pin the method: rest, native, or sftp (large transfers; `ssh` is an alias); scp/fetch/ftp are not built yet. Auto picks the cheapest by size/direction, after a CDB record's `via=` (ssh→sftp). |
|
|
166
169
|
| `--ssh-key` | `<path>` | sftp only: explicit private-key path. Falls back to CENTRS_SSH_KEY / the ssh-agent. |
|
|
167
170
|
| `--insecure` | | Accept a self-signed TLS cert (https/api-ssl) or a new SSH host key. Default verifies. |
|
|
168
171
|
| `--force / --overwrite` | | Skip the existing-destination check on the device for `upload`/`mkdir`/`copy` (`upload` then overwrites). Default refuses an existing target. `download` always replaces the local file. |
|
|
@@ -189,6 +192,7 @@ Usage: centrs transfer <router>|--host <host> upload <local> [remote] | download
|
|
|
189
192
|
| `--yaml` | | Shortcut for --format yaml. |
|
|
190
193
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
191
194
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
195
|
+
| `--match` | <user=name\|target=addr\|record-type> | Pick one of several CDB records saved for `<router>` (as `devices show --match`); single target only. |
|
|
192
196
|
| `--validate / --no-validate` | | Enable or disable the existence-probe preflight. |
|
|
193
197
|
| `--verbose` | | Show the resolved transport in text output, on stderr. |
|
|
194
198
|
|
|
@@ -212,6 +216,7 @@ Usage: centrs terminal <router> [flags] | centrs terminal --host <host> [flags]
|
|
|
212
216
|
| `--insecure` | | `--via ssh`: disable host-key verification (accepts changed/impersonated keys). Default verifies. |
|
|
213
217
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
214
218
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
219
|
+
| `--match` | <user=name\|target=addr\|record-type> | Pick one of several CDB records saved for `<router>` (as `devices show --match`); single target only. |
|
|
215
220
|
| `--resolve` | <none\|arp> | `--via ssh` only: turn a MAC target into an IP. CDB-first; `arp` opts into the host ARP cache (default none). The mac-telnet default ignores it. |
|
|
216
221
|
| `--format` | <text\|json\|yaml> | Error-envelope format on failure. --json / --yaml shortcuts. |
|
|
217
222
|
| `--json` | | Shortcut for --format json. |
|
|
@@ -220,18 +225,32 @@ Usage: centrs terminal <router> [flags] | centrs terminal --host <host> [flags]
|
|
|
220
225
|
|
|
221
226
|
## explain
|
|
222
227
|
|
|
223
|
-
Analyze
|
|
228
|
+
Analyze RouterOS input offline or add read-only device evidence from one router.
|
|
224
229
|
|
|
225
230
|
```text
|
|
226
|
-
Usage: centrs explain '<input>' [flags]
|
|
231
|
+
Usage: centrs explain [<router>] '<input>' [flags]
|
|
227
232
|
```
|
|
228
233
|
|
|
229
234
|
| Flag | Value | Description |
|
|
230
235
|
| ---- | ----- | ----------- |
|
|
236
|
+
| `--via` | <rest-api\|native-api> | Pin the transport; no silent downgrade. Default rest-api. |
|
|
237
|
+
| `--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. |
|
|
238
|
+
| `--port` | `<port>` | Override the resolved management port. |
|
|
239
|
+
| `--username / --user / -u` | `<name>` | RouterOS username. Falls back to `CENTRS_USERNAME`. |
|
|
240
|
+
| `--password` | `<secret>` | RouterOS password. Falls back to `CENTRS_PASSWORD`. |
|
|
241
|
+
| `--insecure` | | Accept a self-signed `api-ssl`/REST TLS cert. Default verifies. |
|
|
242
|
+
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
243
|
+
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
244
|
+
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
245
|
+
| `--timeout` | `<duration>` | Per-request timeout (for REST, max 60s). |
|
|
246
|
+
| `-p` | `<secret>` | Alias for --password. |
|
|
247
|
+
| `--quickchr` | `<name>` | Resolve one running quickchr VM by name. |
|
|
231
248
|
| `--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
249
|
| `--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` | |
|
|
250
|
+
| `--complete` | | Candidates at the cursor: menus and verbs, argument names, or values. Live evidence — offline emits a tip and enumerates nothing. |
|
|
251
|
+
| `--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. |
|
|
252
|
+
| `--cursor` | `<byte>` | UTF-8 byte offset for --complete/--schema. Default: the end of the input. |
|
|
253
|
+
| `--full` | | Lift --complete/--schema sizing to its safety ceiling (1024 names, 128 expansion probes). |
|
|
235
254
|
| `--curl` | | Render a ready-to-edit REST curl for statements covered by a runtime-tested mapping rule. |
|
|
236
255
|
| `--tokens` | | Emit the total, gapless token partition behind `data.tokens[]` (every byte not claimed by an analyzer is `unclassified`). |
|
|
237
256
|
| `--format` | <text\|json\|yaml> | Output format for the CLI response. |
|
|
@@ -244,13 +263,15 @@ Usage: centrs explain '<input>' [flags]
|
|
|
244
263
|
Inspect and mutate the CDB-backed device registry. `devices` is the only command that writes the CDB.
|
|
245
264
|
|
|
246
265
|
```text
|
|
247
|
-
Usage: centrs devices <init|list|show|groups|add|edit|set|remove> [args] [flags]
|
|
266
|
+
Usage: centrs devices <init|encrypt|list|show|groups|add|edit|set|remove> [args] [flags]
|
|
248
267
|
```
|
|
249
268
|
|
|
250
269
|
| Flag | Value | Description |
|
|
251
270
|
| ---- | ----- | ----------- |
|
|
271
|
+
| `--encrypt` | | `init` only — require encrypted creation with a non-empty CDB password; refuse an existing open CDB. |
|
|
272
|
+
| `--no-backup` | | `encrypt` only — skip the plaintext pre-conversion snapshot. Without that snapshot, a forgotten password cannot be recovered. |
|
|
252
273
|
| `--cdb-file` | `<path>` | Override the resolved CDB path (default `~/.config/tikoci/winbox.cdb`). |
|
|
253
|
-
| `--cdb-password` | `<secret>` | Decrypt an encrypted CDB. Falls back to `CENTRS_CDB_PASSWORD`. Also used to re-encrypt on writes. |
|
|
274
|
+
| `--cdb-password` | `<secret>` | Decrypt an encrypted CDB. Falls back to `CENTRS_CDB_PASSWORD`. Also used for `init --encrypt`, `encrypt`, and to re-encrypt on writes. |
|
|
254
275
|
| `--via` | `<protocol>` | Resolve and report the protocol source for provenance examples; no network IO is performed. |
|
|
255
276
|
| `--group` | `<name>` | `list` filter / `add`,`edit` first-class group field for the target entry. |
|
|
256
277
|
| `--where` | `<attr=value>` | `list` only — repeatable device-class filter over CDB facts (AND-combined), e.g. `--where lat=37.7749`. |
|
|
@@ -326,6 +347,7 @@ Usage: centrs btest <client <router>|server> [flags]
|
|
|
326
347
|
| `--port` | `<port>` | Control port. Default 2000. |
|
|
327
348
|
| `--cdb-file` | `<path>` | (client) Read target credentials from a WinBox CDB file. |
|
|
328
349
|
| `--cdb-password` | `<password>` | (client) Decrypt an encrypted WinBox CDB file. |
|
|
350
|
+
| `--match` | <user=name\|target=addr\|record-type> | (client) Pick one of several CDB records saved for `<router>` (as `devices show --match`); `btest server` rejects it. |
|
|
329
351
|
| `--format` | <text\|csv\|json\|yaml> | Output format. Default text. |
|
|
330
352
|
| `--csv` | | Shortcut for `--format csv`. |
|
|
331
353
|
|
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,27 @@ 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's context surface. Its bounded
|
|
193
|
+
submission matrix is grounded (#574); context selection and binding behavior
|
|
194
|
+
remain follow-up work.
|
|
195
|
+
|
|
185
196
|
**First: `explain`** (maintainer decision 2026-10-16 on
|
|
186
197
|
[#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
|
-
|
|
198
|
+
published offline readings honest (#350, #211; complete), give the result a
|
|
199
|
+
statement tree (#497; complete), then the live probe foundation (#491;
|
|
200
|
+
complete). Those probes grounded #351, #358 and #371 (complete), #492
|
|
201
|
+
added the describe/completion facets, and #225 the live value types. The
|
|
202
|
+
closeout of #236 promoted both live cells (complete). The
|
|
190
203
|
plan, and what is deliberately out of this run, is the 2026-10-16 plan
|
|
191
204
|
comment on #90. It is step 3 of the validation track below.
|
|
192
205
|
|
|
193
|
-
**The CMR track is paused
|
|
194
|
-
|
|
206
|
+
**The CMR track is paused** (maintainer decision 2026-10-16; held for the
|
|
207
|
+
7.26beta caller-policy fix — #527 now carries direct 7.26beta3 evidence that a
|
|
208
|
+
restricted caller is refused, with the first refusing release still
|
|
209
|
+
unbracketed). It was first from the 2026-10-12 decision on
|
|
195
210
|
[#441](https://github.com/tikoci/centrs/issues/441). Done: `execute --cmr`
|
|
196
211
|
(#450), which runs one validated command on CMR clients through their
|
|
197
212
|
controller. The contract is in
|
|
@@ -222,11 +237,11 @@ order agreed on [#236](https://github.com/tikoci/centrs/issues/236):
|
|
|
222
237
|
2. **Done** — front the `execute` / `api` device preflight with the offline
|
|
223
238
|
analyzer (#354). The gate is now two-stage; the contract is in
|
|
224
239
|
[`docs/CONSTITUTION.md` → Validation is the product](CONSTITUTION.md#validation-is-the-product).
|
|
225
|
-
3. **
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
[
|
|
240
|
+
3. **Done** — **live `explain` (#236)**, as stage two of a gate that now exists.
|
|
241
|
+
The contract is [`commands/explain/README.md`](../commands/explain/README.md)
|
|
242
|
+
and the live examples in
|
|
243
|
+
[`commands/explain/examples.md`](../commands/explain/examples.md);
|
|
244
|
+
[#90](https://github.com/tikoci/centrs/issues/90) indexes what follows.
|
|
230
245
|
|
|
231
246
|
The offline baseline stays `verified`: #264 closed the measured token vocabulary
|
|
232
247
|
and #322 closed the final bounded-execution gap, including the public depth-axis
|
|
@@ -235,17 +250,17 @@ guard.
|
|
|
235
250
|
The live pass is the device-backed cross-check on the offline result, not a
|
|
236
251
|
replacement for it. It adds read-only `/console/inspect` and `:parse` evidence,
|
|
237
252
|
the describe/completion facets, and the live `observedType`/`schemaType`
|
|
238
|
-
producers
|
|
253
|
+
producers (#225). REST and native-api cells advance only when their
|
|
239
254
|
applicable examples pass on CHR via `bun run test:integration`.
|
|
240
255
|
|
|
241
256
|
Open offline follow-ups remain explicit but do not reopen the verified baseline.
|
|
242
257
|
Issue #211 made an unlisted absolute bare path abstain (rule F, R14); rule
|
|
243
|
-
P's closed-world reading is still not adopted (#372).
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
258
|
+
P's closed-world reading is still not adopted (#372). The non-authoritative
|
|
259
|
+
scratch citation cleanup is complete (#272, shipped in #568). Issues #350 (the broad
|
|
260
|
+
optional `=`), #351 (a relative submenu brace read as a command operand)
|
|
261
|
+
and #355 (`'` is not a RouterOS token) are complete. A submenu block after a navigation statement
|
|
262
|
+
(`/ip⏎address { … }`) keeps the pre-#351 reading; see
|
|
263
|
+
`commands/explain/README.md`. #336, #342 and #498 are complete: the
|
|
249
264
|
corpus is pinned to schema 5, with one recorded session per release channel
|
|
250
265
|
(long-term 7.23.8, stable 7.24.5, testing 7.25rc2, development 7.26beta1) read
|
|
251
266
|
through `scripts/corpus-db.ts`. The highlight slice is cut from those sessions
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# `cdb/encrypt-failed`
|
|
2
|
+
|
|
3
|
+
The existing CDB could not be rewritten with encryption.
|
|
4
|
+
|
|
5
|
+
## Fix
|
|
6
|
+
|
|
7
|
+
Check directory permissions and available disk space, then retry
|
|
8
|
+
`centrs devices encrypt`. Preserve the original file and any timestamped backups
|
|
9
|
+
before recovery. A failed write may already have created a plaintext backup;
|
|
10
|
+
encryption does not protect that backup.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# `cdb/password-required`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CDB password is required for decryption or explicit encryption.
|
|
4
4
|
|
|
5
5
|
## Fix
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Pass `--cdb-password` or set `CENTRS_CDB_PASSWORD` to the CDB password.
|
|
8
|
+
`devices init --encrypt` and `devices encrypt` require a non-empty password,
|
|
9
|
+
even when the file is currently open. A password alone never implicitly
|
|
10
|
+
converts an existing open CDB; use `devices encrypt` explicitly.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# `cdb/plaintext-backups`
|
|
2
|
+
|
|
3
|
+
CDB backups may still contain plaintext credentials after encryption.
|
|
4
|
+
|
|
5
|
+
## Fix
|
|
6
|
+
|
|
7
|
+
Apply your own secure backup and retention policy to the `.bak.*` files beside
|
|
8
|
+
the CDB. `devices encrypt` protects only the active CDB and retains every backup,
|
|
9
|
+
including the plaintext snapshot created during conversion. It does not encrypt
|
|
10
|
+
or delete old copies. This warning also accompanies already-encrypted no-op runs,
|
|
11
|
+
since prior backup protection cannot be inferred from the active file.
|
|
@@ -22,9 +22,11 @@ re-proves it (CMR15 in `commands/execute/examples.md`):
|
|
|
22
22
|
| `read,test,api,rest-api` | `not enough permissions (9)` | `run-script` wrote to the controller's own row; `/cmr/device/reboot` rebooted a client |
|
|
23
23
|
|
|
24
24
|
MikroTik [says](https://forum.mikrotik.com/t/7-26beta-development-is-released/273582/139) this is fixed for 7.26beta2: a `read` group will not
|
|
25
|
-
be allowed to do it.
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
be allowed to do it. #527 now carries direct 7.26beta3 evidence that a
|
|
26
|
+
restricted caller is refused, with the first refusing release still
|
|
27
|
+
unbracketed. centrs still warns on any version, because the warning reads
|
|
28
|
+
the login's policy, not the RouterOS version. On a build that refuses the
|
|
29
|
+
write, the members fail
|
|
28
30
|
instead and the warning only says what the login lacks.
|
|
29
31
|
|
|
30
32
|
`context` names the login (`username`, `group`), the policies it lacks
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
# `input/invalid-routeros-path`
|
|
2
2
|
|
|
3
|
-
The RouterOS path is malformed
|
|
3
|
+
The RouterOS path is malformed: not slash-prefixed, a segment that is not a
|
|
4
|
+
menu name, or characters a REST URL would reinterpret.
|
|
5
|
+
|
|
6
|
+
## Typical triggers
|
|
7
|
+
|
|
8
|
+
- A path without a leading slash: `retrieve <router> ip/address`.
|
|
9
|
+
- A `retrieve` or `api` path segment that is not a RouterOS menu name. Menu
|
|
10
|
+
names are letters, digits and hyphens (case folds) (`/interface/6to4`,
|
|
11
|
+
`/ip/dhcp-server`). The check runs on every transport and even with
|
|
12
|
+
`--validate=false`, because a read must only dispatch a read (#586).
|
|
13
|
+
- A REST request path containing `?`, `#`, `\`, a control character, or a
|
|
14
|
+
`.`/`..` segment, including percent-encoded forms such as `%2e%2e`. The
|
|
15
|
+
URL parser would turn these into a query, a fragment
|
|
16
|
+
or a different path, so the request would not reach the path as written.
|
|
17
|
+
Before this guard, `retrieve '/system/reboot#' --no-validate` posted
|
|
18
|
+
`/rest/system/reboot`.
|
|
19
|
+
|
|
20
|
+
`context.path` names the path; `context.invalidSegments` lists the segments
|
|
21
|
+
that failed the menu grammar.
|
|
4
22
|
|
|
5
23
|
## Fix
|
|
6
24
|
|
|
25
|
+
- Write the path as slash-separated menu names: `/ip/address`,
|
|
26
|
+
`/system/resource`.
|
|
27
|
+
- Select rows with `--query` (for example `--query 'name="a#b"'`), not with
|
|
28
|
+
characters in the path.
|
|
29
|
+
|
|
7
30
|
See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
|
|
8
|
-
contract.
|
|
9
|
-
for `input/invalid-routeros-path`.
|
|
31
|
+
contract.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# `routeros/permission-denied`
|
|
2
|
+
|
|
3
|
+
RouterOS refused the command because the user's group lacks a policy it needs.
|
|
4
|
+
The router answered, and a retry gives the same result. A single refused
|
|
5
|
+
command did not run. A multi-statement script run through `/execute` can be
|
|
6
|
+
partly applied: statements before the refused one already ran.
|
|
7
|
+
|
|
8
|
+
## Typical RouterOS strings
|
|
9
|
+
|
|
10
|
+
- `not enough permissions (9)`: the command needs a policy the group does not
|
|
11
|
+
have, for example a `set` without `write`.
|
|
12
|
+
- `std failure: not allowed (9)`: on REST before RouterOS 7.26beta3, every
|
|
13
|
+
request fails this way (a plain GET included) when the group has `rest-api`
|
|
14
|
+
but not `api`. 7.26beta3 fixed it.
|
|
15
|
+
|
|
16
|
+
Over REST both arrive as HTTP 500 with the string in `detail`; over the native
|
|
17
|
+
API as a `!trap`. Grounded on CHR 7.23.8, 7.25, 7.26beta1 and 7.26beta3.
|
|
18
|
+
|
|
19
|
+
## Fix
|
|
20
|
+
|
|
21
|
+
Grant the missing policy to the user's group in `/user/group`: `read` for
|
|
22
|
+
reads, `write` for changes. Before 7.26beta3 a REST user needs both `api` and
|
|
23
|
+
`rest-api`. Avoid the built-in `read` group for a least-privilege user: it also
|
|
24
|
+
carries `reboot`, `sensitive`, `password` and others.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# `transport/edge-auth-failed`
|
|
2
|
+
|
|
3
|
+
An HTTP layer in front of RouterOS (auth proxy, tunnel edge, WAF) refused the
|
|
4
|
+
request before RouterOS answered it.
|
|
5
|
+
|
|
6
|
+
## How centrs tells
|
|
7
|
+
|
|
8
|
+
RouterOS answers every REST auth refusal with a JSON body whose `error` equals
|
|
9
|
+
the HTTP status (`{"error":401,"message":"Unauthorized"}`), adding
|
|
10
|
+
`WWW-Authenticate: Basic` only when no credentials were sent. A 401 or 403 with
|
|
11
|
+
any other body, or a challenge for another scheme (Cloudflare Access sends
|
|
12
|
+
`Bearer`), came from something in front of the router. `context` records the
|
|
13
|
+
challenge scheme, `server`, content type and whether a `cf-ray` header was
|
|
14
|
+
present. A `cf-ray` header alone does not decide it: a tunnel passes RouterOS's
|
|
15
|
+
own 401 through with one.
|
|
16
|
+
|
|
17
|
+
## Fix
|
|
18
|
+
|
|
19
|
+
centrs sends only RouterOS Basic credentials, so changing the RouterOS username
|
|
20
|
+
or password will not help. Point `--host` at an address that reaches RouterOS
|
|
21
|
+
directly, or at a local proxy that authenticates to the front layer for you.
|
|
22
|
+
The request never reached RouterOS, so a write was not applied.
|
|
@@ -9,6 +9,9 @@ whether the write happened.
|
|
|
9
9
|
|
|
10
10
|
- A connection closed or reset after an `execute` or `api` write was sent.
|
|
11
11
|
- A REST 5xx or a timeout on a write.
|
|
12
|
+
- A multi-statement `execute` or `api /execute` script refused with
|
|
13
|
+
`routeros/permission-denied`: statements before the refused one may already
|
|
14
|
+
have run (#549). A single refused statement did not run and warns nothing.
|
|
12
15
|
|
|
13
16
|
centrs never retries a write that it has sent, in single-target or fan-out
|
|
14
17
|
mode, because replaying an `add`, a script run or a counter change would apply
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# `usage/invalid-cursor`
|
|
2
|
+
|
|
3
|
+
An explain --cursor is not a byte offset inside the input being analyzed.
|
|
4
|
+
|
|
5
|
+
## Typical trigger
|
|
6
|
+
|
|
7
|
+
`centrs explain <router> '<input>' --complete --cursor <byte>` with a value that
|
|
8
|
+
is not a whole number, or that is larger than the input's UTF-8 byte length.
|
|
9
|
+
Library callers get the same code for an original-document position
|
|
10
|
+
(`{ line, character }`) that addresses no character in the input.
|
|
11
|
+
|
|
12
|
+
The check runs before any connection is made, so nothing is sent to the router.
|
|
13
|
+
|
|
14
|
+
## Fix
|
|
15
|
+
|
|
16
|
+
- Count the cursor in **UTF-8 bytes**, not characters: `é` is two bytes and
|
|
17
|
+
`🛰` is four. The end of the input (the default) is the input's byte length.
|
|
18
|
+
- A cursor inside a multi-byte character is accepted and moves back to that
|
|
19
|
+
character's first byte.
|
|
20
|
+
- Omit `--cursor` to complete or describe at the end of the input.
|
|
@@ -19,3 +19,12 @@ Two sources raise it:
|
|
|
19
19
|
|
|
20
20
|
Check the spelling of the segment in `error.context.segment`. `centrs explain
|
|
21
21
|
'<command>'` shows how the rest of the command reads offline.
|
|
22
|
+
|
|
23
|
+
One path is not a typo: `/system/routerboard` exists only on MikroTik
|
|
24
|
+
hardware, so `retrieve` answers its absence on CHR and x86 systems with a
|
|
25
|
+
targeted remediation instead of the generic one. For the device's license
|
|
26
|
+
ID, read `/system/license` (`system-id` on CHR, `software-id` on x86):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
centrs retrieve <router> /system/license
|
|
30
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikoci/centrs",
|
|
3
|
-
"version": "0.1.9-next.
|
|
3
|
+
"version": "0.1.9-next.27",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"packageManager": "bun@1.4.2",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@biomejs/biome": "2.
|
|
15
|
+
"@biomejs/biome": "2.6.3",
|
|
16
16
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.7",
|
|
17
17
|
"@types/bun": "1.4.2",
|
|
18
18
|
"cloc": "^2.6.0-cloc",
|
|
19
19
|
"cspell": "^10.3.6",
|
|
20
20
|
"has-flag": "4.0.0",
|
|
21
|
-
"markdownlint-cli2": "^0.
|
|
21
|
+
"markdownlint-cli2": "^0.24.0",
|
|
22
22
|
"secretlint": "^13.0.7",
|
|
23
23
|
"typedoc": "^0.28.20"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@tikoci/quickchr": "^0.
|
|
26
|
+
"@tikoci/quickchr": "^0.5.2"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"typescript": "^6.0.3"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bin": {
|
|
41
41
|
"centrs": "src/cli.ts"
|
|
42
42
|
},
|
|
43
|
-
"description": "All-in-one TypeScript library/CLI/
|
|
43
|
+
"description": "All-in-one TypeScript library/CLI/MCP for interacting with MikroTik RouterOS devices (TUI stub)",
|
|
44
44
|
"engines": {
|
|
45
45
|
"bun": ">=1.3.11 <2"
|
|
46
46
|
},
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
"explain:operator-readme:check": "bun run scripts/explain-operator-slice.ts --readme --check",
|
|
119
119
|
"explain:operator-slice": "bun run scripts/explain-operator-slice.ts",
|
|
120
120
|
"explain:probe:brace-slots": "bun run scripts/probes/explain-brace-slot-sweep.ts",
|
|
121
|
+
"explain:probe:context-matrix": "bun run scripts/probes/explain-context-matrix.ts",
|
|
122
|
+
"explain:probe:corrected-anchors": "bun run scripts/probes/explain-corrected-anchors.ts",
|
|
121
123
|
"explain:probe:escapes": "bun run scripts/probes/explain-escape-sweep.ts",
|
|
122
124
|
"explain:probe:highlight-recapture": "bun run scripts/probes/explain-highlight-recapture.ts",
|
|
123
125
|
"explain:probe:operators": "bun run scripts/probes/explain-operator-sweep.ts",
|
|
@@ -125,6 +127,8 @@
|
|
|
125
127
|
"explain:probe:symbol-arms-score": "bun run scripts/probes/explain-symbol-arms-score.ts",
|
|
126
128
|
"explain:probe:symbol-bad-sigil-replay": "bun run scripts/probes/explain-symbol-bad-sigil-replay.ts",
|
|
127
129
|
"explain:probe:symbol-classes-replay": "bun run scripts/probes/explain-symbol-classes-replay.ts",
|
|
130
|
+
"explain:probe:import-blocker-validity": "bun run scripts/probes/explain-import-blocker-validity.ts",
|
|
131
|
+
"explain:probe:import-dry-run": "bun run scripts/probes/explain-import-dry-run.ts",
|
|
128
132
|
"explain:probe:version-floors": "bun run scripts/probes/explain-version-floors.ts",
|
|
129
133
|
"explain:menus:check": "bun run scripts/gen-explain-menus.ts --check",
|
|
130
134
|
"explain:value-census": "bun run scripts/explain-value-census.ts",
|
package/src/api-fanout.ts
CHANGED
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
import { promptForWriteConfirmation } from "./execute.ts";
|
|
63
63
|
import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
|
|
64
64
|
import {
|
|
65
|
+
assertNoMatchInFanout,
|
|
65
66
|
emptyWriteSelectionError,
|
|
66
67
|
expandSelection,
|
|
67
68
|
isDefaultRecordTarget,
|
|
@@ -213,6 +214,7 @@ export async function apiFanout(
|
|
|
213
214
|
// missing endpoint or an invalid `-X` is an outer error regardless of how many
|
|
214
215
|
// targets the selection resolves to (so an empty selection cannot mask it).
|
|
215
216
|
validateApiRequestShape(request);
|
|
217
|
+
assertNoMatchInFanout(request.match);
|
|
216
218
|
const config = await loadEnvFileDefaults(env);
|
|
217
219
|
const method = parseApiMethod(request.method);
|
|
218
220
|
const requestSummary = apiRequestSummaryFromRequest(request, env);
|