@tikoci/centrs 0.1.0 → 0.1.2

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +73 -2
  2. package/GLOSSARY.txt +16 -0
  3. package/README.md +1 -1
  4. package/commands/AGENTS.md +3 -2
  5. package/commands/api/AGENTS.md +212 -0
  6. package/commands/api/README.md +159 -0
  7. package/commands/api/examples.md +325 -0
  8. package/commands/btest/README.md +56 -31
  9. package/commands/btest/examples.md +35 -10
  10. package/commands/devices/README.md +26 -0
  11. package/commands/discover/README.md +17 -8
  12. package/commands/discover/examples.md +10 -5
  13. package/commands/execute/README.md +9 -3
  14. package/commands/retrieve/README.md +1 -1
  15. package/docs/CONSTITUTION.md +6 -6
  16. package/docs/MATRIX.md +46 -15
  17. package/docs/errors/routeros/btest-connection-count-single-stream.md +22 -0
  18. package/docs/errors/routeros/btest-udp-tx-size-ignored.md +15 -0
  19. package/docs/errors/usage/invalid-method.md +23 -0
  20. package/package.json +5 -5
  21. package/src/api.ts +1555 -0
  22. package/src/btest.ts +61 -1
  23. package/src/cli/api.ts +536 -0
  24. package/src/cli/btest.ts +45 -1
  25. package/src/cli/common.ts +64 -0
  26. package/src/cli/discover.ts +26 -0
  27. package/src/cli/execute.ts +30 -4
  28. package/src/cli/missing-target.ts +171 -0
  29. package/src/cli/retrieve.ts +32 -14
  30. package/src/cli/terminal.ts +18 -4
  31. package/src/cli/transfer.ts +37 -9
  32. package/src/cli.ts +22 -0
  33. package/src/core/error-catalog.ts +14 -0
  34. package/src/core/fanout.ts +160 -0
  35. package/src/core/inspect.ts +170 -0
  36. package/src/discover.ts +96 -23
  37. package/src/execute.ts +14 -47
  38. package/src/index.ts +61 -8
  39. package/src/protocols/adapter.ts +328 -0
  40. package/src/protocols/btest-session.ts +279 -46
  41. package/src/protocols/btest.ts +6 -2
  42. package/src/protocols/native-api.ts +223 -30
  43. package/src/retrieve-fanout.ts +13 -141
  44. package/src/retrieve.ts +30 -87
  45. package/commands/stream/README.md +0 -70
@@ -52,6 +52,12 @@ Status: `rest-api`, `native-api`, `mac-telnet`, and `ssh` are `CHR-passed` (see
52
52
  else passes its `detail`/body plus the HTTP status through
53
53
  `mapRouterOsError({ transport: "rest-api", httpStatus })`. There is no longer
54
54
  a hand-rolled `routeros/*` table on the REST side.
55
+ - **Run-time fault mapping — later (JG-28).** `mapRouterOsError`'s table covers
56
+ the faults seen so far; a known gap is errors that *pass* `:parse`/validation
57
+ but fail at **run time** (a syntactically valid command the router rejects when
58
+ executed). `tikoci/vscode-tikbook` has an error-string mapping for these worth
59
+ porting into `src/core/routeros-errors.ts`. Background research, not yet
60
+ scheduled — captured here so it isn't lost (no GitHub issue by design).
55
61
  - Output is *string*-shaped for script-mode; richer parsing is a future
56
62
  concern. The envelope must still distinguish RouterOS errors from successful
57
63
  runs (a 200 with a RouterOS error string is still an error — see
@@ -195,9 +201,9 @@ Protocol selection: an unresolved MAC target auto-selects mac-telnet (covered by
195
201
  ## Open shape questions
196
202
 
197
203
  - ~~How to expose multi-line / async / progress output~~ — **resolved:**
198
- open-ended follow/streaming reads are the separate read-only `stream` verb
199
- (NDJSON stream of envelopes; see `commands/stream/README.md`). `execute` stays
200
- single-shot read/write; it does not grow a follow mode.
204
+ open-ended follow/streaming reads are `api … --stream` (NDJSON stream of
205
+ envelopes; see `commands/api/README.md`). `execute` stays single-shot
206
+ read/write; it does not grow a follow mode.
201
207
  - ~~Whether `--strict` should reject "succeeded with stderr-like content."~~ —
202
208
  **decided:** `--strict` promotes any warning on an otherwise-successful run to
203
209
  `ok: false` (nonzero exit), like `-Werror`; the default stays lenient
@@ -45,7 +45,7 @@ centrs retrieve <router> snmp <oid|MIB name> [flags]
45
45
  | `--attributes <a,b,c>` | Alias for `--attribute` with comma-separated input. |
46
46
  | `--all-attributes` | Equivalent to RouterOS `details=true`. Mutually exclusive with `--attribute(s)`. |
47
47
  | `--list-attributes` (alias `--list`) | Return the available attribute names for the path. No `print`/`get` is run. |
48
- | `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is the separate `stream` verb. See constitution: protocol selection. |
48
+ | `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `api … --stream`. See constitution: protocol selection. |
49
49
  | `--query <expr>` | RouterOS-side **row** filter (maps to `.query`), repeatable; returns matching rows as an **array**. This is how you read one row by name (`--query name=ether1`). **Not Implemented** yet — returns `validation/not-implemented`. |
50
50
  | `--filter <expr>` | RouterOS row filter; same `.query` mapping and not-implemented handling as `--query`. |
51
51
  | `--where <attr>=<value>` | Device-class selector: fan out across CDB records whose stored fact/comment-kv matches (e.g. `--where board=RB5009`). Repeatable (AND). Filters *which devices*, not RouterOS rows. See constitution: target selection. |
@@ -228,8 +228,8 @@ repeats. The union is **de-duped by CDB record index** and run through the same
228
228
  per-target pipeline; the `data` array is reassembled in resolved record order,
229
229
  not completion order, so repeated runs diff cleanly. Destructive multi-target
230
230
  operations (e.g. `devices remove` across a group) require `--force`. `terminal`
231
- and single-session `stream` are not fan-out surfaces and reject N>1 with
232
- `usage/fanout-not-supported`.
231
+ and a single-session `api --stream` follow are not fan-out surfaces and reject
232
+ N>1 with `usage/fanout-not-supported`.
233
233
 
234
234
  `--where` filters *which devices* by CDB-stored facts; keep it distinct from
235
235
  `retrieve`/`execute`'s `--query`/`--filter`, which filter *RouterOS rows* in the
@@ -266,7 +266,7 @@ Per-operation preferences, downgrade order in parens:
266
266
  | Operation | Preferred | Downgrade order |
267
267
  | --------- | --------- | --------------- |
268
268
  | retrieve | rest-api, native-api; snmp for OID/MIB reads (future) | rest-api, native-api |
269
- | stream | native-api, ssh | (REST cannot follow — 60s cap; bounded or rejected) |
269
+ | api | rest-api, native-api; `--stream` follow native-api only | rest-api, native-api (no silent downgrade) |
270
270
  | execute | native-api → rest-api → mac-telnet | native-api, rest-api, mac-telnet, ssh, romon, winbox-terminal |
271
271
  | terminal | ssh | mac-telnet (L2 only when ssh fails or MAC given) |
272
272
  | transfer | size/direction-aware: rest-api/native-api for ≤60 KB writes & all reads (chunked), sftp for large uploads | sftp ⇄ rest/native by size; scp · fetch · ftp explicit-only |
@@ -288,9 +288,9 @@ Rules:
288
288
  access needs explicit ARP opt-in.
289
289
  - **`measure` (btest) is explicit-only** — never in the auto-select/downgrade
290
290
  chain, never a substitute, and no other command rides it.
291
- - Per-command selection **depth** lives in the command READMEs: `stream`'s
292
- follow/NDJSON contract and the REST-cannot-follow constraint
293
- (`commands/stream/README.md`), `transfer`'s size/direction-aware method
291
+ - Per-command selection **depth** lives in the command READMEs: `api`'s
292
+ `--stream` follow/NDJSON contract and the REST-cannot-follow constraint
293
+ (`commands/api/README.md`), `transfer`'s size/direction-aware method
294
294
  selection and the explicit-only `scp`/`ftp`/`fetch`
295
295
  (`commands/transfer/README.md`), SNMP's MIB-cache
296
296
  (`commands/retrieve/README.md`), and btest's port/auth
package/docs/MATRIX.md CHANGED
@@ -24,7 +24,7 @@ A cell advances only with the matching evidence in the same change.
24
24
  | Command | rest-api | native-api | ssh | mac-telnet | snmp | mndp | romon | winbox-terminal |
25
25
  | -------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ---------------- |
26
26
  | retrieve | `CHR-passed` | `CHR-passed` | — | — | `not-started` | — | — | — |
27
- | stream | — | `designed` | `designed` | — | — | — | — | — |
27
+ | api | `CHR-passed` | `CHR-passed` | — | — | — | — | — | — |
28
28
  | execute | `CHR-passed` | `CHR-passed` | `CHR-passed` | `CHR-passed` | — | — | `not-started` | `not-started` |
29
29
  | terminal | — | — | `CHR-passed` | `CHR-passed` | — | — | — | — |
30
30
  | transfer | `CHR-passed` | `CHR-passed` | `CHR-passed` | — | — | — | — | — |
@@ -56,13 +56,26 @@ verbose REST `board-name`), and the L2 validation policy are documented in
56
56
  `commands/discover/README.md`.
57
57
 
58
58
  There is no `update` command: `execute` is the single read/write surface for
59
- RouterOS add/set/remove, and `retrieve` stays read-only. See
60
- `docs/CONSTITUTION.md` (protocol selection).
61
-
62
- `stream` is `designed` (no code yet): the read-only follow surface (NDJSON
63
- stream-of-envelopes; native-api/ssh only — REST's 60s cap cannot follow) is
64
- specified in `commands/stream/README.md`. The native-api streaming reader it will
65
- consume already exists (`src/protocols/native-api.ts`).
59
+ RouterOS *CLI-shaped* add/set/remove, and `retrieve` stays read-only. The `api`
60
+ command is the structured one-command-per-operation surface (the verb trichotomy
61
+ in `commands/api/README.md`); it can also write. See `docs/CONSTITUTION.md`
62
+ (protocol selection).
63
+
64
+ `api` is `CHR-passed` for `rest-api` and `native-api` (single-target): endpoint
65
+ normalization, the method→verb map, the `/console/inspect` gate, write
66
+ confirmation, `.query`/`.proplist`, `-f`/`-d`/`--input` bodies, `--raw`,
67
+ `/execute` script-run (`=as-string=` for sync output on both transports), and
68
+ open-ended `--stream` follow (native-api only — `NativeApiSession.listen()` +
69
+ `/cancel`, NDJSON envelope frames with a terminating summary; `--via rest-api
70
+ --stream` errors `transport/capability-unsupported`), green on CHR 7.23.1 via
71
+ `test/integration/api.test.ts` + `api-native.test.ts` + `api-listen.test.ts`.
72
+ Still deferred (own phase): multi-target fan-out (`--stream` is single-session).
73
+
74
+ `api` **absorbs** the former `stream` command (folded — no separate row): the
75
+ read-only follow surface is `api <router> <endpoint> --stream` (alias `--listen`;
76
+ the `/listen` endpoint form infers it), native-api only, since REST's 60s cap
77
+ cannot follow. `centrs stream`/`centrs tail` now error with a pointer to
78
+ `api --stream`.
66
79
 
67
80
  `transfer` is `CHR-passed` for `rest-api`/`native-api` and for **sftp** (the
68
81
  `ssh` column's first method): `src/transfer.ts` + `src/cli/transfer.ts`
@@ -156,7 +169,7 @@ core and carry their own state, tracked here:
156
169
 
157
170
  | Surface | State | Spec | Notes |
158
171
  | ------- | ----- | ---- | ----- |
159
- | api (TS) | `CHR-passed` | `src/index.ts` | Root surface; everything else adapts it. |
172
+ | ts-api | `CHR-passed` | `src/index.ts` | Root **TypeScript library** surface; everything else adapts it. (Renamed from `api (TS)` to avoid colliding with the `api` **command** row in the grid above — the command is a verb, this is the library.) |
160
173
  | cli | `coded` | `src/cli/` | `retrieve`/`execute`/`transfer`/`terminal`/`devices`/`discover`/`btest` wired. |
161
174
  | mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; CDB-as-allowlist safety model. Phase 1 (explain/validate/retrieve/execute, resources) plus first Phase 2 CDB mutation (`centrs_devices add`) green on CHR 7.23 via `test/integration/mcp.test.ts`, including gated write execution. |
162
175
  | tui | `not-started` | `src/tui.ts` | Stub. |
@@ -186,12 +199,30 @@ The wire codec, the EC-SRP5 session/handshake (shared core byte-identical with
186
199
  mac-telnet's MTWEI, plus the net-new server role), the TCP/UDP data engines, the
187
200
  orchestrator + CLI, and the honest grounding caveat (the server gets direct CHR
188
201
  evidence over the QEMU SLIRP gateway `10.0.2.2`; the client gets direct CHR
189
- evidence over a host→guest `tcp:2000` forward — TCP only; UDP-receive is a soft
190
- smoke test and UDP client→CHR-server stays loopback/transitive over the SLIRP
191
- reverse path; TCP `connection-count > 1` fan-out is deferred; NDJSON is not
192
- adopted) are documented in `commands/btest/README.md` and the
193
- `src/protocols/btest.ts`, `src/protocols/btest-session.ts`, and
194
- `src/protocols/ec-srp5.ts` module headers.
202
+ evidence over a host→guest `tcp:2000` forward (the forward carries TCP; UDP rides
203
+ the guest→host gateway instead, below); UDP-receive was
204
+ previously a soft smoke test because the SLIRP reverse path was believed to block
205
+ real validation and a `connect()` socket filter was silently dropping all received
206
+ datagrams — RouterOS sends UDP data **from a different source port** than the
207
+ negotiated `serverUdpPort`, so the BSD receive filter rejected every packet;
208
+ fixed by removing `connect()` and addressing sends explicitly, validated against
209
+ a real RouterOS device at 163–203 Mbps receive and 104 Mbps bidirectional, and
210
+ **CI now gates UDP client receive/both** — the server→client return rides the
211
+ guest→host SLIRP gateway (`10.0.2.2:clientUdpPort`), needing no UDP forward and no
212
+ quickchr change (#88 closed; the `connect()` filter fix in #86 is what enabled it);
213
+ TCP
214
+ `connection-count` reaches the server's command packet (#84) and centrs opens the
215
+ negotiated extra TCP data connections — multi-connection **fan-out** (#87),
216
+ grounded byte-for-byte against RouterOS 7.23.1 (secondary join
217
+ `[token:u16 BE][0x02][0 …]`) and CHR-gated for the realized fan-out
218
+ (`activeConnections == count`; no throughput-rise assertion — the SLIRP loopback is
219
+ bandwidth-bound); authenticated sessions stay single-stream (warned); TCP
220
+ `direction=both` demuxes the server's
221
+ interleaved status frames so the client paces its TX from feedback and does not
222
+ starve server→client RX (#85 fixed); the Windows unit tier skips UDP-loopback
223
+ tests (#69); NDJSON is not adopted) are
224
+ documented in `commands/btest/README.md` and the `src/protocols/btest.ts`,
225
+ `src/protocols/btest-session.ts`, and `src/protocols/ec-srp5.ts` module headers.
195
226
 
196
227
  ## Priority order
197
228
 
@@ -0,0 +1,22 @@
1
+ # `routeros/btest-connection-count-single-stream`
2
+
3
+ A **warning** (not a failure): `centrs btest client --protocol tcp
4
+ --connection-count <n>` opened **fewer** TCP data connections than `n`. centrs
5
+ does implement the multi-connection fan-out — it opens the extra connections that
6
+ join the test with the server-negotiated session token — but it falls back to a
7
+ single stream when the server negotiates **no** token. The common case is an
8
+ **authenticated (EC-SRP5)** session: centrs does not yet capture the post-auth
9
+ session token, so authenticated tests stay single-stream. A secondary connection
10
+ that fails mid-handshake also reduces the realized count.
11
+
12
+ The warning reports both the requested and the actually-active connection count.
13
+
14
+ ## Workaround
15
+
16
+ - For full multi-stream throughput, run the test **unauthenticated**
17
+ (`/tool/bandwidth-server` with `authenticate=no`), where centrs negotiates the
18
+ token and opens all `n` connections.
19
+ - Or run several `centrs btest client` processes in parallel and sum their
20
+ throughput.
21
+
22
+ See `docs/CONSTITUTION.md` for the centrs error/warning contract.
@@ -0,0 +1,15 @@
1
+ # `routeros/btest-udp-tx-size-ignored`
2
+
3
+ A non-fatal warning. For UDP `--direction both`, the btest wire protocol carries
4
+ a **single** `tx-size` field, so the client and the server transmit with the
5
+ same packet size. centrs maps `--local-udp-tx-size` to that field and discards
6
+ `--remote-udp-tx-size`. This warning fires only when both flags are set to
7
+ different values, so you know which size the session actually used.
8
+
9
+ ## Fix
10
+
11
+ Drop `--remote-udp-tx-size` for `--direction both`, or set both flags to the
12
+ same value. To size each direction independently, run two sessions:
13
+ `--direction transmit` (uses `--local-udp-tx-size`) and `--direction receive`
14
+ (uses `--remote-udp-tx-size`). See `commands/btest/README.md` and
15
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md).
@@ -0,0 +1,23 @@
1
+ # `usage/invalid-method`
2
+
3
+ The -X/--method value is not a supported HTTP method.
4
+
5
+ ## Fix
6
+
7
+ `centrs api` honors the HTTP method (`-X` / `--method`) literally against
8
+ RouterOS's REST verb map. The accepted methods are `GET`, `PUT`, `PATCH`,
9
+ `DELETE`, and `POST` (case-insensitive):
10
+
11
+ | Method | RouterOS verb |
12
+ | ------ | -------------------- |
13
+ | `GET` | print / get |
14
+ | `PUT` | add (RouterOS create) |
15
+ | `PATCH`| set |
16
+ | `DELETE`| remove |
17
+ | `POST` | run a command |
18
+
19
+ Pass one of those with `-X`, or omit the flag to use the default `GET`. Note the
20
+ trap: **`PUT` is RouterOS's create**, not `POST`.
21
+
22
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
23
+ contract.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/centrs",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "main": "src/index.ts",
5
5
  "module": "src/index.ts",
6
6
  "repository": {
@@ -12,7 +12,7 @@
12
12
  "packageManager": "bun@1.3.13",
13
13
  "devDependencies": {
14
14
  "@agent-lint/cli": "^0.8.1",
15
- "@biomejs/biome": "2.5.0",
15
+ "@biomejs/biome": "2.5.1",
16
16
  "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
17
17
  "@tikoci/quickchr": "^0.4.2",
18
18
  "@types/bun": "^1.3.13",
@@ -57,11 +57,11 @@
57
57
  "scripts": {
58
58
  "ci": "bun run lint && bun run test && bun run build",
59
59
  "git:pre-commit": "bun run lint",
60
- "git:pre-push": "bun run ci",
60
+ "git:pre-push": "bun run lint:git:push",
61
61
  "lint": "bun run lint:biome && bun run lint:typecheck",
62
62
  "lint:git:commit": "bun run lint",
63
- "lint:git:push": "bun run ci",
64
- "lint:ci": "bun run lint && bun run lint:markdownlint && bun run lint:cspell && bun run lint:secretlint && bun run lint:agents",
63
+ "lint:git:push": "bun run lint:ci && bun run test && bun run build",
64
+ "lint:ci": "bun run lint && bun run lint:markdownlint && bun run lint:cspell && bun run lint:secretlint",
65
65
  "lint:cspell": "cspell lint . --gitignore",
66
66
  "lint:cspell:all": "cspell lint . --no-gitignore",
67
67
  "lint:markdownlint": "markdownlint-cli2 \"**/*.md\" \"!.scratch/**\"",