@tikoci/centrs 0.1.7 → 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 +235 -0
- package/GLOSSARY.txt +32 -0
- package/README.md +2 -2
- package/SECURITY.md +1 -1
- package/commands/AGENTS.md +2 -1
- package/commands/api/AGENTS.md +55 -30
- package/commands/api/README.md +123 -41
- package/commands/api/examples.md +205 -17
- package/commands/devices/README.md +11 -3
- package/commands/execute/README.md +31 -3
- package/commands/execute/examples.md +136 -1
- package/commands/explain/README.md +30 -4
- package/commands/retrieve/README.md +348 -4
- package/commands/retrieve/examples.md +422 -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 +28 -20
- package/docs/CONSTITUTION.md +30 -10
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -1
- package/docs/errors/input/invalid-query.md +36 -0
- 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/settings/invalid-timeout.md +6 -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 +511 -186
- package/src/btest.ts +19 -17
- package/src/cli/api.ts +126 -34
- 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 +56 -1
- package/src/core/inspect.ts +43 -5
- package/src/core/query.ts +508 -0
- package/src/core/routeros-errors.ts +1 -1
- package/src/devices.ts +6 -9
- package/src/discover.ts +7 -8
- package/src/errors.ts +116 -7
- package/src/execute-fanout.ts +13 -8
- package/src/execute.ts +106 -65
- package/src/explain/catalog.ts +58 -10
- package/src/explain/menus.ts +23 -2
- package/src/explain.ts +6 -6
- package/src/index.ts +29 -1
- package/src/mcp/tools.ts +9 -14
- package/src/protocols/adapter.ts +224 -118
- package/src/protocols/btest-session.ts +3 -2
- package/src/protocols/endpoint.ts +10 -0
- package/src/protocols/mac-telnet-console.ts +6 -3
- package/src/protocols/native-api.ts +223 -70
- 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 +53 -7
- 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 +727 -108
- package/src/settings.ts +15 -15
- package/src/transfer-fanout.ts +40 -15
- package/src/transfer.ts +234 -32
|
@@ -373,6 +373,23 @@ centrs execute 127.0.0.1 '/ip/address/add address=198.51.100.51/32 interface=eth
|
|
|
373
373
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`, `meta.via=ssh`,
|
|
374
374
|
and no address is added — the console `:parse` gate catches `bogus` pre-mutation.
|
|
375
375
|
|
|
376
|
+
### S5. An explicit rejected key cannot fall back to an unrelated agent key
|
|
377
|
+
|
|
378
|
+
`test/integration/ssh-identity.test.ts` imports `$KEY` on a disposable CHR and
|
|
379
|
+
loads it into a private SSH agent. `$WRONG_KEY` is a second, untrusted key.
|
|
380
|
+
The test uses an empty OpenSSH config so no other `IdentityFile` is selected.
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
centrs execute 127.0.0.1 ':put [/system/identity/get name]' --via ssh --port $SSH_PORT --username $U --ssh-key $WRONG_KEY --insecure --json
|
|
384
|
+
centrs transfer 127.0.0.1 list --via sftp --port $SSH_PORT --username $U --ssh-key $WRONG_KEY --insecure --json
|
|
385
|
+
printf ':put ("centrs-identity:" . [/system/identity/get name])\n/quit\n' | centrs terminal 127.0.0.1 --via ssh --port $SSH_PORT --username $U --ssh-key $WRONG_KEY --insecure
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Execute and transfer fail with `auth/failed`; the terminal relay preserves
|
|
389
|
+
OpenSSH's nonzero exit and `Permission denied` diagnostic. The test disables
|
|
390
|
+
interactive prompts on its terminal relay. Controls using `$KEY`, or omitting
|
|
391
|
+
`--ssh-key` to use the private agent, succeed for all three commands.
|
|
392
|
+
|
|
376
393
|
## Offline gate (stage 1)
|
|
377
394
|
|
|
378
395
|
Validation runs offline before it runs on the device
|
|
@@ -444,7 +461,9 @@ centrs execute $R '/ip/address/find where=' --via rest-api --username $U --passw
|
|
|
444
461
|
Envelope: `ok: false`, `error.code=validation/syntax`, `error.context.detail`
|
|
445
462
|
is RouterOS's own `missing value for where (line 1 column 24)`,
|
|
446
463
|
`error.position={"line":1,"column":24}`, and `meta.validation.stages` is
|
|
447
|
-
`offline: passed` followed by `device: failed`.
|
|
464
|
+
`offline: passed` followed by `device: failed`. `meta.validation.syntax=false`,
|
|
465
|
+
and `semantic` is absent: `:parse` refused the command, so the name-level check
|
|
466
|
+
was never asked (GH#435).
|
|
448
467
|
|
|
449
468
|
CHR 7.24.4 controls, via `:put [:parse "…"]`: `find where` without `=` parses
|
|
450
469
|
(IL). `print where=` is rejected differently (`expected end of command`, at
|
|
@@ -453,6 +472,64 @@ newlines) returns `missing closing brace (line 3 column 1)`, while an unclosed
|
|
|
453
472
|
`do={` returns `expected closing brace`. Offline analysis usually catches the
|
|
454
473
|
unclosed-brace cases first.
|
|
455
474
|
|
|
475
|
+
### V5. A menu from a package that is not installed is not a syntax error
|
|
476
|
+
|
|
477
|
+
RouterOS's `:parse` reports a menu the device does not have as a syntax fault —
|
|
478
|
+
`syntax error (line 1 column 10)` for `/zerotier/print`, pointing at the `/`
|
|
479
|
+
after the absent segment, and `bad command name container (line 1 column 2)` for
|
|
480
|
+
the space spelling `/container print` (CHR 7.23.7). centrs locates the segment,
|
|
481
|
+
looks the path up in its path catalog, and names the package (GH#361).
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
centrs execute $R '/zerotier/print' --via rest-api --username $U --password $P --json
|
|
485
|
+
centrs execute $R '/container print' --via rest-api --username $U --password $P --json
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Envelope: `ok: false`, `error.code=validation/package-missing`,
|
|
489
|
+
`error.context.path` is `/zerotier` (then `/container`),
|
|
490
|
+
`error.context.packages` names the published package, the remediation points at
|
|
491
|
+
`/system/package/print` and never at the syntax, and `error.position` and
|
|
492
|
+
`error.context.detail` keep RouterOS's own column and words. V5–V7 all report
|
|
493
|
+
`meta.validation.syntax=false`. `semantic` is absent, or `not-applicable` for
|
|
494
|
+
the script-shaped `/container print` (GH#435).
|
|
495
|
+
|
|
496
|
+
### V6. A menu no RouterOS build has is an unknown path
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
centrs execute $R '/ip/nosuchmenu/print' --via rest-api --username $U --password $P --json
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Envelope: `ok: false`, `error.code=validation/unknown-path`,
|
|
503
|
+
`error.context.path=/ip/nosuchmenu`, `error.context.segment=nosuchmenu`. The
|
|
504
|
+
path catalog covers every 7.10–7.25 build with all extra packages, so a path it
|
|
505
|
+
lacks is not a missing package.
|
|
506
|
+
|
|
507
|
+
### V7. A published menu gated on more than a package
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
centrs execute $R '/interface/w60g/print' --via rest-api --username $U --password $P --json
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Envelope: `ok: false`, `error.code=validation/menu-unavailable`,
|
|
514
|
+
`error.context.path=/interface/w60g`, and `error.context.gates` carries both
|
|
515
|
+
published gates — package `wireless-rep` and hardware capability `60ghz` —
|
|
516
|
+
because a 60 GHz menu can be absent with the package installed. Nested and later
|
|
517
|
+
statements locate the same way (`:put [/interface/w60g/get [find] disabled]`
|
|
518
|
+
points at column 22). A menu-scope block (`/ip { nosuchmenu print }`) or a menu
|
|
519
|
+
named on an earlier line abstains and stays `validation/syntax`: the offline
|
|
520
|
+
analyzer does not resolve that parent, and centrs does not guess it.
|
|
521
|
+
|
|
522
|
+
### V8. Wrong credentials: the device stage is not reached (GH#435)
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
centrs execute $R '/system/identity/print' --via rest-api --username centrs-wrong --password centrs-wrong --json
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Envelope: `ok: false`, `error.code=transport/auth-failed`. `meta.validation`
|
|
529
|
+
has no `result`, `syntax` or `semantic`. Its stages are `offline: passed`, then
|
|
530
|
+
`device: skipped` with `reason: "not reached: transport/auth-failed"`. RouterOS
|
|
531
|
+
answered 401 before `:parse` could give a verdict.
|
|
532
|
+
|
|
456
533
|
## Write authorization and returned runtime faults
|
|
457
534
|
|
|
458
535
|
These regressions are transport-independent and run in
|
|
@@ -584,6 +661,17 @@ command). With no `--cdb-file`, both are ad-hoc literals, labeled by
|
|
|
584
661
|
`meta.target.input` with no `recordIndex`: `data.summary = { total: 2, ok: 1,
|
|
585
662
|
failed: 1 }`, exit `2`.
|
|
586
663
|
|
|
664
|
+
### F7. An address that misses a pinned CDB is a literal; a name is not (#427)
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
centrs execute $A:$API_PORT no-such-router --cdb-file $CDB --username $U --password $P --json -- /system/resource/print
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Neither positional matches a record. The address is dialed as a literal host
|
|
671
|
+
(inner `ok: true`). The name fails closed with `cdb/not-found-target`, because
|
|
672
|
+
it can't be told apart from a mistyped record name. `data.summary = { total: 2,
|
|
673
|
+
ok: 1, failed: 1 }`, exit `2`.
|
|
674
|
+
|
|
587
675
|
## Protocol selection notes
|
|
588
676
|
|
|
589
677
|
For an unresolved MAC target, execute auto-selection defaults to mac-telnet
|
|
@@ -599,6 +687,53 @@ Envelope: `meta.via=mac-telnet` when `$MAC` is not resolved from CDB and no
|
|
|
599
687
|
they must opt into MAC-to-IP resolution (`--resolve arp` + `--via …`) before
|
|
600
688
|
protocol selection.
|
|
601
689
|
|
|
690
|
+
### P1. An `http(s)://` target selects rest-api on its own port (#426)
|
|
691
|
+
|
|
692
|
+
```bash
|
|
693
|
+
centrs execute http://$A:$REST_PORT ':put [/system/identity/get name]' --username $U --password $P --json
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Envelope: `ok: true`, `meta.via=rest-api`, `meta.settings.via =
|
|
697
|
+
{ kind: "default", key: "via:target-url" }`. No `--via` is pinned, and the URL's
|
|
698
|
+
scheme and port are what get dialed.
|
|
699
|
+
|
|
700
|
+
### P2. A typed `host:port` keeps native-api on that port (#426)
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
centrs execute $A:$API_PORT ':put [/system/identity/get name]' --username $U --password $P --json
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Envelope: `ok: true`, `meta.via=native-api`, dialing `$API_PORT` (no
|
|
707
|
+
`--port` flag), the same way ssh and REST already treat a typed port.
|
|
708
|
+
|
|
709
|
+
### P3. A pinned CDB doesn't refuse an address it has no record for (#427)
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
centrs execute $A:$API_PORT ':put [/system/identity/get name]' --cdb-file $CDB --username $U --password $P --json
|
|
713
|
+
centrs execute http://$A:$REST_PORT ':put [/system/identity/get name]' --cdb-file $CDB --username $U --password $P --json
|
|
714
|
+
centrs execute no-such-router ':put [/system/identity/get name]' --cdb-file $CDB --username $U --password $P --json
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
`$CDB` has no record for any of the three. The first two are `ok: true` over
|
|
718
|
+
native-api and rest-api: an IP, `[IPv6]`, `http(s)://` URL or MAC that misses a
|
|
719
|
+
pinned CDB is a literal host, exactly as with no CDB. The third is `ok: false`,
|
|
720
|
+
`error.code = cdb/not-found-target`: a name or identity that misses still fails
|
|
721
|
+
closed.
|
|
722
|
+
|
|
723
|
+
## `--host` as the target (#428)
|
|
724
|
+
|
|
725
|
+
### H1. `--host` stands in for `<target>`; a lone positional is the command
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
centrs execute --host http://$A:$REST_PORT ':put [/system/identity/get name]' --username $U --password $P --json
|
|
729
|
+
centrs execute --host $A:$API_PORT --username $U --password $P --json -- ':put [/system/identity/get name]'
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
Both are `ok: true`, the first over rest-api (the URL names REST) and the second
|
|
733
|
+
over native-api on `$API_PORT`. `meta.target.source = { kind: "cli", key:
|
|
734
|
+
"host" }`, with no `recordIndex`: there is no `<target>`, so there is no CDB
|
|
735
|
+
lookup. `retrieve`, `api` and `transfer` have the same rule (their H1).
|
|
736
|
+
|
|
602
737
|
## quickchr targets (#134)
|
|
603
738
|
|
|
604
739
|
`$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
|
|
@@ -199,10 +199,11 @@ today's path/verb/args split and script-vs-structured gate:
|
|
|
199
199
|
Discovery is the union of MikroTik's two published inventories, not the
|
|
200
200
|
sitemap alone. A *branching* menu is served as a trailing-slash category URL
|
|
201
201
|
with no `.md` of its own, while that menu's own entry is published at
|
|
202
|
-
`<dir>/<basename>.md` and listed only in
|
|
203
|
-
discovery dropped 256 of 1,070 pages, silently
|
|
204
|
-
asserts that every category dir still
|
|
205
|
-
|
|
202
|
+
`<dir>/<basename>.md` (since 2026-10-06, `<dir>.md`) and listed only in
|
|
203
|
+
`llms.txt` — so sitemap-only discovery dropped 256 of 1,070 pages, silently
|
|
204
|
+
and field-heavily. Generation asserts that every category dir still
|
|
205
|
+
contributes its own page under one of those two spellings, so the next
|
|
206
|
+
inventory reshape fails loudly instead of shrinking the table (#285, #417,
|
|
206
207
|
tikoci/rosetta#137).
|
|
207
208
|
|
|
208
209
|
**Source refresh (#335, accepted 2026-09-13).** The inspect pins retain the
|
|
@@ -232,6 +233,31 @@ today's path/verb/args split and script-vs-structured gate:
|
|
|
232
233
|
moves both from `inspect` to `both` — 961 `both`, 65 `inspect`, 94
|
|
233
234
|
`published`. Zero navigation-vs-command contradictions, unchanged.
|
|
234
235
|
|
|
236
|
+
**Source refresh (accepted 2026-10-11, #417).** MikroTik changed the CLI
|
|
237
|
+
Reference layout on 2026-10-06: a branching menu's own entry moved from
|
|
238
|
+
`<dir>/<basename>.md` to `<dir>.md`, and pages dropped their MDX `import`
|
|
239
|
+
preamble, so the `# title` line is no longer fenced off by an import. Both
|
|
240
|
+
guards refused, correctly, and the catalog check could not report anything
|
|
241
|
+
for days. The parser now accepts either layout (a category dir needs one of
|
|
242
|
+
the two page spellings, and both at once is reported); losing the page
|
|
243
|
+
entirely still aborts. The inventories reconcile exactly: 846 sitemap pages
|
|
244
|
+
plus 253 category pages equal the 1,099 pages `llms.txt` lists. The inspect
|
|
245
|
+
pins add 7.26beta1 (x86 and arm64) because the manual documents the
|
|
246
|
+
development channel, and a path new in it with no matching tree is
|
|
247
|
+
published-only with no gate to explain it. Reviewed against the committed
|
|
248
|
+
catalog: 47 paths added, 1 removed, 3 gate strings changed, and no kind
|
|
249
|
+
change, no provenance loss and no navigation-vs-command contradiction. The
|
|
250
|
+
additions are the 32 `/cmr` paths, `/interface/wifi/dashboard`,
|
|
251
|
+
`/system/dashboard`, `/tool/apptraffic`, `/interface/ethernet/pon/monitor`,
|
|
252
|
+
`/interface/wireless/snooper/snoop` and
|
|
253
|
+
`/interface/ethernet/switch/rule/reset-counters-all`; the removal is the
|
|
254
|
+
published-only `/system/routerboard/settings/keep-frequency`, which its page
|
|
255
|
+
no longer lists. The CLI Reference moves from 1,055 pages / 1,062 entries to
|
|
256
|
+
1,099 / 1,106; the inspect union from 617 to 636 menus; the catalog from
|
|
257
|
+
1,120 to 1,166 paths: 1,005 `both`, 67 `inspect`, 94 `published`.
|
|
258
|
+
`/interface/wifi/dashboard` and `/system/dashboard` are `inspect` only: the
|
|
259
|
+
trees carry them, the manual publishes no menu entry for them.
|
|
260
|
+
|
|
235
261
|
There are real unknowns in how far offline parsing can go (expression
|
|
236
262
|
grammar, scope fidelity vs `:parse`, `[]`-nesting corner cases). Grounding
|
|
237
263
|
those unknowns was **phase 0** (#185); its findings are folded in throughout
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# retrieve
|
|
2
2
|
|
|
3
3
|
Status: `CHR-passed` over `rest-api` and `native-api`, including multi-target
|
|
4
|
-
fan-out (see **Target selection**)
|
|
5
|
-
`
|
|
4
|
+
fan-out (see **Target selection**), `--query` row filters (see **Query**),
|
|
5
|
+
`--follow` over `native-api` (see **Follow**) and `--sample` over both (see
|
|
6
|
+
**Sample**) and bounded readiness/conditions (see **Wait**). `snmp` is
|
|
7
|
+
`not-started`. Matches `docs/MATRIX.md`.
|
|
6
8
|
|
|
7
9
|
Read RouterOS state. RouterOS menu reads model `<path>/<verb>` where the verb
|
|
8
10
|
is `print`-style (`print`, `get`, and async POST-shaped reads as they're
|
|
@@ -50,6 +52,8 @@ duplicate that table. Behavior notes the generated reference cannot carry:
|
|
|
50
52
|
- `--format json` (shortcut `--json`): set `CENTRS_FORMAT=json` to make the
|
|
51
53
|
structured envelope the default. `--format yaml` renders the same envelope.
|
|
52
54
|
- `--port`: native-api defaults to 8728; `--port 8729` selects TLS `api-ssl`.
|
|
55
|
+
A port typed into the target (`host:port`) is honored too, below the port
|
|
56
|
+
settings ladder (`--port`, `CENTRS_PORT`, a CDB `port=` comment, config).
|
|
53
57
|
- `--query`/`--filter` are RouterOS **row** filters and always return an
|
|
54
58
|
array (see Synopsis); `--where` filters *which devices* fan out, not rows.
|
|
55
59
|
- `--all-attributes` conflicts with `--attribute(s)` (see Validation).
|
|
@@ -63,7 +67,7 @@ Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
|
63
67
|
|
|
64
68
|
| Flag | Designed behavior |
|
|
65
69
|
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
66
|
-
| `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `api …
|
|
70
|
+
| `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `--follow` (below), or `api … <menu>/listen` for the literal wire. See constitution: protocol selection. |
|
|
67
71
|
| `--max-bytes <n>` | Byte budget for the rendered payload. If the response would exceed it, centrs truncates to fit, keeps `ok: true`, and adds a warning + `meta.truncated`. Not an error. (Will replace the implemented `--max-results`, which fails instead of truncating.) |
|
|
68
72
|
| `--max-rows <n>` | Maximum row count for list reads. Excess rows are clipped; `ok: true` with a warning + `meta.truncated`. |
|
|
69
73
|
| `--yaml` | Shortcut for `--format yaml`. |
|
|
@@ -77,6 +81,15 @@ Default validator: `/console/inspect request=syntax path=<comma-joined>,print`
|
|
|
77
81
|
unknown attributes with `validation/*` codes that include the inspect-suggested
|
|
78
82
|
alternatives in the error envelope.
|
|
79
83
|
|
|
84
|
+
`meta.validation.result` follows
|
|
85
|
+
[the constitution](../../docs/CONSTITUTION.md#validation-is-the-product). It is
|
|
86
|
+
`passed` once inspect accepts the read, even if the read fails afterward. It is
|
|
87
|
+
`failed` when the gate refuses: `validation/*`, or `--query` on a singleton.
|
|
88
|
+
It is absent when a `transport/*`, `auth/*`, `routeros/*` or `internal/*` error
|
|
89
|
+
stopped inspect from answering; the device stage then says `not reached`
|
|
90
|
+
(examples 12 and 16b; 16c is the `passed` case). With `--validate=false` it
|
|
91
|
+
is `{enabled: false, source: "disabled"}`.
|
|
92
|
+
|
|
80
93
|
If `--all-attributes` is combined with `--attribute(s)`, fail with
|
|
81
94
|
`usage/conflicting-flags` *before* hitting the network.
|
|
82
95
|
|
|
@@ -140,6 +153,334 @@ warning entry — it is not an error. (Earlier text said a byte overflow returns
|
|
|
140
153
|
*error* with the needed size; superseded — truncation is an `ok: true` footnote so
|
|
141
154
|
partial data stays usable.)
|
|
142
155
|
|
|
156
|
+
A failure **after** target resolution keeps what resolution found. The error
|
|
157
|
+
envelope carries the resolved `meta.target` (host, port, base URL, record,
|
|
158
|
+
sources), `meta.via`, the setting sources in `meta.settings`, and
|
|
159
|
+
`meta.operation.auth` (`username`, `passwordProvided`; provenance only, never
|
|
160
|
+
the secret). This holds for one-shot reads, `--follow`, `--sample`, `--wait`,
|
|
161
|
+
each fan-out member and the MCP `centrs_retrieve` tool. A failure before
|
|
162
|
+
resolution (bad shape, unknown target) keeps `meta.target.input` and a valid
|
|
163
|
+
pinned `via`, with empty `meta.settings`. The
|
|
164
|
+
library exposes this as `retrieveEnvelope()`, which never throws (examples 13,
|
|
165
|
+
16b).
|
|
166
|
+
|
|
167
|
+
## Query (`--query`)
|
|
168
|
+
|
|
169
|
+
`retrieve <router> <menu> --query '<expr>'` returns only the rows that match,
|
|
170
|
+
filtered on the router. `<expr>` is what you would write after `print where`:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
centrs retrieve $R /interface --query 'type=ether and !disabled'
|
|
174
|
+
centrs retrieve $R /ip/route --query '(dst-address="0.0.0.0/0" or distance>=10) and active'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`--filter` is the same flag. Repeat either, and the expressions are AND-ed. A
|
|
178
|
+
list read with `--query` is always an array, `[]` when nothing matches. The
|
|
179
|
+
result works with `--attribute(s)`, `--sample`, `--follow` and fan-out.
|
|
180
|
+
A singleton (`/system/identity`) has no rows to filter and
|
|
181
|
+
fails `usage/conflicting-flags`.
|
|
182
|
+
|
|
183
|
+
**What it compiles to.** centrs parses the expression before connecting and
|
|
184
|
+
sends it as the API query (native `?` words, REST `.query`; one language,
|
|
185
|
+
`commands/api/AGENTS.md`). The router evaluates it, so comparisons use
|
|
186
|
+
RouterOS's own per-property typing:
|
|
187
|
+
|
|
188
|
+
| You write | RouterOS query words |
|
|
189
|
+
| --------- | -------------------- |
|
|
190
|
+
| `a=v`, `a!=v` | `a=v`; `a=v` `#!` |
|
|
191
|
+
| `a<v`, `a>v` | `<a=v` `-a` `#\|`; `>a=v` |
|
|
192
|
+
| `a<=v`, `a>=v` | `>a=v` `#!`; `<a=v` `-a` `#\|` `#!` |
|
|
193
|
+
| bare `a` | `a=yes` if `a` is a boolean, otherwise `a` (has a value) |
|
|
194
|
+
| `!x`, `x and y`, `x or y` | x `#!`; x y `#&`; x y `#\|` |
|
|
195
|
+
|
|
196
|
+
As in `print where`, a row without the property counts as smaller than any
|
|
197
|
+
value: `timeout<1d` includes static address-list entries (no `timeout`), and
|
|
198
|
+
`timeout>1h` does not. API `<`/`>` skip such rows on their own, hence the `-a`
|
|
199
|
+
("has no `a`") word. `and` (`&&`) binds tighter than `or` (`||`), as in RouterOS. Values run to the
|
|
200
|
+
next space or parenthesis, and start right after the operator (RouterOS
|
|
201
|
+
rejects `comment= x`). Quote a value with spaces or an `=` (`comment="uplink a"`,
|
|
202
|
+
`comment="x=yes"`), using RouterOS escapes: `\"`, `\\`, `\$`, `\?`, `\_`, `\a`
|
|
203
|
+
`\b` `\f` `\n` `\r` `\t` `\v`, and two uppercase hex digits (`\41`; `\ff` is `\f`
|
|
204
|
+
then `f`). A backslash before whitespace drops it and the whole whitespace run
|
|
205
|
+
after it, so `"x\<newline> y"` is `xy`. A hex
|
|
206
|
+
byte above `\7F` is refused: centrs sends values as UTF-8, so it could not match
|
|
207
|
+
the raw byte RouterOS stores.
|
|
208
|
+
|
|
209
|
+
**A bare name.** `where disabled` means `disabled=yes`, but `where comment`
|
|
210
|
+
means "comment is set" (RouterOS stores an empty comment as absent). Only the
|
|
211
|
+
router knows which kind a property is, so centrs asks it: `/console/inspect`
|
|
212
|
+
completion after `where <name>=` offers exactly `yes`/`no` for a boolean. With
|
|
213
|
+
`--validate=false` there is no one to ask, and a bare name fails
|
|
214
|
+
`input/invalid-query`; write `disabled=yes`.
|
|
215
|
+
|
|
216
|
+
**Validation.** Every property in the expression is checked against the
|
|
217
|
+
menu's attributes (as for `--attributes`, plus `.id`). RouterOS answers a query
|
|
218
|
+
on a misspelled property with no rows and no error, so a typo fails
|
|
219
|
+
`validation/unknown-attribute` instead.
|
|
220
|
+
|
|
221
|
+
**Same rows as `print where`, with these differences.** On CHR 7.23.7 and
|
|
222
|
+
7.24.5, 58 expressions over three menus return exactly the rows
|
|
223
|
+
`find where` selects (examples QY1, QY2). The exceptions come from the CLI
|
|
224
|
+
typing an unquoted literal, which the query does not do:
|
|
225
|
+
|
|
226
|
+
- `where dst-port=80` reads `80` as a number and matches nothing, and
|
|
227
|
+
`where protocol=tcp` likewise; the query matches the `"80"` and `"tcp"` that
|
|
228
|
+
`retrieve` shows. Quoted (`dst-port="80"`), the CLI agrees. This difference
|
|
229
|
+
is version-dependent: on CHR 7.25beta3, 7.25beta5, 7.25rc1 and 7.26beta1,
|
|
230
|
+
both unquoted CLI expressions also match. QY2 keeps exact expectations for
|
|
231
|
+
both behaviors; API query results stay the same over both transports.
|
|
232
|
+
- Ordering (`<`, `>`, `<=`, `>=`) on a property RouterOS returns as text
|
|
233
|
+
(address-list `address`, `dst-port`) is text order: `192.0.2.10` sorts
|
|
234
|
+
before `192.0.2.9`. Numbers (`mtu`, `vlan-id`, `distance`) compare as
|
|
235
|
+
numbers.
|
|
236
|
+
- The query accepts `disabled=true` as `yes`; the CLI rejects it.
|
|
237
|
+
|
|
238
|
+
**Refused, never approximated.** `~` (regex) and `in` (prefix membership) have
|
|
239
|
+
no query word, and a `$variable`, `[command]` or `{array}` value needs the
|
|
240
|
+
script evaluator: these fail `input/unsupported-query` with a tip. Four
|
|
241
|
+
spellings RouterOS reads differently from how they look fail
|
|
242
|
+
`input/invalid-query`: `!a=v` (RouterOS reads `(!a)=v`; write `!(a=v)` or
|
|
243
|
+
`a!=v`), `not` (not a RouterOS operator; use `!`), conditions separated
|
|
244
|
+
only by a space (join them with `and` or `or`), and an unquoted `=` inside a
|
|
245
|
+
value (RouterOS reads `comment=x=yes` as `(comment=x)=yes`, and `comment=x!=y`
|
|
246
|
+
matches nothing; quote it: `comment="x=yes"`).
|
|
247
|
+
|
|
248
|
+
## Follow (`--follow`)
|
|
249
|
+
|
|
250
|
+
`retrieve <router> <menu> --follow` keeps a menu's rows current: first the
|
|
251
|
+
rows that exist now, then a line for every change, as NDJSON. It is the
|
|
252
|
+
friendly wrapper over native-API `listen`; `api <router> <menu>/listen` is the
|
|
253
|
+
literal wire (`commands/api/README.md`). Decisions and CHR evidence: #396.
|
|
254
|
+
|
|
255
|
+
**What starts it.** `--follow` on a list menu whose `print` takes
|
|
256
|
+
`follow-only` in `/console/inspect` (checked live). A menu without it, a
|
|
257
|
+
singleton included, fails `validation/not-followable` before anything is
|
|
258
|
+
followed. Native-api only: an unset `via` infers native-api, and a `via` of
|
|
259
|
+
`rest-api` from any source (flag, env, CDB, settings) fails
|
|
260
|
+
`transport/capability-unsupported`; there is no polling fallback. Single
|
|
261
|
+
target only: a fan-out selector fails `usage/fanout-not-supported`. Follow is
|
|
262
|
+
filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
263
|
+
|
|
264
|
+
**What each line means.** Every line is one envelope; read
|
|
265
|
+
`meta.operation.stream`:
|
|
266
|
+
|
|
267
|
+
| `kind` | When | `data` | Fields |
|
|
268
|
+
| ------ | ---- | ------ | ------ |
|
|
269
|
+
| `frame` | One row change | the row (`upsert`), `null` (`removed`) | `index`, `phase` (`snapshot`/`live`), `change` (`upsert`/`removed`), `id`, `source` (`print`/`listen`/`sweep`/`membership`) |
|
|
270
|
+
| `synced` | Once, after the snapshot | `null` | `rows`: rows held at that point |
|
|
271
|
+
| `notice` | First, only when there is advice | `null` | — (`tips` carries it) |
|
|
272
|
+
| `summary` | Last, exactly once | the counts | `stopReason`, `frames`, `snapshot`, `changes`, `sweeps`, `synced`, `durationMs` |
|
|
273
|
+
|
|
274
|
+
- **Bootstrap (A1).** centrs starts the `listen`, then the `print`, on one
|
|
275
|
+
connection. It emits the `print` rows, then every `listen` change received
|
|
276
|
+
since the `listen` started, all as `phase: "snapshot"`, then `synced`.
|
|
277
|
+
Applying the lines in order gives the menu's state at `synced`. That order
|
|
278
|
+
had 0 errors over about 14.7k writes on 7.23.7 and 7.24.5; merging by arrival
|
|
279
|
+
order resurrects deleted rows (#396 round 3). `synced` means the bootstrap
|
|
280
|
+
was processed. It is not an atomic snapshot, a full event history, or
|
|
281
|
+
routing convergence.
|
|
282
|
+
- **`upsert`** carries the latest full row; replace what you hold for `id`.
|
|
283
|
+
**`removed`** carries `data: null`; drop `id`. The row identity is always
|
|
284
|
+
`meta.operation.stream.id`, even when `--attributes` leaves `.id` out of
|
|
285
|
+
`data`.
|
|
286
|
+
- **Changes are coalesced state, not an event log.** RouterOS sends a change
|
|
287
|
+
about 200 ms after a write and folds writes in between. A row created and
|
|
288
|
+
deleted inside that window may never appear, and a flap may arrive as one
|
|
289
|
+
line or none. A `.dead` for an `id` centrs never reported is dropped, so a
|
|
290
|
+
removal the sweep saw first is reported once, as `source: "sweep"`.
|
|
291
|
+
- **Removals RouterOS does not send.** View menus (`/interface/<type>`,
|
|
292
|
+
`/ip/route`, `/ipv6/route`) never send `.dead`. An `.id`-only sweep
|
|
293
|
+
(`print .proplist=.id`) runs every `--sweep` (default `10s`) on every followed
|
|
294
|
+
menu and reports an `id` that is gone as `removed` with `source: "sweep"`.
|
|
295
|
+
An `id` that changed since that sweep was sent is never removed by it. A
|
|
296
|
+
failed sweep ends the follow with an error; it never infers a removal. A
|
|
297
|
+
sweep only checks membership: it does not repair a missed field update or
|
|
298
|
+
report rows `listen` never announced. `--sweep` needs a unit (`10s`,
|
|
299
|
+
`500ms`); a bare number fails `settings/invalid-timeout`, as for `--sample`.
|
|
300
|
+
`--sweep 0` turns it off, with a notice tip saying view-menu removals will
|
|
301
|
+
not be reported.
|
|
302
|
+
- **What `listen` never reports.** Hot counters (`rx-byte`, firewall
|
|
303
|
+
`bytes`/`packets`) and some protocol state (BFD sessions accept `listen` but
|
|
304
|
+
stay silent). The follow is correct but quiet there; `--sample` (below)
|
|
305
|
+
is the answer for those.
|
|
306
|
+
- **Filtered membership.** The snapshot and sweep carry the predicate; the
|
|
307
|
+
listen stays unfiltered, retaining `.id,.dead`. For changed ids centrs reads
|
|
308
|
+
the current matching rows from RouterOS, in batches of at most 256 ids with
|
|
309
|
+
one membership read in flight. No client type coercion is involved, and
|
|
310
|
+
predicate fields need not appear in the caller's projection. These frames
|
|
311
|
+
have `source: "membership"`: a matching row is upserted, and a held row that
|
|
312
|
+
no longer matches (or no longer exists) is removed. This removal is a change
|
|
313
|
+
in the selected set, not a claim that RouterOS sent `.dead`. A real `.dead`
|
|
314
|
+
still has `source: "listen"` and only removes a held id. Delayed membership
|
|
315
|
+
results are discarded for ids with newer wire changes; sweeps and membership
|
|
316
|
+
reads are serialized. Bootstrap fixes its changed-id/version cutoff at the
|
|
317
|
+
snapshot's wire completion and reconciles those ids before `synced`. A
|
|
318
|
+
reply superseded by a newer change is discarded; that newer version stays
|
|
319
|
+
queued for live reconciliation. Continuous writes cannot extend bootstrap
|
|
320
|
+
indefinitely or postpone the first sweep and live `--count`. This may
|
|
321
|
+
coalesce more changes than unfiltered A1 replay.
|
|
322
|
+
The filtered sweep also detects silent exits from a predicate (e.g. a
|
|
323
|
+
decreasing address-list `timeout`). It does not discover silent entries or
|
|
324
|
+
repair silent field updates: use `--sample` for such state. With `--sweep 0`,
|
|
325
|
+
silent exits remain held until a listen notification arrives.
|
|
326
|
+
- **Projection.** `--attribute(s)` projects `data`; centrs still asks RouterOS
|
|
327
|
+
for `.id,.dead` and removes them from `data` unless you named them.
|
|
328
|
+
`--all-attributes` sends `detail` to both `listen` and `print`.
|
|
329
|
+
`--list-attributes` and `--max-results` conflict with `--follow`
|
|
330
|
+
(`usage/conflicting-flags`).
|
|
331
|
+
|
|
332
|
+
**What ends it.** `--count N` stops after N `live` frames; snapshot frames and
|
|
333
|
+
`synced` never count, so `--count 1` is "the next observed change" (coalesced,
|
|
334
|
+
so not a guaranteed transition). `--duration <d>` is a wall-clock bound that
|
|
335
|
+
includes the bootstrap. Ctrl-C (`SIGINT`) stops it. Each ends with a
|
|
336
|
+
successful summary whose `stopReason` is `count-reached`, `duration-elapsed`,
|
|
337
|
+
or `interrupted`. A RouterOS error, a lost connection, a failed sweep or a full
|
|
338
|
+
buffer ends it with a failed summary (`ok: false`, `stopReason`
|
|
339
|
+
`routeros-error` or `transport-error`), whose counts are partial.
|
|
340
|
+
`routeros-error` means RouterOS answered with an error; `transport-error`
|
|
341
|
+
covers every other failure, and `error.code` names the cause. Buffers are
|
|
342
|
+
bounded: if more than 50,000 changes are waiting (a consumer that stopped
|
|
343
|
+
reading, or a burst during a long snapshot), the follow ends with
|
|
344
|
+
`transport/stream-overflow`. The state you hold is stale; follow again to get a
|
|
345
|
+
new snapshot. `--sweep` without `--follow`, and `--count` or `--duration`
|
|
346
|
+
without `--follow` or `--sample`, fail `usage/conflicting-flags`.
|
|
347
|
+
|
|
348
|
+
**Format.** Under `--follow`, `json`, `yaml` and `ndjson` all print one compact
|
|
349
|
+
envelope per line (as `api --stream` does); `text` prints one short row per
|
|
350
|
+
line. Every envelope, errors included, goes to stdout, and the exit code is
|
|
351
|
+
nonzero for any failure, even after rows. Without `--follow`,
|
|
352
|
+
`--format ndjson` prints the ordinary envelope as one compact line; it never
|
|
353
|
+
starts a follow.
|
|
354
|
+
|
|
355
|
+
Recipes (bounded, so an agent can run them unattended):
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Watch: hold /ip/address for 30s, then stop.
|
|
359
|
+
centrs retrieve $R /ip/address --follow --duration 30s
|
|
360
|
+
# Wait for the next change, at most 60s.
|
|
361
|
+
centrs retrieve $R /ip/address --follow --count 1 --duration 60s
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Sample (`--sample`)
|
|
365
|
+
|
|
366
|
+
`retrieve <router> <menu> --sample <interval>` reads the menu again every
|
|
367
|
+
interval: one line per complete read, as NDJSON. It sees what `--follow`
|
|
368
|
+
cannot (hot counters such as `rx-byte`, and state `listen` never announces),
|
|
369
|
+
at the cost of missing whatever changes and changes back between two reads.
|
|
370
|
+
|
|
371
|
+
**What starts it.** `--sample` with an interval that has a unit (`5s`,
|
|
372
|
+
`500ms`, `1m`). A bare number fails `settings/invalid-timeout`: RouterOS reads
|
|
373
|
+
`interval=5` as seconds and centrs reads `5` as milliseconds, so centrs does
|
|
374
|
+
not guess. Any readable menu works, list or singleton. Each sample is the
|
|
375
|
+
ordinary one-shot read (`print` or `get`) with the same validation (once, before
|
|
376
|
+
the first read), projection and transport as `retrieve` without `--sample`:
|
|
377
|
+
`rest-api` by default, `native-api` when pinned. Single target only; a fan-out
|
|
378
|
+
selector fails `usage/fanout-not-supported`. `--follow` with `--sample` fails
|
|
379
|
+
`usage/conflicting-flags`.
|
|
380
|
+
|
|
381
|
+
Why reads and not RouterOS's own `print interval=`: on CHR 7.24.5 that feed has
|
|
382
|
+
no end-of-tick marker, so a tick is only known complete when the next tick (or
|
|
383
|
+
`!empty`) arrives, one whole interval later. A tick heavier than its interval
|
|
384
|
+
also stalls the connection. A one-shot read ends with its own `!done` or HTTP
|
|
385
|
+
response, so every sample is known complete when it arrives. The raw tick feed
|
|
386
|
+
stays available as `api <router> <menu>/print -X POST -f interval=1s --stream`
|
|
387
|
+
(`commands/api/README.md`).
|
|
388
|
+
|
|
389
|
+
**What each line means.** Every line is one envelope; read
|
|
390
|
+
`meta.operation.stream`:
|
|
391
|
+
|
|
392
|
+
| `kind` | When | `data` | Fields |
|
|
393
|
+
| ------ | ---- | ------ | ------ |
|
|
394
|
+
| `sample` | One complete read | the whole read: a row array (list), the record (singleton), or the bare value (one `--attribute` on a singleton) | `index` (1-based), `at` (when the read was sent, host clock, ISO 8601), `readMs` |
|
|
395
|
+
| `summary` | Last, exactly once | the counts | `stopReason`, `samples`, `durationMs` |
|
|
396
|
+
|
|
397
|
+
- **A sample is the whole state at that read.** A row missing from a sample
|
|
398
|
+
was gone when it was read; there is no separate removal line. A list read
|
|
399
|
+
with no rows is `data: []`, still a complete sample.
|
|
400
|
+
- **Cadence.** Reads start one interval apart, measured start to start. A
|
|
401
|
+
read slower than the interval delays the next read rather than overlapping
|
|
402
|
+
it, and missed slots are not made up in a burst. `at` gives the real spacing,
|
|
403
|
+
which is what a counter rate needs.
|
|
404
|
+
- **Not an event log.** A change shorter than the interval can fall between
|
|
405
|
+
two reads. Use `--follow` for object changes RouterOS announces.
|
|
406
|
+
|
|
407
|
+
**What ends it.** `--count N` stops after N samples. `--duration <d>` is a
|
|
408
|
+
wall-clock bound counted from the end of validation. Ctrl-C (`SIGINT`) stops
|
|
409
|
+
it. Each ends with a successful summary whose `stopReason` is `count-reached`,
|
|
410
|
+
`duration-elapsed` or `interrupted`. A read still in flight when the run stops
|
|
411
|
+
is not reported, because it is not complete. A failed read (a RouterOS error,
|
|
412
|
+
a lost connection, a timeout) ends the run with a failed summary (`ok: false`,
|
|
413
|
+
`stopReason` `routeros-error` or `transport-error`, as for `--follow`)
|
|
414
|
+
carrying the partial `samples` count; there is no retry. `--list-attributes`, `--max-results` and
|
|
415
|
+
`--sweep` conflict with `--sample` (`usage/conflicting-flags`).
|
|
416
|
+
|
|
417
|
+
**Format.** As for `--follow`: `json`, `yaml` and `ndjson` print one compact
|
|
418
|
+
envelope per line, and `text` prints `index`, `at` and the data on one line.
|
|
419
|
+
Every envelope, errors included, goes to stdout; the exit code is nonzero for
|
|
420
|
+
any failure, even after samples.
|
|
421
|
+
|
|
422
|
+
Recipes (bounded, so an agent can run them unattended):
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Interface counters every 5s, three samples.
|
|
426
|
+
centrs retrieve $R /interface --attributes name,rx-byte,tx-byte --sample 5s --count 3
|
|
427
|
+
# CPU load once a second for a minute.
|
|
428
|
+
centrs retrieve $R /system/resource --attribute cpu-load --sample 1s --duration 1m
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Wait (`--wait`, `--until`, `--until-empty`)
|
|
432
|
+
|
|
433
|
+
`retrieve <router> <menu> --wait <deadline>` waits for a successful read.
|
|
434
|
+
It retries refused, closed, reset and timed-out connections, and a host that is
|
|
435
|
+
down or unreachable, so it can start while a router or CHR is rebooting or its
|
|
436
|
+
services are coming up. Authentication,
|
|
437
|
+
TLS, DNS, RouterOS and validation errors fail immediately. Identity/CDB/quickchr
|
|
438
|
+
resolution errors also fail immediately; `--quickchr` must name a running VM.
|
|
439
|
+
The caller's pinned protocol is preserved.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
centrs retrieve --quickchr sun /system/resource --wait 5m --json
|
|
443
|
+
centrs retrieve $R /routing/ospf/neighbor --wait 2m --until state=Full --json
|
|
444
|
+
centrs retrieve $R /routing/ospf/neighbor --wait 2m --until-empty --json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- **Deadline and cadence.** `--wait` is the overall deadline, including
|
|
448
|
+
validation and retries, and needs a unit (`30s`, `5m`): a bare number is
|
|
449
|
+
refused, as for `--sample`. `--timeout` bounds each request. Reads begin one
|
|
450
|
+
second apart by default; `--sample 500ms` selects another cadence, measured
|
|
451
|
+
start to start without overlapping reads. With `--wait`, `--sample` yields
|
|
452
|
+
one final result rather than a stream. Slow reads delay the next one and
|
|
453
|
+
unfinished reads at the deadline are abandoned.
|
|
454
|
+
- **Any matching row.** `--until '<expr>'` uses the same grammar, property
|
|
455
|
+
validation and device typing as `--query`. Repeat it to AND conditions.
|
|
456
|
+
`--query` selects the rows to consider; `--until` terminates the wait when
|
|
457
|
+
any of those rows match. The final `data` contains those matching rows,
|
|
458
|
+
projected as requested; predicate properties need not appear in `data`.
|
|
459
|
+
Already true succeeds on the initial read. Zero rows never satisfy it.
|
|
460
|
+
Predicates apply to list menus; singleton readiness uses `--wait` alone.
|
|
461
|
+
- **Zero rows.** `--until-empty` succeeds only on a completed read with zero
|
|
462
|
+
rows selected by `--query`. Silence, transport failure, unfinished reads and
|
|
463
|
+
cancellation do not prove emptiness. It needs a list menu and conflicts
|
|
464
|
+
with `--until`.
|
|
465
|
+
- **One result.** After request resolution, every terminal envelope carries `meta.operation.wait` with
|
|
466
|
+
`stopReason`, `elapsedMs`, `attempts` and `observations`. Attempts include
|
|
467
|
+
failed validation/connection attempts; observations count completed data
|
|
468
|
+
reads. `ready` and `condition-met` are successful. On deadline expiry,
|
|
469
|
+
`deadline-elapsed` is failed: it preserves the last transient transport error,
|
|
470
|
+
or reports `wait/deadline-exceeded` when the last completed observation did
|
|
471
|
+
not satisfy the condition. Ctrl-C or an aborted library signal fails with
|
|
472
|
+
`wait/interrupted`, `stopReason: "interrupted"`. Other errors have
|
|
473
|
+
`stopReason: "failed"`. CLI exit is 0 on success and 1 on any failure;
|
|
474
|
+
the one final envelope, including failures, is written to stdout.
|
|
475
|
+
|
|
476
|
+
Single target only. `--until` and `--until-empty` require `--wait`.
|
|
477
|
+
`--follow`, `--count`, `--duration` and `--list-attributes` conflict with it.
|
|
478
|
+
This waiter uses completed reads over REST or native-api, including counters
|
|
479
|
+
and protocol state that listen never reports. For open-ended change feeds use
|
|
480
|
+
`--follow`; `--duration` finishing a watch does not establish a wait condition.
|
|
481
|
+
The library entry is `retrieveWait(request, env?, { signal? })`, returning a
|
|
482
|
+
success or failure envelope; `retrieve()` remains the one-shot entry.
|
|
483
|
+
|
|
143
484
|
## Target selection
|
|
144
485
|
|
|
145
486
|
retrieve fans out over the **shared target-selection grammar** — multiple
|
|
@@ -155,7 +496,10 @@ stays the single-target envelope; a selector flag or a second positional target
|
|
|
155
496
|
switches to fan-out. `--quickchr <name>` (the named-live-provider) behaves like a
|
|
156
497
|
lone positional: one flag stays single-target, repeating it fans out, and mixing
|
|
157
498
|
it with positionals or CDB selectors is `usage/conflicting-flags` (constitution:
|
|
158
|
-
resolution providers).
|
|
499
|
+
resolution providers). The `--host` **flag** with no `<router>` positional
|
|
500
|
+
(`retrieve --host http://192.0.2.10:8080 /system/resource`) is a single target
|
|
501
|
+
too: no CDB lookup, so auth comes from flags, env or config. `CENTRS_HOST` never
|
|
502
|
+
stands in for `<router>`.
|
|
159
503
|
|
|
160
504
|
retrieve-specific behavior on top of the shared engine:
|
|
161
505
|
|