@tikoci/centrs 0.1.9-next.0 → 0.1.9-next.23
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 +177 -40
- package/GLOSSARY.txt +32 -0
- package/README.md +12 -8
- package/SECURITY.md +11 -3
- package/commands/api/README.md +39 -15
- package/commands/api/examples.md +44 -6
- package/commands/execute/README.md +108 -14
- package/commands/execute/examples.md +137 -3
- package/commands/explain/README.md +430 -129
- package/commands/explain/examples.md +260 -1
- package/commands/mcp/README.md +11 -0
- package/commands/mcp/examples.md +6 -0
- package/commands/retrieve/README.md +30 -13
- package/commands/retrieve/examples.md +10 -6
- package/commands/transfer/examples.md +9 -0
- package/docs/CLI.md +8 -8
- package/docs/CONSTITUTION.md +29 -2
- package/docs/MATRIX.md +26 -13
- 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/empty-selection.md +17 -3
- package/docs/errors/cmr/rule-selects-every-device.md +32 -0
- package/docs/errors/cmr/selection-includes-controller.md +27 -0
- package/docs/errors/usage/flag-after-separator.md +34 -0
- package/package.json +10 -5
- package/src/api-fanout.ts +11 -1
- package/src/api.ts +205 -65
- 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 +44 -20
- 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 +78 -0
- package/src/core/envelope.ts +32 -3
- package/src/core/error-catalog.ts +25 -3
- package/src/core/execute-command.ts +144 -19
- package/src/core/fanout.ts +130 -6
- package/src/errors.ts +2 -1
- package/src/execute-cmr.ts +95 -78
- package/src/execute-fanout.ts +11 -1
- package/src/execute.ts +105 -23
- package/src/explain/args.ts +69 -8
- package/src/explain/blocks.ts +3 -4
- package/src/explain/brace-slots.ts +5 -0
- package/src/explain/catalog.ts +13 -6
- package/src/explain/defects.ts +1 -0
- package/src/explain/group-faults.ts +927 -0
- package/src/explain/highlight-projection.ts +10 -8
- package/src/explain/operators.ts +20 -19
- package/src/explain/pathresolve.ts +102 -20
- package/src/explain/quoted-string.ts +41 -6
- package/src/explain/run-tokens.ts +223 -0
- package/src/explain/scope-brace.ts +103 -6
- package/src/explain/segment.ts +41 -3
- package/src/explain/spaced-args.ts +116 -0
- package/src/explain/symbols.ts +17 -1
- package/src/explain/transport.ts +13 -0
- package/src/explain/verbsplit.ts +14 -188
- package/src/explain/write.ts +8 -1
- package/src/explain.ts +378 -18
- package/src/index.ts +18 -1
- package/src/mcp/safety.ts +11 -0
- package/src/mcp/tools.ts +4 -1
- package/src/offline-gate.ts +30 -9
- package/src/protocols/btest-session.ts +30 -10
- package/src/protocols/mac-telnet-console.ts +16 -0
- 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
|
@@ -483,7 +483,8 @@ REST body; the comment text is never rendered as an operand.
|
|
|
483
483
|
|
|
484
484
|
The CHR assertion grounds the wider placement rule on the requested RouterOS
|
|
485
485
|
channel via `/console/inspect request=highlight` plus `:parse`: unquoted `#`
|
|
486
|
-
inside brace arrays and parenthesized expressions is a hard error
|
|
486
|
+
inside brace arrays and parenthesized expressions is a hard error, except at
|
|
487
|
+
column 0 inside an array (example 37); the first
|
|
487
488
|
non-space `#` in `do`, `else`, `foreach do`, and `on-event` bodies that are
|
|
488
489
|
themselves in a statement context is a comment — the same `do={ # c` nested
|
|
489
490
|
inside an array is an array and `#` is an error (placement table, #249), while
|
|
@@ -700,3 +701,261 @@ envelope would otherwise have to derive the name/value boundary from
|
|
|
700
701
|
The same command also shows the two `=` bytes staying apart: the one in
|
|
701
702
|
`($x = 1)` is a comparison and comes back `operator`, decided by fill order
|
|
702
703
|
rather than by a scan for the byte.
|
|
704
|
+
|
|
705
|
+
### 33. A space for the `=` is not an empty argument list
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
centrs explain '/ip/firewall/layer7-protocol add comment "something" regexp 1.1' --json
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
RouterOS accepts this as `comment="something" regexp=1.1`: `:parse` lowers both
|
|
712
|
+
spellings to the same IL, and `highlight` marks each gap `syntax-obsolete`.
|
|
713
|
+
Which word is an argument name is per-command schema, so offline publishes
|
|
714
|
+
neither reading. The statement stays `resolved` on
|
|
715
|
+
`/ip/firewall/layer7-protocol add`, `command.args` is absent, `arguments.read`
|
|
716
|
+
is `false` with a `why` naming `` `comment "something"` ``, and
|
|
717
|
+
`transport.classification` stays `"unknown"`.
|
|
718
|
+
|
|
719
|
+
`data.verdict` is `warn`, with one
|
|
720
|
+
`explain/canonicalizer/possible-spaced-argument` **warning** per pair,
|
|
721
|
+
spanning `comment "something"` and `regexp 1.1`. Each says to write the `=`.
|
|
722
|
+
The warning needs a value that cannot be a flag: `/interface set ether1 name x`
|
|
723
|
+
also abstains but does not warn, because `/interface print detail
|
|
724
|
+
without-paging` is the same shape and is two flags
|
|
725
|
+
(`detail=true;without-paging=true`). The `=` spelling still reads both
|
|
726
|
+
arguments, and a script body may drop its `=` too:
|
|
727
|
+
`/system/script add name=beep source { :beep }` is the `source={ :beep }` body,
|
|
728
|
+
not an invalid brace.
|
|
729
|
+
|
|
730
|
+
### 34. An unlisted bare path does not become a menu
|
|
731
|
+
|
|
732
|
+
```bash
|
|
733
|
+
centrs explain $'/terminal/cuu\nprint\nadd name=x' --json
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
`/terminal/cuu` is a no-argument command that neither the menu table nor the
|
|
737
|
+
command catalog lists. `/ip fire conn` has the same shape and is navigation:
|
|
738
|
+
RouterOS accepts abbreviated menus. CHR 7.24.5 `:parse` confirms both, so a
|
|
739
|
+
table miss cannot decide between a menu and a command.
|
|
740
|
+
|
|
741
|
+
The first statement stays `ambiguous`. The two after it are `unknown`
|
|
742
|
+
(`context lost to an earlier unreadable statement`, `contextCertain: false`),
|
|
743
|
+
with no `command` and no `transport`, where they used to resolve to an
|
|
744
|
+
invented `/terminal/cuu/print` and `/terminal/cuu/add`. On the device they are
|
|
745
|
+
`bad command name print` and `bad command name add` at the root.
|
|
746
|
+
`structure.containsWrite` is `unknown` and `data.verdict` is `warn`. A known
|
|
747
|
+
absolute navigation recovers the context: in
|
|
748
|
+
`/terminal/cuu`, `/ip address`, `print`, the last statement resolves to `print`
|
|
749
|
+
at `/ip/address` again.
|
|
750
|
+
|
|
751
|
+
### 35. A brace after a command is its argument, not a menu block
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
centrs explain '/system/script add name beep source { :beep }' --json
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
`/menu { … }` opens a menu block only when the words before the brace name a
|
|
758
|
+
menu. Here `add` is a command, so the brace is the value of `source` with the
|
|
759
|
+
`=` left out. CHR 7.22.1 and 7.24.5 `:parse` give the same IL as
|
|
760
|
+
`source={ :beep }`.
|
|
761
|
+
|
|
762
|
+
`data.structure.statements` has one statement, `/system/script add`, whose
|
|
763
|
+
span covers the whole input. `:beep` is no longer published as a second
|
|
764
|
+
top-level statement, which RouterOS never runs. `data.verdict` is `warn`, from
|
|
765
|
+
the `possible-spaced-argument` diagnostic on `source { :beep }`.
|
|
766
|
+
|
|
767
|
+
RouterOS accepts any unique prefix of an argument name. An abbreviation that
|
|
768
|
+
can only stand for a script-valued name keeps its body in either spelling:
|
|
769
|
+
`sour { :beep }`, `down-scr { :beep }` and `o={ :beep }` lower to `source=`,
|
|
770
|
+
`down-script=` and `on-event=` on CHR 7.24.5 and 7.23.8. Abbreviating the first
|
|
771
|
+
segment (`down={ :beep }` for `down-script`) is accepted on the device too, but
|
|
772
|
+
offline still reports `invalid-command-brace`. Every word is a prefix of some
|
|
773
|
+
`<word>-script`, so telling it from `comment={ :beep }` needs the command's
|
|
774
|
+
argument list.
|
|
775
|
+
|
|
776
|
+
A brace straight after a command is a device error, and offline now reports it.
|
|
777
|
+
`/system/script add { :beep }`, `/ip/route/print {1;2}` and
|
|
778
|
+
`/system/reboot { :beep }` each get `invalid-command-brace` and `data.verdict`
|
|
779
|
+
`fail`. A run that names no command still opens a block, so
|
|
780
|
+
`/system script { print }` is navigation to `/system/script` followed by
|
|
781
|
+
`print`.
|
|
782
|
+
|
|
783
|
+
### 36. A brace in a `where` clause is an array value
|
|
784
|
+
|
|
785
|
+
```bash
|
|
786
|
+
centrs explain '/ip address print where interface in {"ether1";"ether2"}' --json
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
A `where` clause is expression context, so the brace is the array operand of
|
|
790
|
+
`in`. CHR 7.24.5 and 7.23.8 `:parse` lower it to
|
|
791
|
+
`(in $interface ether1;ether2)`. `data.verdict` is `pass`, so `centrs execute`
|
|
792
|
+
runs this read. Before #502 its offline stage refused it with
|
|
793
|
+
`invalid-command-brace`. `data.values.occurrences` holds the array and its two
|
|
794
|
+
string members, the same three records as
|
|
795
|
+
`where interface in ("ether1","ether2")`.
|
|
796
|
+
|
|
797
|
+
The brace is also a value after `~`, `<`, `>`, `<=` and `>=`, on the left of an
|
|
798
|
+
operator (`where {"ether1"} in interface`), and after `!interface=` or `"a"=`.
|
|
799
|
+
|
|
800
|
+
After `<name>=`, `<name>!=` or `<name>==`, spaced or not, the device reads the
|
|
801
|
+
value with that property's parser and rejects the brace. Two examples are
|
|
802
|
+
`where interface={"ether1"}` (`expected interface value`) and
|
|
803
|
+
`where disabled={"y"}` (`expected yes or no`). Offline keeps
|
|
804
|
+
`invalid-command-brace` at the `{` there. The device does this only for a
|
|
805
|
+
property the menu knows: `where foo-bar={"y"}` parses. Offline cannot tell
|
|
806
|
+
property names apart without a per-menu list, so it reports that one too.
|
|
807
|
+
|
|
808
|
+
### 37. A column-0 `#` line inside an array is a comment
|
|
809
|
+
|
|
810
|
+
```bash
|
|
811
|
+
centrs explain ':local z {a=1;
|
|
812
|
+
# c
|
|
813
|
+
b=2}' --json
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
`data.verdict` is `pass`. `data.spans` holds a `comment` span over `# c`, and
|
|
817
|
+
`data.values.occurrences` holds the array and its two members `a` and `b`. CHR
|
|
818
|
+
7.24.5 and 7.22.1 `:parse` lower it to `value=a=1;b=2`. Before #503, offline
|
|
819
|
+
failed it with `invalid-hash` at the `#`.
|
|
820
|
+
|
|
821
|
+
The rule holds at any depth (`{a={⏎# c⏎1}}`) and in argument values
|
|
822
|
+
(`:foreach i in={1;⏎# c⏎2} do={:put $i}`). It needs column 0: an indented `#`
|
|
823
|
+
(`{a=1;⏎ # c⏎b=2}`), and one after `{`, a value or `;` on the same line, keep
|
|
824
|
+
`invalid-hash` at the `#`. A `#` directly inside `( … )` stays an error too. A
|
|
825
|
+
comment-only array, `:local z {⏎# c⏎}`, fails like `:local z {}`: both are empty
|
|
826
|
+
arrays, which the device rejects (#511, example 38).
|
|
827
|
+
|
|
828
|
+
### 38. An empty array value and a misplaced `;` are rejected
|
|
829
|
+
|
|
830
|
+
```bash
|
|
831
|
+
centrs explain ':local z {}' --json
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
`data.verdict` is `fail` with `explain/canonicalizer/empty-group` over `{}`. CHR
|
|
835
|
+
7.24.5 and 7.23.8 `:parse` answer `syntax error (line 1 column 12)`, the byte
|
|
836
|
+
after the brace. The same holds for `:put {}`, `:return {}`, `[:len {}]` and
|
|
837
|
+
`:foreach i in={} do={…}`. A nested `{}` (`{1;{}}`), one in parentheses
|
|
838
|
+
(`({})`), `where … in {}` and a body (`do={}`, `:execute {}`) parse and pass.
|
|
839
|
+
|
|
840
|
+
`:local z {1⏎;2}` fails with `misplaced-separator` at the `;`: a `;` needs an
|
|
841
|
+
element before it on its line. `{1;}` and `{1⏎2}` pass.
|
|
842
|
+
|
|
843
|
+
### 39. An expression with a missing operand is rejected
|
|
844
|
+
|
|
845
|
+
```bash
|
|
846
|
+
centrs explain ':put (1 +)' --json
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
`data.verdict` is `fail` with `explain/canonicalizer/missing-operand` at the
|
|
850
|
+
`+`. The device answers `syntax error (line 1 column 11)`, at the `)`. `()`
|
|
851
|
+
fails with `empty-group`, and `(1; 2)` and `(1⏎+ 2)` fail with
|
|
852
|
+
`misplaced-separator`. `(1 2)`, `(- 1)` and `(1 + -2)` pass. Inside a query,
|
|
853
|
+
`where (comment=+)` passes: the device reads that value with the property's
|
|
854
|
+
parser.
|
|
855
|
+
|
|
856
|
+
### 40. A `@` in a command word is rejected
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
centrs explain '[admin@MikroTik] > /ip address print' --json
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
`data.verdict` is `fail` with `explain/canonicalizer/invalid-at-sign` at the
|
|
863
|
+
`@`. The device answers `syntax error (line 1 column 7)`, the same byte. This
|
|
864
|
+
is a pasted console prompt. `:put a@b` and `comment=a@b` pass, because a `@` is
|
|
865
|
+
a value byte in an argument.
|
|
866
|
+
|
|
867
|
+
### 41. A named comparison brace inside a `[find where …]` is rejected
|
|
868
|
+
|
|
869
|
+
```bash
|
|
870
|
+
centrs explain ':put [/ip address find where interface={"x"}]' --json
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
`data.verdict` is `fail` with `explain/canonicalizer/invalid-command-brace` at
|
|
874
|
+
the `{`, as at the top level (example 36). The device answers
|
|
875
|
+
`expected interface value (line 1 column 40)`. `where (interface={"x"})` fails
|
|
876
|
+
the same way, and `[find where interface in {"x"}]` passes.
|
|
877
|
+
|
|
878
|
+
### 42. A stray top-level `}` ends the script, and the rest is discarded
|
|
879
|
+
|
|
880
|
+
```bash
|
|
881
|
+
centrs explain ':put 1};:put 2' --json
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
`data.verdict` is `warn` with `explain/canonicalizer/discarded-close` over the
|
|
885
|
+
`}` and everything after it. CHR 7.24.5 and 7.23.8 accept the script and lower
|
|
886
|
+
it to `(evl /putmessage=1)`: the device stops reading at a `}` or `]` that closes
|
|
887
|
+
nothing at the top level, so `:put 2` never runs and
|
|
888
|
+
`/system identity set name=a}` sets `a`. `data.structure` reads only the
|
|
889
|
+
prefix. A stray close inside an open group (`:put (1})`, `{:put 1]}`) and any
|
|
890
|
+
stray `)` are still rejections and still fail with `unbalanced-close`. The
|
|
891
|
+
execute gate refuses this warning (`commands/execute/examples.md` V9).
|
|
892
|
+
|
|
893
|
+
### 43. A variable name is letters and digits
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
centrs explain ':local a_b 1' --json
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
`data.verdict` is `fail` with `explain/canonicalizer/invalid-name` at the `_`.
|
|
900
|
+
The device answers `expected end of command (line 1 column 9)`, the same byte.
|
|
901
|
+
`:global`, `:set`, `:for` and `:foreach` read the name the same way, and
|
|
902
|
+
`:local $a` and `:local (a)` fail at their first byte. `:local "a_b"`,
|
|
903
|
+
`:local 1a`, `:foreach a,b in={1} do={}` and `:local name=x value=1` pass.
|
|
904
|
+
|
|
905
|
+
### 44. A `$` must start a variable
|
|
906
|
+
|
|
907
|
+
```bash
|
|
908
|
+
centrs explain ':put "a$ b"' --json
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
`data.verdict` is `fail` with `explain/canonicalizer/bad-string-variable` over
|
|
912
|
+
the `$` and the space after it. The device answers `syntax error (line 1 column 9)`, at the space. Outside
|
|
913
|
+
a string, `:put $_` fails with `invalid-variable` the same way. `"$v_x"` (which
|
|
914
|
+
is `$v` then `_x`), `"\$ x"`, `"$(1)"` and `$"a b"` pass. `"a$"` also passes,
|
|
915
|
+
although the device rejects it: it reads `$"` as the start of a quoted name, and
|
|
916
|
+
offline's string scanners end the string there, so offline abstains.
|
|
917
|
+
|
|
918
|
+
### 45. Nothing may be glued to a closed token
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
centrs explain ':if (true) do={:put 1}else={:put 2}' --json
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
`data.verdict` is `fail` with `explain/canonicalizer/glued-token` at the `e`.
|
|
925
|
+
The device answers `expected end of command (line 1 column 23)`. The same rule
|
|
926
|
+
rejects `:put "a"b`, `$v.b`, `(1)(2)`, `[:len a]b`, `src-address=$v/24` and a
|
|
927
|
+
bare word run into a `"`, `$` or bracket (`:put a"b"`, `name=a$v`). Inside
|
|
928
|
+
`where` and `find`, and inside a group, the rule does not apply (`where
|
|
929
|
+
comment="a"b` and `("a"b)` parse). A command argument whose property parser
|
|
930
|
+
takes the glued part also passes: `dst-address-list=!"my list"` and
|
|
931
|
+
`dst-port=80,"443"`.
|
|
932
|
+
|
|
933
|
+
### 46. Statement ids and source ownership
|
|
934
|
+
|
|
935
|
+
```bash
|
|
936
|
+
centrs explain ':if (true) do={ :foreach i in={1;2} do={ :put $i } } else={ :put 0 }; :put 3' --json
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
The statements keep discovery order and have ids `s0` through `s4`.
|
|
940
|
+
The `foreach` (`s1`) has `parent: "s0"`; its `put` (`s2`) has `parent: "s1"`;
|
|
941
|
+
the `else` body's `put` (`s3`) has `parent: "s0"`. The final `put` (`s4`) has
|
|
942
|
+
no parent. The published `do` and `else` blocks both have `owner: "s0"`;
|
|
943
|
+
`s1` and `s2` share `block: 0`, and `s3` has `block: 1`. A `[find]` inside a
|
|
944
|
+
body names that body's statement as its `owner`. Links describe source
|
|
945
|
+
ownership; binding and execution-context claims remain separate (#507).
|
|
946
|
+
|
|
947
|
+
### 47. A stored script stays opaque
|
|
948
|
+
|
|
949
|
+
```bash
|
|
950
|
+
centrs explain '/system/script/add name=proof source={ :local x 1; :put $x }; :put 2' --json
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
Only the installing command (`s0`) and final `put` (`s1`) appear in
|
|
954
|
+
`statements[]`; neither has a parent, and `blocks[]` stays empty. The deferred
|
|
955
|
+
body's local and `put` are not exposed as statements. Scheduler `on-event=`
|
|
956
|
+
and Netwatch `down-script=` have the same structural boundary.
|
|
957
|
+
|
|
958
|
+
The CHR controls in `test/integration/explain-structure.test.ts` parse the
|
|
959
|
+
scope examples and compare stored-script creation, explicit `run`, and an
|
|
960
|
+
immediate `:do` body. Those are test operations on disposable CHR; explain
|
|
961
|
+
itself never executes the analyzed input.
|
package/commands/mcp/README.md
CHANGED
|
@@ -178,6 +178,17 @@ MCP server applies it as a hard allowlist:
|
|
|
178
178
|
regardless of `confirm`; a local CDB mutation through `centrs_devices` or
|
|
179
179
|
`centrs_discover` `save` fails with `usage/confirmation-required` unless
|
|
180
180
|
explicitly confirmed.
|
|
181
|
+
- **`mcp=ro` does not rest on RouterOS policy.** On a 7.26beta1 CMR
|
|
182
|
+
controller, a login without `write` or `reboot` wrote to a client and to the
|
|
183
|
+
controller's own row through `/cmr/device/run-script`, and rebooted a client
|
|
184
|
+
through `run-script` and `/cmr/device/reboot`. That is the CHR reproduction
|
|
185
|
+
on #456; the integration test re-proves the `run-script` write (CMR15).
|
|
186
|
+
MikroTik [says](https://forum.mikrotik.com/t/7-26beta-development-is-released/273582/139) 7.26beta2 will refuse this for a `read` group, but
|
|
187
|
+
centrs' classification does not depend on it: every CMR fleet command
|
|
188
|
+
(`run-script`, `reboot`, `upgrade`, `pair`, `/cmr/upgrade/trigger`, …) is
|
|
189
|
+
write-shaped over every path, pinned by `test/unit/write-gate-tables.test.ts`,
|
|
190
|
+
and `mcp=ro` refuses them whatever the controller login's group is. Any
|
|
191
|
+
future MCP route to CMR clients (#449) must keep that assumption.
|
|
181
192
|
- **`--allow-adhoc-targets`** (off by default) is reserved for one-off lab/power
|
|
182
193
|
user flows. Current RouterOS-facing tools expose no inline host+credential
|
|
183
194
|
schema, so the CDB allowlist remains the only active target source.
|
package/commands/mcp/examples.md
CHANGED
|
@@ -131,6 +131,12 @@ With `$NAME` registered `mcp=ro` (default):
|
|
|
131
131
|
Envelope: `ok: false`, `error.code = "cdb/write-not-permitted"`. The router is not
|
|
132
132
|
mutated even though `confirm` is true — the CDB policy is authoritative.
|
|
133
133
|
|
|
134
|
+
Output-file arguments count as writes even when abbreviated:
|
|
135
|
+
`:serialize to=json value=1 file-n=out.json` and
|
|
136
|
+
`/system identity export fi=out` are refused with or without `confirm`.
|
|
137
|
+
The CHR refusal and file read-back controls are
|
|
138
|
+
[execute example W5](../execute/examples.md#w5-abbreviated-output-file-arguments-keep-the-write-gate).
|
|
139
|
+
|
|
134
140
|
### 8. Write against an `mcp=rw` target succeeds with `confirm: true`
|
|
135
141
|
|
|
136
142
|
With `$NAME` registered `mcp=rw`:
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -104,8 +104,8 @@ Usage: centrs execute <target> <command> [flags] | centrs execute <target...> --
|
|
|
104
104
|
| `--max-results` | `<bytes>` | Fail if the rendered envelope exceeds this byte budget. |
|
|
105
105
|
| `--format` | <text\|json\|yaml> | Output format for the CLI response. Defaults to text; use --json or --format json for the structured envelope. |
|
|
106
106
|
| `--json` | | Shortcut for `--format json`. |
|
|
107
|
-
| `--verbose` | | Include additional context in text output. |
|
|
108
|
-
| `--` | | 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`. |
|
|
109
109
|
|
|
110
110
|
## api
|
|
111
111
|
|
|
@@ -126,7 +126,7 @@ Usage: centrs api <router> <endpoint> [flags] | centrs api --host <host|url> <en
|
|
|
126
126
|
| `--attribute / --proplist` | `<a,b>` | Property projection → `.proplist`. |
|
|
127
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`. |
|
|
128
128
|
| `--yes` | | Confirm a mutating (non-read) request in non-interactive runs. |
|
|
129
|
-
| `--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. |
|
|
130
130
|
| `--count` | `<n>` | Stop a `--stream` after N `!re` rows; `!empty` replies do not count (not the device count= argument). |
|
|
131
131
|
| `--duration` | `<dur>` | Stop a `--stream` after this wall-clock window (e.g. `5s`). |
|
|
132
132
|
| `--via` | <rest-api\|native-api> | Pin the transport; no silent downgrade. Default rest-api. |
|
|
@@ -150,7 +150,7 @@ Usage: centrs api <router> <endpoint> [flags] | centrs api --host <host|url> <en
|
|
|
150
150
|
| `--validate / --no-validate` | | Run `/console/inspect` validation before the request (default true; `--validate=false` also accepted). |
|
|
151
151
|
| `--format` | <json\|ndjson\|yaml\|text> | Output format. Defaults to json for api; `CENTRS_FORMAT` overrides. |
|
|
152
152
|
| `--json` | | Shortcut for `--format json`. |
|
|
153
|
-
| `--verbose` | | Include additional context in text output. |
|
|
153
|
+
| `--verbose` | | Include additional context in text output, on stderr. |
|
|
154
154
|
|
|
155
155
|
## transfer
|
|
156
156
|
|
|
@@ -190,7 +190,7 @@ Usage: centrs transfer <router>|--host <host> upload <local> [remote] | download
|
|
|
190
190
|
| `--cdb-file` | `<path>` | Read target credentials from a WinBox CDB file. |
|
|
191
191
|
| `--cdb-password` | `<password>` | Decrypt an encrypted WinBox CDB file. |
|
|
192
192
|
| `--validate / --no-validate` | | Enable or disable the existence-probe preflight. |
|
|
193
|
-
| `--verbose` | | Show the resolved transport in text output. |
|
|
193
|
+
| `--verbose` | | Show the resolved transport in text output, on stderr. |
|
|
194
194
|
|
|
195
195
|
## terminal
|
|
196
196
|
|
package/docs/CONSTITUTION.md
CHANGED
|
@@ -58,6 +58,10 @@ is unsafe the offline stage is what keeps the gate from degrading to nothing. A
|
|
|
58
58
|
clean offline pass is **necessary, never sufficient** — `explain` publishes
|
|
59
59
|
`runtimeAcceptance: "not-proven"` for exactly this reason. An offline `warn` is
|
|
60
60
|
an ABSTENTION and passes through to the device stage; only a `fail` rejects.
|
|
61
|
+
The one exception is `discarded-close`: a stray top-level `}` or `]` that the
|
|
62
|
+
device accepts and then silently stops reading at (#504). It is a warning,
|
|
63
|
+
because nothing is rejected, and the gate refuses it by code
|
|
64
|
+
(`src/offline-gate.ts`), because the device would run a different script.
|
|
61
65
|
|
|
62
66
|
`meta.validation.stages[]` reports what each stage concluded, including a stage
|
|
63
67
|
that was skipped and why. A reader must always be able to tell "the device
|
|
@@ -177,8 +181,29 @@ Notes:
|
|
|
177
181
|
lossless rule applies to **stdout** only: **stderr** carries progress and
|
|
178
182
|
interactive prompts (discover scan progress, write/ambiguity confirmation) and
|
|
179
183
|
is exempt — use it sparingly, since most operations are fast.
|
|
184
|
+
- **Text stdout is data** (#470). In text mode a single-target `execute`,
|
|
185
|
+
`api`, `retrieve` or `transfer` writes only `data` (or its error) to stdout.
|
|
186
|
+
The notes around it go to stderr: the `--verbose` header first, then one
|
|
187
|
+
`warning [<code>]: <message>` line per warning, then the `Tips:` footer, on
|
|
188
|
+
success and on failure alike. `transport/provider-trust` is left out of text
|
|
189
|
+
because it rides every quickchr run over TLS or SSH. `--json` keeps
|
|
190
|
+
everything, `api --raw` stays a bare payload, and fan-out text is a report,
|
|
191
|
+
not data, so it keeps its warnings on stdout. A text stream writes only its
|
|
192
|
+
data rows (`frame`, `sample`) to stdout; its `started`, `synced` and
|
|
193
|
+
`summary` lines, tips, warnings and errors go to stderr (#473).
|
|
180
194
|
- `data` is the only field whose *shape* varies by command. Everything else —
|
|
181
195
|
including `warnings` and `tips` — is invariant in shape; only contents differ.
|
|
196
|
+
- **Shared meta is written once** (#461, #462). A fan-out target's
|
|
197
|
+
`meta.settings` keeps only the keys whose source differs from the outer
|
|
198
|
+
`meta.settings`, and its `meta.operation` drops `request` and `auth` when
|
|
199
|
+
they equal the outer operation's. When every target agrees on `auth`, or
|
|
200
|
+
on a setting the outer `meta.settings` lacks, it moves to the outer
|
|
201
|
+
envelope. A reader takes the target's value, else
|
|
202
|
+
the outer one (`expandFanoutTarget`). A stream (`retrieve --follow`,
|
|
203
|
+
`retrieve --sample`, `api --stream`) opens with a `started` line that
|
|
204
|
+
carries the full meta and any request advice in `tips`, and ends with a
|
|
205
|
+
full-meta `summary` (`--follow`'s `synced` is full too). The data lines
|
|
206
|
+
between (`frame`, `sample`) carry only `meta.operation` (#473).
|
|
182
207
|
|
|
183
208
|
## Error model
|
|
184
209
|
|
|
@@ -304,8 +329,10 @@ Fan-out output is the locked **`FanoutData`** envelope: `data = { summary, targe
|
|
|
304
329
|
is an inner `ok:false` envelope (output, not metadata). `targets[]` is reassembled
|
|
305
330
|
in resolved record order, not completion order, so repeated runs diff cleanly. The
|
|
306
331
|
**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.
|
|
332
|
+
target ok (or an empty selection for a read), `2` partial (some ok, some
|
|
333
|
+
failed), `1` orchestration error or every target failed. An empty selection is
|
|
334
|
+
a warning for a read and an orchestration error for a write, `--yes` or not,
|
|
335
|
+
so a mistyped selector never passes for a successful bulk write (#460).
|
|
309
336
|
|
|
310
337
|
A multi-target **RouterOS write** (e.g. `execute` / `api` add/set/remove across a
|
|
311
338
|
selection) is gated by **`--yes`**, confirmed **once** up front; when it is missing
|
package/docs/MATRIX.md
CHANGED
|
@@ -182,16 +182,26 @@ Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
|
|
|
182
182
|
|
|
183
183
|
## Current priority
|
|
184
184
|
|
|
185
|
-
**First:
|
|
186
|
-
[#
|
|
185
|
+
**First: `explain`** (maintainer decision 2026-10-16 on
|
|
186
|
+
[#90](https://github.com/tikoci/centrs/issues/90)). In order: make the
|
|
187
|
+
published offline readings honest (#350, #211), give the result a statement
|
|
188
|
+
tree, then the live probe foundation (#491). Then use those probes to
|
|
189
|
+
ground #351, #358 and #371, and finish live (#492 → #236 closeout). The
|
|
190
|
+
plan, and what is deliberately out of this run, is the 2026-10-16 plan
|
|
191
|
+
comment on #90. It is step 3 of the validation track below.
|
|
192
|
+
|
|
193
|
+
**The CMR track is paused until RouterOS 7.26beta2** (maintainer decision
|
|
194
|
+
2026-10-16). It was first from the 2026-10-12 decision on
|
|
195
|
+
[#441](https://github.com/tikoci/centrs/issues/441). Done: `execute --cmr`
|
|
187
196
|
(#450), which runs one validated command on CMR clients through their
|
|
188
197
|
controller. The contract is in
|
|
189
198
|
[`commands/execute/README.md` → CMR](../commands/execute/README.md#cmr-clients-through-their-controller---cmr),
|
|
190
|
-
with examples CMR0–
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
with examples CMR0–CMR15. Also done: the fleet blast-radius gate for
|
|
200
|
+
controller fleet commands run with plain `execute` or `api` (#453), with
|
|
201
|
+
examples CMR16–CMR18 and api CMR1. The feature map of CMR against centrs is
|
|
202
|
+
on #441 (2026-10-13). When it resumes, next is the read-only slice of #449 (a
|
|
203
|
+
`devices` view of members and a default controller). Deferred: CDB linkage
|
|
204
|
+
(#449) and `retrieve --cmr` (#441).
|
|
195
205
|
|
|
196
206
|
**The native-api / NDJSON line** (maintainer decision on
|
|
197
207
|
[#396](https://github.com/tikoci/centrs/issues/396), 2026-10-07): agents in the
|
|
@@ -229,16 +239,19 @@ producers owned with #225. REST and native-api cells advance only when their
|
|
|
229
239
|
applicable examples pass on CHR via `bun run test:integration`.
|
|
230
240
|
|
|
231
241
|
Open offline follow-ups remain explicit but do not reopen the verified baseline.
|
|
232
|
-
Issue #211
|
|
242
|
+
Issue #211 made an unlisted absolute bare path abstain (rule F, R14); rule
|
|
243
|
+
P's closed-world reading is still not adopted (#372). #272 owns
|
|
233
244
|
non-authoritative `.scratch/` citation cleanup. #350 (the broad optional `=`,
|
|
234
245
|
schema-dependent and so belonging to step 3), #351 (an unresolvable menu that
|
|
235
246
|
abstains at 2-deep nesting but hard-fails at 3-deep) and #355 (`'` is not a
|
|
236
247
|
RouterOS token — a false ACCEPT the corpus join cannot see) are bounded analyzer
|
|
237
|
-
defects that step 2 now surfaces through the `execute`/`api` gate. #336 and #
|
|
238
|
-
corpus is
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
defects that step 2 now surfaces through the `execute`/`api` gate. #336, #342 and #498 are complete: the
|
|
249
|
+
corpus is pinned to schema 5, with one recorded session per release channel
|
|
250
|
+
(long-term 7.23.8, stable 7.24.5, testing 7.25rc2, development 7.26beta1) read
|
|
251
|
+
through `scripts/corpus-db.ts`. The highlight slice is cut from those sessions
|
|
252
|
+
and gated in CI, and the operator sweep is re-run on the same builds — plus
|
|
253
|
+
7.21.5 retained as an age anchor — so the operator axis and the token partition
|
|
254
|
+
are scored on the same firmware. The #335
|
|
242
255
|
source refresh pins both architectures of those versions beside the 7.10.2 /
|
|
243
256
|
7.16 history anchors and accepts the current CLI-Reference drift. Issue #223 is
|
|
244
257
|
a separate MCP response migration. If live evidence finds a wrong offline
|