@tikoci/centrs 0.1.9-next.23 → 0.1.9-next.24
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 +106 -0
- package/GLOSSARY.txt +14 -0
- package/README.md +2 -2
- package/commands/api/AGENTS.md +18 -1
- package/commands/api/README.md +10 -6
- package/commands/api/examples.md +1 -1
- package/commands/execute/README.md +26 -6
- package/commands/execute/examples.md +24 -5
- package/commands/explain/README.md +452 -61
- package/commands/explain/examples.md +191 -41
- package/commands/retrieve/README.md +13 -2
- package/commands/retrieve/examples.md +30 -1
- package/commands/transfer/README.md +8 -4
- package/docs/CLI.md +19 -5
- package/docs/CONSTITUTION.md +14 -9
- package/docs/MATRIX.md +34 -21
- package/docs/errors/cmr/caller-policy-not-enforced.md +5 -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 +2 -2
- package/src/api.ts +34 -39
- package/src/cli/explain.ts +206 -47
- package/src/core/envelope.ts +10 -3
- package/src/core/error-catalog.ts +15 -0
- 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/routeros-errors.ts +25 -0
- package/src/execute-cmr.ts +2 -8
- package/src/execute.ts +48 -20
- package/src/explain/args.ts +115 -10
- package/src/explain/group-faults.ts +162 -29
- package/src/explain/operators.ts +2 -1
- package/src/explain/scope-brace.ts +226 -11
- package/src/explain/segment.ts +136 -1
- package/src/explain/symbols.ts +146 -6
- package/src/explain/values.ts +55 -4
- 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 +395 -43
- package/src/index.ts +6 -0
- package/src/protocols/adapter.ts +88 -10
- package/src/protocols/mac-telnet.ts +38 -10
- package/src/protocols/native-api.ts +13 -12
- 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 +77 -27
- package/src/transfer.ts +38 -13
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,29 @@ part of this section.
|
|
|
17
17
|
|
|
18
18
|
### Added
|
|
19
19
|
|
|
20
|
+
- **Live `explain` over rest-api + native-api is `CHR-passed`** (#236).
|
|
21
|
+
Examples 7–16, 19, 24, 48 and 52–53 pass on both transports on stable
|
|
22
|
+
7.24.5 and long-term 7.23.8. `runtimeAcceptance` stays `not-proven`.
|
|
23
|
+
- `explain <router> '<input>'` and `explainLiveEnvelope()` add read-only parse,
|
|
24
|
+
highlight and command-argument evidence over REST/native API. Probe outcomes,
|
|
25
|
+
byte coverage and provenance remain explicit; analyzed input never executes (#491).
|
|
26
|
+
- Live `explain` fills the two live value facts (#225). `observedType` names a
|
|
27
|
+
type only where the device visibly converts a bare scripting literal
|
|
28
|
+
(`2.2` → `2.0.0.2` is `ip`, `0x10` → `16` is `num`), read from an escaped
|
|
29
|
+
`:parse` of the literal alone; an unchanged rendering abstains, and no
|
|
30
|
+
`:typeof` is sent. `schemaType` normalizes the argument's live `syntax`
|
|
31
|
+
declaration into leaf types (`gateway` → `ip`, `ip6`, `enum`), with the
|
|
32
|
+
device's own term in `declared`. Menu arguments are never observed, and
|
|
33
|
+
`op` has no producer.
|
|
34
|
+
- Live `explain --schema` describes the path at the cursor: a menu's verbs and
|
|
35
|
+
submenus with counts, a command's arguments with their declared syntax, and
|
|
36
|
+
`print`'s proplist. `--complete [--cursor <byte>]` returns menu, verb,
|
|
37
|
+
argument-name, `where` variable or value candidates. They are the device's
|
|
38
|
+
own input completion at the cursor, cited by `ev`; declared names and
|
|
39
|
+
syntax stay in `--schema`. `--full` lifts the sizing limits up to a safety
|
|
40
|
+
ceiling.
|
|
41
|
+
Only probe shapes that answer on RouterOS 7.12.2 and 7.20.8 are sent (#492).
|
|
42
|
+
|
|
20
43
|
- `centrs --version` / `-V` prints the package version alone; `--json` (or
|
|
21
44
|
`--format json|yaml`) returns an envelope with `data.version` (#463).
|
|
22
45
|
- `execute --cmr` warns `cmr/caller-policy-not-enforced` when a write runs
|
|
@@ -72,6 +95,34 @@ part of this section.
|
|
|
72
95
|
|
|
73
96
|
### Changed
|
|
74
97
|
|
|
98
|
+
- **Breaking:** permission and edge-auth failures get their own error codes
|
|
99
|
+
(#541). Three codes change for `--json` and library readers:
|
|
100
|
+
- A REST policy refusal (HTTP 500 `not enough permissions (9)` or `std
|
|
101
|
+
failure: not allowed (9)`): `transport/connection-closed` →
|
|
102
|
+
`routeros/permission-denied`. It is a deterministic router answer, so it
|
|
103
|
+
is never retried and no longer carries
|
|
104
|
+
`transport/write-outcome-unknown`.
|
|
105
|
+
- A native-API `not enough permissions (9)` trap: `routeros/api-trap` →
|
|
106
|
+
`routeros/permission-denied`. Retrieve's path probe and live explain's
|
|
107
|
+
child probe surface it instead of reading "path doesn't exist".
|
|
108
|
+
- A 401/403 from a layer in front of RouterOS (auth proxy, tunnel edge,
|
|
109
|
+
WAF): `transport/auth-failed` → `transport/edge-auth-failed`, with the
|
|
110
|
+
challenge scheme, server, content type and `cf-ray` presence as context
|
|
111
|
+
(no secrets). RouterOS's own 401 — JSON `{"error":401,...}`, measured on
|
|
112
|
+
CHR 7.12.1, 7.16.2, 7.23.8, 7.25, 7.26beta1 and 7.26beta3 — stays
|
|
113
|
+
`transport/auth-failed`.
|
|
114
|
+
- **`meta.validation` success shapes (#535).** This is a breaking change for
|
|
115
|
+
`--json` readers of the flat fields. `syntax` and `semantic` are now present
|
|
116
|
+
only when a gate stage gave that verdict; absent means no stage was asked.
|
|
117
|
+
- `api` structured requests (no `:parse`) drop `syntax: false`.
|
|
118
|
+
- Script input drops `semantic: "not-applicable"`; the value is gone from
|
|
119
|
+
the type.
|
|
120
|
+
- `--validate=false` drops both fields on `execute` and `api`.
|
|
121
|
+
- `retrieve`, `transfer` and `api` structured successes now report
|
|
122
|
+
`stages[]`. `transfer list` runs no existence probe, so it reports
|
|
123
|
+
`{ enabled: false }` instead of claiming one.
|
|
124
|
+
- `explain` reports `meta.validation: { enabled: false }` with no `result`
|
|
125
|
+
in all three cases (offline, live, error); it is not a gate.
|
|
75
126
|
- Text stdout is data (#470). In text mode, single-target `execute`, `api`,
|
|
76
127
|
`retrieve` and `transfer` write only the result to stdout. Everything else
|
|
77
128
|
goes to **stderr** on success and on failure: the `--verbose` header, one
|
|
@@ -150,6 +201,13 @@ part of this section.
|
|
|
150
201
|
|
|
151
202
|
### Fixed
|
|
152
203
|
|
|
204
|
+
- `retrieve --sample`, `--follow` and `--wait` no longer report a validation
|
|
205
|
+
verdict the gate never gave (#537). A cancellation before the gate ran now
|
|
206
|
+
reports the device stage `not reached` (`interrupted` for `--sample` and
|
|
207
|
+
`--follow`, `wait/interrupted` for `--wait`) instead of
|
|
208
|
+
`{enabled: true, result: "skipped"}`. A `--wait` cancelled after retried,
|
|
209
|
+
unanswered attempts no longer reports `result: "failed"`. With validation
|
|
210
|
+
off, all three report `{enabled: false, source: "disabled"}`.
|
|
153
211
|
- Abbreviated output-file arguments (`fi=`, `file-n=`), and the spaced
|
|
154
212
|
`file-name value` form under print/export/serialize, require write
|
|
155
213
|
confirmation in `execute`, including inside scripts, and are refused for
|
|
@@ -181,6 +239,50 @@ part of this section.
|
|
|
181
239
|
a native API login. A write that failed after it was sent, in single-target
|
|
182
240
|
or fan-out mode, carries the new warning `transport/write-outcome-unknown`,
|
|
183
241
|
because it may have run. Reads keep the retry.
|
|
242
|
+
- A bare known-menu path (`/system/identity`) reads as a read in `execute`,
|
|
243
|
+
not `usage/confirmation-required` (#543). It still answers nothing, so the
|
|
244
|
+
envelope steers to `/print` / `retrieve` with `tip/execute-bare-menu-path`.
|
|
245
|
+
Unknown paths, known-mutator verbs, and any attribute or query stay gated.
|
|
246
|
+
- `retrieve /system/routerboard` on a router without the menu (CHR) names the
|
|
247
|
+
workaround: the `validation/unknown-path` remediation points at
|
|
248
|
+
`/system/license --attributes system-id` instead of the generic text (#545).
|
|
249
|
+
- `execute` warns when a multi-statement write is refused for permission after
|
|
250
|
+
earlier statements may have run (#549). Such a refusal carries
|
|
251
|
+
`transport/write-outcome-unknown` alongside `routeros/permission-denied`,
|
|
252
|
+
counting statements inside executed `:if`/`:for`/`:foreach`/`:while`/`:do`/`:retry`/`:onerror` scopes and
|
|
253
|
+
command substitutions (`[…]`) as well as top-level ones; a `/menu { … }`
|
|
254
|
+
prefix is navigation, not a statement. Single refused statements, including
|
|
255
|
+
`remove [find …]` and one wrapped in a menu block, stay silent. The refusal itself
|
|
256
|
+
now surfaces as the error instead of `ok:true`: `/execute` answers HTTP 200
|
|
257
|
+
with `not enough permissions (9) (…; line N)` as the last `ret` line after
|
|
258
|
+
earlier statements' output (CHR 7.25, both transports).
|
|
259
|
+
- The offline gate no longer hard-rejects first-segment script-body
|
|
260
|
+
abbreviations it cannot prove (#525). `down={ :beep }`, accepted on device
|
|
261
|
+
as `down-script=`, warns `explain/canonicalizer/possible-script-body-abbreviation` and
|
|
262
|
+
reaches device validation instead of failing with `invalid-command-brace`. Bodies
|
|
263
|
+
opening with one bare command word or after a comment line warn too, and
|
|
264
|
+
the brace reads as a body for every reader, and whitespace after the `=`
|
|
265
|
+
(`down= { :beep }`) warns like the glued spelling. A bare word followed by
|
|
266
|
+
`;`-separated members keeps its error, as do the other array-content
|
|
267
|
+
braces (`comment= {1;2}` included), the space-for-`=` spelling
|
|
268
|
+
(`comment { :beep }`), positional braces, root directive slots
|
|
269
|
+
(`:put x={…}`), and `where`; `comment={ :beep }` deliberately warns though
|
|
270
|
+
the device rejects it.
|
|
271
|
+
- Native-API traps classify authentication by sentence, never trap text
|
|
272
|
+
(#540). Only a trap on `/login` is `transport/auth-failed`; a later command
|
|
273
|
+
rejected with login-like text (`/user/add name=-2` answering `invalid user
|
|
274
|
+
name`) is `routeros/command-failed`, agreeing with REST.
|
|
275
|
+
- `explain` symbols declare the value of named `name=`/`counter=` spellings in
|
|
276
|
+
any argument order (#533). `:local name=no` declares `no`, and
|
|
277
|
+
`:for from=1 counter=i to=2` binds `i`, so later `$` references link;
|
|
278
|
+
previously the argument name was declared and a reordered `counter=` left
|
|
279
|
+
`$i` unbound.
|
|
280
|
+
- mac-telnet with an explicit directed-broadcast `--host` (`192.168.74.255`)
|
|
281
|
+
sends the owning interface's real MAC (#546, #547). On Linux, `connect()` to
|
|
282
|
+
a broadcast reports `0.0.0.0`, so the session fell back to a synthetic
|
|
283
|
+
source MAC, which hardware ignores: a 30 s timeout on an RB1100Dx4 running
|
|
284
|
+
7.26beta3. On that unit a real MAC gets a reply in ~0.35 s and a synthetic
|
|
285
|
+
one none. CHR answers both, so the CHR suite never saw it.
|
|
184
286
|
|
|
185
287
|
### Maintenance
|
|
186
288
|
|
|
@@ -200,6 +302,10 @@ part of this section.
|
|
|
200
302
|
(Security tab), and locally with `bun run lint:actions` (#266). Its first
|
|
201
303
|
run found two `${{ … }}` expansions inside shell scripts, in `lab.yaml` and
|
|
202
304
|
`qa.yaml`; both now go through `env:`.
|
|
305
|
+
- The unit tier no longer reads the developer's `~/.config/tikoci` state
|
|
306
|
+
(settings, CDB) (#550, #557). A `bunfig.toml` preload sandboxes `HOME` and
|
|
307
|
+
`XDG_CONFIG_HOME`, so a real and a fake home give the same result. Ambient
|
|
308
|
+
`CENTRS_*` variables are still read.
|
|
203
309
|
|
|
204
310
|
## 0.1.8 — 2026-10-12
|
|
205
311
|
|
package/GLOSSARY.txt
CHANGED
|
@@ -821,3 +821,17 @@ apptraffic # two paths new in 7.26beta1 with different published gates: the `/to
|
|
|
821
821
|
rle # the corpus's `highlight-rle-v1` response encoding: each highlight stream stored as run-length `[class, count]` pairs, decoded by `decodeHighlightRuns` in scripts/corpus-db.ts (#498)
|
|
822
822
|
removenumbers # the IL head `/ip/firewall/address-list/removenumbers=…` that `remove [find …]` renders as, quoted verbatim in the bracket-`=` corroboration test (#498)
|
|
823
823
|
vscod # the last segment of a corpus path the forum importer truncated at its slug limit (`…-editors-like-vscod/post-0014-snippet-02.rsc`); cited as the path, so the spelling must match the corpus row (#498)
|
|
824
|
+
|
|
825
|
+
# #358/#371 unquoted-byte controls. Deliberate NON-words: each puts a non-ASCII
|
|
826
|
+
# byte where RouterOS reads only ASCII, so the spelling is the probe.
|
|
827
|
+
systemé # `/systemé/identity print` — a non-ASCII byte in a path, `expected command name` at the `é` on CHR 7.24.5/7.23.8 (#371)
|
|
828
|
+
printé # `/system/identity printé` — a non-ASCII byte in a verb, rejected at the `é` (#371)
|
|
829
|
+
namé # `/system/identity/set namé=x` — a non-ASCII byte in a menu command's argument name, rejected at the name's first byte (#371)
|
|
830
|
+
addré # `/ip/address/add addré=1` — as `namé`: the device reports the argument name's first byte, not the `é` (#371)
|
|
831
|
+
caf # the ASCII prefix the device reads of `café` before the `é`: `:put "a$café"` is `$caf` then `é` (#371)
|
|
832
|
+
|
|
833
|
+
# #225 live value types: CLI Reference `Type` cell spellings the schema vocabulary maps.
|
|
834
|
+
iface # the CLI Reference type `iface_enum`: an argument that names an interface, which `schemaType` calls `name` (#225)
|
|
835
|
+
ubit # the CLI Reference type `ubit (…)`: a set of listed bits, e.g. `ubit (pap, chap, mschap1, mschap2)`, a list of `enum` (#225)
|
|
836
|
+
forcounter # the IL head `/forcounter=$i;…` that `:for i …` renders as; quoted verbatim in the #225 directive-argument probe test (#225)
|
|
837
|
+
foreachcounter # the IL head `/foreachcounter=$i;…` that `:foreach i …` renders as, in any `counter=` order; quoted verbatim in the #533 reorder test (#533)
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# centrs
|
|
2
2
|
|
|
3
3
|
`centrs` is the tikoci RouterOS interaction hub: a Bun/TypeScript library and
|
|
4
|
-
CLI (with MCP
|
|
5
|
-
RouterOS devices through a regularized, validated interface.
|
|
4
|
+
CLI (with an MCP server; TUI and HTTP proxy frontends planned) for talking to
|
|
5
|
+
MikroTik RouterOS devices through a regularized, validated interface.
|
|
6
6
|
|
|
7
7
|
Preview builds are published on npm. To try the current offline analyzer without
|
|
8
8
|
installing centrs or connecting to a router:
|
package/commands/api/AGENTS.md
CHANGED
|
@@ -184,7 +184,7 @@ green on CHR 7.23.1.
|
|
|
184
184
|
validity (`request=child` ⇒ `validation/unknown-attribute`) fire
|
|
185
185
|
identically over rest-api and native-api, **before** any write — no `:put
|
|
186
186
|
[:parse]` involved (api input is a path, not a CLI string). A `/execute` script
|
|
187
|
-
is a CLI string ⇒ `meta.validation.semantic
|
|
187
|
+
is a CLI string ⇒ no `meta.validation.semantic` field (#535).
|
|
188
188
|
|
|
189
189
|
## CONFIRMED ON CHR (RouterOS 7.12.2 compatibility — GH#343, 2026-09-14)
|
|
190
190
|
|
|
@@ -197,6 +197,23 @@ green on CHR 7.23.1.
|
|
|
197
197
|
validation therefore uses command-level `request=child` only. This does not
|
|
198
198
|
prohibit argument-level completion probes such as `print,proplist`, which are
|
|
199
199
|
a separate shape and returned immediately on the same 7.12.2 controls.
|
|
200
|
+
- **Command-level `request=syntax` is unsafe too, on 7.12.2 and 7.20.8** (#492,
|
|
201
|
+
2026-10-19, reproduced on a fresh boot of each).
|
|
202
|
+
- REST: `syntax` with `path=` set to a command (`ip,address,set`,
|
|
203
|
+
`system,identity,set`, `ip,route,add`, `interface,print`) got no answer
|
|
204
|
+
within 12 s, 5 of 5 times. The `child` controls queued behind it timed out
|
|
205
|
+
until the handler recovered about 30 s later.
|
|
206
|
+
- Native API closed the connection.
|
|
207
|
+
- The same syntax requests at a menu (`ip,address`) or an argument
|
|
208
|
+
(`ip,address,set,address`, `put,message`, `do,command`) answered in about
|
|
209
|
+
25 ms. 7.23.8 and 7.24.5 answer every shape.
|
|
210
|
+
- restraml's crawlers send `syntax` to `arg` nodes only, so `inspect.json`
|
|
211
|
+
never exercised the hanging shape.
|
|
212
|
+
- Input-based `completion` (`input="/ip/address add comm"`) answered at every
|
|
213
|
+
position tried on both builds, including scripting keywords and `do={`
|
|
214
|
+
bodies on 7.20.8.
|
|
215
|
+
- Explain's `--schema`/`--complete` therefore send `syntax` for argument
|
|
216
|
+
paths and `completion` with `input=` only.
|
|
200
217
|
|
|
201
218
|
## CONFIRMED ON CHR (Phase 4 `--stream` follow — CHR 7.23.1, 2026-06-30)
|
|
202
219
|
|
package/commands/api/README.md
CHANGED
|
@@ -114,7 +114,9 @@ vs `--query` distinction.
|
|
|
114
114
|
before they were sent, keep the `transport/network` /
|
|
115
115
|
`transport/connection-closed` retry. A mutating request that failed after it
|
|
116
116
|
was sent carries the warning `transport/write-outcome-unknown`, in single-target
|
|
117
|
-
and fan-out mode alike.
|
|
117
|
+
and fan-out mode alike. A multi-statement `/execute` script refused with
|
|
118
|
+
`routeros/permission-denied` carries the same warning (#549); a single
|
|
119
|
+
refused statement, and a refused structured request, stay silent.
|
|
118
120
|
- **`--stream` (or a `/listen` endpoint) is single-session** → `usage/fanout-not-supported` in
|
|
119
121
|
fan-out mode. **`--raw` strips the envelope** → `usage/conflicting-flags` in
|
|
120
122
|
fan-out mode (per-target envelopes can't be bare).
|
|
@@ -134,7 +136,8 @@ gate is `/console/inspect` — **not** `:put [:parse]`:
|
|
|
134
136
|
does) answers normally on the same build. See the 7.12.2 finding in
|
|
135
137
|
`AGENTS.md` (GH#343).
|
|
136
138
|
- **Carve-out:** a script-shaped `POST /rest/execute` is a CLI string, not a path,
|
|
137
|
-
so structured inspect
|
|
139
|
+
so structured inspect does not apply and `meta.validation.semantic` is
|
|
140
|
+
absent. It
|
|
138
141
|
follows the CLI-string pipeline instead: offline analysis, then live
|
|
139
142
|
`:put [:parse ...]`, then run. An offline rejection is still a byte span with
|
|
140
143
|
no connection; an offline pass or abstention continues to the device stage.
|
|
@@ -148,10 +151,11 @@ gate is `/console/inspect` — **not** `:put [:parse]`:
|
|
|
148
151
|
unaffected — the result matchers are anchored, not substring.
|
|
149
152
|
- Structured path requests are **not** offline-gated. The input is a path plus a
|
|
150
153
|
body, not a CLI string, and an offline *path* gate collides with GH#211's
|
|
151
|
-
unlisted-path decision.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
unlisted-path decision. `meta.validation.stages[]` reports the offline stage
|
|
155
|
+
`skipped` with that reason, on success and on a `validation/*` rejection
|
|
156
|
+
alike, so the breakdown never implies an analysis that did not run. On
|
|
157
|
+
success the device stage is `passed`, `semantic` is `true`, and there is no
|
|
158
|
+
`syntax` field, because no `:parse` ran (#535).
|
|
155
159
|
|
|
156
160
|
`--validate=false` skips **both** stages of the preflight — one flag, one meaning
|
|
157
161
|
(constitution: validation); RouterOS still re-validates writes server-side.
|
package/commands/api/examples.md
CHANGED
|
@@ -156,7 +156,7 @@ centrs api $R execute -X POST -f script=':put [/system/identity/get name]' --use
|
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
Envelope: `ok: true`, `data` is string-shaped and contains the CHR identity;
|
|
159
|
-
`meta.validation.
|
|
159
|
+
`meta.validation.syntax=true` with no `semantic` field (script, not a path) and
|
|
160
160
|
`meta.validation.stages` is `offline: passed` then live `device: passed` from
|
|
161
161
|
`:put [:parse ...]`.
|
|
162
162
|
|
|
@@ -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
|
|
@@ -520,8 +535,13 @@ How it is wired:
|
|
|
520
535
|
correctness issue — see `commands/terminal/README.md` for the optional fix).
|
|
521
536
|
- **Delivery:** the UDP transport sends to `--host`/`--port` (default L2 broadcast
|
|
522
537
|
`255.255.255.255:20561`); the device replies to our source IP:port, and the
|
|
523
|
-
in-packet MACs do the addressing. The
|
|
524
|
-
|
|
538
|
+
in-packet MACs do the addressing. The device answers only broadcast delivery
|
|
539
|
+
(unicast gets no reply), and only when the in-packet *source* MAC is the
|
|
540
|
+
egress interface's real MAC — a synthetic source gets no reply on hardware
|
|
541
|
+
(CHR answers it, so the suite cannot catch a wrong source). With the default
|
|
542
|
+
host centrs sprays per-interface discovery with real MACs; with an explicit
|
|
543
|
+
`--host <directed-broadcast>` it resolves the owning interface's MAC. The CHR
|
|
544
|
+
test points `--host`/`--port` at the loopback L2 bridge.
|
|
525
545
|
|
|
526
546
|
Protocol selection: an unresolved MAC target auto-selects mac-telnet (covered by
|
|
527
547
|
`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
|