@tikoci/centrs 0.1.1 → 0.1.3
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 +107 -2
- package/GLOSSARY.txt +87 -0
- package/README.md +25 -9
- package/SECURITY.md +3 -3
- package/commands/AGENTS.md +3 -2
- package/commands/api/AGENTS.md +212 -0
- package/commands/api/README.md +177 -0
- package/commands/api/examples.md +428 -0
- package/commands/btest/README.md +56 -31
- package/commands/btest/examples.md +35 -10
- package/commands/check/README.md +371 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/execute/README.md +48 -7
- package/commands/execute/examples.md +90 -6
- package/commands/retrieve/README.md +37 -51
- package/commands/retrieve/examples.md +40 -9
- package/commands/settings/README.md +286 -22
- package/commands/settings/examples.md +408 -0
- package/commands/terminal/README.md +6 -0
- package/commands/transfer/README.md +32 -2
- package/commands/transfer/examples.md +66 -0
- package/docs/CONSTITUTION.md +40 -15
- package/docs/MATRIX.md +69 -142
- package/docs/errors/README.md +2 -1
- package/docs/errors/cdb/empty-selection.md +19 -0
- package/docs/errors/input/incomplete-gps.md +9 -0
- package/docs/errors/input/invalid-altitude.md +9 -0
- package/docs/errors/input/invalid-bbox.md +9 -0
- package/docs/errors/input/invalid-coordinate.md +9 -0
- package/docs/errors/input/invalid-radius.md +9 -0
- package/docs/errors/internal/settings-failed.md +9 -0
- package/docs/errors/routeros/btest-connection-count-single-stream.md +22 -0
- package/docs/errors/routeros/btest-udp-tx-size-ignored.md +15 -0
- package/docs/errors/settings/consequential-value.md +26 -0
- package/docs/errors/settings/invalid-boolean.md +21 -3
- package/docs/errors/settings/invalid-format.md +18 -3
- package/docs/errors/settings/invalid-integer.md +18 -3
- package/docs/errors/settings/invalid-timeout.md +20 -3
- package/docs/errors/settings/invalid-via.md +22 -3
- package/docs/errors/settings/reserved-key.md +29 -0
- package/docs/errors/settings/skip-env-file-active.md +25 -0
- package/docs/errors/settings/unknown-key.md +25 -0
- package/docs/errors/usage/fanout-not-supported.md +16 -0
- package/docs/errors/usage/invalid-method.md +23 -0
- package/package.json +9 -9
- package/src/api-fanout.ts +454 -0
- package/src/api.ts +1627 -0
- package/src/btest.ts +61 -1
- package/src/cli/api.ts +660 -0
- package/src/cli/btest.ts +20 -2
- package/src/cli/common.ts +75 -0
- package/src/cli/devices.ts +235 -6
- package/src/cli/discover.ts +3 -1
- package/src/cli/execute.ts +107 -6
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +135 -90
- package/src/cli/selection.ts +200 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +171 -47
- package/src/cli.ts +29 -0
- package/src/core/error-catalog.ts +69 -0
- package/src/core/fanout.ts +301 -0
- package/src/core/inspect.ts +170 -0
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/execute-fanout.ts +438 -0
- package/src/execute.ts +134 -70
- package/src/index.ts +166 -9
- package/src/mcp/config.ts +12 -2
- package/src/mcp/tools.ts +137 -2
- package/src/protocols/adapter.ts +357 -9
- package/src/protocols/btest-session.ts +279 -46
- package/src/protocols/btest.ts +6 -2
- package/src/protocols/index.ts +11 -5
- package/src/protocols/native-api.ts +223 -30
- package/src/resolver/cdb.ts +10 -2
- package/src/resolver/comment-kv.ts +58 -4
- package/src/resolver/config-file.ts +136 -0
- package/src/resolver/facts.ts +78 -0
- package/src/resolver/geo.ts +453 -0
- package/src/resolver/index.ts +46 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/selection.ts +304 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +20 -2
- package/src/retrieve-fanout.ts +232 -268
- package/src/retrieve.ts +69 -94
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +527 -0
- package/src/transfer.ts +61 -11
- package/commands/stream/README.md +0 -70
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# check — examples
|
|
2
|
+
|
|
3
|
+
Each numbered example is an executable spec. Once `check` is implemented, the
|
|
4
|
+
integration test under `test/integration/check.test.ts` will run the CHR-backed
|
|
5
|
+
examples against a CHR booted by `@tikoci/quickchr`, and the offline examples
|
|
6
|
+
(flag parsing, `--list-checks`, fixture-backed `--save`) will run under
|
|
7
|
+
`test/unit/check.test.ts` — one assertion per example (example N ↔ assertion N).
|
|
8
|
+
Until then these are the **target**: if a line here is not exercised by a test
|
|
9
|
+
when the code lands, the test file is wrong; if a line passes only with
|
|
10
|
+
`--validate=false`, the **implementation** is wrong (see `docs/CONSTITUTION.md`).
|
|
11
|
+
|
|
12
|
+
These examples are the **target** for `CHR-passed`; the cells are `designed`
|
|
13
|
+
today, so nothing here is green yet.
|
|
14
|
+
|
|
15
|
+
`$R` is `<host>:<rest-port>` resolved by quickchr. `$U` / `$P` are CHR
|
|
16
|
+
credentials from the harness. Envelope-asserting examples pass `--json`.
|
|
17
|
+
|
|
18
|
+
## Smart battery (default)
|
|
19
|
+
|
|
20
|
+
### 1. Bare check runs the smart battery
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
centrs check $R --username $U --password $P --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Envelope: `ok: true` (a healthy CHR raises warnings/tips at most, not errors),
|
|
27
|
+
`data.checks[]` includes at least `reach`, `services`, `resource`, `cloud`,
|
|
28
|
+
`dns`; each entry has `ran: true` and a `metadata` object. `data.profile`
|
|
29
|
+
carries `board`, `arch`, `version`, `channel`. `meta.via` is the transport the
|
|
30
|
+
battery rode.
|
|
31
|
+
|
|
32
|
+
### 2. Profile block is populated
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
centrs check $R --username $U --password $P --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Assert `data.profile.ports` maps `rest-api`/`native-api` to the live
|
|
39
|
+
`/ip/service` ports, and `data.profile.reachable` lists the transports that
|
|
40
|
+
answered. (Shape is additive/non-normative — assert presence, not an exact key
|
|
41
|
+
set.)
|
|
42
|
+
|
|
43
|
+
### 3. No credentials → reach only, tip for the rest
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
centrs check $R --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Envelope: `ok: true`; `data.checks[]` has `reach` (`ran: true`) while the health
|
|
50
|
+
checks report `ran: false` with `skipReason` naming missing credentials, and a
|
|
51
|
+
`tip/reach/credentials-needed` is present.
|
|
52
|
+
|
|
53
|
+
## Selection and presets
|
|
54
|
+
|
|
55
|
+
### 4. `--preset reach` is the fast slice
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
centrs check $R --username $U --password $P --preset reach --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Only the `reach` check ran; no health-phase reads were issued. (This is the slice
|
|
62
|
+
`discover`'s IP-scan iterates.)
|
|
63
|
+
|
|
64
|
+
### 5. `--checks` runs an explicit subset (+ dependencies)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
centrs check $R --username $U --password $P --checks services,resource --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`data.checks[]` contains exactly `services` and `resource` (and any `dependsOn`),
|
|
71
|
+
nothing else.
|
|
72
|
+
|
|
73
|
+
### 6. `--skip` subtracts from the default
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
centrs check $R --username $U --password $P --skip dns --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The default battery ran except `dns` (absent from `data.checks[]`).
|
|
80
|
+
|
|
81
|
+
### 7. `--list-checks` explains the catalog (offline)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
centrs check --list-checks --json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
No network IO. `data` lists every registered check with `id`, `title`,
|
|
88
|
+
`description`, `tags`, `phase`, `default`. Exit `0`.
|
|
89
|
+
|
|
90
|
+
## Findings and reaching green
|
|
91
|
+
|
|
92
|
+
### 8. A risky service raises a warning
|
|
93
|
+
|
|
94
|
+
Precondition: telnet is enabled on the CHR (stock CHR 7.23.1 has it enabled;
|
|
95
|
+
if the fixture hardened services first, run `/ip/service enable telnet`).
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
centrs check $R --username $U --password $P --checks services --json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`data.checks[].findings` includes a `check/services/telnet-enabled` warning with
|
|
102
|
+
a `fix`. Top-level `warnings[]` carries the same code (roll-up).
|
|
103
|
+
|
|
104
|
+
### 9. `--ignore` suppresses a known finding back to green
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
centrs check $R --username $U --password $P --checks services \
|
|
108
|
+
--ignore check/services/telnet-enabled --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The warning no longer appears in `findings[]` or `warnings[]`; `ok: true`.
|
|
112
|
+
|
|
113
|
+
### 10. `--fail-on warning` gates the exit code, not `ok`
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
centrs check $R --username $U --password $P --checks services --fail-on warning --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
With telnet enabled: the run stays **`ok: true`** with the full `data` present and
|
|
120
|
+
`data.verdict = "warn"`, but the process **exit code is `2`** (reachable, over the
|
|
121
|
+
chosen bar). Re-running with `--ignore check/services/telnet-enabled` gives
|
|
122
|
+
`data.verdict = "pass"` and exit `0`. (At the default `--fail-on error` the same
|
|
123
|
+
warning is a footnote — exit `0` — see example 19.)
|
|
124
|
+
|
|
125
|
+
## Capability pre-flight and transport
|
|
126
|
+
|
|
127
|
+
### 11. An unsupported path is skipped, not errored
|
|
128
|
+
|
|
129
|
+
Simulated against a device/version where a check's `requires` path is absent
|
|
130
|
+
(fixture or older CHR).
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
centrs check $R --username $U --password $P --checks routerboard --json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
On CHR (no RouterBOARD) `routerboard` reports `ran: false`,
|
|
137
|
+
`skipReason` referencing capability/CHR — `ok` stays `true`, no raw protocol
|
|
138
|
+
error surfaces.
|
|
139
|
+
|
|
140
|
+
### 12. `--via` pins the transport with no silent downgrade
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
centrs check $R --username $U --password $P --via native-api --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`meta.via = native-api`; the battery rode native-api. A pin to a transport that
|
|
147
|
+
cannot serve the battery errors with a `transport/*` code (constitution: never
|
|
148
|
+
silently downgrade across `--via`).
|
|
149
|
+
|
|
150
|
+
### 13. A slow check is bounded by `--check-timeout`
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
centrs check $R --username $U --password $P --checks traffic --check-timeout 2s --json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`traffic` (a ~10 s probe) is cut at 2 s: its entry carries a
|
|
157
|
+
`check/traffic/timed-out` finding and the run still returns rather than hanging.
|
|
158
|
+
|
|
159
|
+
## Custom check script
|
|
160
|
+
|
|
161
|
+
### 14. Custom scripts merge as synthetic checks (repeatable, `--yes`-gated)
|
|
162
|
+
|
|
163
|
+
Two policy scripts, one inline and one from a fixture the test will add (the path
|
|
164
|
+
shown is its planned location), both required to `:put` an
|
|
165
|
+
`{errors,warnings,tips,metadata}` JSON envelope. An unset channel is
|
|
166
|
+
`[:toarray ""]`, which RouterOS serializes as an empty array `[]` (not `[""]`) —
|
|
167
|
+
grounded on CHR 7.24rc1, where three `[:toarray ""]`-initialized channels
|
|
168
|
+
serialized as `[]`:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
centrs check $R --username $U --password $P --yes --json \
|
|
172
|
+
--check-script '{:put [:serialize to=json {"errors"=[:toarray ""];"warnings"=[:toarray ""];"tips"=[:toarray ""];"metadata"={"ipv6-disabled"=[/ipv6/settings/get disable-ipv6]}}]}' \
|
|
173
|
+
--check-script-file test/integration/fixtures/check/policy.rsc
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Each merges under its own `check/custom/<n>`. Without `--yes` the run errors
|
|
177
|
+
`usage/confirmation-required` (a custom script is a trusted, possibly-mutating
|
|
178
|
+
path); with it, the envelope carries a `check/custom-script/ran` audit
|
|
179
|
+
warning. A policy `error` in a script sets `data.verdict = "fail"` → exit `2` at
|
|
180
|
+
the default `--fail-on` (the run itself is `ok: true`; the battery is present).
|
|
181
|
+
|
|
182
|
+
## `--save` (CDB reconcile, fixture-backed)
|
|
183
|
+
|
|
184
|
+
### 15. `--save` writes the port + derived facts to the CDB
|
|
185
|
+
|
|
186
|
+
Fixture CDB with a record for `$R` lacking `port=`/derived facts.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
centrs check $R --username $U --password $P --save --yes --json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The record gains `port=` for the resolved transport (where non-default),
|
|
193
|
+
`board`/`version`/`software-id`, and an `updated=` stamp, through the `devices`
|
|
194
|
+
write layer. RouterOS is unchanged, and the SNMP community is **not** persisted.
|
|
195
|
+
Without `--save`, an identical run writes nothing to the CDB.
|
|
196
|
+
|
|
197
|
+
## Fan-out
|
|
198
|
+
|
|
199
|
+
### 16. Fan-out across a group
|
|
200
|
+
|
|
201
|
+
Fixture CDB group `lab` with two reachable CHRs.
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
centrs check --group lab --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Locked `FanoutData` envelope (`data = { summary, targets[] }`); `targets[]` in
|
|
208
|
+
record order; exit `0` all-ok, `2` partial, `1` all-failed. A `--save --group lab`
|
|
209
|
+
without `--yes` errors, naming the blast radius.
|
|
210
|
+
|
|
211
|
+
## Reach signals and redaction
|
|
212
|
+
|
|
213
|
+
### 17. Reach reports latency and ARP presence
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
centrs check $R --username $U --password $P --preset reach --json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`data.profile` carries a host→router connect latency (RTT, ms) and an ARP-presence
|
|
220
|
+
fact for the target; `data.checks[].reach.metadata` lists the transports that
|
|
221
|
+
answered. No health-phase reads were issued.
|
|
222
|
+
|
|
223
|
+
### 18. Secrets are redacted from metadata
|
|
224
|
+
|
|
225
|
+
Precondition: an enabled SNMP community and `/ip/cloud` Back-To-Home on the CHR.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
centrs check $R --username $U --password $P --checks snmp,cloud --json
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`snmp` metadata reports version/scope/count but **omits the community string**;
|
|
232
|
+
`cloud` metadata keeps `dns-name`/`status` but **omits** `vpn-wireguard-client-config`
|
|
233
|
+
and the VPN keys. A full-envelope grep for the community string / WireGuard config
|
|
234
|
+
finds nothing.
|
|
235
|
+
|
|
236
|
+
## Presets, selection, and progressive control
|
|
237
|
+
|
|
238
|
+
### 19. Warnings do not fail the run at the default `--fail-on`
|
|
239
|
+
|
|
240
|
+
Precondition: telnet enabled (as example 8).
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
centrs check $R --username $U --password $P --json
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
A `services` warning is present, yet exit `0`: at the default `--fail-on error`,
|
|
247
|
+
`data.verdict = "warn"` is a footnote, not a failure. (Contrast example 10, which
|
|
248
|
+
raises the bar to `warning`.)
|
|
249
|
+
|
|
250
|
+
### 20. `--preset exposure` is a findings rollup, not extra reads
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
centrs check $R --username $U --password $P --preset exposure --json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`data.checks[]` contains only `services`, `certificates`, `snmp` (the preset
|
|
257
|
+
forces its members, including the normally-triggered `certificates`); no other
|
|
258
|
+
check ran and no path outside those three was read.
|
|
259
|
+
|
|
260
|
+
### 21. `--preset full` runs the opt-off checks
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
centrs check $R --username $U --password $P --preset full --json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`data.checks[]` additionally includes the `default: off` rows — `health`,
|
|
267
|
+
`packages`, `console`, `traffic`, `connections` — each `ran: true`.
|
|
268
|
+
|
|
269
|
+
### 22. `--deep` forces triggered checks even when their predicate is quiet
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
centrs check $R --username $U --password $P --deep --json
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
On an idle CHR (CPU low, `www-ssl` fine) `cpu-detail` and `certificates` still
|
|
276
|
+
ran (`ran: true`), where a bare run leaves them untriggered.
|
|
277
|
+
|
|
278
|
+
### 23. `--fast` suppresses progressive triggers
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
centrs check $R --username $U --password $P --fast --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
No `trig` check appears in `data.checks[]` regardless of what the default battery
|
|
285
|
+
flagged; only cheap default checks ran.
|
|
286
|
+
|
|
287
|
+
### 24. `--tag` filters to tagged checks
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
centrs check $R --username $U --password $P --tag hardware --json
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Only on-by-default checks carrying the `hardware` tag ran (e.g. `resource`,
|
|
294
|
+
`routerboard`); untagged checks are absent. (The tag taxonomy is provisional —
|
|
295
|
+
see README.)
|
|
296
|
+
|
|
297
|
+
## Suppression
|
|
298
|
+
|
|
299
|
+
### 25. `--ignore <checkId>` mutes findings but keeps metadata; `--skip` drops the check
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
centrs check $R --username $U --password $P --checks services --ignore services --json
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`services` still appears in `data.checks[]` with `metadata` populated, but its
|
|
306
|
+
`findings[]` is empty and nothing rolls up to top-level `warnings[]`. By contrast
|
|
307
|
+
`--skip services` removes the check entirely (no entry, no metadata).
|
|
308
|
+
|
|
309
|
+
### 26. `CENTRS_CHECK_IGNORE` suppresses via env (precedence)
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
CENTRS_CHECK_IGNORE=check/services/telnet-enabled \
|
|
313
|
+
centrs check $R --username $U --password $P --checks services --json
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Same effect as passing `--ignore check/services/telnet-enabled`; the winning
|
|
317
|
+
suppression source is reported in `meta.settings`. A `--ignore` arg for a
|
|
318
|
+
different code composes on top (both suppressed).
|
|
319
|
+
|
|
320
|
+
### 27. `--fail-on never` never fails the run
|
|
321
|
+
|
|
322
|
+
Precondition: a custom script that emits an `error` finding (as in example 14).
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
centrs check $R --username $U --password $P --yes --fail-on never --json \
|
|
326
|
+
--check-script '{:put [:serialize to=json {"errors"=[:toarray "policy breach"];"warnings"=[:toarray ""];"tips"=[:toarray ""];"metadata"={}}]}'
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`data.verdict = "fail"` but exit `0`; the error finding is still reported. This is
|
|
330
|
+
the pure-data-collection mode #149's IP-scan uses.
|
|
331
|
+
|
|
332
|
+
### 28. Unknown check name → did-you-mean (offline)
|
|
333
|
+
|
|
334
|
+
<!-- cspell:ignore servces -->
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
centrs check $R --checks servces --username $U --password $P
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
A `usage/*` error names `servces` as unknown and lists the closest catalog ids
|
|
341
|
+
(`services`) — the shared did-you-mean behavior (constitution / commands help).
|
|
342
|
+
Exit non-zero, no network IO.
|
|
343
|
+
|
|
344
|
+
## Fan-out and the write gate
|
|
345
|
+
|
|
346
|
+
### 29. Fan-out reports partial success with exit 2
|
|
347
|
+
|
|
348
|
+
Fixture CDB group `lab`: one reachable CHR, one unreachable host.
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
centrs check --group lab --json
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`FanoutData`: the reachable target's inner envelope is `ok: true`, the unreachable
|
|
355
|
+
one `ok: false` (`transport/*`); outer orchestration `ok: true`, process exit `2`.
|
|
356
|
+
|
|
357
|
+
### 30. `--save` fan-out requires `--yes`
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
centrs check --group lab --save --json
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Without `--yes` it errors before writing, naming the blast radius (how many CDB
|
|
364
|
+
records would change); adding `--yes` reconciles each target's record.
|
|
365
|
+
|
|
366
|
+
## L2 and MNDP (CHR L2 bridge)
|
|
367
|
+
|
|
368
|
+
### 31. A bare-MAC target runs the battery over mac-telnet
|
|
369
|
+
|
|
370
|
+
CHR-gated: needs the `socket-connect` L2 bridge (see `commands/discover/README.md`).
|
|
371
|
+
`$MAC` is the CHR's ether MAC.
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
centrs check $MAC --username $U --password $P --json
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
With no IP path, the battery rode mac-telnet (`meta.via = mac-telnet`) as one
|
|
378
|
+
consolidated script (Execution model); the health checks are `ran: true`.
|
|
379
|
+
|
|
380
|
+
### 32. MNDP enrichment survives an auth failure
|
|
381
|
+
|
|
382
|
+
CHR-gated (L2 bridge). Precondition: wrong password.
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
centrs check $R --username $U --password wrong --json
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`reach` still returns `ran: true` and `data.profile` carries MNDP-advertised
|
|
389
|
+
`identity`/`version`/`platform`/`board`; the health checks are `ran: false` (auth
|
|
390
|
+
failed). An auth error finding is surfaced, but the free MNDP facts are not lost.
|
|
@@ -89,22 +89,29 @@ when it actually selected between candidates.
|
|
|
89
89
|
## Comment kv-soup (per-device overrides)
|
|
90
90
|
|
|
91
91
|
The CDB `comment` field is free text. centrs additionally parses tokens
|
|
92
|
-
shaped like `key=value` out of it. Recognized keys (allowlist) fall in
|
|
92
|
+
shaped like `key=value` out of it. Recognized keys (allowlist) fall in three
|
|
93
93
|
groups: **lookup keys** affect how `<router>` resolves (see "Identity model");
|
|
94
|
-
**override keys** are per-device defaults for centrs settings
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
94
|
+
**override keys** are per-device defaults for centrs settings; **geo keys**
|
|
95
|
+
(issue #146) are facts, not settings — queryable via `--where` and returned in
|
|
96
|
+
the `location` block (see "Location / GPS" below), but never coerced into
|
|
97
|
+
resolver settings.
|
|
98
|
+
|
|
99
|
+
| Key | Group | Effect |
|
|
100
|
+
| --------------- | -------- | ---------------------------------------------------------------------- |
|
|
101
|
+
| `identity` | lookup | Device `/system/identity`; a resolvable handle. May repeat across records. |
|
|
102
|
+
| `mac` | lookup | A MAC, when `target` is not the MAC. |
|
|
103
|
+
| `ip` | lookup | An IP, when `target` is not the IP. |
|
|
104
|
+
| `via` | override | Default transport for this device (`rest-api`, `native-api`, `ssh`, …). |
|
|
105
|
+
| `validate` | override | `true` / `false`. `false` is the escape hatch; CLI overrides. |
|
|
106
|
+
| `timeout` | override | Default request timeout in ms. Per-transport caps still apply. |
|
|
107
|
+
| `port` | override | Non-default transport port. Omitted when equal to the protocol default. |
|
|
108
|
+
| `ssh-key` | override | Per-device private key file path for SSH transports. |
|
|
109
|
+
| `source` | override | Provenance marker for discovered/imported records (`mndp`, `dude`, …). |
|
|
110
|
+
| `mcp` | override | MCP write policy for this device: `ro` (default) or `rw`. See `commands/mcp/`. |
|
|
111
|
+
| `lat` | geo | Latitude, decimal degrees (`-90..90`). See "Location / GPS". |
|
|
112
|
+
| `lon` | geo | Longitude, decimal degrees (`-180..180`). Paired with `lat`. |
|
|
113
|
+
| `altitude` | geo | Meters, may be negative. Metadata-only (not a fan-out query predicate). |
|
|
114
|
+
| `altitude-type` | geo | `MSL`\|`AGL` (default `MSL`). Normalized to upper case for storage. |
|
|
108
115
|
|
|
109
116
|
Rules:
|
|
110
117
|
|
|
@@ -128,9 +135,9 @@ Rules:
|
|
|
128
135
|
is no current need to store a literally-empty value; revisit if one arises.
|
|
129
136
|
- `commentMirror` is kept in sync with `comment` on every write.
|
|
130
137
|
|
|
131
|
-
Precedence (lowest → highest): built-in defaults →
|
|
132
|
-
comment-kv → env (`CENTRS_*`) → CLI flag / API arg.
|
|
133
|
-
the winner and source per setting.
|
|
138
|
+
Precedence (lowest → highest): built-in defaults → user config file
|
|
139
|
+
(`centrs.env`) → comment-kv → env (`CENTRS_*`) → CLI flag / API arg.
|
|
140
|
+
`meta.settings` reports the winner and source per setting.
|
|
134
141
|
|
|
135
142
|
### Derived facts (queryable, may be stale)
|
|
136
143
|
|
|
@@ -147,7 +154,126 @@ a device); `license-id` is convenience metadata to match by hand against
|
|
|
147
154
|
MikroTik's CHR licensing page. The `--where` selector plus this derived-fact
|
|
148
155
|
allowlist are the planned home for "operate on a class of devices," distinct from
|
|
149
156
|
`group`. (Promoting these keys to the comment-kv allowlist is a tracked code
|
|
150
|
-
follow-up
|
|
157
|
+
follow-up; `lat`/`lon`/`altitude`/`altitude-type` — see "Location / GPS" below —
|
|
158
|
+
are the first keys promoted this way, issue #146.)
|
|
159
|
+
|
|
160
|
+
## Location / GPS
|
|
161
|
+
|
|
162
|
+
Per-device GPS (issue #146) is a **fact, not a setting**: recognized
|
|
163
|
+
(`devices set lat=…` no longer warns `cdb/unknown-option`), stored, queryable via
|
|
164
|
+
`--where` (exact) and `--near`/`--bbox` (geo), and returned as a `location` block
|
|
165
|
+
in `devices show`/`list` output — but never coerced into resolver settings. This
|
|
166
|
+
section covers storage/flags/validation/envelope; the geo query predicates are in
|
|
167
|
+
"Geo query" below.
|
|
168
|
+
|
|
169
|
+
### Storage keys
|
|
170
|
+
|
|
171
|
+
See the comment-kv table above for `lat`/`lon`/`altitude`/`altitude-type`.
|
|
172
|
+
Values are stored **verbatim as typed** (validated, not reformatted), so an
|
|
173
|
+
exact `--where lat=<value>` still matches what was written — `altitude-type` is
|
|
174
|
+
the one exception, since it's a closed two-value enum, so it is normalized to
|
|
175
|
+
upper case (`msl` -> `MSL`) before storage. `lat`/`lon` must be written as a
|
|
176
|
+
pair: setting one without the other, when the other is not already on the
|
|
177
|
+
record, errors `input/incomplete-gps`. Only the pairing rule fires per-write —
|
|
178
|
+
an unrelated `set timeout=…` on a record that already carries a lone `lat` is
|
|
179
|
+
left untouched.
|
|
180
|
+
|
|
181
|
+
`lat`/`lon`/`altitude` accept **strict decimal** only (optional leading `-`,
|
|
182
|
+
digits, optional dotted fraction); hex (`0x10`), scientific (`1e2`), and values
|
|
183
|
+
that overflow to `Infinity` are rejected with `input/invalid-coordinate` /
|
|
184
|
+
`input/invalid-altitude` even though `Number()` would accept them, because the
|
|
185
|
+
fields are decimal degrees / meters.
|
|
186
|
+
|
|
187
|
+
GPS validation (range, strict-decimal, pairing, `altitude-type` enum) applies to
|
|
188
|
+
the **setter flags** (`--lat`/`--lon`/`--altitude`/`--gps`) and to **bare `k=v`
|
|
189
|
+
update tokens** (`lat=…`, `lon=…`). It does **not** re-parse arbitrary base
|
|
190
|
+
`--comment` text: `devices add <t> --comment "lat=91"` is a freeform escape hatch
|
|
191
|
+
that stores the comment as-is (such a value simply won't resolve to a `location`
|
|
192
|
+
on read). Use the flags or `k=v` tokens when you want validated GPS.
|
|
193
|
+
|
|
194
|
+
### Flags
|
|
195
|
+
|
|
196
|
+
`devices add`/`set` accept:
|
|
197
|
+
|
|
198
|
+
- `--lat <deg>` (alias `--latitude`)
|
|
199
|
+
- `--lon <deg>` (aliases `--lng`, `--longitude`, `--long`)
|
|
200
|
+
- `--altitude <meters>` (aliases `--alt`, `--ele`, `--elevation`) — `altitude`
|
|
201
|
+
stays the canonical field (the ISO 6709 / EPSG / IETF `geo:` / W3C-Geolocation
|
|
202
|
+
term); `ele`/`elevation` are GPX-muscle-memory aliases, not a rename
|
|
203
|
+
- `--altitude-type <MSL|AGL>` (alias `--alt-type`), case-insensitive
|
|
204
|
+
- `--gps <lat>,<lon>[,<altitude>[,<altitude-type>]]` — combined convenience;
|
|
205
|
+
minimum `--gps <lat>,<lon>`; a missing altitude-type in the 3-part form
|
|
206
|
+
defaults to `MSL`.
|
|
207
|
+
|
|
208
|
+
Bare `k=v` positionals also work (`lat=…`, `lon=…`, `altitude=…`,
|
|
209
|
+
`altitude-type=…`), through the same validation as the flags above. Aliases are
|
|
210
|
+
canonicalized to their table form in one place before storage/validation, so
|
|
211
|
+
the canonical key is always what lands in the comment and the envelope; typos
|
|
212
|
+
beyond the alias list are caught by the existing "Did you mean?" flag
|
|
213
|
+
suggester (`closestFlags`/`unknownFlagError`).
|
|
214
|
+
|
|
215
|
+
Validation errors reuse the `input/` arg-validation family:
|
|
216
|
+
`input/invalid-coordinate` (lat/lon NaN or out of range),
|
|
217
|
+
`input/invalid-altitude` (altitude NaN, or a bad altitude-type), and
|
|
218
|
+
`input/incomplete-gps` (lat/lon not paired, or `--gps` wrong arity).
|
|
219
|
+
|
|
220
|
+
### Coordinate order is lat-first
|
|
221
|
+
|
|
222
|
+
`--gps 37.7749,-122.4194` is `lat,lon` — **latitude first, everywhere** —
|
|
223
|
+
matching ISO 6709 / EPSG:4326 authority order (and Google Maps / Leaflet /
|
|
224
|
+
most GPS devices), not GeoJSON's lon-first order. RFC 7946 (GeoJSON) describes
|
|
225
|
+
its own order as the opposite of "most GPS coordinates" and calls swapping them
|
|
226
|
+
"the most common GeoJSON bug"; lat-first is both what an agent guesses from the
|
|
227
|
+
`--gps` name and the standards-backed (ISO 6709) choice. A future GeoJSON
|
|
228
|
+
(lon-first) input path is deliberately out of scope here — it would ride a
|
|
229
|
+
structurally-typed shape (a real GeoJSON object / TikTOML import), never a bare
|
|
230
|
+
CSV.
|
|
231
|
+
|
|
232
|
+
### Altitude is metadata-only
|
|
233
|
+
|
|
234
|
+
`altitude`/`altitude-type` are stored and returned in `devices show`/`list`
|
|
235
|
+
output, but are **not** part of any fan-out query predicate — the `--near`/
|
|
236
|
+
`--bbox` geo selectors are 2-D lat/lon only.
|
|
237
|
+
|
|
238
|
+
### Geo query (`--near` / `--bbox`)
|
|
239
|
+
|
|
240
|
+
Two geo selectors filter/select by device GPS. On `devices list` they filter the
|
|
241
|
+
listing; on every non-terminal fan-out command (`execute`/`retrieve`/`api`/
|
|
242
|
+
`transfer`) they are union selector terms in the shared target-selection grammar
|
|
243
|
+
(`docs/CONSTITUTION.md`, Target selection). Coordinates are lat-first (above); a
|
|
244
|
+
geo-less device carries no location and is silently excluded (never an error).
|
|
245
|
+
|
|
246
|
+
- `--near <lat>,<lon>,<radius>` — devices within a great-circle (haversine)
|
|
247
|
+
radius of the point. `radius` takes a unit suffix `m`/`km`/`mi`/`ft`
|
|
248
|
+
(case-insensitive); a bare number is kilometers. A malformed/negative radius is
|
|
249
|
+
`input/invalid-radius`; wrong arity is `input/invalid-command`.
|
|
250
|
+
- `--bbox <south>,<west>,<north>,<east>` — devices inside the axis-aligned box
|
|
251
|
+
(`minLat,minLon,maxLat,maxLon`, edges inclusive). Requires `south <= north` and
|
|
252
|
+
`west <= east` (an antimeridian-crossing box is not supported in v1), else
|
|
253
|
+
`input/invalid-bbox`.
|
|
254
|
+
|
|
255
|
+
On `devices list`, `--group`/`--where`/`--near`/`--bbox` AND-narrow the listing
|
|
256
|
+
(a filter surface); `--near` and `--bbox` union with each other. In the fan-out
|
|
257
|
+
grammar every selector unions (see the constitution). `--near`/`--bbox` are
|
|
258
|
+
rejected on `add`/`set`/`show`/`remove` with `input/invalid-command` — they
|
|
259
|
+
select, they do not store.
|
|
260
|
+
|
|
261
|
+
### Envelope
|
|
262
|
+
|
|
263
|
+
`devices show`/`list` add an optional `location` block to each entry that
|
|
264
|
+
carries a well-formed `lat`+`lon` pair, parsed from the comment facts:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{ "lat": 37.7749, "lon": -122.4194, "altitude": 16, "altitudeType": "MSL" }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
A record with no (or malformed) `lat`/`lon` simply omits `location`.
|
|
271
|
+
|
|
272
|
+
### Group-level GPS defers to #137
|
|
273
|
+
|
|
274
|
+
CDB groups are a bare string field on each entry — there is no
|
|
275
|
+
group-definition record to hang a group-level GPS or lat/lon inheritance on.
|
|
276
|
+
That needs TikTOML (#137); not doable in the CDB today.
|
|
151
277
|
|
|
152
278
|
## Groups
|
|
153
279
|
|
|
@@ -182,27 +308,33 @@ split lands.)
|
|
|
182
308
|
## Fanout (multi-target invocations)
|
|
183
309
|
|
|
184
310
|
Any command may receive multiple positional targets, one or more `--group`
|
|
185
|
-
flags, `--all`, `--default`,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
311
|
+
flags, `--all`, `--default`, `--where <attr>=<value>` (device-class), and/or the
|
|
312
|
+
geo selectors `--near <lat>,<lon>,<radius>` / `--bbox <south>,<west>,<north>,<east>`
|
|
313
|
+
(see "Geo query" above) — see `docs/CONSTITUTION.md` (Target selection grammar).
|
|
314
|
+
The resolved member set is
|
|
315
|
+
de-duplicated by CDB record index (ad-hoc literal targets by host), and members
|
|
316
|
+
are reassembled in record-index order regardless of completion order, so repeated
|
|
317
|
+
runs produce stable diffs.
|
|
318
|
+
|
|
319
|
+
- Each member runs in parallel up to `--concurrency` (transport-aware defaults:
|
|
320
|
+
`rest-api` 8, `native-api` 4).
|
|
321
|
+
- Output is the locked `FanoutData` envelope with the granular `0`/`2`/`1` exit
|
|
322
|
+
code. The envelope shape (outer `ok` = orchestration success; a per-target
|
|
323
|
+
failure is an inner `ok: false`) and the exit-code contract are defined once in
|
|
324
|
+
[`docs/CONSTITUTION.md`](../../docs/CONSTITUTION.md) (Target selection) — not
|
|
325
|
+
restated here.
|
|
326
|
+
- A multi-target **write** is gated by `--yes`, confirmed once up front (not per
|
|
327
|
+
target); without it the error/tip names the blast radius (how many routers).
|
|
328
|
+
`--force` stays scoped to destructive `devices` CDB mutations.
|
|
329
|
+
- Commands that are not safe to fan out (`terminal`, and `api --stream` /
|
|
330
|
+
`--listen`) reject a multi-target selection with `usage/fanout-not-supported`.
|
|
201
331
|
|
|
202
332
|
## Subcommands
|
|
203
333
|
|
|
204
334
|
```text
|
|
205
|
-
centrs devices list [--group G] [--
|
|
335
|
+
centrs devices list [--group G] [--where attr=value]
|
|
336
|
+
[--near lat,lon,radius] [--bbox south,west,north,east]
|
|
337
|
+
[--format json|yaml|text]
|
|
206
338
|
centrs devices show <target> [--explain] [--via <protocol>] [--match <type>]
|
|
207
339
|
centrs devices groups [--members]
|
|
208
340
|
centrs devices discover [--timeout 15s] [--group G] # = discover --save (save implied)
|
|
@@ -210,9 +342,13 @@ centrs devices add <target> [--user U] [--password P] [--new-group G]
|
|
|
210
342
|
[--profile P|--profile-none|--profile-own]
|
|
211
343
|
[--session S] [--comment "text k=v"]
|
|
212
344
|
[--record-type ipAdmin|ipUser|macTarget|...]
|
|
345
|
+
[--gps lat,lon[,altitude[,altitude-type]]]
|
|
346
|
+
[--lat D] [--lon D] [--altitude M] [--altitude-type MSL|AGL]
|
|
213
347
|
centrs devices set <selector…> [--user U] [--password P] [--new-group G]
|
|
214
348
|
[--profile P|--profile-none|--profile-own]
|
|
215
349
|
[--session S] [k=v ...]
|
|
350
|
+
[--gps lat,lon[,altitude[,altitude-type]]]
|
|
351
|
+
[--lat D] [--lon D] [--altitude M] [--altitude-type MSL|AGL]
|
|
216
352
|
centrs devices remove <selector…>
|
|
217
353
|
centrs devices edit <target> # future: clack/TUI wizard
|
|
218
354
|
```
|
|
@@ -226,7 +362,11 @@ field-editing verb, and invoking it today returns `usage/not-implemented`. There
|
|
|
226
362
|
is no `update`.
|
|
227
363
|
|
|
228
364
|
- `list` (alias `print`) shows resolved targets, their record type, group, and
|
|
229
|
-
a one-line provenance summary. No network IO.
|
|
365
|
+
a one-line provenance summary. No network IO. `--where attr=value`
|
|
366
|
+
(repeatable, AND-combined) filters by CDB-stored facts/comment-kv plus core
|
|
367
|
+
record fields (`target`/`group`/`identity`/`mac`) — e.g. `--where
|
|
368
|
+
lat=37.7749`. `--near lat,lon,radius` and `--bbox south,west,north,east`
|
|
369
|
+
filter by device GPS (see "Geo query"). All three are `list`-only.
|
|
230
370
|
- `show` (alias `get`) returns a single resolved target with the full per-field
|
|
231
371
|
source map in `meta.target`. `--explain` adds the raw CDB record dump under
|
|
232
372
|
`data.record`; `--via <protocol>` reports the protocol that would be selected
|