@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
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,111 @@ documenting cross-cutting shifts that affect contributors and consumers.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.1.3 — 2026-07-06
|
|
12
|
+
|
|
13
|
+
Patch release. `@tikoci/centrs@0.1.3`.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`api` command over rest-api + native-api is `CHR-passed`.** `centrs api`
|
|
18
|
+
brings a `gh api`-style structured passthrough to both transports, and folds
|
|
19
|
+
the old standalone `stream` command into `api --stream` (native-api only,
|
|
20
|
+
`--listen` alias): a `NativeApiSession.listen()` reader with `/cancel`
|
|
21
|
+
support emits NDJSON frames plus a run summary. Grounded against real CHR
|
|
22
|
+
7.23.1.
|
|
23
|
+
- **Multi-target fan-out is uniform across `api`, `retrieve`, `execute`, and
|
|
24
|
+
`transfer`.** A shared `expandCdbSelection` + `runFanout` + `fanoutExitCode`
|
|
25
|
+
core (extracted once, consumed by all four commands) lets any command take a
|
|
26
|
+
CDB selector and run over every matching device. The positional boundary
|
|
27
|
+
that ends the selector differs per command (final positional for
|
|
28
|
+
`api`/`retrieve`, `--` for `execute`, the verb keyword for `transfer`);
|
|
29
|
+
`terminal` and `api --stream` explicitly reject `N > 1` selections since
|
|
30
|
+
neither has a sane multi-target framing.
|
|
31
|
+
- **`settings` command (`print` / `get` / `set` / `reset`).** A managed-key
|
|
32
|
+
registry for the real `CENTRS_*` environment keys backs an atomic
|
|
33
|
+
`centrs.env` writer: `set` validates per key, canonicalizes booleans to
|
|
34
|
+
`1`/`0` on disk, warns (never blocks) on consequential values
|
|
35
|
+
(`insecure=true`, `transfer-via=ftp`), and never partially writes on a
|
|
36
|
+
validation failure; `reset` deletes the managed line rather than blanking it
|
|
37
|
+
(a blank value would itself mean "set to empty" for a bash-sourced file) and
|
|
38
|
+
leaves foreign lines/comments untouched. Bare `centrs settings` behaves like
|
|
39
|
+
`print` regardless of TTY. Four new error/warning codes
|
|
40
|
+
(`settings/reserved-key`, `settings/unknown-key`,
|
|
41
|
+
`settings/consequential-value`, `settings/skip-env-file-active`) plus
|
|
42
|
+
`internal/settings-failed`, all cataloged. `CHR-passed` via 32
|
|
43
|
+
fixture-backed integration examples.
|
|
44
|
+
- **Device GPS: lat/lon/altitude storage + `--near` / `--bbox` geo
|
|
45
|
+
predicates.** `centrs devices` now stores GPS coordinates per device
|
|
46
|
+
(lat-first ordering, chosen so agents guess the field order correctly) and
|
|
47
|
+
supports proximity
|
|
48
|
+
(`--near`) and bounding-box (`--bbox`) selection predicates. `CHR-passed`
|
|
49
|
+
via fixture-backed integration coverage.
|
|
50
|
+
- **Registry⇄MATRIX drift guard + "did you mean?" flag errors.** A CI-checked
|
|
51
|
+
guard keeps the command/protocol registry and `docs/MATRIX.md` from
|
|
52
|
+
silently drifting apart; unrecognized CLI flags now suggest the closest
|
|
53
|
+
known flag instead of a bare rejection.
|
|
54
|
+
- **GitHub Pages: published API reference + error catalog.** `typedoc` output
|
|
55
|
+
and the generated error-code catalog now publish to GitHub Pages on push.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **Windows unit-test portability for `settings` and MNDP.** Path-separator
|
|
60
|
+
and related platform assumptions in the `settings` and MNDP unit suites are
|
|
61
|
+
now Windows-safe (#170).
|
|
62
|
+
- **A real CodeQL-caught ReDoS in the device GPS geo-predicate parser**, fixed
|
|
63
|
+
rather than suppressed.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **CodeQL house-rules pack promoted to the default run**, alongside CodeRabbit
|
|
68
|
+
review-tone tuning and a reduced GitHub-checks timeout (15 → 7 minutes).
|
|
69
|
+
|
|
70
|
+
## 0.1.2 — 2026-06-26
|
|
71
|
+
|
|
72
|
+
Patch release. `@tikoci/centrs@0.1.2`.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **`btest` TCP multi-connection fan-out is `CHR-passed`.** `centrs btest
|
|
77
|
+
--connection-count N` now realizes the full fan-out: the client reads the
|
|
78
|
+
session token from the primary's OK handshake and opens `N-1` additional TCP
|
|
79
|
+
data connections, each sending the grounded secondary-join
|
|
80
|
+
(`[token:u16 BE][0x02][0…]`). All connections drive into one shared
|
|
81
|
+
`BandwidthCounters` so throughput aggregates. Wire format confirmed
|
|
82
|
+
byte-for-byte against RouterOS 7.23.1 (the server sends a 4-byte HELLO per
|
|
83
|
+
connection then waits for all to join before streaming — sequential opens must
|
|
84
|
+
not block on a reply, or the first secondary deadlocks the server). Unauthenticated
|
|
85
|
+
TCP fan-out works end-to-end; authenticated (EC-SRP5) sessions stay single-stream
|
|
86
|
+
and warn when the realized `activeConnections` falls short of the requested count.
|
|
87
|
+
`BtestRunSummary` / `BtestClientData` gain an `activeConnections` field.
|
|
88
|
+
Grounded via unit tests (secondary-join byte format, loopback fan-out) and
|
|
89
|
+
integration example 11 on real CHR (4 connections open, data flows on each).
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- **`btest` UDP client receive reported 0 bps.** The UDP socket was bound with
|
|
94
|
+
`connect()` (filtering by remote address/port), which dropped the
|
|
95
|
+
bandwidth-server's probe packets because they arrive from the server's data
|
|
96
|
+
port, not the control port. The socket is now unconnected; `--remote-udp-tx-size`
|
|
97
|
+
emits a warning when unset (needed by the server-push direction only).
|
|
98
|
+
UDP `receive` and `both` cycles are now CHR-gated on real RouterOS (rx ≈ 474 KB
|
|
99
|
+
in CI).
|
|
100
|
+
- **`btest --connection-count` was silently ignored.** The count was validated and
|
|
101
|
+
accepted but never encoded into command byte 3 of the client hello, so RouterOS
|
|
102
|
+
always saw `count=0` and opened a single stream. The value is now threaded through
|
|
103
|
+
`BtestClientSessionOptions` → `clientHandshake`. A
|
|
104
|
+
`routeros/btest-connection-count-single-stream` warning fires for
|
|
105
|
+
`connection-count > 1` sessions that fall back to one stream (e.g. EC-SRP5 auth).
|
|
106
|
+
- **`btest direction=both` TX was unbounded, starving RX.** In TCP `both` the client
|
|
107
|
+
ran no status reader, so TX had no feedback, saturated the link, and collapsed
|
|
108
|
+
server→client RX. The `tcpRxLoop` now demuxes embedded 12-byte status frames
|
|
109
|
+
in-place by their structural marker (`0x07 ?? 00 00`) and passes them through the
|
|
110
|
+
shared `applyStatusFeedback` / `adaptTxFromStatus` gate used by
|
|
111
|
+
`tcpStatusReaderLoop`. A per-frame plausibility guard (CPU byte ≤ 100; sane
|
|
112
|
+
sequence ceiling) filters false matches from `--random-data` bulk streams.
|
|
113
|
+
Grounded: a CHR `both` run fires 5 feedback events (server bytesReceived
|
|
114
|
+
78840 → 643860) and paces TX; RX is sustained instead of collapsing.
|
|
115
|
+
|
|
11
116
|
## 0.1.1 — 2026-06-22
|
|
12
117
|
|
|
13
118
|
Patch release. `@tikoci/centrs@0.1.1`.
|
|
@@ -214,8 +319,8 @@ First published pre-release (odd minor → npm `next`). `@tikoci/centrs@0.1.0`.
|
|
|
214
319
|
result envelope, error model + URL scheme, settings precedence, identity,
|
|
215
320
|
protocol selection, done = CHR-passed).
|
|
216
321
|
- `docs/MATRIX.md` — command×protocol grid; the only status surface.
|
|
217
|
-
- `commands/<name>/
|
|
218
|
-
executable example list that gates "done."
|
|
322
|
+
- `commands/<name>/README.md` and `commands/<name>/examples.md` —
|
|
323
|
+
per-command design and executable example list that gates "done."
|
|
219
324
|
- Done definition codified in
|
|
220
325
|
`.github/instructions/done-definition.instructions.md`: a feature is done
|
|
221
326
|
when its examples are green on real CHR via `bun run test:integration`.
|
package/GLOSSARY.txt
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
# https://github.com/tikoci/restraml
|
|
26
26
|
# %lsp-routeros-ts: RouterOS parser and language-server utilities.
|
|
27
27
|
# https://github.com/tikoci/lsp-routeros-ts
|
|
28
|
+
# Language Server Protocol — the "lsp" in lsp-routeros-ts, referenced as a
|
|
29
|
+
# grounding source for /console/inspect client patterns (e.g. commands/api).
|
|
30
|
+
lsp
|
|
31
|
+
# RouterOS maximum transmission unit property (e.g. mtu, actual-mtu).
|
|
32
|
+
mtu
|
|
28
33
|
# %donny: Dude/discovery grounding for RouterOS device inventories.
|
|
29
34
|
# https://github.com/tikoci/donny
|
|
30
35
|
# %m2ir: protocol IR (WinBox, nova messages) + the declarative schema-as-data
|
|
@@ -89,6 +94,8 @@ datagram
|
|
|
89
94
|
davidanson
|
|
90
95
|
DDNS
|
|
91
96
|
defconf
|
|
97
|
+
demux
|
|
98
|
+
demuxes
|
|
92
99
|
dogtail
|
|
93
100
|
donny
|
|
94
101
|
dotenv
|
|
@@ -132,6 +139,7 @@ MNDP
|
|
|
132
139
|
NAND # NAND flash memory; write-back caches file writes up to ~40s on some MikroTik boards
|
|
133
140
|
netmiko # [Multi-vendor library to simplify Paramiko SSH connections to network devices](https://github.com/ktbyers/netmiko)
|
|
134
141
|
netwatch
|
|
142
|
+
nullglob # bash shopt that makes an unmatched glob expand to zero words instead of the literal pattern
|
|
135
143
|
npk # MikroTik package format
|
|
136
144
|
nvme-over-tcp # supported by RouterOS **with** rose-storage.npk extra-package
|
|
137
145
|
OID
|
|
@@ -141,11 +149,13 @@ PRGA
|
|
|
141
149
|
proplist
|
|
142
150
|
pywinauto
|
|
143
151
|
QML
|
|
152
|
+
qll # CodeQL library file extension (.qll), e.g. CleartextLoggingCustomizations.qll
|
|
144
153
|
qmljsdebugger
|
|
145
154
|
quickchr
|
|
146
155
|
RAML
|
|
147
156
|
readcdb
|
|
148
157
|
remotehub
|
|
158
|
+
retarget
|
|
149
159
|
restraml
|
|
150
160
|
romon
|
|
151
161
|
ros
|
|
@@ -153,12 +163,15 @@ rosetta
|
|
|
153
163
|
routeros
|
|
154
164
|
routeros-skills
|
|
155
165
|
rsc # RouterOS script / export file extension (.rsc)
|
|
166
|
+
SARIF # Static Analysis Results Interchange Format; CodeQL's code-scanning analysis download format
|
|
156
167
|
scp
|
|
157
168
|
scriptability
|
|
158
169
|
scriptable
|
|
170
|
+
shopt # bash builtin for toggling shell options (e.g. nullglob)
|
|
159
171
|
Sikuli
|
|
160
172
|
SikuliX
|
|
161
173
|
skillcheck
|
|
174
|
+
startswith # jq builtin filter that tests whether a string begins with a given prefix
|
|
162
175
|
SNMP
|
|
163
176
|
SPI
|
|
164
177
|
SRP
|
|
@@ -176,6 +189,7 @@ TIKOCI
|
|
|
176
189
|
tiktui
|
|
177
190
|
TLV
|
|
178
191
|
traceroute
|
|
192
|
+
tostring # jq builtin filter that renders a value as its JSON string representation
|
|
179
193
|
tsc
|
|
180
194
|
tzsp
|
|
181
195
|
TZSP
|
|
@@ -418,3 +432,76 @@ trippable
|
|
|
418
432
|
# btest integration-test fixtures.
|
|
419
433
|
btestuser
|
|
420
434
|
btestpass
|
|
435
|
+
|
|
436
|
+
# RouterOS scripting builtin referenced in commands/api grounding notes
|
|
437
|
+
# (:toarray coerces a comma string into an array; explains the inspect path form).
|
|
438
|
+
toarray
|
|
439
|
+
|
|
440
|
+
# Logic shorthand ("if and only if") used in api write-gate doc comments; cspell's
|
|
441
|
+
# markdown dictionary knows it but the TypeScript one does not.
|
|
442
|
+
iff
|
|
443
|
+
|
|
444
|
+
# GitHub Pages build vocabulary (scripts/build-pages-site.ts, pages.yaml)
|
|
445
|
+
# typedoc/pandoc: the API-reference generator and the markdown->HTML converter
|
|
446
|
+
# the Pages build chains together. gfm: GitHub-Flavored Markdown, pandoc's
|
|
447
|
+
# input format for the error pages. larr: the "←" left-arrow HTML entity
|
|
448
|
+
# in the back-to-docs link. nojekyll: the marker file that disables GitHub
|
|
449
|
+
# Pages' Jekyll processing for an Actions-deployed artifact. extensionless:
|
|
450
|
+
# describes the `details_url` contract (docs/errors/README.md) — no `.html`.
|
|
451
|
+
typedoc
|
|
452
|
+
pandoc
|
|
453
|
+
gfm
|
|
454
|
+
larr
|
|
455
|
+
nojekyll
|
|
456
|
+
extensionless
|
|
457
|
+
# nothrow: Bun Shell's `$` method to disable throwing on a non-zero exit
|
|
458
|
+
# (scripts/agent-check.ts), so the caller can inspect output either way.
|
|
459
|
+
nothrow
|
|
460
|
+
|
|
461
|
+
# check-command spec vocabulary (commands/check/README.md, examples.md)
|
|
462
|
+
# TikTOML: centrs-owned readable device-inventory format (#137); check's
|
|
463
|
+
# metadata feeds it. routerboard: RouterBOARD firmware/hardware menu
|
|
464
|
+
# (/system/routerboard). rscmd: the tikbook RouterOS command-notebook file
|
|
465
|
+
# extension (.rscmd), the format the check spec was grounded against.
|
|
466
|
+
TikTOML
|
|
467
|
+
routerboard
|
|
468
|
+
rscmd
|
|
469
|
+
# RTT: round-trip time, the host->router connect-latency reach signal. tojson: the
|
|
470
|
+
# /system/script helper (:serialize to=json) the batched execute reads reuse.
|
|
471
|
+
# qrcode: the /ip/cloud vpn-wireguard-client-config-qrcode field token (redacted
|
|
472
|
+
# by the cloud check, referenced when documenting redaction).
|
|
473
|
+
RTT
|
|
474
|
+
tojson
|
|
475
|
+
qrcode
|
|
476
|
+
|
|
477
|
+
# Device GPS vocabulary (issue #146, commands/devices/README.md "Location /
|
|
478
|
+
# GPS", src/resolver/geo.ts). MSL/AGL: the two supported altitude-type vertical
|
|
479
|
+
# datums (mean sea level / above ground level); stored case-insensitively but
|
|
480
|
+
# normalized to upper case. EPSG: the geodetic coordinate-reference-system
|
|
481
|
+
# registry (EPSG:4326 = WGS84 lat/lon), cited for the lat-first ordering
|
|
482
|
+
# rationale. bbox: the --bbox geo selector (bounding box). wgs: WGS84, the
|
|
483
|
+
# geodetic datum GPS coordinates use; appears only as a deliberately-invalid
|
|
484
|
+
# --altitude-type value in tests/examples. antimeridian: the 180° meridian a
|
|
485
|
+
# --bbox may not cross in v1. GEOSEARCH: the Redis command whose m/km/mi/ft
|
|
486
|
+
# radius-unit vocabulary --near borrows. IUGG: the geodesy union whose mean
|
|
487
|
+
# Earth radius the haversine uses. SFO/OAK: airports used as a known-distance
|
|
488
|
+
# haversine fixture in tests. ele/GPX: `ele` is GPX's altitude element, kept as
|
|
489
|
+
# an input alias for `altitude` (GPX = the GPS Exchange Format). Geolocation:
|
|
490
|
+
# the W3C Geolocation API, cited (with ISO 6709 / EPSG / IETF geo:) for why
|
|
491
|
+
# `altitude` — not `elevation` — is the canonical field name.
|
|
492
|
+
MSL
|
|
493
|
+
msl
|
|
494
|
+
AGL
|
|
495
|
+
agl
|
|
496
|
+
Agl
|
|
497
|
+
EPSG
|
|
498
|
+
bbox
|
|
499
|
+
wgs
|
|
500
|
+
antimeridian
|
|
501
|
+
GEOSEARCH
|
|
502
|
+
IUGG
|
|
503
|
+
SFO
|
|
504
|
+
sfo
|
|
505
|
+
ele
|
|
506
|
+
GPX
|
|
507
|
+
Geolocation
|
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
CLI (with MCP, TUI, and HTTP proxy frontends planned) for talking to MikroTik
|
|
5
5
|
RouterOS devices through a regularized, validated interface.
|
|
6
6
|
|
|
7
|
+
Preview builds are published on npm. To inspect the CLI without installing it:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx @tikoci/centrs --help
|
|
11
|
+
```
|
|
12
|
+
|
|
7
13
|
It is a **friendly conduit**, not a high-level configuration abstraction. It
|
|
8
14
|
helps humans and agents reach RouterOS over the right protocol, with the right
|
|
9
15
|
credentials and ports, and validates RouterOS-shaped commands before running
|
|
@@ -11,9 +17,12 @@ them. It does **not** hide RouterOS behind helpers like `createVlanOnBridge()`
|
|
|
11
17
|
— you still speak RouterOS. Validation and structured diagnostics are the
|
|
12
18
|
product; without them this would just be a worse `curl`.
|
|
13
19
|
|
|
14
|
-
> **Status:** early
|
|
15
|
-
>
|
|
16
|
-
>
|
|
20
|
+
> **Status:** early npm preview, active development. `@tikoci/centrs` is
|
|
21
|
+
> published on npm, but command flags and envelopes may still change before a
|
|
22
|
+
> stable release. Check current dist-tags with
|
|
23
|
+
> `npm view @tikoci/centrs dist-tags --json`. The library, device registry, and
|
|
24
|
+
> first MCP server phases are CHR-verified; the CLI is wired (`coded`);
|
|
25
|
+
> encrypted-CDB writes are still blocked, and the TUI/proxy frontends are planned.
|
|
17
26
|
> `docs/MATRIX.md` is the single source of truth for what works today — treat
|
|
18
27
|
> anything not green there as not-yet-shipped.
|
|
19
28
|
|
|
@@ -32,8 +41,8 @@ product; without them this would just be a worse `curl`.
|
|
|
32
41
|
| Command | Purpose |
|
|
33
42
|
| ---------- | ------- |
|
|
34
43
|
| `retrieve` | Read RouterOS state over REST/native API and SNMP OID/MIB values. |
|
|
35
|
-
| `stream` | Follow live RouterOS output (`print follow`/monitor/sniffer) as an NDJSON stream of envelopes. |
|
|
36
44
|
| `execute` | Run RouterOS CLI-shaped read/write commands (add/set/remove) over native API/REST/L2 surfaces. |
|
|
45
|
+
| `api` | Structured RouterOS API passthrough (gh-api style): one command per operation, structured in/out, can write, over REST or native API. Open-ended follow is `api … --stream` (native API; NDJSON). |
|
|
37
46
|
| `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
|
|
38
47
|
| `check` | Probe reachability and management protocol availability. |
|
|
39
48
|
| `devices` | View and maintain the CDB-backed device registry (the only writer). |
|
|
@@ -104,16 +113,23 @@ CDB tags, and `devices set` refuses to put them there. Unknown keys produce a
|
|
|
104
113
|
`cdb/unknown-option` warning and are ignored, so a stray `=` in human prose
|
|
105
114
|
never breaks anything.
|
|
106
115
|
|
|
107
|
-
Settings resolve lowest-to-highest: **built-in defaults →
|
|
108
|
-
comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every
|
|
109
|
-
reports which source won each setting under `meta.settings`,
|
|
110
|
-
see *why* a given port or transport was chosen.
|
|
116
|
+
Settings resolve lowest-to-highest: **built-in defaults → user config file
|
|
117
|
+
(`centrs.env`) → comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every
|
|
118
|
+
result envelope reports which source won each setting under `meta.settings`,
|
|
119
|
+
so you can always see *why* a given port or transport was chosen.
|
|
111
120
|
|
|
112
121
|
### Groups, discovery, and the MCP allowlist all ride on the CDB
|
|
113
122
|
|
|
114
123
|
- **Groups** are CDB-native: an entry's `group` field is just a string, and
|
|
115
124
|
`--group prod-edge` fans a single command out to every entry that shares it.
|
|
116
|
-
There is no separate group-definition file.
|
|
125
|
+
There is no separate group-definition file. Groups are one selector in a
|
|
126
|
+
**uniform fan-out grammar** shared by `api`, `retrieve`, `execute`, and
|
|
127
|
+
`transfer`: mix `<router>` positionals, repeatable `--group`/`--where
|
|
128
|
+
<attr>=<value>`, `--all`, and `--default` (de-duped by CDB record index). Every
|
|
129
|
+
fan-out returns the locked `FanoutData` envelope (per-target results, not
|
|
130
|
+
metadata) with a granular exit code (`0` all ok / `2` partial / `1` all failed);
|
|
131
|
+
a multi-target write is gated once by `--yes` naming the blast radius. See
|
|
132
|
+
`docs/CONSTITUTION.md` (Target selection grammar).
|
|
117
133
|
- **Discovery** writes back into the CDB: `discover --save` records MNDP
|
|
118
134
|
neighbors as new entries tagged `group=discovered` with a `source=mndp`
|
|
119
135
|
marker — hints to curate, not authoritative inventory.
|
package/SECURITY.md
CHANGED
|
@@ -14,14 +14,14 @@ Please include the affected files or workflow, reproduction details, and impact.
|
|
|
14
14
|
|
|
15
15
|
The repository's [Security tab](https://github.com/tikoci/centrs/security) is the live source of current alerts and advisories. This section describes *what* checks run and *why*, so the doc stays meaningful even when the badge is at 0.
|
|
16
16
|
|
|
17
|
-
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality). Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
-
- **Code Quality (AI findings, preview)** —
|
|
17
|
+
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality) plus a small custom pack, [`.github/codeql-house-rules/`](.github/codeql-house-rules/), that structurally checks a handful of repo-specific invariants (currently: `apiFanout` must call its named protocol/validation gates) — see `.github/instructions/github-security-quality.instructions.md` for scope and the verify-before-promote discipline for adding more rules there. Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
+
- **Code Quality (AI findings, preview)** — confirmed active in GitHub as of 2026-07-03 (not just "intended"; see [issue #166](https://github.com/tikoci/centrs/issues/166)). [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) includes a non-failing forward-compat probe for a future AI-findings API. AI findings are noisy and self-contradicting; we accept the noise because the second-opinion catches real issues that the static suite misses. Steady-state goal is 0 open findings. False positives are dismissed via the GitHub UI with a written justification — that text is the audit-log contract. Side effect: GitHub's own Code Quality scan runs in parallel with our Advanced Setup and posts a same-named `Analyze (javascript-typescript)` check, so PR checks lists show it twice — cosmetic, tracked in #166, not a required check either way (see issue #118).
|
|
19
19
|
- **Dependency review** — not enabled. GitHub dependency review follows dependency-graph ecosystem support, and this Bun-only repository currently does not have a supported lockfile for that gate.
|
|
20
20
|
- **Dependabot security updates** — enabled and configured in [`.github/dependabot.yaml`](.github/dependabot.yaml).
|
|
21
21
|
- **Secret scanning** — enabled in GitHub, with push protection.
|
|
22
22
|
- **Private vulnerability reporting** — enabled via GitHub Security Advisories.
|
|
23
23
|
|
|
24
|
-
Some GitHub Security tab data, especially Code Quality AI findings, is not
|
|
24
|
+
Some GitHub Security tab data, especially Code Quality AI findings, is not reachable through the default workflow `GITHUB_TOKEN` (`repos/{owner}/{repo}/code-quality/findings` 403s there even with `security-events: read`) — it does respond with an elevated/owner-scoped token, but no findings have been recorded yet to confirm the response shape end-to-end (see #166). Human review in the GitHub UI remains part of the security workflow until that's wired up.
|
|
25
25
|
|
|
26
26
|
## Supported versions
|
|
27
27
|
|
package/commands/AGENTS.md
CHANGED
|
@@ -18,8 +18,9 @@ README's "Status" line must agree with it.
|
|
|
18
18
|
|
|
19
19
|
## Verb vocabulary
|
|
20
20
|
|
|
21
|
-
Commands are centrs **verbs** (`retrieve`, `
|
|
22
|
-
`discover`, `check`, `terminal`, `settings`), never one tool per RouterOS command.
|
|
21
|
+
Commands are centrs **verbs** (`retrieve`, `execute`, `api`, `devices`,
|
|
22
|
+
`discover`, `check`, `terminal`, `settings`), never one tool per RouterOS command. Open-ended follow
|
|
23
|
+
is folded into `api --stream` (no separate `stream` verb). Sub-verbs and their
|
|
23
24
|
aliases are canonical across CLI / API / MCP:
|
|
24
25
|
|
|
25
26
|
| Canonical | Aliases | Meaning |
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# commands/api — local constraints + grounded RouterOS facts
|
|
2
|
+
|
|
3
|
+
This file is the **durable home** for the RouterOS API-behavior facts the `api`
|
|
4
|
+
command depends on. It exists so the knowledge survives context resets: a fresh
|
|
5
|
+
agent picking up any `api` phase reads this first. Add a fact here only with a
|
|
6
|
+
source (a doc URL, a `tikoci/*` project, or a CHR run); follow the global
|
|
7
|
+
grounding discipline — one CHR result is a signal, reproduce before treating it as
|
|
8
|
+
fact.
|
|
9
|
+
|
|
10
|
+
Read order for any `api` work: `docs/CONSTITUTION.md` → `commands/api/README.md` →
|
|
11
|
+
`commands/api/examples.md` → this file.
|
|
12
|
+
|
|
13
|
+
## Local constraints
|
|
14
|
+
|
|
15
|
+
- `api` is the structured one-command-per-operation surface (the verb trichotomy
|
|
16
|
+
in `README.md`). It must not grow code-block / multi-command behavior — that is
|
|
17
|
+
`execute`'s job.
|
|
18
|
+
- Reuse the shared cores, do not fork: `src/core/inspect.ts` (the
|
|
19
|
+
`/console/inspect` client) and `src/core/fanout.ts` (the fan-out engine).
|
|
20
|
+
- Honor `--via` literally; never silently downgrade transport.
|
|
21
|
+
|
|
22
|
+
## Grounded facts (doc sources)
|
|
23
|
+
|
|
24
|
+
REST API — <https://help.mikrotik.com/docs/spaces/ROS/pages/47579162/REST+API> ·
|
|
25
|
+
native API — <https://help.mikrotik.com/docs/spaces/ROS/pages/47579160/API> ·
|
|
26
|
+
inspect-client TS patterns — `tikoci/lsp-routeros-ts`
|
|
27
|
+
(`server/src/routeros.ts` `InspectRequest`, `server/src/validation.ts`).
|
|
28
|
+
|
|
29
|
+
- **HTTP method map (REST):** `GET`→print, `PUT`→add (**create**), `PATCH`→set,
|
|
30
|
+
`DELETE`→remove, `POST`→universal (run any console command). PUT — not POST — is
|
|
31
|
+
RouterOS's create.
|
|
32
|
+
- **Multi-frame results: bounded → array; open-ended → native `/listen` only.**
|
|
33
|
+
*Nuance (do not over-read the REST docs).* RouterOS REST **does** run
|
|
34
|
+
`duration=` / `monitor` commands — it regularizes the native `!re` frames into a
|
|
35
|
+
JSON **array of `.section` records** and returns the whole array in one response,
|
|
36
|
+
bounded by the **60 s** REST cap (a command asked to run longer terminates early
|
|
37
|
+
with an error). Native has **no** such cap. So a `duration=`/`monitor` command is
|
|
38
|
+
an **ordinary bounded `api` call** that returns a `.section` array on both
|
|
39
|
+
transports (native's `talk()` accumulates the `!re` until `!done`) — **not**
|
|
40
|
+
NDJSON. The only thing that needs the NDJSON streaming path is **open-ended
|
|
41
|
+
follow** (native `/listen`, which never sends `!done`); REST genuinely cannot do
|
|
42
|
+
open-ended follow. ⇒ `--listen` (open-ended) is native-only; `duration=` commands
|
|
43
|
+
are normal one-shot calls. (CONFIRMED below.)
|
|
44
|
+
- **`/listen` semantics.** Emits `!re` on change; a deleted/disappeared item's
|
|
45
|
+
`!re` carries the dead flag (docs say `=.dead=yes`; **CHR 7.23.1 sends
|
|
46
|
+
`.dead=true`** — see CONFIRMED below); it never self-terminates. Cancel with
|
|
47
|
+
`/cancel tag=<listen-tag>` (the `/cancel` carries its own `.tag`); the listener
|
|
48
|
+
then gets `!trap … message=interrupted` followed by `!done`.
|
|
49
|
+
- **Query.** REST `.query` words are **identical to native `?` words minus the
|
|
50
|
+
`?` prefix** (CONFIRMED below). Forms: eq `name=val`; comparison `>name=val` /
|
|
51
|
+
`<name=val` (operator **before** the name); has-property `name`; absence
|
|
52
|
+
`-name`; stack ops `#!` (NOT top), `#&` (AND), `#|` (OR). Multiple words with no
|
|
53
|
+
stack op are **implicitly AND-ed**. See the centrs mapping section below.
|
|
54
|
+
- **Proplist.** REST `.proplist` accepts `"name,type"` or `["name","type"]`.
|
|
55
|
+
Native: `=.proplist=name,type`.
|
|
56
|
+
- **Object id.** Set/remove by id: REST id-in-URL (`PATCH /rest/ip/address/*1`),
|
|
57
|
+
native `=.id=*1` word. **No native get-one-by-id shorthand** — use
|
|
58
|
+
`print ?.id=*1`. REST has the convenience `GET /rest/ip/address/*1` → a single
|
|
59
|
+
object; native maps it to `print ?.id=*1`.
|
|
60
|
+
- **`/console/inspect` request modes** (`InspectRequest` in lsp): `request ∈
|
|
61
|
+
{child, completion, highlight, syntax}`, with `input?`, `path?`, `.proplist?`,
|
|
62
|
+
`.query?`. `child` lists menus/args (path + attribute validity); `completion`
|
|
63
|
+
+ `input=` yields allowed values (tips/warnings); `highlight` + `input=` is
|
|
64
|
+
token-level error detection (lsp's primary validator); `syntax` is the parse
|
|
65
|
+
tree.
|
|
66
|
+
|
|
67
|
+
## Query mapping (centrs `--query` / `--raw-query`)
|
|
68
|
+
|
|
69
|
+
Two layers, both grounded above:
|
|
70
|
+
|
|
71
|
+
- **`--query` / `--filter`** — structured convenience, **AND-combined** (RouterOS
|
|
72
|
+
default), repeatable. Each maps to one query word:
|
|
73
|
+
- `name=value` → `name=value` (eq)
|
|
74
|
+
- `name!=value` → two words `name=value`, `#!` (eq then NOT-top)
|
|
75
|
+
- `name>value` / `name<value` → `>name=value` / `<name=value`
|
|
76
|
+
- `name` (no `=`) → `name` (has-property)
|
|
77
|
+
Multiple `--query` emit their words in order; RouterOS's implicit AND applies (no
|
|
78
|
+
`#&` injected). `--query` cannot express OR or arbitrary stacks — that is
|
|
79
|
+
`--raw-query`.
|
|
80
|
+
- **`--raw-query <word>`** — power-user escape hatch, repeatable. Each value is a
|
|
81
|
+
**verbatim RouterOS query word** (absence `-name`, OR `#|`, AND `#&`, NOT `#!`,
|
|
82
|
+
or any `>`/`<`/`=` form). Emitted as-is: a `.query` element over REST, a
|
|
83
|
+
`?`-prefixed word over native. centrs does **not** parse or reorder raw words —
|
|
84
|
+
the caller owns the stack. When `--query` and `--raw-query` are both given, the
|
|
85
|
+
structured words are emitted first, then the raw words, in order.
|
|
86
|
+
|
|
87
|
+
This keeps the common case (`--query type=ether`) clean while giving full
|
|
88
|
+
stack-language access without centrs modelling the whole query grammar.
|
|
89
|
+
|
|
90
|
+
## CONFIRMED ON CHR (Phase 0 grounding spikes — CHR 7.23.1, 2026-06-29)
|
|
91
|
+
|
|
92
|
+
Probes: scratchpad `api-grounding-spike.ts` (listen/CRUD/execute) and
|
|
93
|
+
`api-grounding-spike2.ts` (duration/monitor + query forms + inspect path). Raw
|
|
94
|
+
native codec for the listen probe; `fetch` for REST; `connectNativeApi().talk()`
|
|
95
|
+
for native.
|
|
96
|
+
|
|
97
|
+
- **CONFIRMED — native `/listen` + `/cancel`.** `/ip/address/listen .tag=2`
|
|
98
|
+
emits an `!re tag=2` carrying the **full new record** on add; on delete it emits
|
|
99
|
+
a **minimal** `!re tag=2 { ".id":"*3", ".dead":"true" }`. **Correction to the
|
|
100
|
+
docs:** the wire flag is **`.dead=true`**, not `=.dead=yes`. `/cancel =tag=2
|
|
101
|
+
.tag=3` yields `!trap tag=2 {category:2, message:interrupted}`, then `!done
|
|
102
|
+
tag=3` (the cancel) **and** `!done tag=2` (the listen closes). Modern plaintext
|
|
103
|
+
`/login` returns a bare `!done` (no challenge) on 7.23.1.
|
|
104
|
+
- **CONFIRMED — native CRUD by id.** `add` returns the new id in `=ret=`; `set`
|
|
105
|
+
and `remove` via `=.id=<id>` succeed with zero `!re` (only `!done`); get-one is
|
|
106
|
+
`print ?.id=<id>` → exactly one record. `numbers=` is **not** needed (that is
|
|
107
|
+
execute's CLI idiom, not the API's).
|
|
108
|
+
- **CONFIRMED — query + proplist.** Native `/interface/print ?type=ether` filters
|
|
109
|
+
server-side; `=.proplist=address,interface` returns only those keys. REST
|
|
110
|
+
`POST /rest/interface/print {".query":["type=ether"],".proplist":"name,type"}`
|
|
111
|
+
→ `200 [{"name":"ether1","type":"ether"}]` (a complete **array**, not a stream).
|
|
112
|
+
- **CONFIRMED — duration/monitor returns a bounded `.section` array (both
|
|
113
|
+
transports).** `POST /rest/interface/monitor-traffic {interface:ether1,
|
|
114
|
+
duration:"5s"}` → `200`, ~5.0 s, a **JSON array of 5 records** keyed
|
|
115
|
+
`.section:"0".."4"` (each `…-bits-per-second` etc.). The native
|
|
116
|
+
`/interface/monitor-traffic duration=2s` returns the same frames via `talk()` (2
|
|
117
|
+
records, each with `.section`) before `!done`. So duration commands are ordinary
|
|
118
|
+
bounded calls, **not** NDJSON. (Corrects the first spike's over-strong "REST does
|
|
119
|
+
not support continuous commands" reading; the `/rest/interface/monitor` 400 there
|
|
120
|
+
was just a wrong command path — `monitor-traffic` is the real menu.) The 60 s
|
|
121
|
+
unbounded cap stays doc-grounded — not worth a 60 s CI wait to reproduce.
|
|
122
|
+
- **CONFIRMED — `/console/inspect` `path` is array-typed; pass COMMA tokens, not a
|
|
123
|
+
slash command.** `request=child path=system,license` → children `license:dir,
|
|
124
|
+
export:cmd, generate-new-id:cmd, get:cmd, print:cmd, renew:cmd`; the **slash**
|
|
125
|
+
forms (`system/license`, `/system/license`) return **nothing**. (This is why the
|
|
126
|
+
first spike's license probe was empty — wrong path form.) **Why (rationale, not
|
|
127
|
+
yet re-spiked):** RouterOS's inspect `path` argument is internally an **array**
|
|
128
|
+
type — a comma string like `ip,address,set` is `:toarray`-split into
|
|
129
|
+
`["ip","address","set"]`, the menu walk inspect expects. A `/`-prefixed
|
|
130
|
+
command-style string is **not** an array and isn't split, so it matches no menu.
|
|
131
|
+
A JSON **array** body over REST (`{"path":["ip","address"]}`) should therefore
|
|
132
|
+
work too — **plausible but unconfirmed; do not rely on it.** The contract centrs
|
|
133
|
+
uses is the **comma-joined string** (confirmed; what both `retrieve` and
|
|
134
|
+
`execute` already build via their duplicate `join(",")` helpers — the Phase-1
|
|
135
|
+
`src/core/inspect.ts` is the single home for it). And **`renew` is a `cmd`
|
|
136
|
+
node** → `/system/license/renew` is a write/command, confirming the "streaming ≠
|
|
137
|
+
read-only" rule concretely.
|
|
138
|
+
- **CONFIRMED — `.query` forms (REST word == native `?`-word minus `?`).** On
|
|
139
|
+
`/interface/print` (2 interfaces: ether, loopback): eq `type=ether` → 1; ne
|
|
140
|
+
`["type=ether","#!"]` → loopback; OR `["type=ether","type=loopback","#|"]` → 2;
|
|
141
|
+
gt `[">actual-mtu=1000"]` → 2; has `["running"]` → 2; absence `["-running"]` →
|
|
142
|
+
0; implicit-AND `["type=ether","running"]` → 1. Identical results native
|
|
143
|
+
(`?`-prefixed). REST license singleton: `GET /rest/system/license` →
|
|
144
|
+
`{"level":"free","system-id":"…"}`.
|
|
145
|
+
- **CONFIRMED — `POST /rest/execute` modes.** With `"as-string":""` it runs
|
|
146
|
+
**synchronously** → `{"ret":"CHR"}`. **Without** `as-string` it is **fire-and-
|
|
147
|
+
forget**: it schedules a job and returns the **job id** (`{"ret":"*18"}`, HTTP
|
|
148
|
+
200) — a `:error` does **not** surface synchronously. ⇒ `api`'s script-POST must
|
|
149
|
+
send `as-string` (reuse execute's `restPost("/execute",{script,"as-string":""})`,
|
|
150
|
+
`adapter.ts:199`) so output/errors are synchronous; the bare async-job form is
|
|
151
|
+
not the default.
|
|
152
|
+
- **RESOLVED — `/system/license`** (was inconclusive in spike 1 due to the slash
|
|
153
|
+
path; see the COMMA-path finding above). Independently, `isApiMutating` keys the
|
|
154
|
+
write gate on the method/verb, so even an async/streamable write like
|
|
155
|
+
`/system/license/renew` confirms regardless.
|
|
156
|
+
|
|
157
|
+
## CONFIRMED ON CHR (Phase 2+3 integration — CHR 7.23.1, 2026-06-29)
|
|
158
|
+
|
|
159
|
+
Validated by `test/integration/api.test.ts` (rest, examples 1–20, 98 assertions)
|
|
160
|
+
and `test/integration/api-native.test.ts` (native, N1–N8, 31 assertions), both
|
|
161
|
+
green on CHR 7.23.1.
|
|
162
|
+
|
|
163
|
+
- **CONFIRMED — native `/execute` supports `=as-string=` for synchronous output.**
|
|
164
|
+
Native `talk /execute =script=…` **without** `as-string` is **fire-and-forget**:
|
|
165
|
+
it schedules a job and returns the **job id** in `=ret=` (observed `"*31"`), not
|
|
166
|
+
the script output — exactly the REST behavior. Adding `=as-string=` (empty value)
|
|
167
|
+
makes it run synchronously and return the captured output, identical to REST
|
|
168
|
+
`/rest/execute {as-string}`. **This corrects `src/execute.ts`'s long-standing
|
|
169
|
+
assumption that native script mode is unsupported** (execute still blocks it; the
|
|
170
|
+
`api` path proves native `/execute =as-string=` works). `api` always sends
|
|
171
|
+
`as-string` for a script run on both transports.
|
|
172
|
+
- **CONFIRMED — native CRUD re-mapping to rest-style.** `add` returns the new id
|
|
173
|
+
only in the `!done` `=ret=` word → centrs re-maps it to `{".id": ret}` (REST PUT
|
|
174
|
+
returns the full created object with `.id`; native gives just the id). `set`/
|
|
175
|
+
`remove` reply with a bare `!done` (no `!re`) → no body → centrs surfaces `null`.
|
|
176
|
+
get-one is `print ?.id=<id>` → exactly one record → returned as a single object
|
|
177
|
+
(not a 1-element array). All grounded in `restStyleMutationData` /
|
|
178
|
+
`restStyleRunData` (`src/protocols/adapter.ts`).
|
|
179
|
+
- **CONFIRMED — REST `monitor-traffic` over the `api` path returns a `.section`
|
|
180
|
+
array.** `POST /rest/interface/monitor-traffic {interface,duration}` →
|
|
181
|
+
`[{".section":"0",…}, {".section":"1",…}]` (re-confirms the spike-2 finding via
|
|
182
|
+
the real `api` REST adapter). It is a command (terminal verb `monitor-traffic`,
|
|
183
|
+
not `print`/`get`), so it is a `POST` and **write-classed** → needs `--yes`.
|
|
184
|
+
- **CONFIRMED — the inspect gate over both transports.** Path existence
|
|
185
|
+
(`request=child` empty ⇒ `validation/unknown-path`) and add/set attribute
|
|
186
|
+
validity (`request=child`+`completion` ⇒ `validation/unknown-attribute`) fire
|
|
187
|
+
identically over rest-api and native-api, **before** any write — no `:put
|
|
188
|
+
[:parse]` involved (api input is a path, not a CLI string). A `/execute` script
|
|
189
|
+
is a CLI string ⇒ `meta.validation.semantic = "not-applicable"`.
|
|
190
|
+
|
|
191
|
+
## CONFIRMED ON CHR (Phase 4 `--stream` follow — CHR 7.23.1, 2026-06-30)
|
|
192
|
+
|
|
193
|
+
Validated by `test/integration/api-listen.test.ts` (L1–L4) on CHR 7.23.1.
|
|
194
|
+
`--stream` is the primary flag; `--listen` is an alias; a `/listen` endpoint
|
|
195
|
+
infers both. Native-api only (REST's 60s cap → `transport/capability-unsupported`).
|
|
196
|
+
|
|
197
|
+
- **CONFIRMED — `NativeApiSession.listen()` + `/cancel` end-to-end.** Opening
|
|
198
|
+
`/ip/address/listen` (own `.tag`) and adding an address over REST yields an
|
|
199
|
+
`!re` frame; stopping (`--count`/`--duration`/Ctrl-C) sends `/cancel =tag=<t>
|
|
200
|
+
.tag=<n>` and the listen closes via the `interrupted` trap (category 2) then
|
|
201
|
+
`!done` — the generator ends **without** throwing (the interrupted trap is the
|
|
202
|
+
normal cancel, not an error). A non-interrupted trap or transport closure does
|
|
203
|
+
throw. Re-confirms the Phase-0 spike through the real `apiListen()` path.
|
|
204
|
+
- **CONFIRMED — deletion frames carry `.dead=true`.** Removing a pre-seeded
|
|
205
|
+
address over REST while listening emits a minimal `{ ".id", ".dead":"true" }`
|
|
206
|
+
`!re` (re-confirms `.dead=true`, not the docs' `=.dead=yes`).
|
|
207
|
+
- **Stream shape:** each `!re` → one NDJSON envelope frame
|
|
208
|
+
(`meta.operation.stream.kind="frame"`, string values, `.dead` preserved); the
|
|
209
|
+
stream ends with a summary envelope (`stream.kind="summary"`,
|
|
210
|
+
`data.stopReason` ∈ `count-reached`/`duration-elapsed`/`interrupted`/
|
|
211
|
+
`transport-error`, plus `frames`/`durationMs`). The CLI exit code keys on
|
|
212
|
+
whether the stream *started* cleanly, not on per-frame `ok`.
|