@tikoci/centrs 0.1.6 → 0.1.8-next.0
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 +342 -0
- package/GLOSSARY.txt +32 -0
- package/README.md +10 -6
- package/SECURITY.md +1 -1
- package/commands/AGENTS.md +2 -1
- package/commands/api/AGENTS.md +55 -30
- package/commands/api/README.md +135 -42
- package/commands/api/examples.md +275 -19
- package/commands/devices/README.md +21 -4
- package/commands/devices/examples.md +33 -2
- package/commands/execute/README.md +60 -18
- package/commands/execute/examples.md +224 -0
- package/commands/explain/README.md +42 -5
- package/commands/mcp/README.md +2 -1
- package/commands/retrieve/README.md +348 -4
- package/commands/retrieve/examples.md +442 -5
- package/commands/terminal/README.md +14 -4
- package/commands/terminal/examples.md +22 -3
- package/commands/transfer/README.md +55 -9
- package/commands/transfer/examples.md +106 -7
- package/docs/CLI.md +29 -21
- package/docs/CONSTITUTION.md +41 -16
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -1
- package/docs/errors/cdb/create-failed.md +20 -0
- package/docs/errors/cdb/file-exists.md +23 -0
- package/docs/errors/input/invalid-query.md +36 -0
- package/docs/errors/input/invalid-target.md +25 -3
- package/docs/errors/input/local-destination.md +20 -0
- package/docs/errors/input/unsupported-query.md +19 -0
- package/docs/errors/internal/byte-string.md +13 -0
- package/docs/errors/routeros/invalid-value.md +3 -2
- package/docs/errors/settings/invalid-timeout.md +6 -0
- package/docs/errors/transport/cancel-unacknowledged.md +24 -0
- package/docs/errors/transport/stream-overflow.md +21 -0
- package/docs/errors/usage/removed-flag.md +20 -0
- package/docs/errors/validation/menu-unavailable.md +22 -0
- package/docs/errors/validation/not-followable.md +23 -0
- package/docs/errors/validation/package-missing.md +29 -0
- package/docs/errors/validation/unknown-path.md +16 -4
- package/docs/errors/wait/deadline-exceeded.md +14 -0
- package/docs/errors/wait/interrupted.md +10 -0
- package/package.json +14 -11
- package/src/absent-menu.ts +280 -0
- package/src/api-fanout.ts +22 -12
- package/src/api.ts +603 -194
- package/src/btest.ts +19 -17
- package/src/cli/api.ts +126 -34
- package/src/cli/devices.ts +16 -3
- package/src/cli/execute.ts +18 -5
- package/src/cli/retrieve.ts +216 -22
- package/src/cli/terminal.ts +10 -5
- package/src/cli/transfer.ts +23 -8
- package/src/core/envelope.ts +14 -0
- package/src/core/error-catalog.ts +70 -1
- package/src/core/execute-command.ts +313 -6
- package/src/core/inspect.ts +65 -27
- package/src/core/query.ts +508 -0
- package/src/core/routeros-errors.ts +67 -1
- package/src/data/winbox-cdb-write.ts +22 -8
- package/src/devices.ts +148 -37
- package/src/discover.ts +7 -8
- package/src/errors.ts +116 -7
- package/src/execute-fanout.ts +14 -9
- package/src/execute.ts +128 -116
- package/src/explain/catalog.ts +64 -16
- package/src/explain/menus.ts +27 -6
- package/src/explain.ts +6 -6
- package/src/index.ts +31 -1
- package/src/mcp/tools.ts +10 -15
- package/src/protocols/adapter.ts +235 -118
- package/src/protocols/btest-session.ts +3 -2
- package/src/protocols/endpoint.ts +10 -0
- package/src/protocols/mac-telnet-console.ts +12 -7
- package/src/protocols/native-api.ts +252 -73
- package/src/protocols/sftp.ts +44 -14
- package/src/protocols/ssh.ts +42 -14
- package/src/resolver/cdb.ts +28 -7
- package/src/resolver/index.ts +1 -0
- package/src/resolver/target.ts +84 -18
- package/src/retrieve-fanout.ts +51 -12
- package/src/retrieve-follow.ts +862 -0
- package/src/retrieve-sample.ts +244 -0
- package/src/retrieve-stream.ts +115 -0
- package/src/retrieve-wait.ts +286 -0
- package/src/retrieve.ts +741 -109
- package/src/settings.ts +15 -15
- package/src/transfer-fanout.ts +40 -15
- package/src/transfer.ts +234 -32
|
@@ -84,6 +84,18 @@ Rejected before any network call, with `usage/conflicting-flags`.
|
|
|
84
84
|
centrs retrieve $R /system/resource --attribute uptime --all-attributes --username $U --password $P --json
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### 20. Comma-list --attributes on any singleton
|
|
88
|
+
|
|
89
|
+
Singletons are recognized from `/console/inspect` — their `get` takes no
|
|
90
|
+
`number` argument — so every singleton validates against its `get value-name`
|
|
91
|
+
properties, not against `print`'s flags (#377).
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
centrs retrieve $R /tool/romon --attributes enabled,id --username $U --password $P --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Envelope: `data` is an object containing only `enabled` and `id`.
|
|
98
|
+
|
|
87
99
|
## --list-attributes
|
|
88
100
|
|
|
89
101
|
### 9. List attributes for a path
|
|
@@ -127,6 +139,9 @@ centrs retrieve $R /system/resource --attribute bogus --username $U --password $
|
|
|
127
139
|
centrs retrieve $R /system/resource --username wrong --password wrong --json
|
|
128
140
|
```
|
|
129
141
|
|
|
142
|
+
`meta.validation` has no `result`. Its one stage is `device: skipped` with
|
|
143
|
+
`reason: "not reached: transport/auth-failed"` (GH#435).
|
|
144
|
+
|
|
130
145
|
### 13. Unreachable host
|
|
131
146
|
|
|
132
147
|
`transport/connection-refused` (not the more general `transport/network`).
|
|
@@ -135,6 +150,10 @@ centrs retrieve $R /system/resource --username wrong --password wrong --json
|
|
|
135
150
|
centrs retrieve 127.0.0.1:1 /system/resource --username $U --password $P --json
|
|
136
151
|
```
|
|
137
152
|
|
|
153
|
+
The envelope still carries the resolved endpoint: `meta.via="rest-api"`,
|
|
154
|
+
`meta.target.host`/`port`/`baseUrl`, and `meta.settings` with the source of
|
|
155
|
+
each setting (#433).
|
|
156
|
+
|
|
138
157
|
### 14. REST timeout ceiling
|
|
139
158
|
|
|
140
159
|
`--timeout 70000` against `--via rest-api` is rejected up front with
|
|
@@ -162,6 +181,41 @@ Call succeeds with a `cdb/password-not-needed` warning in `meta.warnings`.
|
|
|
162
181
|
centrs retrieve $R /system/resource --cdb-file $CDB --cdb-password ignored --json
|
|
163
182
|
```
|
|
164
183
|
|
|
184
|
+
### 16b. A refused transport still names what resolution found (#433)
|
|
185
|
+
|
|
186
|
+
`$CLOSED` is a loopback port with nothing listening.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
centrs retrieve $R /system/resource --cdb-file $CDB --port $CLOSED --json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Exit 1, `transport/connection-refused`. `meta.via="rest-api"`, and
|
|
193
|
+
`meta.target` has the CDB record's host, `port=$CLOSED`, `recordIndex=0` and
|
|
194
|
+
`source.kind="cdb"`. `meta.settings.port={kind:"cli",key:"port"}` and
|
|
195
|
+
`meta.settings.username`/`password` have `kind:"cdb"`.
|
|
196
|
+
`meta.operation.auth.passwordProvided=true`. The password itself appears
|
|
197
|
+
nowhere. `meta.validation` has no `result`; its device stage is `skipped` with
|
|
198
|
+
`reason: "not reached: transport/connection-refused"` (GH#435).
|
|
199
|
+
|
|
200
|
+
### 16c. The gate passed, then a later step failed (GH#435)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
centrs retrieve $R /system/resource --max-results 10 --username $U --password $P --json
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Exit 1, `input/max-results-exceeded`. The read was validated and ran, and the
|
|
207
|
+
answer was larger than the byte budget. `meta.validation.result="passed"`,
|
|
208
|
+
because the gate did pass.
|
|
209
|
+
|
|
210
|
+
### H1. `--host` stands in for `<target>` (#428)
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
centrs retrieve --host $R /system/identity --username $U --password $P --json
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`ok: true`, with `data.name` the router's identity. The lone positional is the
|
|
217
|
+
path, and there is no CDB lookup.
|
|
218
|
+
|
|
165
219
|
## Target selection (fan-out)
|
|
166
220
|
|
|
167
221
|
These exercise the shared target-selection grammar
|
|
@@ -233,11 +287,13 @@ outputs round-trip to the same JS value.
|
|
|
233
287
|
centrs retrieve $R /system/resource --format yaml --username $U --password $P
|
|
234
288
|
```
|
|
235
289
|
|
|
236
|
-
##
|
|
290
|
+
## Row filters
|
|
237
291
|
|
|
238
|
-
### 18. --query
|
|
292
|
+
### 18. --query with a regex
|
|
239
293
|
|
|
240
|
-
|
|
294
|
+
A regex has no RouterOS API query word, so it fails
|
|
295
|
+
`input/unsupported-query` before anything is read. The full `--query`
|
|
296
|
+
contract is in the **Query** section below.
|
|
241
297
|
|
|
242
298
|
```bash
|
|
243
299
|
centrs retrieve $R /ip/address --query 'address~"192"' --username $U --password $P --json
|
|
@@ -245,10 +301,10 @@ centrs retrieve $R /ip/address --query 'address~"192"' --username $U --password
|
|
|
245
301
|
|
|
246
302
|
### 19. --filter
|
|
247
303
|
|
|
248
|
-
|
|
304
|
+
`--filter` is `--query`: the router filters, and every returned row matches.
|
|
249
305
|
|
|
250
306
|
```bash
|
|
251
|
-
centrs retrieve $R /
|
|
307
|
+
centrs retrieve $R /interface --filter 'type=ether' --username $U --password $P --json
|
|
252
308
|
```
|
|
253
309
|
|
|
254
310
|
## native-api (`--via native-api`)
|
|
@@ -336,6 +392,14 @@ centrs retrieve $A /system/resource --attribute bogus-attr --via native-api --po
|
|
|
336
392
|
centrs retrieve $A /system/resource --via native-api --port $API_PORT --username wrong --password wrong
|
|
337
393
|
```
|
|
338
394
|
|
|
395
|
+
### N12. Singleton `--attributes` projection (#377)
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
centrs retrieve $A /tool/romon --attributes enabled,id --via native-api --port $API_PORT --username $U --password $P
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`data` is an object containing only `enabled` and `id`.
|
|
402
|
+
|
|
339
403
|
## quickchr targets (#134)
|
|
340
404
|
|
|
341
405
|
`$NAME` is the machine name of a running quickchr-managed CHR
|
|
@@ -351,3 +415,376 @@ centrs retrieve --quickchr $NAME /system/resource --json
|
|
|
351
415
|
Envelope: `ok: true`, `meta.via=rest-api` (normal default; quickchr does not
|
|
352
416
|
change protocol selection), `meta.target.source.kind=provider`,
|
|
353
417
|
`meta.target.identity=$NAME`, no `recordIndex` (the CDB was never consulted).
|
|
418
|
+
|
|
419
|
+
## Follow (`--follow`, #396)
|
|
420
|
+
|
|
421
|
+
Contract: [README → Follow](README.md#follow---follow). `$A`/`$API_PORT` as in
|
|
422
|
+
the native-api section; `--via` is left unset because `--follow` infers
|
|
423
|
+
native-api. Covered by `test/integration/retrieve-follow.test.ts`; the engine
|
|
424
|
+
orderings a CHR cannot produce on demand (A1 replay, the sweep's
|
|
425
|
+
changed-since-sent exclusion, overflow) are pinned in
|
|
426
|
+
`test/unit/retrieve-follow.test.ts`.
|
|
427
|
+
|
|
428
|
+
### FL1. Bootstrap: snapshot frames, one `synced`, a summary
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
centrs retrieve $A /ip/address --follow --duration 3s --port $API_PORT --username $U --password $P --json
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Every line parses as an envelope. Frames before `synced` have
|
|
435
|
+
`phase: "snapshot"` and `source: "print"`; exactly one `synced`; the last line
|
|
436
|
+
is the summary with `stopReason: "duration-elapsed"`, `synced: true`,
|
|
437
|
+
`changes: 0`. Every frame carries `meta.operation.stream.id`.
|
|
438
|
+
|
|
439
|
+
### FL2. A live change, and `--count` that ignores the snapshot
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
centrs retrieve $A /ip/address --follow --count 1 --duration 15s --port $API_PORT --username $U --password $P --json
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
After `synced`, add an address. The follow ends with `stopReason:
|
|
446
|
+
"count-reached"` after exactly one `live` `upsert` frame from `listen`, however
|
|
447
|
+
many snapshot frames came first.
|
|
448
|
+
|
|
449
|
+
### FL3. A removal RouterOS sends
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
centrs retrieve $A /ip/address --follow --sweep 0 --count 1 --duration 15s --port $API_PORT --username $U --password $P --json
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
After `synced`, remove an address that existed before the follow. One `live`
|
|
456
|
+
`removed` frame, `source: "listen"`, `data: null`. `--sweep 0` keeps the sweep
|
|
457
|
+
out of the race, and its `tip/follow-sweep-off` notice is the first line.
|
|
458
|
+
|
|
459
|
+
### FL4. A removal RouterOS does not send (view menu → sweep)
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
centrs retrieve $A /interface/bridge --follow --sweep 1s --duration 15s --port $API_PORT --username $U --password $P --json
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Create a bridge after `synced`, then remove it. Its `upsert` comes from
|
|
466
|
+
`listen`; its removal comes from the sweep (`source: "sweep"`), because
|
|
467
|
+
`/interface/<type>` never sends `.dead`.
|
|
468
|
+
|
|
469
|
+
### FL5. `--attributes` projects `data`, identity stays in meta
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
centrs retrieve $A /ip/address --follow --attributes address --duration 3s --port $API_PORT --username $U --password $P --json
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Every snapshot frame's `data` has only `address`; `meta.operation.stream.id`
|
|
476
|
+
still names the row.
|
|
477
|
+
|
|
478
|
+
### FL6. A menu RouterOS cannot follow
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
centrs retrieve $A /system/resource --follow --port $API_PORT --username $U --password $P --json
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
One envelope: `ok: false`, `validation/not-followable`, exit 1. No summary.
|
|
485
|
+
|
|
486
|
+
### FL7. A pinned REST transport
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
centrs retrieve $R /ip/address --via rest-api --follow --username $U --password $P --json
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
One envelope: `ok: false`, `transport/capability-unsupported`. No polling
|
|
493
|
+
fallback.
|
|
494
|
+
|
|
495
|
+
### FL8. NDJSON is readable before the process exits; Ctrl-C ends it cleanly
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
centrs retrieve $A /ip/address --follow --format ndjson --port $API_PORT --username $U --password $P
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Run as a subprocess. The first frame line parses while the process is still
|
|
502
|
+
running; after `SIGINT` the last line is a successful summary with
|
|
503
|
+
`stopReason: "interrupted"`, and the exit code is 0.
|
|
504
|
+
|
|
505
|
+
### FL9. A1 and the sweep under churn (regression harness)
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
centrs retrieve $A /ip/firewall/address-list --follow --sweep 200ms --duration 8s --port $API_PORT --username $U --password $P --json
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
A second connection updates, adds and removes address-list rows while the
|
|
512
|
+
follow bootstraps and runs. Applying every line in order must equal a fresh
|
|
513
|
+
`print` taken after the churn settles: no stale value, no resurrected row, no
|
|
514
|
+
row removed by a sweep while it still exists. #396 round 3 measured 0 errors
|
|
515
|
+
over about 14.7k writes with this bootstrap; arrival-order merging failed the
|
|
516
|
+
same harness.
|
|
517
|
+
|
|
518
|
+
## Sample (`--sample`)
|
|
519
|
+
|
|
520
|
+
Contract: [README → Sample](README.md#sample---sample). `$R` and `$A`/`$API_PORT`
|
|
521
|
+
as above. Covered by `test/integration/retrieve-sample.test.ts`; cadence, the
|
|
522
|
+
in-flight read at a stop, and the flag rules are pinned in
|
|
523
|
+
`test/unit/retrieve-sample.test.ts`.
|
|
524
|
+
|
|
525
|
+
### SA1. Three samples, one interval apart, on either transport
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
centrs retrieve $R /ip/address --sample 1s --count 3 --username $U --password $P --json
|
|
529
|
+
centrs retrieve $A /ip/address --via native-api --sample 1s --count 3 --port $API_PORT --username $U --password $P --json
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Each run prints three `sample` lines (`index` 1–3, `data` a row array) and a
|
|
533
|
+
summary with `stopReason: "count-reached"`, `samples: 3`. Consecutive `at`
|
|
534
|
+
values are at least one second apart. `meta.via` is the transport used.
|
|
535
|
+
|
|
536
|
+
### SA2. Counters `listen` never reports
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
centrs retrieve $A /interface --via native-api --attributes name,rx-byte --sample 1s --count 3 --port $API_PORT --username $U --password $P --json
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Every row has only `name` and `rx-byte`. `ether1`'s `rx-byte` in the third
|
|
543
|
+
sample is higher than in the first, since the test's own API traffic arrives
|
|
544
|
+
on `ether1`.
|
|
545
|
+
|
|
546
|
+
### SA3. A singleton with one `--attribute`
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
centrs retrieve $R /system/resource --attribute uptime --sample 1s --count 2 --username $U --password $P --json
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Each sample's `data` is the bare `uptime` string, and the two differ.
|
|
553
|
+
|
|
554
|
+
### SA4. A removal is absence in the next sample
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
centrs retrieve $A /ip/firewall/address-list --via native-api --sample 500ms --count 2 --port $API_PORT --username $U --password $P --json
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Add an address-list row before the run and remove it after the first sample.
|
|
561
|
+
The first sample's rows include its `.id`; the second sample's do not.
|
|
562
|
+
|
|
563
|
+
### SA5. `--follow` and `--sample` are exclusive
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
centrs retrieve $R /ip/address --sample 1s --follow --username $U --password $P --json
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
One envelope: `ok: false`, `usage/conflicting-flags`, exit 1. No summary.
|
|
570
|
+
|
|
571
|
+
### SA6. NDJSON is readable before the process exits; Ctrl-C ends it cleanly
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
centrs retrieve $A /system/resource --via native-api --sample 1s --format ndjson --port $API_PORT --username $U --password $P
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Run as a subprocess. The first `sample` line parses while the process is still
|
|
578
|
+
running; after `SIGINT` the last line is a successful summary with
|
|
579
|
+
`stopReason: "interrupted"`, and the exit code is 0.
|
|
580
|
+
|
|
581
|
+
## Query (`--query`, #397)
|
|
582
|
+
|
|
583
|
+
Contract: [README → Query](README.md#query---query). `$R` and `$A`/`$API_PORT`
|
|
584
|
+
as above. Covered by `test/integration/retrieve-query.test.ts`, which seeds
|
|
585
|
+
address-list, VLAN and firewall-filter rows; the grammar and compiled words are
|
|
586
|
+
pinned in `test/unit/query.test.ts`.
|
|
587
|
+
|
|
588
|
+
### QY1. The same rows as RouterOS's `find where`
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
centrs retrieve $R /ip/firewall/address-list --query 'comment and !disabled' --username $U --password $P --json
|
|
592
|
+
centrs retrieve $A /interface/vlan --via native-api --query 'vlan-id>10 and vlan-id<100 or disabled' --port $API_PORT --username $U --password $P --json
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
For each of 58 expressions across three menus (equality, `!=`, numeric
|
|
596
|
+
ordering, ordering on a property some rows lack, quoted values and their escapes, bare booleans, bare "is set" names, `and`/`or`
|
|
597
|
+
precedence, parentheses, `!(…)`), the `.id`s returned over both transports
|
|
598
|
+
equal the ids `:put [<menu> find where <expression>]` prints on the same
|
|
599
|
+
router.
|
|
600
|
+
|
|
601
|
+
### QY2. Where the query and the CLI differ, as documented
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
centrs retrieve $R /ip/firewall/filter --query 'dst-port=80' --username $U --password $P --json
|
|
605
|
+
centrs retrieve $R /ip/firewall/address-list --query 'list=qa-397 and address>192.0.2.9' --username $U --password $P --json
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
The first returns the rule whose `dst-port` is `"80"`, although
|
|
609
|
+
`find where dst-port=80` selects nothing on CHR 7.23.7/7.24.5 (the CLI reads
|
|
610
|
+
`80` as a number). On CHR 7.25beta3, 7.25beta5, 7.25rc1 and 7.26beta1 the CLI
|
|
611
|
+
also selects that rule. The test covers the same split for `protocol=tcp`
|
|
612
|
+
and checks API query results over both transports. The second compares
|
|
613
|
+
`address` as text, so `192.0.2.10` is not above `192.0.2.9`.
|
|
614
|
+
|
|
615
|
+
### QY3. `--filter` and repeated `--query` are AND-ed
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
centrs retrieve $R /ip/firewall/address-list --filter list=qa-397 --query comment --query '!disabled' --attributes address --username $U --password $P --json
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
`data` is exactly `[{ "address": "192.0.2.9" }]`.
|
|
622
|
+
|
|
623
|
+
### QY4. A regex fails before anything is sent
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
centrs retrieve $R /interface --query 'name~"^ether"' --username $U --password $P --json
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
`ok: false`, `input/unsupported-query`, exit 1.
|
|
630
|
+
|
|
631
|
+
### QY5. A misspelled property fails validation
|
|
632
|
+
|
|
633
|
+
```bash
|
|
634
|
+
centrs retrieve $A /ip/firewall/address-list --via native-api --query 'lists=qa-397' --port $API_PORT --username $U --password $P --json
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
`validation/unknown-attribute`. Without the check RouterOS would answer with
|
|
638
|
+
no rows and no error.
|
|
639
|
+
|
|
640
|
+
### QY6. A singleton has no rows to filter
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
centrs retrieve $R /system/identity --query 'name=MikroTik' --username $U --password $P --json
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
`usage/conflicting-flags`.
|
|
647
|
+
|
|
648
|
+
### QY7. `--sample` reads the filtered rows each time
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
centrs retrieve $R /ip/firewall/address-list --query 'list=qa-397 and disabled' --sample 500ms --count 2 --username $U --password $P --json
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Both samples hold only the disabled `qa-397` row.
|
|
655
|
+
|
|
656
|
+
## Filtered follow
|
|
657
|
+
|
|
658
|
+
Covered by `test/integration/retrieve-filtered-follow.test.ts` (FQ1–FQ5).
|
|
659
|
+
The harness seeds address-list rows and changes them while following, with
|
|
660
|
+
validation enabled. `$A`/`$API_PORT` are the native-API target as above.
|
|
661
|
+
|
|
662
|
+
### FQ1. Projection and both membership directions
|
|
663
|
+
|
|
664
|
+
```bash
|
|
665
|
+
centrs retrieve $A /ip/firewall/address-list --via native-api --port $API_PORT --username $U --password $P --follow --query 'list=fq1' --query 'comment=a and !disabled' --attributes address --count 2 --duration 10s --json
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
Initially only the matching row appears. Changing its comment out of the
|
|
669
|
+
predicate emits a `removed` frame with `source: "membership"`; changing an
|
|
670
|
+
excluded row into it emits an `upsert` with the same source. `data` contains
|
|
671
|
+
only `address`, while identity remains in meta.
|
|
672
|
+
|
|
673
|
+
### FQ2. Minimal deletion frames
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
centrs retrieve $A /ip/firewall/address-list --via native-api --port $API_PORT --username $U --password $P --follow --query 'list=fq1 and comment=a' --sweep 0 --count 1 --duration 10s --json
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Deleting an excluded row emits nothing; deleting the held row emits one
|
|
680
|
+
`removed` with `source: "listen"`. Predicate fields are absent from `.dead`.
|
|
681
|
+
|
|
682
|
+
### FQ3. Silent predicate exit
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
centrs retrieve $A /ip/firewall/address-list --via native-api --port $API_PORT --username $U --password $P --follow --query 'list=fq3 and timeout>8s' --sweep 200ms --count 1 --duration 6s --json
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
The harness adds a row with a 10-second timeout. A sweep removes it from the
|
|
689
|
+
selected set as its remaining timeout crosses 8 seconds, with
|
|
690
|
+
`source: "membership"`; the row still exists in an unfiltered read.
|
|
691
|
+
|
|
692
|
+
### FQ4. CLI alias and predicate validation
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
centrs retrieve $A /ip/firewall/address-list --port $API_PORT --username $U --password $P --follow --filter list=fq3 --attributes address --duration 500ms --json
|
|
696
|
+
centrs retrieve $A /ip/firewall/address-list --port $API_PORT --username $U --password $P --follow --query lits=fq3 --json
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
The first emits projected snapshot rows and `synced`. The typo in the second
|
|
700
|
+
fails `validation/unknown-attribute` before listen starts.
|
|
701
|
+
|
|
702
|
+
### FQ5. Filtered bootstrap and live churn
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
centrs retrieve $A /ip/firewall/address-list --port $API_PORT --username $U --password $P --follow --query 'list=fq5 and comment=in' --attributes address --sweep 200ms --duration 6s --json
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
The harness changes membership during bootstrap and live operation, then
|
|
709
|
+
deletes rows. Applying all frames yields exactly the ids and projected rows
|
|
710
|
+
of a fresh filtered print after churn settles. Deterministic delayed-reply,
|
|
711
|
+
delete, cancellation and sustained-write bootstrap orderings are anchored in
|
|
712
|
+
`retrieve-follow.test.ts`; newer versions cannot starve `synced`, sweeps or
|
|
713
|
+
live `--count`.
|
|
714
|
+
|
|
715
|
+
## Readiness and conditions
|
|
716
|
+
|
|
717
|
+
Covered by `test/integration/retrieve-wait.test.ts` (WT1–WT7) on disposable
|
|
718
|
+
CHR, with validation enabled over REST and native-api. `$R` and
|
|
719
|
+
`$A`/`$API_PORT` are the targets as above.
|
|
720
|
+
|
|
721
|
+
### WT1. Readiness and already-true any-match
|
|
722
|
+
|
|
723
|
+
```bash
|
|
724
|
+
centrs retrieve $R /system/resource --wait 5s --username $U --password $P --json
|
|
725
|
+
centrs retrieve $A /interface --via native-api --port $API_PORT --query name=ether1 --until '!disabled and mtu>=1500' --attributes name --wait 5s --username $U --password $P --json
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Each succeeds with one attempt and one observation. The second returns only
|
|
729
|
+
`[{ "name": "ether1" }]`, with `stopReason: "condition-met"`.
|
|
730
|
+
|
|
731
|
+
### WT2. Wait for a row to match
|
|
732
|
+
|
|
733
|
+
```bash
|
|
734
|
+
centrs retrieve $R /ip/firewall/address-list --query list=wait-364 --until comment=ready --attributes address --wait 10s --sample 100ms --username $U --password $P --json
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
The harness adds a matching row after the wait starts. The waiter returns its
|
|
738
|
+
projected address, with more than one completed observation. The same test
|
|
739
|
+
runs over native-api.
|
|
740
|
+
|
|
741
|
+
### WT3. Drop to zero and already empty
|
|
742
|
+
|
|
743
|
+
```bash
|
|
744
|
+
centrs retrieve $R /ip/firewall/address-list --query list=wait-364 --until-empty --wait 10s --sample 100ms --username $U --password $P --json
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
The harness removes the selected row after the wait starts; `data: []`,
|
|
748
|
+
`stopReason: "condition-met"`. Repeating the wait on the now-empty selection
|
|
749
|
+
succeeds on its first completed observation.
|
|
750
|
+
|
|
751
|
+
### WT4. Condition expiry and fail-fast faults
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
centrs retrieve $R /interface --until name=missing --wait 500ms --sample 100ms --username $U --password $P --json
|
|
755
|
+
centrs retrieve $R /interface --until unknown-property=yes --wait 5s --username $U --password $P --json
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
The first fails `wait/deadline-exceeded`, `stopReason: "deadline-elapsed"`.
|
|
759
|
+
The second fails `validation/unknown-attribute` in one attempt. Incorrect
|
|
760
|
+
credentials also fail in one attempt; singleton `--until-empty` is refused.
|
|
761
|
+
|
|
762
|
+
### WT5. Real reboot readiness
|
|
763
|
+
|
|
764
|
+
```bash
|
|
765
|
+
centrs retrieve $R /system/resource --wait 60s --timeout 2s --sample 500ms --username $U --password $P --json
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
The harness reboots the CHR and starts this call while its services are down.
|
|
769
|
+
The waiter succeeds with multiple attempts and exactly one completed data
|
|
770
|
+
observation, without an external retry loop.
|
|
771
|
+
|
|
772
|
+
### WT6. OSPF reaches Full
|
|
773
|
+
|
|
774
|
+
```bash
|
|
775
|
+
centrs retrieve $A /routing/ospf/neighbor --via native-api --port $API_PORT --until state=Full --wait 40s --sample 200ms --username $U --password $P --json
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Two disposable CHRs share a point-to-point Ethernet link. After configuring
|
|
779
|
+
OSPF, the waiter returns a neighbor whose state is `Full`. A REST wait on
|
|
780
|
+
the established adjacency succeeds on its initial observation.
|
|
781
|
+
|
|
782
|
+
### WT7. CLI failure is one final envelope
|
|
783
|
+
|
|
784
|
+
```bash
|
|
785
|
+
centrs retrieve $R /interface --until name=missing --wait 500ms --username $U --password $P --json
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
One envelope goes to stdout, with `stopReason: "deadline-elapsed"`; exit 1.
|
|
789
|
+
No intermediate sample frames are printed. Unit anchors cover slow validation,
|
|
790
|
+
unfinished reads, cancellation and narrow retry classification.
|
|
@@ -6,10 +6,13 @@ Open an interactive RouterOS console.
|
|
|
6
6
|
flag (`--group` / `--where` / `--all` / `--default` / `--concurrency`) is rejected
|
|
7
7
|
with `usage/fanout-not-supported` — open a console to one router at a time; use
|
|
8
8
|
`execute` / `api` for multi-target commands. See `docs/CONSTITUTION.md`
|
|
9
|
-
(Target selection grammar).
|
|
9
|
+
(Target selection grammar). With no `<router>`, the `--host` **flag** is the
|
|
10
|
+
target (`terminal --host 192.0.2.10` opens an ssh console; no CDB lookup).
|
|
11
|
+
`CENTRS_HOST` never stands in for `<router>`. mac-telnet still needs a MAC
|
|
12
|
+
`<router>`, since its `--host` is only the UDP delivery endpoint.
|
|
10
13
|
|
|
11
14
|
Status: `terminal / mac-telnet` **and** `terminal / ssh` are both **`CHR-passed`**
|
|
12
|
-
(see `docs/MATRIX.md` and `examples.md` T1–T3 / TS1–TS2). The two transports take
|
|
15
|
+
(see `docs/MATRIX.md` and `examples.md` T1–T3 / TS1–TS2, TS4). The two transports take
|
|
13
16
|
different paths: **mac-telnet** is the in-process raw passthrough over
|
|
14
17
|
`MacTelnetConsole.attachInteractive`; **ssh** execs the host `ssh` with inherited
|
|
15
18
|
stdio (RouterOS grants no PTY, but `ssh user@host` opens the console and the OS
|
|
@@ -73,6 +76,12 @@ CLI/API. When no key is set, terminal delegates identity selection to system
|
|
|
73
76
|
`ssh` config and the SSH agent; `--ssh-key` is an explicit override and must not
|
|
74
77
|
silently merge with a conflicting CDB/env key.
|
|
75
78
|
|
|
79
|
+
A selected key from any settings source adds `IdentitiesOnly=yes` alongside
|
|
80
|
+
`-i`, preventing unrelated agent identities from authenticating instead. The
|
|
81
|
+
agent can still sign for the selected key. OpenSSH continues to honor explicit
|
|
82
|
+
`IdentityFile` entries in its config; this does not isolate the user's SSH
|
|
83
|
+
configuration. The same selection applies to execute and SFTP.
|
|
84
|
+
|
|
76
85
|
Unlike the batch sftp/execute clients, the **interactive** `terminal / ssh` argv
|
|
77
86
|
omits `BatchMode=yes` (`interactive: true` in `sshCommonOptions`), so the host
|
|
78
87
|
`ssh` can prompt on the inherited TTY for an encrypted key's passphrase or a
|
|
@@ -86,8 +95,9 @@ longer open for the file path: **host-key verification** rides the unified
|
|
|
86
95
|
a changed key → `transport/host-key-mismatch`; `--insecure` →
|
|
87
96
|
`StrictHostKeyChecking=no` — see `docs/CONSTITUTION.md`, Transport trust);
|
|
88
97
|
**agent vs explicit-key** interplay and **algorithm negotiation** are delegated to
|
|
89
|
-
the host OpenSSH (`-i <ssh-key
|
|
90
|
-
MAC-on-file-but-SSH-pinned case is settled (see *MAC target
|
|
98
|
+
the host OpenSSH (`-i <ssh-key> -o IdentitiesOnly=yes` when set, else the agent /
|
|
99
|
+
`~/.ssh/config`). The MAC-on-file-but-SSH-pinned case is settled (see *MAC target
|
|
100
|
+
over SSH* above):
|
|
91
101
|
centrs resolves a MAC for `--via ssh` CDB-first, then tips `--via mac-telnet` —
|
|
92
102
|
it does **not** silently fall back to L2 (constitution: a pinned `--via` is never
|
|
93
103
|
swapped). The no-pseudo-tty console reader is likewise resolved — a CHR spike
|
|
@@ -91,9 +91,10 @@ on the argv.
|
|
|
91
91
|
|
|
92
92
|
Harness inputs (from `test/integration/terminal-ssh.test.ts`): `$SSH_PORT` is
|
|
93
93
|
`chr.sshPort` (passed via `--port`), and `$KEY` is the ephemeral private key the
|
|
94
|
-
test mints with `ssh-keygen -f <tmp>/id` and passes via `--ssh-key`. **TS1
|
|
95
|
-
the CHR-tested set
|
|
96
|
-
|
|
94
|
+
test mints with `ssh-keygen -f <tmp>/id` and passes via `--ssh-key`. **TS1, TS2,
|
|
95
|
+
TS4 and TS5 are the CHR-tested set**. The matrix's `CHR-passed` claim rests on
|
|
96
|
+
TS1, TS2 and TS4. TS1, TS2 and TS5 run in `test/integration/terminal-ssh.test.ts`; TS4 runs in the shared
|
|
97
|
+
`test/integration/ssh-identity.test.ts`. **TS3 is the interactive hand-verified
|
|
97
98
|
analog of T4** (a piped subprocess stdin cannot be a TTY), not in CI.
|
|
98
99
|
|
|
99
100
|
### TS1. Run a command over an SSH terminal (batch relay)
|
|
@@ -130,6 +131,24 @@ RouterOS console to the user; `/quit` (or `Ctrl-D`) ends the session. RouterOS's
|
|
|
130
131
|
no-PTY limitation applies (e.g. multi-line brace blocks are not supported over
|
|
131
132
|
SSH — see the SSH page); single-line commands work.
|
|
132
133
|
|
|
134
|
+
### TS4. A rejected selected key cannot authenticate through another agent identity
|
|
135
|
+
|
|
136
|
+
The shared [SSH identity example S5](../execute/examples.md#s5-an-explicit-rejected-key-cannot-fall-back-to-an-unrelated-agent-key)
|
|
137
|
+
checks the real terminal relay against CHR with a private agent and prompts
|
|
138
|
+
disabled. The rejected key preserves OpenSSH's nonzero exit and
|
|
139
|
+
`Permission denied`; selecting the trusted key or leaving the key unset
|
|
140
|
+
returns the device identity. Covered by `test/integration/ssh-identity.test.ts`.
|
|
141
|
+
|
|
142
|
+
### TS5. `--host` stands in for `<router>` (#428)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
printf '/system/identity/print\n/quit\n' | centrs terminal --host 127.0.0.1 --port $SSH_PORT --username $U --ssh-key $KEY --insecure
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
There's no `<router>` and no `--via`. The host-only target selects ssh, as a
|
|
149
|
+
host `<router>` does, with no CDB lookup. stdout contains the device identity,
|
|
150
|
+
checked as in TS1.
|
|
151
|
+
|
|
133
152
|
## MAC target over `--via ssh` resolution (behavior note — not a numbered CHR example)
|
|
134
153
|
|
|
135
154
|
This path resolves a MAC to an IP and, on the unresolved / no-opt-in branch,
|