@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
|
@@ -651,7 +651,7 @@ centrs devices add 192.0.2.1 --user robot-reader --cdb-file ./lab/winbox.cdb --j
|
|
|
651
651
|
```
|
|
652
652
|
|
|
653
653
|
Creates missing parent directories and an empty open CDB with mode `0600`.
|
|
654
|
-
`data` is `{ cdbFile, created: true, recordCount: 0 }`. The `add` then succeeds.
|
|
654
|
+
`data` is `{ cdbFile, created: true, encrypted: false, recordCount: 0 }`. The `add` then succeeds.
|
|
655
655
|
|
|
656
656
|
### 55. `init` never clobbers
|
|
657
657
|
|
|
@@ -660,8 +660,63 @@ centrs devices init --cdb-file ./lab/winbox.cdb --json
|
|
|
660
660
|
```
|
|
661
661
|
|
|
662
662
|
On an existing, readable CDB: `ok: true`, `data.created: false`,
|
|
663
|
+
`data.encrypted` reports the actual encryption mode,
|
|
663
664
|
`data.recordCount` is the current count, and a `cdb/file-exists` warning says
|
|
664
665
|
nothing was written. As with `git init`, re-running is safe. An existing file that
|
|
665
666
|
does not load fails with that load error (`cdb/parse-failed`,
|
|
666
667
|
`cdb/password-required`) and is left untouched. A location that cannot be
|
|
667
668
|
written fails with `cdb/create-failed`.
|
|
669
|
+
|
|
670
|
+
### 56. Initialize encrypted from the start
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
CENTRS_CDB_PASSWORD="$CDB_SECRET" centrs devices init --encrypt --cdb-file ./prod.cdb --json
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Creates a complete encrypted empty CDB with mode `0600`; subsequent loads require
|
|
677
|
+
that password. `data.encrypted` is `true`. Re-running verifies the password
|
|
678
|
+
and reports `created: false`, `encrypted: true`.
|
|
679
|
+
Test: `centrs devices encryption (#558)` in `test/integration/devices.test.ts`.
|
|
680
|
+
|
|
681
|
+
### 57. Convert a lab CDB to encrypted
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
centrs devices init --cdb-file ./lab.cdb
|
|
685
|
+
centrs devices add 192.0.2.1 --user reader --cdb-file ./lab.cdb
|
|
686
|
+
CENTRS_CDB_PASSWORD="$CDB_SECRET" centrs devices encrypt --cdb-file ./lab.cdb --json
|
|
687
|
+
CENTRS_CDB_PASSWORD="$CDB_SECRET" centrs devices list --cdb-file ./lab.cdb --json
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Conversion reports `encrypted: true`, `changed: true`, `recordCount: 1`, and
|
|
691
|
+
`backupPath`. Records survive a password-protected reload; loading without a
|
|
692
|
+
password fails. All backups remain, including the plaintext pre-conversion file;
|
|
693
|
+
`cdb/plaintext-backups` warns explicitly. Repeating with the same password returns
|
|
694
|
+
`changed: false`; a wrong password fails without writing. Without `--json`,
|
|
695
|
+
text reports conversion or verified unchanged, the record count, and the
|
|
696
|
+
plaintext snapshot path when created; the warning remains visible.
|
|
697
|
+
Test: `centrs devices encryption (#558)` in `test/integration/devices.test.ts`.
|
|
698
|
+
|
|
699
|
+
### 58. Encryption intent fails closed
|
|
700
|
+
|
|
701
|
+
`init --encrypt` and `encrypt` refuse a missing or empty CDB password before
|
|
702
|
+
creating anything. `init --encrypt` refuses an existing open CDB without changing
|
|
703
|
+
it. `--encrypt` on other subcommands and positional targets on `encrypt` are
|
|
704
|
+
invalid commands. Record, selection, and provenance flags (`--user`, `--group`,
|
|
705
|
+
`--password`, `--via`, …) on `init`/`encrypt`, and `--no-backup` on anything but
|
|
706
|
+
`encrypt`, are `input/invalid-command` rather than silently ignored. Test:
|
|
707
|
+
`centrs devices encryption (#558)` in `test/integration/devices.test.ts`.
|
|
708
|
+
|
|
709
|
+
### 59. Convert without a plaintext snapshot
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
centrs devices init --cdb-file ./lab-fresh.cdb
|
|
713
|
+
centrs devices add 192.0.2.1 --user reader --cdb-file ./lab-fresh.cdb
|
|
714
|
+
CENTRS_CDB_PASSWORD="$CDB_SECRET" centrs devices encrypt --no-backup --cdb-file ./lab-fresh.cdb --json
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
A fresh path, so it does not depend on example 57 having already converted
|
|
718
|
+
`./lab.cdb`. Conversion reports `changed: true`, `recordCount: 1`, and no
|
|
719
|
+
`backupPath`, and no new `.bak.*` file appears. The backup the `add` wrote
|
|
720
|
+
is retained, so
|
|
721
|
+
`cdb/plaintext-backups` still warns. Test: `centrs devices encryption (#558)`
|
|
722
|
+
in `test/integration/devices.test.ts`.
|
|
@@ -44,7 +44,14 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
44
44
|
`isWriteShaped` is deliberately independent: script fallback for `[find]`, a
|
|
45
45
|
positional id, or other non-`key=value` input never bypasses confirmation.
|
|
46
46
|
Known read verbs are allowlisted; an unknown menu verb fails closed as a
|
|
47
|
-
write-shaped operation.
|
|
47
|
+
write-shaped operation. A truly bare known-menu path (`/system/identity`,
|
|
48
|
+
example W6) is the one exception: structured mode proves the input is a
|
|
49
|
+
single path token, so it is console navigation — a no-op read — rather than
|
|
50
|
+
a command. The device still answers truthfully (`no such command` on the
|
|
51
|
+
APIs) and the envelope carries a steering tip naming the read that answers.
|
|
52
|
+
A bare no-argument *command* (`/system/reboot`) stays gated, as does
|
|
53
|
+
script-branch navigation (#387), where a later line may carry a relative
|
|
54
|
+
verb. In script mode every command head in a statement
|
|
48
55
|
(the statement start and each `[…]` or `{…}` body) must be a read: its verb
|
|
49
56
|
is the first word past the known menu prefix, so `/ip address print` is a
|
|
50
57
|
read and `/user expire-password print` is not (examples W3). Script
|
|
@@ -283,7 +290,14 @@ A sent write is **never retried** (#448). Fan-out retries a target only for
|
|
|
283
290
|
when the failure came before the request was sent (for example while it was
|
|
284
291
|
being validated). A write that failed after it was sent may have run, so its
|
|
285
292
|
error envelope, single-target or fan-out, carries the warning
|
|
286
|
-
`transport/write-outcome-unknown`.
|
|
293
|
+
`transport/write-outcome-unknown`. A multi-statement write refused with
|
|
294
|
+
`routeros/permission-denied` carries the same warning: statements before the
|
|
295
|
+
refused one may already have run (#549). Statements inside executed control
|
|
296
|
+
scopes (`:if … do={…}`) and command substitutions (`[…]`) count; stored bodies
|
|
297
|
+
(`source={…}`, `:global f do={…}`) do not, and a `/menu { … }` prefix is
|
|
298
|
+
navigation, not a statement. A single refused statement did not
|
|
299
|
+
run and stays silent, including one fed by a lone substitution
|
|
300
|
+
(`remove [find …]`) or wrapped in a menu block.
|
|
287
301
|
|
|
288
302
|
## CMR clients through their controller (`--cmr`)
|
|
289
303
|
|
|
@@ -361,8 +375,9 @@ when the target is itself a CMR client.
|
|
|
361
375
|
controller's own row through `run-script`, and rebooted a client through
|
|
362
376
|
both. So a "reader" login on a 7.26beta1 controller is not read-only, and
|
|
363
377
|
centrs' write gate (and `mcp=ro` on MCP) is the only gate. MikroTik
|
|
364
|
-
[says](https://forum.mikrotik.com/t/7-26beta-development-is-released/273582/139) 7.26beta2 refuses this for a `read` group;
|
|
365
|
-
|
|
378
|
+
[says](https://forum.mikrotik.com/t/7-26beta-development-is-released/273582/139) 7.26beta2 refuses this for a `read` group; #527 now carries
|
|
379
|
+
direct 7.26beta3 evidence that a restricted caller is refused, with the
|
|
380
|
+
first refusing release still unbracketed. For a write,
|
|
366
381
|
centrs reads the login's own `/user` row and `/user/group` once (both are
|
|
367
382
|
readable with just `read` + `api`). When the login lacks `write`, `test` or
|
|
368
383
|
`reboot`, the result and the confirmation error carry
|
|
@@ -514,14 +529,30 @@ How it is wired:
|
|
|
514
529
|
but rejects the MD5 proof, so centrs advertises MTWEI (`src/protocols/mtwei.ts`)
|
|
515
530
|
by default. `END_AUTH` ≠ success (a failed login emits `END_AUTH` then "Login
|
|
516
531
|
failed" + `END`) → `transport/auth-failed`.
|
|
532
|
+
- **MTWEI hash encoding (grounded, #565):** RouterOS hashes shared `z` and the
|
|
533
|
+
REDP1 loop seed minimal-length; `haakonnessjoen/MAC-Telnet`'s `mtwei.c` pads
|
|
534
|
+
both to 32 bytes. When they differ, the padded proof fails. On CHR 7.25 over
|
|
535
|
+
the L2 harness (2026-10-31): short-`z` logins accepted the minimal proof 8 of
|
|
536
|
+
8 and rejected the padded proof 1 of 1 (about 700 logins). The `z` edge hits
|
|
537
|
+
about 1 in 256 logins. The REDP1 edge depends on the salt RouterOS generates
|
|
538
|
+
at each password change, about 1 in 317 changes; it fails every login until
|
|
539
|
+
the next change. A password-rotation scan over raw handshakes to PASSSALT
|
|
540
|
+
found divergent salts, and on one the minimal proof was accepted 3 of 3 while
|
|
541
|
+
the padded proof was rejected 3 of 3, all with a clean 32-byte `z` to isolate
|
|
542
|
+
the edge. The shared core is `src/protocols/ec-srp5.ts`.
|
|
517
543
|
- **Console quirks to know:** a successful write **prints no output** (no `.id`,
|
|
518
544
|
unlike REST/native — `data.ret` is empty on a successful add); and there is a
|
|
519
545
|
~10s terminal-negotiation stall on every login (a latency cost, not a
|
|
520
546
|
correctness issue — see `commands/terminal/README.md` for the optional fix).
|
|
521
547
|
- **Delivery:** the UDP transport sends to `--host`/`--port` (default L2 broadcast
|
|
522
548
|
`255.255.255.255:20561`); the device replies to our source IP:port, and the
|
|
523
|
-
in-packet MACs do the addressing. The
|
|
524
|
-
|
|
549
|
+
in-packet MACs do the addressing. The device answers only broadcast delivery
|
|
550
|
+
(unicast gets no reply), and only when the in-packet *source* MAC is the
|
|
551
|
+
egress interface's real MAC — a synthetic source gets no reply on hardware
|
|
552
|
+
(CHR answers it, so the suite cannot catch a wrong source). With the default
|
|
553
|
+
host centrs sprays per-interface discovery with real MACs; with an explicit
|
|
554
|
+
`--host <directed-broadcast>` it resolves the owning interface's MAC. The CHR
|
|
555
|
+
test points `--host`/`--port` at the loopback L2 bridge.
|
|
525
556
|
|
|
526
557
|
Protocol selection: an unresolved MAC target auto-selects mac-telnet (covered by
|
|
527
558
|
`test/unit/execute.test.ts`); callers wanting IP-level execution opt into ARP
|
|
@@ -67,9 +67,9 @@ centrs execute $R ':local a true; :if ($a = true) do={:put [/system/identity/get
|
|
|
67
67
|
|
|
68
68
|
Envelope: `ok: true`, `data` is string-shaped (`data.output` or `data.ret`) and
|
|
69
69
|
contains the CHR identity name, `meta.via=rest-api`, and
|
|
70
|
-
`meta.validation.syntax=true`. `meta.validation.semantic` is absent
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
`meta.validation.syntax=true`. `meta.validation.semantic` is absent because
|
|
71
|
+
`/console/inspect` validates path-shaped commands, not arbitrary script
|
|
72
|
+
expressions (#535). The `$a` variable must survive the validation
|
|
73
73
|
wrapper: if that outer string expands it before `:parse`, this example fails even
|
|
74
74
|
though RouterOS accepts the original command.
|
|
75
75
|
|
|
@@ -490,8 +490,8 @@ Envelope: `ok: false`, `error.code=validation/package-missing`,
|
|
|
490
490
|
`error.context.packages` names the published package, the remediation points at
|
|
491
491
|
`/system/package/print` and never at the syntax, and `error.position` and
|
|
492
492
|
`error.context.detail` keep RouterOS's own column and words. V5–V7 all report
|
|
493
|
-
`meta.validation.syntax=false
|
|
494
|
-
|
|
493
|
+
`meta.validation.syntax=false`, and `semantic` is absent because the
|
|
494
|
+
name-level check was never asked (GH#435, #535).
|
|
495
495
|
|
|
496
496
|
### V6. A menu no RouterOS build has is an unknown path
|
|
497
497
|
|
|
@@ -633,6 +633,25 @@ This is a grounding version, not an introduction boundary. Unknown or
|
|
|
633
633
|
ambiguous output-file prefixes stay write-shaped until the device resolves
|
|
634
634
|
them.
|
|
635
635
|
|
|
636
|
+
### W6. A bare menu path needs no confirmation and answers with a steering tip
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
centrs execute $R '/system/identity' --via rest-api --username $U --password $P
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
No `--yes` gate: a truly bare known-menu path is console navigation, a no-op
|
|
643
|
+
read, and structured mode proves the input is a single path token with no
|
|
644
|
+
follow-up. The device still answers truthfully — REST reports
|
|
645
|
+
`routeros/request-failed`, native API `routeros/api-trap`, both carrying
|
|
646
|
+
RouterOS's own `no such command` — and the envelope carries
|
|
647
|
+
`tip/execute-bare-menu-path`, naming `/system/identity/print` (or
|
|
648
|
+
`centrs retrieve $R /system/identity`) as the read that answers. A bare
|
|
649
|
+
no-argument *command* such as `/system/reboot` stays write-shaped and still
|
|
650
|
+
requires confirmation.
|
|
651
|
+
|
|
652
|
+
`test/integration/execute-write-gate.test.ts` proves the gate stays open for
|
|
653
|
+
navigation and shut for no-arg writes, on REST and native API.
|
|
654
|
+
|
|
636
655
|
### R1. A post-validation `/execute` rejection fails the overall envelope
|
|
637
656
|
|
|
638
657
|
```bash
|