@tikoci/centrs 0.1.8 → 0.1.9-next.22
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 +216 -31
- package/GLOSSARY.txt +8 -0
- package/README.md +16 -8
- package/SECURITY.md +11 -3
- package/commands/api/README.md +44 -15
- package/commands/api/examples.md +44 -6
- package/commands/execute/README.md +218 -7
- package/commands/execute/examples.md +278 -1
- package/commands/explain/README.md +2 -1
- package/commands/mcp/README.md +11 -0
- package/commands/mcp/examples.md +6 -0
- package/commands/retrieve/README.md +32 -14
- package/commands/retrieve/examples.md +10 -6
- package/commands/transfer/examples.md +9 -0
- package/docs/CLI.md +10 -8
- package/docs/CONSTITUTION.md +54 -3
- package/docs/MATRIX.md +20 -3
- package/docs/errors/cdb/empty-group.md +19 -3
- package/docs/errors/cdb/empty-selection.md +9 -3
- package/docs/errors/cmr/caller-policy-not-enforced.md +46 -0
- package/docs/errors/cmr/cancelled.md +15 -0
- package/docs/errors/cmr/client-disconnected.md +19 -0
- package/docs/errors/cmr/client-script-not-validated.md +22 -0
- package/docs/errors/cmr/console-only.md +29 -0
- package/docs/errors/cmr/empty-selection.md +33 -0
- package/docs/errors/cmr/membership-changed.md +20 -0
- package/docs/errors/cmr/not-a-controller.md +26 -0
- package/docs/errors/cmr/outcome-unknown.md +29 -0
- package/docs/errors/cmr/output-truncated.md +16 -0
- package/docs/errors/cmr/policy-denied.md +31 -0
- package/docs/errors/cmr/rule-selects-every-device.md +32 -0
- package/docs/errors/cmr/selection-includes-controller.md +27 -0
- package/docs/errors/cmr/selection-incomplete.md +18 -0
- package/docs/errors/cmr/validation-incomplete.md +23 -0
- package/docs/errors/routeros/version-unsupported.md +4 -0
- package/docs/errors/transport/write-outcome-unknown.md +24 -0
- package/docs/errors/usage/flag-after-separator.md +34 -0
- package/docs/errors/usage/missing-labels.md +19 -0
- package/package.json +7 -5
- package/src/api-fanout.ts +18 -2
- package/src/api.ts +230 -71
- package/src/btest.ts +7 -2
- package/src/cli/api.ts +30 -16
- package/src/cli/common.ts +180 -1
- package/src/cli/devices.ts +0 -2
- package/src/cli/discover.ts +0 -1
- package/src/cli/execute.ts +176 -21
- package/src/cli/explain.ts +2 -0
- package/src/cli/mcp.ts +0 -1
- package/src/cli/retrieve.ts +40 -18
- package/src/cli/settings.ts +0 -3
- package/src/cli/terminal.ts +0 -2
- package/src/cli/transfer.ts +10 -8
- package/src/cli/version.ts +87 -0
- package/src/cli.ts +5 -3
- package/src/core/cmr-fleet.ts +486 -0
- package/src/core/cmr.ts +182 -0
- package/src/core/envelope.ts +46 -1
- package/src/core/error-catalog.ts +95 -2
- package/src/core/execute-command.ts +144 -19
- package/src/core/fanout.ts +207 -14
- package/src/errors.ts +3 -1
- package/src/execute-cmr.ts +1442 -0
- package/src/execute-fanout.ts +15 -1
- package/src/execute.ts +173 -40
- package/src/index.ts +35 -1
- package/src/mcp/safety.ts +11 -0
- package/src/mcp/tools.ts +4 -1
- package/src/protocols/adapter.ts +54 -12
- package/src/resolver/cdb.ts +13 -1
- package/src/resolver/index.ts +4 -0
- package/src/resolver/selection.ts +37 -1
- package/src/resolver/target.ts +96 -8
- package/src/retrieve-fanout.ts +4 -1
- package/src/retrieve-follow.ts +29 -33
- package/src/retrieve-sample.ts +14 -8
- package/src/retrieve-stream.ts +38 -6
- package/src/retrieve.ts +73 -59
- package/src/transfer-fanout.ts +8 -1
- package/src/transfer.ts +15 -19
|
@@ -262,13 +262,19 @@ target only: a fan-out selector fails `usage/fanout-not-supported`. Follow is
|
|
|
262
262
|
filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
263
263
|
|
|
264
264
|
**What each line means.** Every line is one envelope; read
|
|
265
|
-
`meta.operation.stream
|
|
265
|
+
`meta.operation.stream`. Only `started`, `synced` and `summary` carry the full
|
|
266
|
+
`meta` (target, settings, validation, request, auth). A `frame` line carries
|
|
267
|
+
only `meta.operation` (`kind`, `objectCount`, `stream`), because the rest is
|
|
268
|
+
the same on every line. An error line is a full error envelope: the one line
|
|
269
|
+
of a follow that fails before it starts (resolution, validation, a
|
|
270
|
+
non-followable menu, a non-native transport), and a failed `summary`
|
|
271
|
+
([constitution](../../docs/CONSTITUTION.md#result-envelope)).
|
|
266
272
|
|
|
267
273
|
| `kind` | When | `data` | Fields |
|
|
268
274
|
| ------ | ---- | ------ | ------ |
|
|
269
|
-
| `frame` | One row change | the row (`upsert`), `null` (`removed`) | `index`, `phase` (`snapshot`/`live`), `change` (`upsert`/`removed`), `id`, `source` (`print`/`listen`/`sweep`/`membership`) |
|
|
275
|
+
| `frame` | One row change | the row (`upsert`), `null` (`removed`) | `index`, `observedAt`, `phase` (`snapshot`/`live`), `change` (`upsert`/`removed`), `id`, `source` (`print`/`listen`/`sweep`/`membership`) |
|
|
276
|
+
| `started` | First, once validation passed | `null` | — (`tips` carries any advice) |
|
|
270
277
|
| `synced` | Once, after the snapshot | `null` | `rows`: rows held at that point |
|
|
271
|
-
| `notice` | First, only when there is advice | `null` | — (`tips` carries it) |
|
|
272
278
|
| `summary` | Last, exactly once | the counts | `stopReason`, `frames`, `snapshot`, `changes`, `sweeps`, `synced`, `durationMs` |
|
|
273
279
|
|
|
274
280
|
- **Bootstrap (A1).** centrs starts the `listen`, then the `print`, on one
|
|
@@ -279,6 +285,11 @@ filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
|
279
285
|
order resurrects deleted rows (#396 round 3). `synced` means the bootstrap
|
|
280
286
|
was processed. It is not an atomic snapshot, a full event history, or
|
|
281
287
|
routing convergence.
|
|
288
|
+
- **`observedAt`** (ISO 8601, host clock) is when centrs received the reply
|
|
289
|
+
the frame is built from: the `listen` change, the snapshot row, the
|
|
290
|
+
membership read or the sweep. A bootstrap change replayed after the
|
|
291
|
+
snapshot keeps its arrival time, so `observedAt` is not ordered by `index`.
|
|
292
|
+
A slow reader does not move it.
|
|
282
293
|
- **`upsert`** carries the latest full row; replace what you hold for `id`.
|
|
283
294
|
**`removed`** carries `data: null`; drop `id`. The row identity is always
|
|
284
295
|
`meta.operation.stream.id`, even when `--attributes` leaves `.id` out of
|
|
@@ -297,8 +308,8 @@ filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
|
297
308
|
sweep only checks membership: it does not repair a missed field update or
|
|
298
309
|
report rows `listen` never announced. `--sweep` needs a unit (`10s`,
|
|
299
310
|
`500ms`); a bare number fails `settings/invalid-timeout`, as for `--sample`.
|
|
300
|
-
`--sweep 0` turns it off, with a
|
|
301
|
-
not be reported.
|
|
311
|
+
`--sweep 0` turns it off, with a tip on `started` saying view-menu removals
|
|
312
|
+
will not be reported.
|
|
302
313
|
- **What `listen` never reports.** Hot counters (`rx-byte`, firewall
|
|
303
314
|
`bytes`/`packets`) and some protocol state (BFD sessions accept `listen` but
|
|
304
315
|
stay silent). The follow is correct but quiet there; `--sample` (below)
|
|
@@ -320,7 +331,8 @@ filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
|
320
331
|
indefinitely or postpone the first sweep and live `--count`. This may
|
|
321
332
|
coalesce more changes than unfiltered A1 replay.
|
|
322
333
|
The filtered sweep also detects silent exits from a predicate (e.g. a
|
|
323
|
-
decreasing address-list `timeout`)
|
|
334
|
+
decreasing address-list `timeout`), reported as `removed` with
|
|
335
|
+
`source: "sweep"`. It does not discover silent entries or
|
|
324
336
|
repair silent field updates: use `--sample` for such state. With `--sweep 0`,
|
|
325
337
|
silent exits remain held until a listen notification arrives.
|
|
326
338
|
- **Projection.** `--attribute(s)` projects `data`; centrs still asks RouterOS
|
|
@@ -346,9 +358,11 @@ new snapshot. `--sweep` without `--follow`, and `--count` or `--duration`
|
|
|
346
358
|
without `--follow` or `--sample`, fail `usage/conflicting-flags`.
|
|
347
359
|
|
|
348
360
|
**Format.** Under `--follow`, `json`, `yaml` and `ndjson` all print one compact
|
|
349
|
-
envelope per line (as `api --stream` does)
|
|
350
|
-
|
|
351
|
-
|
|
361
|
+
envelope per line (as `api --stream` does), errors included, on stdout.
|
|
362
|
+
`text` prints one short row per `frame` on stdout; the `started` tips, the
|
|
363
|
+
`synced` and summary lines, warnings and errors go to stderr, since text
|
|
364
|
+
stdout is data ([constitution](../../docs/CONSTITUTION.md#result-envelope)).
|
|
365
|
+
The exit code is nonzero for any failure, even after rows. Without `--follow`,
|
|
352
366
|
`--format ndjson` prints the ordinary envelope as one compact line; it never
|
|
353
367
|
starts a follow.
|
|
354
368
|
|
|
@@ -387,10 +401,13 @@ stays available as `api <router> <menu>/print -X POST -f interval=1s --stream`
|
|
|
387
401
|
(`commands/api/README.md`).
|
|
388
402
|
|
|
389
403
|
**What each line means.** Every line is one envelope; read
|
|
390
|
-
`meta.operation.stream
|
|
404
|
+
`meta.operation.stream`. `started` and `summary` carry the full `meta`; a
|
|
405
|
+
`sample` line carries only `meta.operation` (`kind`, `objectCount`, `stream`),
|
|
406
|
+
as a follow `frame` does:
|
|
391
407
|
|
|
392
408
|
| `kind` | When | `data` | Fields |
|
|
393
409
|
| ------ | ---- | ------ | ------ |
|
|
410
|
+
| `started` | First, once validation passed | `null` | — |
|
|
394
411
|
| `sample` | One complete read | the whole read: a row array (list), the record (singleton), or the bare value (one `--attribute` on a singleton) | `index` (1-based), `at` (when the read was sent, host clock, ISO 8601), `readMs` |
|
|
395
412
|
| `summary` | Last, exactly once | the counts | `stopReason`, `samples`, `durationMs` |
|
|
396
413
|
|
|
@@ -415,9 +432,9 @@ carrying the partial `samples` count; there is no retry. `--list-attributes`, `-
|
|
|
415
432
|
`--sweep` conflict with `--sample` (`usage/conflicting-flags`).
|
|
416
433
|
|
|
417
434
|
**Format.** As for `--follow`: `json`, `yaml` and `ndjson` print one compact
|
|
418
|
-
envelope per line,
|
|
419
|
-
|
|
420
|
-
any failure, even after samples.
|
|
435
|
+
envelope per line, errors included, on stdout. `text` prints `index`, `at` and
|
|
436
|
+
the data on one stdout line per sample, and the summary, warnings and errors
|
|
437
|
+
on stderr. The exit code is nonzero for any failure, even after samples.
|
|
421
438
|
|
|
422
439
|
Recipes (bounded, so an agent can run them unattended):
|
|
423
440
|
|
|
@@ -513,7 +530,8 @@ retrieve-specific behavior on top of the shared engine:
|
|
|
513
530
|
`transport/connection-closed` (REST 5xx is mapped to
|
|
514
531
|
`transport/connection-closed`). It does **not** retry `routeros/*`,
|
|
515
532
|
`validation/*`, `auth/*`, `cdb/*`, `target/*`, timeouts, DNS, TLS, or refused
|
|
516
|
-
connections.
|
|
533
|
+
connections. (`execute` and `api` share this engine but never replay a write
|
|
534
|
+
that was sent.)
|
|
517
535
|
|
|
518
536
|
## Definition of done
|
|
519
537
|
|
|
@@ -434,7 +434,9 @@ centrs retrieve $A /ip/address --follow --duration 3s --port $API_PORT --usernam
|
|
|
434
434
|
Every line parses as an envelope. Frames before `synced` have
|
|
435
435
|
`phase: "snapshot"` and `source: "print"`; exactly one `synced`; the last line
|
|
436
436
|
is the summary with `stopReason: "duration-elapsed"`, `synced: true`,
|
|
437
|
-
`changes: 0`. Every frame carries `meta.operation.stream.id
|
|
437
|
+
`changes: 0`. Every frame carries `meta.operation.stream.id` and an ISO 8601
|
|
438
|
+
`observedAt`, and no other meta; `synced` and the summary carry the full
|
|
439
|
+
`meta` (`target`, `via`, `settings`, …).
|
|
438
440
|
|
|
439
441
|
### FL2. A live change, and `--count` that ignores the snapshot
|
|
440
442
|
|
|
@@ -454,7 +456,8 @@ centrs retrieve $A /ip/address --follow --sweep 0 --count 1 --duration 15s --por
|
|
|
454
456
|
|
|
455
457
|
After `synced`, remove an address that existed before the follow. One `live`
|
|
456
458
|
`removed` frame, `source: "listen"`, `data: null`. `--sweep 0` keeps the sweep
|
|
457
|
-
out of the race, and its `tip/follow-sweep-off`
|
|
459
|
+
out of the race, and its `tip/follow-sweep-off` tip rides the first line,
|
|
460
|
+
`started`.
|
|
458
461
|
|
|
459
462
|
### FL4. A removal RouterOS does not send (view menu → sweep)
|
|
460
463
|
|
|
@@ -529,9 +532,10 @@ centrs retrieve $R /ip/address --sample 1s --count 3 --username $U --password $P
|
|
|
529
532
|
centrs retrieve $A /ip/address --via native-api --sample 1s --count 3 --port $API_PORT --username $U --password $P --json
|
|
530
533
|
```
|
|
531
534
|
|
|
532
|
-
Each run prints three `sample` lines (`index` 1–3, `data` a row
|
|
533
|
-
summary with `stopReason: "count-reached"`, `samples: 3`.
|
|
534
|
-
values are at least one second apart. `
|
|
535
|
+
Each run prints `started`, three `sample` lines (`index` 1–3, `data` a row
|
|
536
|
+
array) and a summary with `stopReason: "count-reached"`, `samples: 3`.
|
|
537
|
+
Consecutive `at` values are at least one second apart. The `started` line's
|
|
538
|
+
`meta.via` is the transport used.
|
|
535
539
|
|
|
536
540
|
### SA2. Counters `listen` never reports
|
|
537
541
|
|
|
@@ -687,7 +691,7 @@ centrs retrieve $A /ip/firewall/address-list --via native-api --port $API_PORT -
|
|
|
687
691
|
|
|
688
692
|
The harness adds a row with a 10-second timeout. A sweep removes it from the
|
|
689
693
|
selected set as its remaining timeout crosses 8 seconds, with
|
|
690
|
-
`source: "
|
|
694
|
+
`source: "sweep"`; the row still exists in an unfiltered read.
|
|
691
695
|
|
|
692
696
|
### FQ4. CLI alias and predicate validation
|
|
693
697
|
|
|
@@ -463,6 +463,15 @@ centrs transfer --group no-such-group list --via rest --cdb-file $CDB --json
|
|
|
463
463
|
`ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`,
|
|
464
464
|
`warnings` include `cdb/empty-group`. Exit `0`.
|
|
465
465
|
|
|
466
|
+
A write verb (`upload`, `remove`, `mkdir`, `copy`) over the same group is an
|
|
467
|
+
outer error, with or without `--yes` (#460):
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
centrs transfer --group no-such-group remove fanout-test.txt --via rest --cdb-file $CDB --yes --json
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`ok: false`, `error.code` `cdb/empty-group`. Exit `1`.
|
|
474
|
+
|
|
466
475
|
### F3. `--where` device-class selector (subset)
|
|
467
476
|
|
|
468
477
|
```bash
|
package/docs/CLI.md
CHANGED
|
@@ -51,8 +51,8 @@ Usage: centrs retrieve <target> <routeros-path> [flags] | centrs retrieve <targe
|
|
|
51
51
|
| `--all-attributes` | | Request the RouterOS detail/all-attributes shape. |
|
|
52
52
|
| `--list-attributes / --list` | | List inspect-derived attributes without running the data call. |
|
|
53
53
|
| `--query / --filter` | `<expr>` | Row filter written as after `print where` (`disabled`, `mtu>=1500 and !dynamic`), run on the router as an API query; repeatable, AND-ed. No `~` or `in`. |
|
|
54
|
-
| `--follow` | | Keep the menu's rows current over native-api `listen`: snapshot frames, a `synced` line, then a line per change, then a summary (NDJSON for json/yaml/ndjson). Single target, list menus whose print takes follow-only. |
|
|
55
|
-
| `--sample` | `<interval>` | Read the menu again every interval (with a unit, e.g. `5s`): one line per complete read, then a summary. With --wait, sets polling cadence and returns one final envelope. Sees counters and state listen never reports. Single target; rest-api or native-api. |
|
|
54
|
+
| `--follow` | | Keep the menu's rows current over native-api `listen`: a `started` line, snapshot frames, a `synced` line, then a line per change, then a summary (NDJSON for json/yaml/ndjson). Single target, list menus whose print takes follow-only. |
|
|
55
|
+
| `--sample` | `<interval>` | Read the menu again every interval (with a unit, e.g. `5s`): a `started` line, one line per complete read, then a summary. With --wait, sets polling cadence and returns one final envelope. Sees counters and state listen never reports. Single target; rest-api or native-api. |
|
|
56
56
|
| `--wait` | `<deadline>` | Wait for a successful read or a condition until this overall deadline, with a unit (e.g. `2m`). Retries refused, closed, reset and timed-out connections and a down or unreachable host; other errors fail at once. One final envelope; single target. |
|
|
57
57
|
| `--until` | `<expr>` | With --wait: succeed when any selected row matches this --query grammar predicate; repeatable, AND-ed. Returns the matching rows. Already true succeeds on the initial read. |
|
|
58
58
|
| `--until-empty` | | With --wait: succeed on a completed read with zero rows selected by --query. Silence and failed reads never satisfy it. |
|
|
@@ -66,7 +66,7 @@ Usage: centrs retrieve <target> <routeros-path> [flags] | centrs retrieve <targe
|
|
|
66
66
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
67
67
|
| `--resolve` | <none\|arp> | Resolve a MAC-address target to an IP via the host ARP cache (default none). |
|
|
68
68
|
| `--validate / --no-validate` | | Enable or disable inspect-backed preflight validation. |
|
|
69
|
-
| `--verbose` | | Show resolved setting sources in text output. |
|
|
69
|
+
| `--verbose` | | Show resolved setting sources in text output, on stderr. |
|
|
70
70
|
|
|
71
71
|
## execute
|
|
72
72
|
|
|
@@ -87,6 +87,8 @@ Usage: centrs execute <target> <command> [flags] | centrs execute <target...> --
|
|
|
87
87
|
| `--default` | | Select the reserved `__default__` record. |
|
|
88
88
|
| `--quickchr` | `<name>` | Target a running quickchr-managed CHR VM by name (repeatable; fans out when repeated). Exclusive of positional targets and CDB selectors; conflicts with direct connection overrides (--host/--port/--username/--password, and --ssh-key where the command has it). |
|
|
89
89
|
| `--concurrency` | `<n>` | Max in-flight targets during fan-out (transport-aware default: rest-api 8, native-api 4). |
|
|
90
|
+
| `--cmr` | | Run on CMR clients through their controller: the `<target>` is the controller (RouterOS 7.26beta1+, `cmr` package). Needs `--labels`; native-api or rest-api only. |
|
|
91
|
+
| `--labels` | `<expr>` | With `--cmr`: RouterOS label expression selecting clients (comma list; plain = OR, `+` AND, `-` AND NOT, `all`). |
|
|
90
92
|
| `--host` | <host\|url> | Host or base URL to dial. With no `<target>` it is the target (no CDB lookup); with one it overrides the resolved host. |
|
|
91
93
|
| `--port` | `<port>` | Override the resolved management port. |
|
|
92
94
|
| `--username / --user / -u` | `<name>` | RouterOS username (aliases `--user`, `-u`). Falls back to `CENTRS_USERNAME`. |
|
|
@@ -102,8 +104,8 @@ Usage: centrs execute <target> <command> [flags] | centrs execute <target...> --
|
|
|
102
104
|
| `--max-results` | `<bytes>` | Fail if the rendered envelope exceeds this byte budget. |
|
|
103
105
|
| `--format` | <text\|json\|yaml> | Output format for the CLI response. Defaults to text; use --json or --format json for the structured envelope. |
|
|
104
106
|
| `--json` | | Shortcut for `--format json`. |
|
|
105
|
-
| `--verbose` | | Include additional context in text output. |
|
|
106
|
-
| `--` | | End centrs option parsing: every following token is the literal RouterOS command
|
|
107
|
+
| `--verbose` | | Include additional context in text output, on stderr. |
|
|
108
|
+
| `--` | | End centrs option parsing: every following token is the literal RouterOS command (e.g. `-- /interface print where disabled=yes`). Put centrs flags before it; one of execute's own long flags after it is refused as `usage/flag-after-separator`. |
|
|
107
109
|
|
|
108
110
|
## api
|
|
109
111
|
|
|
@@ -124,7 +126,7 @@ Usage: centrs api <router> <endpoint> [flags] | centrs api --host <host|url> <en
|
|
|
124
126
|
| `--attribute / --proplist` | `<a,b>` | Property projection → `.proplist`. |
|
|
125
127
|
| `--raw` | | Strip the envelope; emit bare RouterOS JSON. Defaults `--validate` to false (an explicit `--validate=true` still runs the gate); does not imply `--yes`. |
|
|
126
128
|
| `--yes` | | Confirm a mutating (non-read) request in non-interactive runs. |
|
|
127
|
-
| `--stream` | | Stream the command's native replies (`!re` rows and `!empty`) as NDJSON, then a summary envelope. Never changes the command: a GET streams one print. To follow changes, request `<menu>/listen`, which implies it. |
|
|
129
|
+
| `--stream` | | Stream the command's native replies (`!re` rows and `!empty`) as NDJSON: a `started` line, a line per reply, then a summary envelope. Never changes the command: a GET streams one print. To follow changes, request `<menu>/listen`, which implies it. |
|
|
128
130
|
| `--count` | `<n>` | Stop a `--stream` after N `!re` rows; `!empty` replies do not count (not the device count= argument). |
|
|
129
131
|
| `--duration` | `<dur>` | Stop a `--stream` after this wall-clock window (e.g. `5s`). |
|
|
130
132
|
| `--via` | <rest-api\|native-api> | Pin the transport; no silent downgrade. Default rest-api. |
|
|
@@ -148,7 +150,7 @@ Usage: centrs api <router> <endpoint> [flags] | centrs api --host <host|url> <en
|
|
|
148
150
|
| `--validate / --no-validate` | | Run `/console/inspect` validation before the request (default true; `--validate=false` also accepted). |
|
|
149
151
|
| `--format` | <json\|ndjson\|yaml\|text> | Output format. Defaults to json for api; `CENTRS_FORMAT` overrides. |
|
|
150
152
|
| `--json` | | Shortcut for `--format json`. |
|
|
151
|
-
| `--verbose` | | Include additional context in text output. |
|
|
153
|
+
| `--verbose` | | Include additional context in text output, on stderr. |
|
|
152
154
|
|
|
153
155
|
## transfer
|
|
154
156
|
|
|
@@ -188,7 +190,7 @@ Usage: centrs transfer <router>|--host <host> upload <local> [remote] | download
|
|
|
188
190
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
189
191
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
190
192
|
| `--validate / --no-validate` | | Enable or disable the existence-probe preflight. |
|
|
191
|
-
| `--verbose` | | Show the resolved transport in text output. |
|
|
193
|
+
| `--verbose` | | Show the resolved transport in text output, on stderr. |
|
|
192
194
|
|
|
193
195
|
## terminal
|
|
194
196
|
|
package/docs/CONSTITUTION.md
CHANGED
|
@@ -177,8 +177,29 @@ Notes:
|
|
|
177
177
|
lossless rule applies to **stdout** only: **stderr** carries progress and
|
|
178
178
|
interactive prompts (discover scan progress, write/ambiguity confirmation) and
|
|
179
179
|
is exempt — use it sparingly, since most operations are fast.
|
|
180
|
+
- **Text stdout is data** (#470). In text mode a single-target `execute`,
|
|
181
|
+
`api`, `retrieve` or `transfer` writes only `data` (or its error) to stdout.
|
|
182
|
+
The notes around it go to stderr: the `--verbose` header first, then one
|
|
183
|
+
`warning [<code>]: <message>` line per warning, then the `Tips:` footer, on
|
|
184
|
+
success and on failure alike. `transport/provider-trust` is left out of text
|
|
185
|
+
because it rides every quickchr run over TLS or SSH. `--json` keeps
|
|
186
|
+
everything, `api --raw` stays a bare payload, and fan-out text is a report,
|
|
187
|
+
not data, so it keeps its warnings on stdout. A text stream writes only its
|
|
188
|
+
data rows (`frame`, `sample`) to stdout; its `started`, `synced` and
|
|
189
|
+
`summary` lines, tips, warnings and errors go to stderr (#473).
|
|
180
190
|
- `data` is the only field whose *shape* varies by command. Everything else —
|
|
181
191
|
including `warnings` and `tips` — is invariant in shape; only contents differ.
|
|
192
|
+
- **Shared meta is written once** (#461, #462). A fan-out target's
|
|
193
|
+
`meta.settings` keeps only the keys whose source differs from the outer
|
|
194
|
+
`meta.settings`, and its `meta.operation` drops `request` and `auth` when
|
|
195
|
+
they equal the outer operation's. When every target agrees on `auth`, or
|
|
196
|
+
on a setting the outer `meta.settings` lacks, it moves to the outer
|
|
197
|
+
envelope. A reader takes the target's value, else
|
|
198
|
+
the outer one (`expandFanoutTarget`). A stream (`retrieve --follow`,
|
|
199
|
+
`retrieve --sample`, `api --stream`) opens with a `started` line that
|
|
200
|
+
carries the full meta and any request advice in `tips`, and ends with a
|
|
201
|
+
full-meta `summary` (`--follow`'s `synced` is full too). The data lines
|
|
202
|
+
between (`frame`, `sample`) carry only `meta.operation` (#473).
|
|
182
203
|
|
|
183
204
|
## Error model
|
|
184
205
|
|
|
@@ -304,12 +325,16 @@ Fan-out output is the locked **`FanoutData`** envelope: `data = { summary, targe
|
|
|
304
325
|
is an inner `ok:false` envelope (output, not metadata). `targets[]` is reassembled
|
|
305
326
|
in resolved record order, not completion order, so repeated runs diff cleanly. The
|
|
306
327
|
**process exit code** is granular and uniform across fan-out commands: `0` every
|
|
307
|
-
target ok (or an empty selection), `2` partial (some ok, some
|
|
308
|
-
orchestration error or every target failed.
|
|
328
|
+
target ok (or an empty selection for a read), `2` partial (some ok, some
|
|
329
|
+
failed), `1` orchestration error or every target failed. An empty selection is
|
|
330
|
+
a warning for a read and an orchestration error for a write, `--yes` or not,
|
|
331
|
+
so a mistyped selector never passes for a successful bulk write (#460).
|
|
309
332
|
|
|
310
333
|
A multi-target **RouterOS write** (e.g. `execute` / `api` add/set/remove across a
|
|
311
334
|
selection) is gated by **`--yes`**, confirmed **once** up front; when it is missing
|
|
312
|
-
the error names the blast radius (how many routers).
|
|
335
|
+
the error names the blast radius (how many routers). A write that was **sent** is
|
|
336
|
+
never retried, because it may already have run; it fails with the warning
|
|
337
|
+
`transport/write-outcome-unknown` instead (#448). The separate **`--force`**
|
|
313
338
|
gate is scoped to destructive **`devices` CDB mutations** (e.g. `devices remove`
|
|
314
339
|
across a group), not RouterOS writes. `terminal`, `api --stream`,
|
|
315
340
|
`retrieve --follow` and `retrieve --sample` are single-session, not fan-out
|
|
@@ -365,6 +390,32 @@ single host/port: that shape is the contract quickchr's descriptor and TikTOML's
|
|
|
365
390
|
exists to prevent. A `--via` whose service key a provider does not supply is a
|
|
366
391
|
typed unsupported-provider error, never a silent fallback.
|
|
367
392
|
|
|
393
|
+
#### Controller-mediated selection (CMR)
|
|
394
|
+
|
|
395
|
+
A **CMR controller** (RouterOS 7.26beta1+) is a third kind of source: a live
|
|
396
|
+
inventory whose members centrs never dials. `execute <controller> --cmr
|
|
397
|
+
--labels <expr>` (#450) resolves the **controller** as an ordinary single
|
|
398
|
+
target, and only its credentials are used. RouterOS's label expression selects
|
|
399
|
+
the members, and the command runs on each through the controller's
|
|
400
|
+
`run-script`. Normative:
|
|
401
|
+
|
|
402
|
+
- CMR is a selection source, **not a protocol**. `--via` pins the controller
|
|
403
|
+
transport, and a CMR failure never falls back to dialing a client.
|
|
404
|
+
- A member is bound by the controller's row id, never by its display name. A
|
|
405
|
+
result that cannot be bound to the member it was validated on is not run, or
|
|
406
|
+
is reported as an unknown outcome.
|
|
407
|
+
- Validation runs **on each client** (`:parse` through CMR) and must be complete
|
|
408
|
+
before it counts. A client that fails validation is never sent the command,
|
|
409
|
+
and an incomplete selection runs nothing.
|
|
410
|
+
- A CMR dispatch is **never retried**. A timeout, cancellation or lost call
|
|
411
|
+
after sending is `cmr/outcome-unknown`, because the client script may still
|
|
412
|
+
be running.
|
|
413
|
+
- CMR mode is not on MCP until delegated authorization is designed (#449).
|
|
414
|
+
Registering a controller must not authorize its clients.
|
|
415
|
+
|
|
416
|
+
The contract and its device evidence are in
|
|
417
|
+
[`commands/execute/README.md` → CMR](../commands/execute/README.md#cmr-clients-through-their-controller---cmr).
|
|
418
|
+
|
|
368
419
|
### MCP surface: the CDB is the allowlist
|
|
369
420
|
|
|
370
421
|
The MCP frontend (`commands/mcp/`) is an adapter over this core, not a new set of
|
package/docs/MATRIX.md
CHANGED
|
@@ -56,6 +56,11 @@ and implementation sequencing in the linked issues.
|
|
|
56
56
|
- `retrieve`, `api`, `execute`, and `transfer` share the target-selection and
|
|
57
57
|
fan-out contracts from `docs/CONSTITUTION.md`; command-specific boundaries and
|
|
58
58
|
evidence are in their command READMEs and integration tests.
|
|
59
|
+
- `execute --cmr` (#450) is not a grid column: CMR is a selection source, and
|
|
60
|
+
its controller transports are the `execute` rest-api/native-api cells.
|
|
61
|
+
CMR1–CMR7 and CMR10–CMR14 pass on a 7.26beta1 CHR lab over both, and
|
|
62
|
+
CMR8–CMR9 over native-api (`test/integration/execute-cmr.test.ts`). CMR0 (a
|
|
63
|
+
router without CMR) runs on the channel's CHR in `execute.test.ts`.
|
|
59
64
|
- There is no `update` command. RouterOS CLI-shaped writes ride `execute`; the
|
|
60
65
|
structured operation surface is `api`.
|
|
61
66
|
- `api --stream` absorbs the former `stream`/`tail` surface; native-api stream
|
|
@@ -177,15 +182,27 @@ Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
|
|
|
177
182
|
|
|
178
183
|
## Current priority
|
|
179
184
|
|
|
180
|
-
**First: the
|
|
185
|
+
**First: the CMR track** (maintainer decision 2026-10-12 on
|
|
186
|
+
[#441](https://github.com/tikoci/centrs/issues/441)). Done: `execute --cmr`
|
|
187
|
+
(#450), which runs one validated command on CMR clients through their
|
|
188
|
+
controller. The contract is in
|
|
189
|
+
[`commands/execute/README.md` → CMR](../commands/execute/README.md#cmr-clients-through-their-controller---cmr),
|
|
190
|
+
with examples CMR0–CMR15. Also done: the fleet blast-radius gate for
|
|
191
|
+
controller fleet commands run with plain `execute` or `api` (#453), with
|
|
192
|
+
examples CMR16–CMR18 and api CMR1. The feature map of CMR against centrs is
|
|
193
|
+
on #441 (2026-10-13). Next is the read-only slice of #449 (a `devices` view
|
|
194
|
+
of members and a default controller). Deferred: CDB linkage (#449) and `retrieve --cmr` (#441).
|
|
195
|
+
Live `explain` resumes after this.
|
|
196
|
+
|
|
197
|
+
**The native-api / NDJSON line** (maintainer decision on
|
|
181
198
|
[#396](https://github.com/tikoci/centrs/issues/396), 2026-10-07): agents in the
|
|
182
199
|
field poll with `sleep` where a native follow would answer. In order:
|
|
183
200
|
(1) done: #402, literal `api --stream` with `!empty` frames and
|
|
184
201
|
`--format ndjson`; (2) done: `retrieve --follow` (#396, unfiltered);
|
|
185
202
|
(3) done: `retrieve --sample`; (4) #397: one-shot `retrieve --query` done,
|
|
186
203
|
filtered `--follow` done; #364 (`--wait`/`--until`, same grammar) done;
|
|
187
|
-
(5) #398 and #401. Each slice's contract lives in its command
|
|
188
|
-
README.
|
|
204
|
+
(5) #398 and #401 remain open. Each slice's contract lives in its command
|
|
205
|
+
README.
|
|
189
206
|
|
|
190
207
|
**Tightening validation as a whole** is the next track, in the three-step
|
|
191
208
|
order agreed on [#236](https://github.com/tikoci/centrs/issues/236):
|
|
@@ -2,8 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
The named CDB group contains no records.
|
|
4
4
|
|
|
5
|
+
## Typical trigger
|
|
6
|
+
|
|
7
|
+
A fan-out selected only by `--group` (no other selector) whose group matched
|
|
8
|
+
no record: a misspelled or case-mismatched group name, or a group whose last
|
|
9
|
+
member was removed.
|
|
10
|
+
|
|
11
|
+
- For a **read**, it is a warning on an `ok: true` fan-out envelope with
|
|
12
|
+
`summary { total: 0, ok: 0, failed: 0 }` and exit code 0.
|
|
13
|
+
- For a **write-shaped** fan-out (`execute` write, a mutating `api` method,
|
|
14
|
+
`transfer upload|remove|mkdir|copy`) it is an outer error (`ok: false`, exit
|
|
15
|
+
code 1), with or without `--yes`. `context.write` is `true`.
|
|
16
|
+
|
|
17
|
+
Any other empty selection (`--where`, `--all`, positionals, mixed) is
|
|
18
|
+
[`cdb/empty-selection`](empty-selection.md).
|
|
19
|
+
|
|
5
20
|
## Fix
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
for
|
|
22
|
+
Run `centrs devices groups` to see the groups that exist and their member
|
|
23
|
+
counts, then re-run with the right name. See
|
|
24
|
+
[`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Target selection) for the
|
|
25
|
+
selector grammar.
|
|
@@ -5,9 +5,15 @@ The target selection matched no CDB records.
|
|
|
5
5
|
## Fix
|
|
6
6
|
|
|
7
7
|
A fan-out selection involving `--where` / `--all` / `--default` / positionals (on
|
|
8
|
-
its own or mixed with `--group`) resolved to zero targets.
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
its own or mixed with `--group`) resolved to zero targets.
|
|
9
|
+
|
|
10
|
+
- For a **read**, it is a warning on an `ok: true` fan-out envelope with
|
|
11
|
+
`summary { total: 0, ok: 0, failed: 0 }` and exit code 0: there was simply
|
|
12
|
+
nothing to read.
|
|
13
|
+
- For a **write-shaped** fan-out (`execute` write, a mutating `api` method,
|
|
14
|
+
`transfer upload|remove|mkdir|copy`) it is an outer error (`ok: false`, exit
|
|
15
|
+
code 1), with or without `--yes`, so a mistyped selector does not look like
|
|
16
|
+
a successful bulk write. `context.write` is `true`.
|
|
11
17
|
|
|
12
18
|
A **group-only** empty (bare `--group`, no other selector) is reported as
|
|
13
19
|
[`cdb/empty-group`](empty-group.md) instead, naming the group that matched nothing;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `cmr/caller-policy-not-enforced`
|
|
2
|
+
|
|
3
|
+
A warning on `execute --cmr`, and on `/cmr/device/run-script` or
|
|
4
|
+
`/cmr/device/reboot` run directly with `execute` or `api`: the controller login
|
|
5
|
+
lacks a policy that the command runs with on the clients, and RouterOS does
|
|
6
|
+
not check it. The login does not limit what a CMR command can change.
|
|
7
|
+
|
|
8
|
+
## Typical trigger
|
|
9
|
+
|
|
10
|
+
A write through `--cmr` with a controller login whose group lacks `write`,
|
|
11
|
+
`test` or `reboot`, such as a "reader" account given to an agent. CMR runs
|
|
12
|
+
every `run-script` script on each selected client with `read`, `write`,
|
|
13
|
+
`test` and `reboot`, the controller's own row included. On 7.26beta1,
|
|
14
|
+
RouterOS checked only that the caller had `read` and `api`. These results
|
|
15
|
+
come from the CHR reproduction on #456 (REST, two-CHR lab, 2026-10-13); the
|
|
16
|
+
same `run-script` write was seen on real hardware, and the integration test
|
|
17
|
+
re-proves it (CMR15 in `commands/execute/examples.md`):
|
|
18
|
+
|
|
19
|
+
| Controller login | Direct write or reboot | Through CMR |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `read,api,rest-api` | `not enough permissions (9)` | `run-script` wrote to a client and rebooted it |
|
|
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
|
+
|
|
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. That has not been checked on a beta2 build yet. centrs
|
|
26
|
+
still warns on any version, because the warning reads the login's policy, not
|
|
27
|
+
the RouterOS version. On a build that refuses the write, the members fail
|
|
28
|
+
instead and the warning only says what the login lacks.
|
|
29
|
+
|
|
30
|
+
`context` names the login (`username`, `group`), the policies it lacks
|
|
31
|
+
(`missing`) and the ones the command runs with (`runsWith`): `read`, `write`,
|
|
32
|
+
`test` and `reboot` for `run-script`, and `reboot` for `/cmr/device/reboot`.
|
|
33
|
+
centrs reads the policy once per run, from the login's own `/user` row and
|
|
34
|
+
`/user/group`, and only for a write. When that read fails (for example a
|
|
35
|
+
RADIUS login with no `/user` row) there is no warning. Other fleet commands
|
|
36
|
+
(`/cmr/device/upgrade`, `pair`, the rule menus) were not measured, so they
|
|
37
|
+
carry no such warning.
|
|
38
|
+
|
|
39
|
+
## Fix
|
|
40
|
+
|
|
41
|
+
Nothing failed; this is information. Do not treat a controller login as
|
|
42
|
+
read-only once the `cmr` package is installed (on 7.26beta1 at least). The
|
|
43
|
+
gates that hold are centrs' own: pass `--yes` only when you intend the write
|
|
44
|
+
on every selected client, and over MCP keep the controller's record at
|
|
45
|
+
`mcp=ro` (the default), which refuses CMR fleet commands outright. Give an
|
|
46
|
+
agent a login on the controller only if it may change every paired device.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `cmr/cancelled`
|
|
2
|
+
|
|
3
|
+
The operation was cancelled before the command was sent to this CMR client, so
|
|
4
|
+
nothing ran on it.
|
|
5
|
+
|
|
6
|
+
## Typical trigger
|
|
7
|
+
|
|
8
|
+
The caller aborted (Ctrl-C, or an `AbortSignal` in the library) during
|
|
9
|
+
preflight or before this member's dispatch. Members that had already been sent
|
|
10
|
+
the command are reported as [`cmr/outcome-unknown`](./outcome-unknown.md)
|
|
11
|
+
instead, because cancelling does not stop a client script that has started.
|
|
12
|
+
|
|
13
|
+
## Fix
|
|
14
|
+
|
|
15
|
+
Re-run when ready.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# `cmr/client-disconnected`
|
|
2
|
+
|
|
3
|
+
CMR skipped the client because it is not connected to the controller. The
|
|
4
|
+
command was **not** sent to it.
|
|
5
|
+
|
|
6
|
+
## Typical trigger
|
|
7
|
+
|
|
8
|
+
The controller answered `run-script` for this client with status `skipped` and
|
|
9
|
+
output `disconnected`. The client is down, unpaired, or cannot reach the
|
|
10
|
+
controller on TCP/54321. A client that has just gone down may instead stall
|
|
11
|
+
until the controller notices; that case surfaces as
|
|
12
|
+
[`cmr/validation-incomplete`](./validation-incomplete.md) or
|
|
13
|
+
[`cmr/outcome-unknown`](./outcome-unknown.md).
|
|
14
|
+
|
|
15
|
+
## Fix
|
|
16
|
+
|
|
17
|
+
- On the client, `/cmr/client print` should show `status: paired,connected`.
|
|
18
|
+
- Check its route and firewall towards the controller (TCP/54321).
|
|
19
|
+
- Re-run once it reconnects; the other members' results are unaffected.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# `cmr/client-script-not-validated`
|
|
2
|
+
|
|
3
|
+
A warning: a caller-addressed `/cmr/device/run-script`, sent through `api` or
|
|
4
|
+
plain `execute`, passed validation **as a controller request**. The `script=`
|
|
5
|
+
it carries was not validated on any CMR client.
|
|
6
|
+
|
|
7
|
+
## Typical trigger
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
centrs api ctl /cmr/device/run-script -X POST -f labels=all -f 'script=…' --yes
|
|
11
|
+
centrs execute ctl --yes -- '/cmr/device/run-script labels=all script="…"'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`/console/inspect` on the controller checks `labels=` and `script=` as
|
|
15
|
+
arguments. To the controller's `:parse`, the script is an opaque string, so a
|
|
16
|
+
`passed` gate says nothing about whether each client accepts it.
|
|
17
|
+
|
|
18
|
+
## Fix
|
|
19
|
+
|
|
20
|
+
Use `centrs execute <controller> --cmr --labels <expr> -- '<command>'`. It runs
|
|
21
|
+
`:parse` on every selected client through CMR and sends the command only to the
|
|
22
|
+
clients that accepted it.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# `cmr/console-only`
|
|
2
|
+
|
|
3
|
+
The command is one of CMR's interactive console views, so centrs refused it
|
|
4
|
+
before running it.
|
|
5
|
+
|
|
6
|
+
## Typical trigger
|
|
7
|
+
|
|
8
|
+
`/cmr/device/dashboard` through `execute` or `api`. On RouterOS 7.26beta1:
|
|
9
|
+
|
|
10
|
+
- over the **native API** it closed the API session;
|
|
11
|
+
- over **REST** it never returned, and every other REST request to the
|
|
12
|
+
controller then waited 10–30 s until it timed out.
|
|
13
|
+
|
|
14
|
+
A plain path, abbreviated or not (`/cmr/dev/dash`), is refused before any
|
|
15
|
+
connection opens. Every other spelling is found in the controller's `:parse`
|
|
16
|
+
return during validation, which resolves it to one invocation: console
|
|
17
|
+
spacing, a relative menu change (`/cmr/device; dashboard`), a `:do`/`:if`
|
|
18
|
+
block, a `[...]` substitution, or an `api POST /execute` script. Strings and
|
|
19
|
+
comments that only spell the words are not refused.
|
|
20
|
+
|
|
21
|
+
With `--validate=false` there is no `:parse` return, so only the plain path is
|
|
22
|
+
refused.
|
|
23
|
+
|
|
24
|
+
## Fix
|
|
25
|
+
|
|
26
|
+
- Open the dashboard in WinBox 4 or WebFig, or run it in
|
|
27
|
+
`centrs terminal <controller>`.
|
|
28
|
+
- For the data behind it, read the menus directly: `centrs retrieve
|
|
29
|
+
<controller> /cmr/device`, `/cmr/alert` or `/cmr/upgrade/job`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# `cmr/empty-selection`
|
|
2
|
+
|
|
3
|
+
The `--labels` expression of `execute --cmr` matched no device in the
|
|
4
|
+
controller's `/cmr/device` inventory, so nothing ran.
|
|
5
|
+
|
|
6
|
+
- For a **read**, it is a warning: `ok: true` with an empty selection and exit
|
|
7
|
+
code 0, the same as an empty CDB group. A collector that polls a sometimes
|
|
8
|
+
empty label stays quiet.
|
|
9
|
+
- For a **write-shaped** command it is an error (`ok: false`, exit code 1),
|
|
10
|
+
with or without `--yes`, so a misspelled label does not look like a
|
|
11
|
+
successful bulk write. RouterOS refuses an empty selection the same way
|
|
12
|
+
(`no devices selected`). `context.write` is `true`.
|
|
13
|
+
|
|
14
|
+
The same code is a **warning** on a CMR fleet command run directly with
|
|
15
|
+
`execute` or `api`, such as `/cmr/vlan/add labels=EDGE` or
|
|
16
|
+
`/cmr/device/reboot labels=Edge`: by the controller's inventory now, its
|
|
17
|
+
selection reaches no device. RouterOS refuses an empty selection for a
|
|
18
|
+
`/cmr/device` command itself; it accepts a rule whose labels match nothing,
|
|
19
|
+
and the rule applies to devices that get the label later.
|
|
20
|
+
|
|
21
|
+
## Typical trigger
|
|
22
|
+
|
|
23
|
+
- A misspelled label. RouterOS label names are case-sensitive, and an unknown
|
|
24
|
+
label is accepted silently rather than rejected.
|
|
25
|
+
- A missing comma: `labels=office+ap` is one label named `office+ap`; use
|
|
26
|
+
`office,+ap`.
|
|
27
|
+
- Every matching device was removed from the controller.
|
|
28
|
+
|
|
29
|
+
## Fix
|
|
30
|
+
|
|
31
|
+
Check the labels on the controller (`/cmr/device print` shows `labels` and
|
|
32
|
+
`auto-labels`) and re-run. `--labels all` selects every device, including the
|
|
33
|
+
controller.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# `cmr/membership-changed`
|
|
2
|
+
|
|
3
|
+
The CMR client was validated, but before the command was dispatched it changed
|
|
4
|
+
or left the selection. The command was **not** sent to it.
|
|
5
|
+
|
|
6
|
+
## Typical trigger
|
|
7
|
+
|
|
8
|
+
- The client's labels changed, so `numbers=<id> labels=<expr>` at dispatch
|
|
9
|
+
selected nothing (`failure: no devices selected`).
|
|
10
|
+
- Its `/cmr/device` row disappeared, or its `ids` key changed, between the
|
|
11
|
+
validation pass and dispatch. centrs re-reads the inventory just before
|
|
12
|
+
each member's own call, after any write prompt, so a client replaced
|
|
13
|
+
while the prompt or the fan-out queue waited is caught here. A validation
|
|
14
|
+
result belongs to the key it ran on, so it is not reused for a different one,
|
|
15
|
+
and a replacement row is never adopted.
|
|
16
|
+
|
|
17
|
+
## Fix
|
|
18
|
+
|
|
19
|
+
Re-run. centrs validates the current selection again and only sends the
|
|
20
|
+
command to clients that passed validation in that run.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# `cmr/not-a-controller`
|
|
2
|
+
|
|
3
|
+
`execute --cmr` was pointed at a router that does not answer `/cmr/device`, so
|
|
4
|
+
it is not a CMR controller.
|
|
5
|
+
|
|
6
|
+
centrs then reads the target's version. RouterOS older than 7.26beta1, where
|
|
7
|
+
CMR does not exist, is reported as `routeros/version-unsupported` instead. This
|
|
8
|
+
error means the version has CMR (or could not be read), and `context.version`
|
|
9
|
+
says which.
|
|
10
|
+
|
|
11
|
+
## Typical trigger
|
|
12
|
+
|
|
13
|
+
- The optional `cmr` package is not installed. Clients only have the built-in
|
|
14
|
+
`/cmr/client`; the controller needs the package.
|
|
15
|
+
- The target is a CMR *client*, not its controller. `context.controllerIdentity`
|
|
16
|
+
and `context.controllerAddress` then name the controller it is paired to.
|
|
17
|
+
|
|
18
|
+
Only a missing menu raises this: native API reports `no such command prefix`,
|
|
19
|
+
REST `no such command or directory (cmr)`. Any other failure reading
|
|
20
|
+
`/cmr/device`, such as a permissions error, is reported unchanged.
|
|
21
|
+
|
|
22
|
+
## Fix
|
|
23
|
+
|
|
24
|
+
Point the command at the controller: the router with the `cmr` package and
|
|
25
|
+
`/cmr set enabled=yes`. Its `/cmr/device print` lists the clients `--labels`
|
|
26
|
+
selects from.
|