@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
package/CHANGELOG.md
CHANGED
|
@@ -6,16 +6,41 @@ The product evolves as a stream of changes; there are no alpha gates.
|
|
|
6
6
|
Per-feature status is tracked in `docs/MATRIX.md`. Use this file for
|
|
7
7
|
documenting cross-cutting shifts that affect contributors and consumers.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Each stable version has one section. Work in progress goes under
|
|
10
|
+
`## X.Y.Z — next`, which is what `@next` installs (`X.Y.Z-next.N`); the stable
|
|
11
|
+
release dates that heading. A `-next.N` build has no section of its own.
|
|
10
12
|
|
|
11
|
-
## 0.1.9
|
|
13
|
+
## 0.1.9 — next
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`next` after the next round of CMR work.
|
|
15
|
+
In progress on npm tag `next`. The `0.1.9-next.0` cut (2026-10-13) carried
|
|
16
|
+
part of this section.
|
|
16
17
|
|
|
17
18
|
### Added
|
|
18
19
|
|
|
20
|
+
- `centrs --version` / `-V` prints the package version alone; `--json` (or
|
|
21
|
+
`--format json|yaml`) returns an envelope with `data.version` (#463).
|
|
22
|
+
- `execute --cmr` warns `cmr/caller-policy-not-enforced` when a write runs
|
|
23
|
+
through a controller login that lacks `write`, `test` or `reboot`. On
|
|
24
|
+
7.26beta1 RouterOS runs the client script with all four anyway, so the
|
|
25
|
+
login is not the limit (#456). MikroTik says 7.26beta2 refuses this for a
|
|
26
|
+
`read` group. The warning is also in the confirmation
|
|
27
|
+
error. centrs reads the login's own `/user` row and group once, only for a
|
|
28
|
+
write; a failed read only loses the warning.
|
|
29
|
+
- The write gate of a CMR controller fleet command run with plain `execute` or
|
|
30
|
+
`api` names the devices it reaches (#453). `/cmr/device/reboot labels=all`
|
|
31
|
+
used to read as one router; centrs now reads the controller's `/cmr/device`
|
|
32
|
+
inventory first and says, for example, "2 CMR device(s) through controller
|
|
33
|
+
ctl, the controller itself among them" (`error.context.cmr`). The covered commands are
|
|
34
|
+
`/cmr/device/{reboot,upgrade,pair,run-script}`, `/cmr/upgrade/trigger` and
|
|
35
|
+
rule changes on `/cmr/vlan`, `/cmr/wifi`, `/cmr/wifi/radio` and
|
|
36
|
+
`/cmr/upgrade`. New warnings, with or without `--yes`:
|
|
37
|
+
`cmr/selection-includes-controller` and `cmr/rule-selects-every-device`
|
|
38
|
+
(a VLAN rule with no `labels` and no `port-labels` cuts the fleet's
|
|
39
|
+
management access). `cmr/empty-selection` is also a warning here.
|
|
40
|
+
`run-script` and `reboot` carry `cmr/caller-policy-not-enforced` as
|
|
41
|
+
`execute --cmr` does. The command still runs as typed, and a failed read
|
|
42
|
+
never blocks it.
|
|
43
|
+
|
|
19
44
|
- `execute <controller> --cmr --labels <expr> -- '<command>'` runs one
|
|
20
45
|
validated command on CMR clients through their controller (RouterOS
|
|
21
46
|
7.26beta1+; #450, #451). Only the controller is dialed, with its own
|
|
@@ -45,8 +70,109 @@ Pre-release (npm tag `next`; `latest` stays on 0.1.8). Install with
|
|
|
45
70
|
- `execute --cmr` accepts a command that opens with a relative menu change
|
|
46
71
|
(`/system; identity/print`); its `:parse` return was read as incomplete.
|
|
47
72
|
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Text stdout is data (#470). In text mode, single-target `execute`, `api`,
|
|
76
|
+
`retrieve` and `transfer` write only the result to stdout. Everything else
|
|
77
|
+
goes to **stderr** on success and on failure: the `--verbose` header, one
|
|
78
|
+
`warning [<code>]: <message>` line per warning, and the `Tips:` footer.
|
|
79
|
+
Plain `execute` and `api` used to drop warnings and envelope tips, so a
|
|
80
|
+
`--yes` write never showed, for example, `cmr/rule-selects-every-device` or
|
|
81
|
+
`transport/write-outcome-unknown`. `retrieve` and `transfer` used to mix
|
|
82
|
+
them into stdout. `transport/provider-trust` stays out of text output.
|
|
83
|
+
`--json` keeps everything, `api --raw` is unchanged, and fan-out text still
|
|
84
|
+
lists warnings in its report.
|
|
85
|
+
- **Breaking:** shared meta is written once (#461, #462). In a fan-out
|
|
86
|
+
envelope, each target's `meta.settings` keeps only the keys whose source differs from the
|
|
87
|
+
outer `meta.settings`. Its `meta.operation` drops `request` and `auth` when
|
|
88
|
+
they equal the outer operation's. When every target agrees on `auth`, or on
|
|
89
|
+
a setting the outer `meta.settings` lacks (`api` and `transfer` had none), it
|
|
90
|
+
moves to the outer envelope. An `execute --cmr` member no longer
|
|
91
|
+
repeats the controller in `meta.target.cmr`, because the outer `meta.target`
|
|
92
|
+
is the controller. A 3-client `execute --cmr` result went from 9.8 KB to
|
|
93
|
+
5.4 KB. `expandFanoutTarget()` returns a target's full meta. A
|
|
94
|
+
`retrieve --follow` frame now carries only `meta.operation`;
|
|
95
|
+
`started`, `synced` and the summary still carry the full meta. A frame line
|
|
96
|
+
went from about 1.15 KB to 270 bytes.
|
|
97
|
+
- **Breaking:** all three streams (`retrieve --follow`, `retrieve --sample`,
|
|
98
|
+
`api --stream`) open with a full-meta `started` line (`data: null`), replacing
|
|
99
|
+
`notice` where it was emitted (#473, #480). Advice is in its `tips`. Only
|
|
100
|
+
`meta.operation` remains on each `frame` or `sample`; the full meta stays
|
|
101
|
+
on `started`, `summary`, and follow's `synced`. Consumers must handle
|
|
102
|
+
`started` and read shared meta from those lines. `apiStream()` now yields
|
|
103
|
+
`ApiStreamLine`, and `retrieveSample()` yields `RetrieveStreamLine`;
|
|
104
|
+
`ApiStreamFrameEnvelope`, `StreamLineEnvelope` and `isStreamDataLine` are
|
|
105
|
+
exported for consumers. API frames also gain `observedAt` (host receipt
|
|
106
|
+
time, ISO 8601). In text mode only frame/sample rows go to stdout; status
|
|
107
|
+
lines, tips, warnings and errors go to stderr. Structured formats keep
|
|
108
|
+
every line on stdout, and `api --raw` omits `started`.
|
|
109
|
+
- `retrieve --follow` frames carry `observedAt` (ISO 8601, host clock): when
|
|
110
|
+
centrs received the reply the frame is built from (#462).
|
|
111
|
+
- With `--query`, a removal the follow's sweep finds is `source: "sweep"`,
|
|
112
|
+
as documented; it was labelled `membership` (#462).
|
|
113
|
+
- An empty selection is an error for a write: `execute`, mutating `api` and
|
|
114
|
+
`transfer upload|remove|mkdir|copy` fan-outs (`cdb/empty-group`,
|
|
115
|
+
`cdb/empty-selection`) and `execute --cmr` (`cmr/empty-selection`) now exit 1
|
|
116
|
+
with `ok: false`, with or without `--yes`. A mistyped group or label passed
|
|
117
|
+
for a successful bulk write with exit 0 (#460). A read over an empty
|
|
118
|
+
selection is unchanged: `ok: true` with the warning.
|
|
119
|
+
- The write gate treats pure script functions as reads, so a structured read
|
|
120
|
+
such as `:put [:serialize to=json [/system/resource/print as-value]]` no
|
|
121
|
+
longer needs `--yes` (#458). The added directives are `:serialize`,
|
|
122
|
+
`:deserialize`, `:convert`, `:delay`, `:find`, `:timestamp`, `:jobname`,
|
|
123
|
+
`:nothing`, `:range`, `:rndnum`, `:rndstr`, `:retry`, `:time` and the `:to*`
|
|
124
|
+
converters, all from the grounded root-command vocabulary. Commands inside
|
|
125
|
+
their arguments and `{…}` bodies are still checked; on CHR 7.24.5 and
|
|
126
|
+
7.26beta1, RouterOS refuses a quoted string as a script body. `file-name=`
|
|
127
|
+
(`:serialize`'s output file) now gates like `file=`. `api POST
|
|
128
|
+
/console/inspect` is a read.
|
|
129
|
+
- CMR fleet commands (`/cmr/device/{run-script,reboot,upgrade,pair}`,
|
|
130
|
+
`/cmr/upgrade/trigger`, `/cmr/upgrade/job/run-next`) are pinned as writes in
|
|
131
|
+
every spelling, over `execute`, `api` and MCP `mcp=ro`. On 7.26beta1 a
|
|
132
|
+
controller login with only `read` + `api` can run them (#456).
|
|
133
|
+
- CI, release, Pages and the Docker image run Bun 1.4.2 (was 1.3.14), the
|
|
134
|
+
version local development already used; `@types/bun` moves with it (#438).
|
|
135
|
+
`engines.bun` stays `>=1.3.11 <2`. The `typescript` peer stays `^6.0.3`:
|
|
136
|
+
the source passes `tsc --noEmit` under TypeScript 7.0.2, but typedoc 0.28 (the
|
|
137
|
+
API reference build) supports TypeScript up to 6.0.x only.
|
|
138
|
+
- QA records a CHR leg in `qa-history` only when its test step passed or
|
|
139
|
+
failed. A leg cancelled by a newer push, or skipped after a setup failure,
|
|
140
|
+
was recorded as `fail` (#442).
|
|
141
|
+
- CHR integration runs prefetch their selected image before tests start,
|
|
142
|
+
including direct file runs, so a cold-cache download does not consume a
|
|
143
|
+
test timeout. `bun run chr:prefetch` is the explicit entry point (#468).
|
|
144
|
+
- A centrs flag written after `--` is refused before anything dials, as
|
|
145
|
+
`usage/flag-after-separator`, with the corrected command line (#459). This
|
|
146
|
+
applies to `execute` (including `--cmr`) and `explain`. Before, a trailing
|
|
147
|
+
`--json` was sent to RouterOS and failed there as `unknown-attribute`. Only
|
|
148
|
+
a command's own long flags are matched (short aliases such as `-u` stay
|
|
149
|
+
RouterOS input), and password values are not echoed.
|
|
150
|
+
|
|
48
151
|
### Fixed
|
|
49
152
|
|
|
153
|
+
- Abbreviated output-file arguments (`fi=`, `file-n=`), and the spaced
|
|
154
|
+
`file-name value` form under print/export/serialize, require write
|
|
155
|
+
confirmation in `execute`, including inside scripts, and are refused for
|
|
156
|
+
MCP `mcp=ro` records. RouterOS accepted these spellings while centrs
|
|
157
|
+
recognized only `file=` and `file-name=`, letting file creation bypass
|
|
158
|
+
both gates.
|
|
159
|
+
- The suggested command in `usage/flag-after-separator` redacts credentials
|
|
160
|
+
in positional targets and `--host` values, including a host flag moved
|
|
161
|
+
before `--` and percent-encoded URL credentials. Inline value flags keep their
|
|
162
|
+
flag names in the correction. This closes error-path gaps in the target
|
|
163
|
+
redaction below; explicit URL credentials are refused before CDB lookup too.
|
|
164
|
+
- Serialized error causes keep `name`, `message` and `code` and omit raw JS
|
|
165
|
+
stack traces and their local installation paths (#476, #480).
|
|
166
|
+
- A target with credentials in it (`http://admin:pass@host`, `admin:pass@host`,
|
|
167
|
+
`admin@host`) is refused with `input/invalid-target`, which names
|
|
168
|
+
`--username` and `--password` (#478). centrs never used those credentials,
|
|
169
|
+
and it echoed the whole string, password included, in `meta.target.input`,
|
|
170
|
+
the fan-out selection and the `--verbose` header. The refusal covers the
|
|
171
|
+
positional, `--host`, `CENTRS_HOST`, CDB targets and fan-out literals, and
|
|
172
|
+
any displayed target has `user:pass@` replaced by `***@`, even when the
|
|
173
|
+
password holds `/`, `?` or `#`. A CDB identity containing `@` (`rtr@site`)
|
|
174
|
+
still resolves.
|
|
175
|
+
|
|
50
176
|
- `execute` and `api` never replay a write that was sent (#448). Fan-out
|
|
51
177
|
retried every `transport/network` and `transport/connection-closed` failure
|
|
52
178
|
up to twice, so a write whose connection dropped after the router had run
|
|
@@ -56,44 +182,28 @@ Pre-release (npm tag `next`; `latest` stays on 0.1.8). Install with
|
|
|
56
182
|
or fan-out mode, carries the new warning `transport/write-outcome-unknown`,
|
|
57
183
|
because it may have run. Reads keep the retry.
|
|
58
184
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Release (npm tag `latest`; `next` moves forward to 0.1.8). Everything in
|
|
62
|
-
0.1.8-next.0 below, plus two fixes from field-testing it.
|
|
63
|
-
|
|
64
|
-
### Fixed
|
|
65
|
-
|
|
66
|
-
- `retrieve --wait` with no deadline value names `--wait` (#445). Written as a
|
|
67
|
-
bare switch, `--wait --until 'expr'` read `--until` as the deadline and
|
|
68
|
-
shifted the predicate into a second target, so the CLI reported
|
|
69
|
-
`usage/fanout-not-supported`. It now fails `settings/invalid-timeout`
|
|
70
|
-
("Received the flag: --until"), and every deadline without a unit gets the
|
|
71
|
-
`--wait`-specific message. A real multi-target `--wait` still gets the
|
|
72
|
-
fanout refusal.
|
|
73
|
-
- MCP accepts a registered device by its `identity=`, `mac=` or `ip=` comment
|
|
74
|
-
key, as the CLI does (#444). The allowlist gate now matches with the same
|
|
75
|
-
resolver the call dials with, so the `mcp=` write policy checked is the
|
|
76
|
-
policy of the record the call runs against. A name on more than one record
|
|
77
|
-
fails `identity/ambiguous` with the candidates in `context.matches` and an
|
|
78
|
-
MCP remediation, instead of resolving to the first.
|
|
79
|
-
|
|
80
|
-
## 0.1.8-next.0 — 2026-10-12
|
|
185
|
+
### Maintenance
|
|
81
186
|
|
|
82
|
-
|
|
83
|
-
`
|
|
187
|
+
- Releases are one dispatch from `main` (#484, #485). `@next` publishes
|
|
188
|
+
`X.Y.Z-next.<run_number>` with no commit, PR or tag, and reuses main's QA
|
|
189
|
+
run on the commit instead of sweeping every channel again. `@latest` creates
|
|
190
|
+
the tag and a GitHub Release. A pushed `v*` tag no longer publishes. The
|
|
191
|
+
changelog keeps one section per stable version; the `0.1.8-next.0` and
|
|
192
|
+
`0.1.9-next.0` sections are folded into theirs.
|
|
193
|
+
- The CodeQL workflow no longer runs the AI-findings probe job (#486). Code
|
|
194
|
+
Quality AI findings are a paid feature this repository does not enable.
|
|
195
|
+
- Workflows reference actions by major tag, never by commit SHA, and
|
|
196
|
+
`SECURITY.md` records why (#266). Every `setup-bun` step reads the Bun
|
|
197
|
+
version from `packageManager`, so changing Bun is a one-line edit. Every
|
|
198
|
+
checkout except the QA history push drops its git credentials.
|
|
199
|
+
- zizmor audits the workflows: in CI as `Analyze (zizmor)` in `codeql.yaml`
|
|
200
|
+
(Security tab), and locally with `bun run lint:actions` (#266). Its first
|
|
201
|
+
run found two `${{ … }}` expansions inside shell scripts, in `lab.yaml` and
|
|
202
|
+
`qa.yaml`; both now go through `env:`.
|
|
84
203
|
|
|
85
|
-
|
|
204
|
+
## 0.1.8 — 2026-10-12
|
|
86
205
|
|
|
87
|
-
|
|
88
|
-
Dependabot ecosystem for `bun.lock` (#390). Preserve the dependency group
|
|
89
|
-
and the `has-flag` major-version ignore. Remaining dependency advisories
|
|
90
|
-
and updater verification are tracked in #390.
|
|
91
|
-
- Pre-release dependency alignment (#390): runtime ranges now floor at the
|
|
92
|
-
tested versions (`@modelcontextprotocol/sdk ^1.32.1`, `zod ^4.6.7`),
|
|
93
|
-
`smol-toml` is overridden past its advisories, and `@types/bun` is pinned
|
|
94
|
-
exactly to the 1.3.14 runtime (re-pin tracked in #438). The two residual
|
|
95
|
-
advisories (`braces`, `katex`) are dev-tool-only and do not ship in the
|
|
96
|
-
package.
|
|
206
|
+
Most of this section first shipped as `0.1.8-next.0` (2026-10-12).
|
|
97
207
|
|
|
98
208
|
### Added
|
|
99
209
|
|
|
@@ -131,6 +241,20 @@ Pre-release (npm tag `next`; `latest` stays on 0.1.7). Install with
|
|
|
131
241
|
|
|
132
242
|
### Fixed
|
|
133
243
|
|
|
244
|
+
- `retrieve --wait` with no deadline value names `--wait` (#445). Written as a
|
|
245
|
+
bare switch, `--wait --until 'expr'` read `--until` as the deadline and
|
|
246
|
+
shifted the predicate into a second target, so the CLI reported
|
|
247
|
+
`usage/fanout-not-supported`. It now fails `settings/invalid-timeout`
|
|
248
|
+
("Received the flag: --until"), and every deadline without a unit gets the
|
|
249
|
+
`--wait`-specific message. A real multi-target `--wait` still gets the
|
|
250
|
+
fanout refusal.
|
|
251
|
+
- MCP accepts a registered device by its `identity=`, `mac=` or `ip=` comment
|
|
252
|
+
key, as the CLI does (#444). The allowlist gate now matches with the same
|
|
253
|
+
resolver the call dials with, so the `mcp=` write policy checked is the
|
|
254
|
+
policy of the record the call runs against. A name on more than one record
|
|
255
|
+
fails `identity/ambiguous` with the candidates in `context.matches` and an
|
|
256
|
+
MCP remediation, instead of resolving to the first.
|
|
257
|
+
|
|
134
258
|
- `execute` honors an explicit target URL, and native-api honors a typed port
|
|
135
259
|
(#426, from #384). With no `via` pinned, an `http(s)://host[:port]` target
|
|
136
260
|
selects rest-api on that scheme and port instead of silently dialing native
|
|
@@ -310,6 +434,19 @@ Pre-release (npm tag `next`; `latest` stays on 0.1.7). Install with
|
|
|
310
434
|
unchanged apart from those remediation strings and the restored
|
|
311
435
|
`context.detail`.
|
|
312
436
|
|
|
437
|
+
### Maintenance
|
|
438
|
+
|
|
439
|
+
- Refresh vulnerable dependencies within existing ranges and use Bun's
|
|
440
|
+
Dependabot ecosystem for `bun.lock` (#390). Preserve the dependency group
|
|
441
|
+
and the `has-flag` major-version ignore. Remaining dependency advisories
|
|
442
|
+
and updater verification are tracked in #390.
|
|
443
|
+
- Pre-release dependency alignment (#390): runtime ranges now floor at the
|
|
444
|
+
tested versions (`@modelcontextprotocol/sdk ^1.32.1`, `zod ^4.6.7`),
|
|
445
|
+
`smol-toml` is overridden past its advisories, and `@types/bun` is pinned
|
|
446
|
+
exactly to the 1.3.14 runtime (re-pin tracked in #438). The two residual
|
|
447
|
+
advisories (`braces`, `katex`) are dev-tool-only and do not ship in the
|
|
448
|
+
package.
|
|
449
|
+
|
|
313
450
|
## 0.1.7 — 2026-09-27
|
|
314
451
|
|
|
315
452
|
### Added
|
package/GLOSSARY.txt
CHANGED
|
@@ -206,6 +206,8 @@ tzsp
|
|
|
206
206
|
TZSP
|
|
207
207
|
UDP
|
|
208
208
|
UIA
|
|
209
|
+
# unscorable: a corpus observation with no device answer (failed/missing capture, unknown or ambiguous readout); `parseVerdict` in scripts/corpus-db.ts.
|
|
210
|
+
unscorable
|
|
209
211
|
uvx
|
|
210
212
|
vcs
|
|
211
213
|
venv
|
|
@@ -216,6 +218,8 @@ vrf
|
|
|
216
218
|
webproxy
|
|
217
219
|
WinAppDriver
|
|
218
220
|
winbox
|
|
221
|
+
# wiresage/routeros-lsp: third-party RouterOS checker; its 875 conformance cases are the corpus's `wiresage-conformance` collection (lsp-routeros-ts#42).
|
|
222
|
+
wiresage
|
|
219
223
|
Wireshark
|
|
220
224
|
workstream
|
|
221
225
|
workstreams
|
|
@@ -318,6 +322,9 @@ REUSEADDR
|
|
|
318
322
|
# L2 bridge frame-parsing terms (test/integration/mndp-l2-bridge.ts):
|
|
319
323
|
# ethertype: 16-bit EtherType field in an Ethernet frame (0x0800 = IPv4).
|
|
320
324
|
ethertype
|
|
325
|
+
# binpad: OpenSSL `BN_bn2binpad`, a big number as fixed-width big-endian bytes;
|
|
326
|
+
# MAC-Telnet's mtwei.c hashes EC-SRP `z` with it, btest does not (#516).
|
|
327
|
+
binpad
|
|
321
328
|
# ihl: IPv4 Internet Header Length (low nibble of the first byte, in 32-bit words).
|
|
322
329
|
ihl
|
|
323
330
|
# PIIX: Intel PCI-ISA bridge chipset QEMU emulates; shows up in CHR's REST board-name.
|
|
@@ -409,6 +416,16 @@ globalname
|
|
|
409
416
|
localname
|
|
410
417
|
setnote
|
|
411
418
|
runnumber
|
|
419
|
+
# addcomment/setcomment/setname/setdisabled/setnumbers/printdetail/pickbegin: the
|
|
420
|
+
# same IL rendering, quoted as evidence for the spaced-argument rule (GH#350) —
|
|
421
|
+
# "/interface set ether1 comment \"x\"" is "(evl /interface/setcomment=x;numbers=ether1)".
|
|
422
|
+
addcomment
|
|
423
|
+
setcomment
|
|
424
|
+
setname
|
|
425
|
+
setdisabled
|
|
426
|
+
setnumbers
|
|
427
|
+
printdetail
|
|
428
|
+
pickbegin
|
|
412
429
|
# unconfigured: a freshly-flashed RouterOS device with no IP — the mac-telnet use case.
|
|
413
430
|
unconfigured
|
|
414
431
|
unwired
|
|
@@ -487,6 +504,14 @@ iff
|
|
|
487
504
|
smol
|
|
488
505
|
katex
|
|
489
506
|
|
|
507
|
+
# CI tooling vocabulary
|
|
508
|
+
# %zizmor: GitHub Actions static analyzer; config .github/zizmor.yml (#266).
|
|
509
|
+
# https://docs.zizmor.sh
|
|
510
|
+
# artipacked: zizmor audit for git credentials persisted by checkout.
|
|
511
|
+
zizmor
|
|
512
|
+
zizmorcore
|
|
513
|
+
artipacked
|
|
514
|
+
|
|
490
515
|
# GitHub Pages build vocabulary (scripts/build-pages-site.ts, pages.yaml)
|
|
491
516
|
# typedoc/pandoc: the API-reference generator and the markdown->HTML converter
|
|
492
517
|
# the Pages build chains together. gfm: GitHub-Flavored Markdown, pandoc's
|
|
@@ -775,6 +800,8 @@ dgram # Node's `node:dgram` UDP socket module — reached by `mac-telnet`, and o
|
|
|
775
800
|
# pair, so its spelling is load-bearing and must not be "corrected".
|
|
776
801
|
elsy # the misspelled `else` in `:if (1=1) do={…} elsy {…}` — rejected as `expected end of command` on CHR 7.21.5/7.23.5/7.24.2/7.25beta3, which is what proves the bare-brace `else {` acceptance is a real device fact and not a probe artifact (#347)
|
|
777
802
|
prnt # a misspelt `print` verb — `/ip/address/prnt` is the offline analyzer's ABSTENTION fixture (`verdict: warn`, not `fail`), which is what proves the two-stage validation gate passes an abstention through to the device rather than refusing it (#354)
|
|
803
|
+
zzznotanarg # an argument name no RouterOS command has — `/system/script add zzznotanarg beep` is `bad parameter zzznotanarg` (7.24.5) or `expected end of command` (7.22.1), which proves the device pairs a spaced `name value` against per-command schema (#350)
|
|
804
|
+
zzzscript # a `…script`-shaped name no command has — `zzzscript={ :beep }` is a `syntax error` on CHR 7.22.1/7.24.5, the control for the script-body brace exemption (#350)
|
|
778
805
|
zzznotamenu # a menu name no RouterOS build has — `/ip { zzznotamenu { print } }` is a `syntax error` on device, so menu-scope resolution is schema-dependent and a catalog MISS must abstain rather than accept (#348)
|
|
779
806
|
|
|
780
807
|
# #361 absent-menu controls. Deliberate NON-words: each names a path no RouterOS
|
|
@@ -789,3 +816,8 @@ nochr # a CLI-Reference syscap that excludes CHR — `/system/license/output` is
|
|
|
789
816
|
# #417 CLI Reference refresh (7.26beta1).
|
|
790
817
|
cmr # MikroTik's optional `cmr` controller package and the `/cmr` menu tree it adds (7.26beta1); the catalog carries `package="cmr"` as its published gate, e.g. `/cmr/device/reboot` (#417)
|
|
791
818
|
apptraffic # two paths new in 7.26beta1 with different published gates: the `/tool/apptraffic` settings menu (conditions `!mmips, !powerpc, !smips`) and the `/cmr/device/apptraffic` command (package `cmr`, no conditions) (#417)
|
|
819
|
+
|
|
820
|
+
# #498 schema-5 corpus repin.
|
|
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
|
+
removenumbers # the IL head `/ip/firewall/address-list/removenumbers=…` that `remove [find …]` renders as, quoted verbatim in the bracket-`=` corroboration test (#498)
|
|
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)
|
package/README.md
CHANGED
|
@@ -11,13 +11,15 @@ installing centrs or connecting to a router:
|
|
|
11
11
|
bunx @tikoci/centrs@next explain '/ip/address print' --json
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Use `bunx @tikoci/centrs@next --help` to inspect the full preview CLI
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`next`
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
Use `bunx @tikoci/centrs@next --help` to inspect the full preview CLI, and
|
|
15
|
+
`bunx @tikoci/centrs@next --version` (`-V`; `--json` for an envelope) to print
|
|
16
|
+
the exact version for scripts and field reports. The dist-tag follows standard
|
|
17
|
+
SemVer semantics: a plain version (`0.1.8`) publishes to `latest` and carries
|
|
18
|
+
`next` forward with it, while a `next` build (`0.1.9-next.23`) publishes to
|
|
19
|
+
`next` only and never moves `latest`. A `next` build is described by the
|
|
20
|
+
`## 0.1.9 — next` section of [`CHANGELOG.md`](CHANGELOG.md). So `@next` is the
|
|
21
|
+
leading edge and can equal `@latest` between cuts —
|
|
22
|
+
`npm view @tikoci/centrs dist-tags` is the authority.
|
|
21
23
|
|
|
22
24
|
The full flag reference for every command is generated from the CLI metadata
|
|
23
25
|
into [`docs/CLI.md`](docs/CLI.md); per-command behavior lives in
|
|
@@ -211,8 +213,10 @@ GitHub issues it links are the roadmap.
|
|
|
211
213
|
|
|
212
214
|
Requirements:
|
|
213
215
|
|
|
214
|
-
- Bun 1.3.11 or newer (CI
|
|
216
|
+
- Bun 1.3.11 or newer (CI runs the `packageManager` version in `package.json`;
|
|
217
|
+
Bun 2.x not yet validated).
|
|
215
218
|
- Git.
|
|
219
|
+
- `uv`, only for `bun run lint:actions` (zizmor workflow audit).
|
|
216
220
|
- For integration tests: QEMU plus `@tikoci/quickchr` (handles CHR image
|
|
217
221
|
download and boot).
|
|
218
222
|
|
package/SECURITY.md
CHANGED
|
@@ -14,14 +14,22 @@ Please include the affected files or workflow, reproduction details, and impact.
|
|
|
14
14
|
|
|
15
15
|
The repository's [Security tab](https://github.com/tikoci/centrs/security) is the live source of current alerts and advisories. This section describes *what* checks run and *why*, so the doc stays meaningful even when the badge is at 0.
|
|
16
16
|
|
|
17
|
-
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality) plus a small custom pack, [`.github/codeql-house-rules/`](.github/codeql-house-rules/), that structurally checks a handful of repo-specific invariants (currently: `apiFanout` must call its named protocol/validation gates) — see `.github/instructions/github-security-quality.instructions.md` for scope and the verify-before-promote discipline for adding more rules there. Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
-
- **Code Quality
|
|
17
|
+
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality) plus a small custom pack, [`.github/codeql-house-rules/`](.github/codeql-house-rules/), that structurally checks a handful of repo-specific invariants (currently: `apiFanout` must call its named protocol/validation gates) — see `.github/instructions/github-security-quality.instructions.md` for scope and the verify-before-promote discipline for adding more rules there. Languages: `javascript-typescript`, `actions`. `test/` is excluded (`paths-ignore`); it is not shipped, and its CLI argv credentials over-tainted every CLI render path. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
+
- **Code Quality** — GitHub's own scan, enabled in repository settings (not a workflow here). It runs as the separate "CodeQL - Code Quality" run on every PR and push and reports different findings from the CodeQL workflow above. Steady-state goal is 0 open findings. False positives are dismissed via the GitHub UI with a written justification — that text is the audit-log contract. Side effect: it posts a same-named `Analyze (javascript-typescript)` check, so PR checks lists show it twice — cosmetic (see [issue #166](https://github.com/tikoci/centrs/issues/166)), not a required check either way (see issue #118).
|
|
19
|
+
- **Code Quality AI findings** — not used: a paid feature, not enabled for this repository.
|
|
20
|
+
- **zizmor** — GitHub Actions static analysis, as the `Analyze (zizmor)` job in [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml). Findings go to the Security tab and do not fail the job. [`.github/zizmor.yml`](.github/zizmor.yml) accepts tag refs (see GitHub Actions below) and turns off the Dependabot cooldown audit. `bun run lint:actions` runs it locally (needs `uv`); it is not part of `lint:ci`.
|
|
19
21
|
- **Dependency review** — not enabled. GitHub dependency review follows dependency-graph ecosystem support, and this Bun-only repository currently does not have a supported lockfile for that gate.
|
|
20
22
|
- **Dependabot security updates** — unsupported for Bun in GitHub's [ecosystem support table](https://docs.github.com/en/code-security/reference/supply-chain-security/supported-ecosystems-and-repositories). [`.github/dependabot.yaml`](.github/dependabot.yaml) configures weekly Bun **version updates** and GitHub Actions updates. Dependency security cleanup also requires `bun audit` and reviewed lockfile upgrades.
|
|
21
23
|
- **Secret scanning** — enabled in GitHub, with push protection.
|
|
22
24
|
- **Private vulnerability reporting** — enabled via GitHub Security Advisories.
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Human review of the Security tab in the GitHub UI is part of the security workflow; no workflow gates on Code Quality findings.
|
|
27
|
+
|
|
28
|
+
## GitHub Actions
|
|
29
|
+
|
|
30
|
+
- **Action refs are tags, not commit SHAs** (`actions/checkout@v7`, `oven-sh/setup-bun@v2`), for first-party and third-party actions alike. Use the major tag; an action that publishes none (`zizmorcore/zizmor-action`) gets its release tag. This is a decision ([#266](https://github.com/tikoci/centrs/issues/266)), not an oversight, so a scanner's unpinned-action finding is not a defect here: CodeQL's `actions/unpinned-tag` is excluded in [`.github/codeql-config.yml`](.github/codeql-config.yml), and zizmor's `unpinned-uses` accepts tags in [`.github/zizmor.yml`](.github/zizmor.yml). A tag picks up an action's fixes, including security fixes, on the next run. A SHA pin goes stale silently until a bot bumps it. We accept the risk of a moved tag or a compromised release in exchange. Dependabot's weekly `github-actions` updates move major and release tags.
|
|
31
|
+
- **The Bun version lives in one place.** Every `setup-bun` step reads `packageManager` from `package.json` (`bun-version-file`); the Dockerfile base image is the only other copy.
|
|
32
|
+
- **Checkouts drop git credentials** (`persist-credentials: false`) unless the job pushes. The only one that pushes is the QA accumulate job in `qa.yaml`, which commits to the `qa-history` branch.
|
|
25
33
|
|
|
26
34
|
## Supported versions
|
|
27
35
|
|
package/commands/api/README.md
CHANGED
|
@@ -170,14 +170,28 @@ the suspect.
|
|
|
170
170
|
## Write confirmation
|
|
171
171
|
|
|
172
172
|
Read-only **iff** the method is `GET`, or the endpoint terminal verb is
|
|
173
|
-
`print`/`get`, or it is `listen
|
|
174
|
-
|
|
175
|
-
`
|
|
173
|
+
`print`/`get`, or it is `listen`, or it is a POST to exactly `/console/inspect`
|
|
174
|
+
(it describes and parses commands, never runs them; #458). Everything else is
|
|
175
|
+
treated as a write and needs confirmation (`--yes` non-interactively, or a TTY
|
|
176
|
+
prompt) → otherwise `usage/confirmation-required`. Consequences:
|
|
176
177
|
|
|
177
178
|
- A `POST …/print` paged read does **not** prompt (keyed on the verb, not the
|
|
178
179
|
wire HTTP method).
|
|
179
180
|
- Streaming does **not** imply read-only: an async-but-mutating command like
|
|
180
181
|
`/system/license/renew --stream` still confirms.
|
|
182
|
+
- A controller's CMR fleet commands (`/cmr/device/{run-script,reboot,upgrade,pair}`,
|
|
183
|
+
`/cmr/upgrade/trigger`, `/cmr/upgrade/job/run-next`) are writes whatever
|
|
184
|
+
RouterOS policy the login has: on 7.26beta1 `run-script` needs only `read` +
|
|
185
|
+
`api` on the controller (#456).
|
|
186
|
+
Before the gate, `api` reads the controller's `/cmr/device` inventory and
|
|
187
|
+
names the devices the request reaches, the controller's own row included,
|
|
188
|
+
with the same warnings as plain `execute`. Where the reach cannot be
|
|
189
|
+
resolved (for example `/cmr/upgrade/job/run-next`), the gate says so and
|
|
190
|
+
why (#453; see
|
|
191
|
+
[execute → Fleet commands run directly](../execute/README.md#fleet-commands-run-directly-453)).
|
|
192
|
+
`PUT`, `PATCH` and `DELETE` on `/cmr/vlan`, `/cmr/wifi`, `/cmr/wifi/radio`
|
|
193
|
+
and `/cmr/upgrade` are the menus' `add`, `set` and `remove`, and the `*id`
|
|
194
|
+
is the rule they act on.
|
|
181
195
|
|
|
182
196
|
`--raw` does not bypass this gate.
|
|
183
197
|
|
|
@@ -209,7 +223,7 @@ centrs never substitutes polling.
|
|
|
209
223
|
`ip/address/*1/listen` follows one row (`?.id=*1`).
|
|
210
224
|
- **GET menu + `--stream`** streams one literal print and completes; it does
|
|
211
225
|
not follow. Its summary carries `tip/stream-print` naming the `/listen`
|
|
212
|
-
form (see
|
|
226
|
+
form (see `started` below). A streamed GET (or a `/listen`) rejects `-f` fields rather than
|
|
213
227
|
silently dropping them.
|
|
214
228
|
- **POST command + `--stream`** runs the literal command with its attributes:
|
|
215
229
|
`api $R /tool/ping -X POST -f address=10.0.2.2 -f count=3 --stream --yes`.
|
|
@@ -219,14 +233,22 @@ centrs never substitutes polling.
|
|
|
219
233
|
`usage/conflicting-flags` instead of being ignored. `/execute` retains
|
|
220
234
|
synchronous `as-string` script semantics. Streaming never makes a command
|
|
221
235
|
read-only.
|
|
222
|
-
-
|
|
223
|
-
|
|
236
|
+
- A stream that passed its preflight opens with one `started` line
|
|
237
|
+
(`meta.operation.stream.kind=started`, `data: null`). It carries the full
|
|
238
|
+
`meta` (target, settings, validation, request, auth) and any advice in
|
|
239
|
+
`tips`, and is not counted in `frames`/`rows`.
|
|
240
|
+
- Each wire reply emits one envelope with `meta.operation.stream.kind=frame`,
|
|
241
|
+
`observedAt` (when centrs received it, host clock, ISO 8601; a slow reader
|
|
242
|
+
does not move it) and `reply`: **`re`** (a row; `data` is the row, device `.section`,
|
|
224
243
|
`.dead`, `.nextid` and `.about` verbatim) or **`empty`** (RouterOS `!empty`:
|
|
225
244
|
zero rows; `data` is `null`). RouterOS sends `!empty` for an empty finite
|
|
226
245
|
read, for **each zero-row tick** of `print interval=`, and for a listen
|
|
227
246
|
cancelled before it sent anything. A frame received after centrs stopped the
|
|
228
247
|
stream locally carries `afterStop: true` — that cancellation `!empty` is not
|
|
229
|
-
evidence of an empty table. A
|
|
248
|
+
evidence of an empty table. A frame carries only `meta.operation` (`kind`,
|
|
249
|
+
`objectCount`, `stream`); the rest is on `started` and the summary
|
|
250
|
+
([constitution](../../docs/CONSTITUTION.md#result-envelope)). A terminal
|
|
251
|
+
`!done` is not a frame: its
|
|
230
252
|
attributes, when received, appear in successful summary `data.done`,
|
|
231
253
|
including a terminal result received during local cancellation. The stop
|
|
232
254
|
reason still reports the first local bound.
|
|
@@ -245,17 +267,19 @@ centrs never substitutes polling.
|
|
|
245
267
|
one error envelope without starting a stream.
|
|
246
268
|
- Structured streams put **all** envelopes, including errors, on stdout as
|
|
247
269
|
NDJSON and exit nonzero for any failure, even after successful rows. Text
|
|
248
|
-
mode uses the same
|
|
249
|
-
|
|
270
|
+
mode uses the same exit rules but puts only frame rows on stdout; the
|
|
271
|
+
`started` tips, the summary, warnings and errors go to stderr. `--raw`
|
|
272
|
+
retains bare payloads and stderr errors; it does not provide the
|
|
273
|
+
structured stream contract.
|
|
250
274
|
|
|
251
275
|
`api` never edits a change feed to make it safer; it says what RouterOS will
|
|
252
276
|
not report as tips (CHR 7.23.7 + 7.24.5). Because that advice depends only on
|
|
253
|
-
the request, it arrives **first
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
the request, it arrives **first**, in the `tips` of the leading `started`
|
|
278
|
+
line, before any reply — so a filtered watch that never gets a reply still
|
|
279
|
+
shows it. The same tips are repeated on the terminal summary, successful or
|
|
280
|
+
failed. Advice from the live gate (such as `tip/rest-verb-mapping`) rides the
|
|
281
|
+
same two lines, as it does on a one-shot call. `--raw` omits the `started`
|
|
282
|
+
line.
|
|
259
283
|
|
|
260
284
|
- `tip/filtered-follow` — a `listen` (or `follow`) with a query reports
|
|
261
285
|
neither rows leaving the filter nor deletes. Follow unfiltered and filter
|
package/commands/api/examples.md
CHANGED
|
@@ -277,6 +277,18 @@ key. Contrast example 13, where bare `--raw` skips the preflight entirely. This
|
|
|
277
277
|
pair is how you tell a RouterOS rejection from a centrs bug while staying on the
|
|
278
278
|
bare-passthrough surface.
|
|
279
279
|
|
|
280
|
+
### 23. POST `/console/inspect` is a read: no `--yes` needed (#458)
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
centrs api $R console/inspect -X POST -f request=child -f path=system,identity --username $U --password $P </dev/null
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Envelope: `ok: true`, `data` lists the children of `/system/identity`
|
|
287
|
+
(including `print`). Inspect describes and parses commands but never runs them,
|
|
288
|
+
so a POST to exactly `console/inspect` skips the write gate, non-TTY and
|
|
289
|
+
without `--yes`. PUT/PATCH/DELETE to it (add/set/remove) and abbreviations
|
|
290
|
+
such as `con/insp` are still gated like example 15.
|
|
291
|
+
|
|
280
292
|
### H1. `--host` stands in for `<router>` (#428)
|
|
281
293
|
|
|
282
294
|
```bash
|
|
@@ -484,8 +496,8 @@ centrs api $A ip/address/listen --proplist address,.id,.dead --duration 3s --via
|
|
|
484
496
|
```
|
|
485
497
|
|
|
486
498
|
The harness deletes a seeded address during each. With `address` alone no
|
|
487
|
-
frame carries `.dead` for it, and the leading
|
|
488
|
-
`tip/follow-proplist`.
|
|
499
|
+
frame carries `.dead` for it, and the leading `started` line and the summary
|
|
500
|
+
carry `tip/follow-proplist`.
|
|
489
501
|
With `address,.id,.dead` the delete frame is `{ ".id", ".dead": "true" }` and
|
|
490
502
|
there is no tip.
|
|
491
503
|
|
|
@@ -497,7 +509,7 @@ centrs api $A ip/address/listen --query interface=ether1 --duration 3s --via nat
|
|
|
497
509
|
|
|
498
510
|
The harness deletes a seeded `ether1` address while listening. RouterOS sends
|
|
499
511
|
nothing for it: the only frame is the cancellation `!empty` (`afterStop`).
|
|
500
|
-
The first line
|
|
512
|
+
The first line, `started`, carries `tip/filtered-follow`; the summary is `ok`
|
|
501
513
|
and repeats it. `--raw-query` is sent
|
|
502
514
|
the same way.
|
|
503
515
|
|
|
@@ -570,8 +582,8 @@ centrs api $A ip/address --stream --via native-api --port $API_PORT --username $
|
|
|
570
582
|
```
|
|
571
583
|
|
|
572
584
|
The print completes on its own (`stopReason=completed`) with one `re` frame
|
|
573
|
-
per row (`rows` equals a one-shot GET's row count);
|
|
574
|
-
summary carry `tip/stream-print` naming `ip/address/listen`. It never becomes a listen.
|
|
585
|
+
per row (`rows` equals a one-shot GET's row count); the leading `started` line
|
|
586
|
+
and the summary carry `tip/stream-print` naming `ip/address/listen`. It never becomes a listen.
|
|
575
587
|
|
|
576
588
|
### L18. Zero-row ticks are `!empty` frames and do not use up `--count`
|
|
577
589
|
|
|
@@ -621,7 +633,7 @@ centrs api --where board=chr ip/address --cdb-file $CDB --json
|
|
|
621
633
|
The device-class selector matches only record 0, so `data.summary = { total: 1,
|
|
622
634
|
ok: 1, failed: 0 }`, one inner success. Exit code 0.
|
|
623
635
|
|
|
624
|
-
### F3. An empty selection is `ok:true` with summary 0/0/0
|
|
636
|
+
### F3. An empty selection is `ok:true` with summary 0/0/0 for a read
|
|
625
637
|
|
|
626
638
|
```bash
|
|
627
639
|
centrs api --group no-such-group ip/address --cdb-file $CDB --json
|
|
@@ -630,6 +642,15 @@ centrs api --group no-such-group ip/address --cdb-file $CDB --json
|
|
|
630
642
|
`ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`, `data.targets = []`,
|
|
631
643
|
and a `cdb/empty-group` warning. Exit code 0.
|
|
632
644
|
|
|
645
|
+
A mutating method over the same group is an outer error, with or without
|
|
646
|
+
`--yes`, so a misspelled group cannot pass for a successful bulk write (#460):
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
centrs api -X PUT --group no-such-group ip/address -f address=10.99.0.9/32 -f interface=ether1 --yes --cdb-file $CDB --json
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
`ok: false`, `error.code` `cdb/empty-group`. Exit code 1.
|
|
653
|
+
|
|
633
654
|
### F4. A mutating fan-out without `--yes` is rejected, naming the blast radius
|
|
634
655
|
|
|
635
656
|
```bash
|
|
@@ -691,6 +712,23 @@ target fails deterministically: inner `ok:false` with
|
|
|
691
712
|
`error.code=target/unresolved` (never a `transport/dns` attempt on `"__default__"`).
|
|
692
713
|
`data.summary = { total: 1, ok: 0, failed: 1 }`. Exit code 1 (every target failed).
|
|
693
714
|
|
|
715
|
+
## CMR fleet commands (#453)
|
|
716
|
+
|
|
717
|
+
`test/integration/execute-cmr.test.ts` runs these on the CMR lab described in
|
|
718
|
+
[execute → CMR](../execute/examples.md#cmr-clients-through-a-controller-450),
|
|
719
|
+
over both `--via native-api` and `--via rest-api`.
|
|
720
|
+
|
|
721
|
+
### CMR1. `POST /cmr/device/reboot` names the devices it reaches
|
|
722
|
+
|
|
723
|
+
```bash
|
|
724
|
+
centrs api --quickchr $CTL -X POST /cmr/device/reboot -f labels=<client>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Without `--yes` (no TTY) this is `usage/confirmation-required`. Its summary
|
|
728
|
+
names `1 CMR device(s)`, and `error.context.cmr` is
|
|
729
|
+
`{devices: 1, controllerIncluded: false, identities: ["<client>"]}`. Nothing
|
|
730
|
+
reboots.
|
|
731
|
+
|
|
694
732
|
## `--query` grammar (#397)
|
|
695
733
|
|
|
696
734
|
`--query` takes the `print where` grammar shared with `retrieve --query`. The
|