@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +107 -2
  2. package/GLOSSARY.txt +87 -0
  3. package/README.md +25 -9
  4. package/SECURITY.md +3 -3
  5. package/commands/AGENTS.md +3 -2
  6. package/commands/api/AGENTS.md +212 -0
  7. package/commands/api/README.md +177 -0
  8. package/commands/api/examples.md +428 -0
  9. package/commands/btest/README.md +56 -31
  10. package/commands/btest/examples.md +35 -10
  11. package/commands/check/README.md +371 -53
  12. package/commands/check/examples.md +390 -0
  13. package/commands/devices/README.md +177 -37
  14. package/commands/devices/examples.md +148 -0
  15. package/commands/execute/README.md +48 -7
  16. package/commands/execute/examples.md +90 -6
  17. package/commands/retrieve/README.md +37 -51
  18. package/commands/retrieve/examples.md +40 -9
  19. package/commands/settings/README.md +286 -22
  20. package/commands/settings/examples.md +408 -0
  21. package/commands/terminal/README.md +6 -0
  22. package/commands/transfer/README.md +32 -2
  23. package/commands/transfer/examples.md +66 -0
  24. package/docs/CONSTITUTION.md +40 -15
  25. package/docs/MATRIX.md +69 -142
  26. package/docs/errors/README.md +2 -1
  27. package/docs/errors/cdb/empty-selection.md +19 -0
  28. package/docs/errors/input/incomplete-gps.md +9 -0
  29. package/docs/errors/input/invalid-altitude.md +9 -0
  30. package/docs/errors/input/invalid-bbox.md +9 -0
  31. package/docs/errors/input/invalid-coordinate.md +9 -0
  32. package/docs/errors/input/invalid-radius.md +9 -0
  33. package/docs/errors/internal/settings-failed.md +9 -0
  34. package/docs/errors/routeros/btest-connection-count-single-stream.md +22 -0
  35. package/docs/errors/routeros/btest-udp-tx-size-ignored.md +15 -0
  36. package/docs/errors/settings/consequential-value.md +26 -0
  37. package/docs/errors/settings/invalid-boolean.md +21 -3
  38. package/docs/errors/settings/invalid-format.md +18 -3
  39. package/docs/errors/settings/invalid-integer.md +18 -3
  40. package/docs/errors/settings/invalid-timeout.md +20 -3
  41. package/docs/errors/settings/invalid-via.md +22 -3
  42. package/docs/errors/settings/reserved-key.md +29 -0
  43. package/docs/errors/settings/skip-env-file-active.md +25 -0
  44. package/docs/errors/settings/unknown-key.md +25 -0
  45. package/docs/errors/usage/fanout-not-supported.md +16 -0
  46. package/docs/errors/usage/invalid-method.md +23 -0
  47. package/package.json +9 -9
  48. package/src/api-fanout.ts +454 -0
  49. package/src/api.ts +1627 -0
  50. package/src/btest.ts +61 -1
  51. package/src/cli/api.ts +660 -0
  52. package/src/cli/btest.ts +20 -2
  53. package/src/cli/common.ts +75 -0
  54. package/src/cli/devices.ts +235 -6
  55. package/src/cli/discover.ts +3 -1
  56. package/src/cli/execute.ts +107 -6
  57. package/src/cli/mcp.ts +8 -1
  58. package/src/cli/retrieve.ts +135 -90
  59. package/src/cli/selection.ts +200 -0
  60. package/src/cli/settings.ts +286 -0
  61. package/src/cli/terminal.ts +22 -7
  62. package/src/cli/transfer.ts +171 -47
  63. package/src/cli.ts +29 -0
  64. package/src/core/error-catalog.ts +69 -0
  65. package/src/core/fanout.ts +301 -0
  66. package/src/core/inspect.ts +170 -0
  67. package/src/devices.ts +212 -20
  68. package/src/discover.ts +7 -2
  69. package/src/execute-fanout.ts +438 -0
  70. package/src/execute.ts +134 -70
  71. package/src/index.ts +166 -9
  72. package/src/mcp/config.ts +12 -2
  73. package/src/mcp/tools.ts +137 -2
  74. package/src/protocols/adapter.ts +357 -9
  75. package/src/protocols/btest-session.ts +279 -46
  76. package/src/protocols/btest.ts +6 -2
  77. package/src/protocols/index.ts +11 -5
  78. package/src/protocols/native-api.ts +223 -30
  79. package/src/resolver/cdb.ts +10 -2
  80. package/src/resolver/comment-kv.ts +58 -4
  81. package/src/resolver/config-file.ts +136 -0
  82. package/src/resolver/facts.ts +78 -0
  83. package/src/resolver/geo.ts +453 -0
  84. package/src/resolver/index.ts +46 -0
  85. package/src/resolver/mac.ts +12 -3
  86. package/src/resolver/selection.ts +304 -0
  87. package/src/resolver/settings.ts +54 -5
  88. package/src/resolver/target.ts +20 -2
  89. package/src/retrieve-fanout.ts +232 -268
  90. package/src/retrieve.ts +69 -94
  91. package/src/settings.ts +1078 -0
  92. package/src/terminal.ts +11 -1
  93. package/src/transfer-fanout.ts +527 -0
  94. package/src/transfer.ts +61 -11
  95. package/commands/stream/README.md +0 -70
@@ -17,7 +17,10 @@ on the centrs **server**; `test/integration/btest-client.test.ts` runs the centr
17
17
  **client** against a real RouterOS `/tool/bandwidth-server` over a host→guest
18
18
  `tcp:2000` forward — unauthenticated TCP receive, an **EC-SRP5 client proof
19
19
  verified by RouterOS's own verifier**, and a wrong-password reject. TCP
20
- multi-connection (`connection-count > 1`) data fan-out is a follow-up. See
20
+ multi-connection (`connection-count > 1`) data fan-out is implemented for
21
+ unauthenticated tests (the client joins the negotiated extra connections; the join
22
+ format is grounded against RouterOS 7.23.1); authenticated sessions stay
23
+ single-stream. See
21
24
  `docs/MATRIX.md` ("Peer measurement (`btest`)") for the cell states — that is the
22
25
  only status surface. v1 scope: **both** modes (client + server), **EC-SRP5 +
23
26
  unauthenticated** auth, **TCP and UDP** tests. Legacy pre-6.43 double-MD5 is out of
@@ -85,7 +88,7 @@ centrs btest server [--authenticate[=false]] [--user U] [--password P] \
85
88
  | `--direction <receive\|transmit\|both>` | `direction`, default `receive` | `receive` = download (server→client), `transmit` = upload. |
86
89
  | `--duration <dur>` | `duration` | Bounds the run; reuses `parseDuration`. Omit for open-ended (Ctrl-C). |
87
90
  | `--interval <dur>` | `interval`, default `1s` | Report cadence (`20ms`..`5s`). One report frame per interval. |
88
- | `--connection-count <n>` | `connection-count` (1..255) | **TCP only.** Parallel TCP data connections. |
91
+ | `--connection-count <n>` | `connection-count` (1..255) | **TCP only.** Parallel TCP data connections. centrs opens the negotiated extra connections (join format grounded against RouterOS 7.23.1) so throughput aggregates across streams. **Authenticated (EC-SRP5) sessions stay single-stream** — the post-auth token is not captured — and emit a `routeros/btest-connection-count-single-stream` warning. |
89
92
  | `--local-udp-tx-size <n>` | `local-udp-tx-size` (28..64000) | **UDP only.** Client→server packet size. |
90
93
  | `--remote-udp-tx-size <n>` | `remote-udp-tx-size` (28..64000) | **UDP only.** Server→client packet size. |
91
94
  | `--local-tx-speed <bps>` | `local-tx-speed` | Cap on client→server rate (bits/sec). |
@@ -120,10 +123,20 @@ test, data then flows on the server's allocated UDP ports
120
123
  from sequence gaps. For a TCP test, the bulk data rides `connection-count` TCP
121
124
  connections and goodput is measured on the data stream (TCP/IP headers excluded,
122
125
  matching RouterOS accounting). For multi-connection TCP the server's OK response
123
- carries a 2-byte **session token**; the extra connections reconnect and present
124
- that token to join the same test (no re-auth). Throughput and CPU are exchanged
126
+ carries a 2-byte **session token**; the client opens `connection-count − 1`
127
+ additional connections, each presenting a 16-byte join `[token:u16 BE][0x02][0 …]`
128
+ (grounded against RouterOS 7.23.1, direction-independent) to join the same test
129
+ without re-auth, and drives them into the shared counters so throughput aggregates.
130
+ Throughput and CPU are exchanged
125
131
  once per `interval` as a 12-byte **status message** on the TCP channel
126
- (`[0x07][0x80|cpu][00 00][seq u32 LE][bytesReceived u32 LE]`).
132
+ (`[0x07][0x80|cpu][00 00][seq u32 LE][bytesReceived u32 LE]`). The receiver of a
133
+ transmit (server for `transmit`, both peers for `both`) sends this so the
134
+ transmitter paces from the peer's `bytesReceived`. For `direction=both` the
135
+ status frames are **interleaved into the bulk data stream** (no length framing),
136
+ so the client demuxes them out of its receive loop by their structural marker —
137
+ without that feedback the client's TX saturates the link and starves
138
+ server→client RX. RouterOS clears the CPU high bit the centrs client sets, so the
139
+ marker matches on the two reserved zero bytes, not the high bit.
127
140
 
128
141
  ### Authentication (EC-SRP5)
129
142
 
@@ -141,7 +154,7 @@ will not offer EC-SRP5 against an `authenticate=yes` server) maps to
141
154
 
142
155
  btest runs over time, so it emits **live records** during the run plus a final
143
156
  **summary**. v1 keeps the formats simple and **defers a broader JSON-streaming
144
- decision** — it does *not* adopt the `stream` command's NDJSON-stream-of-envelopes
157
+ decision** — it does *not* adopt `api --stream`'s NDJSON-stream-of-envelopes
145
158
  contract yet (see Open questions):
146
159
 
147
160
  - **`text`** (default) — human-readable live reports during the run
@@ -214,10 +227,11 @@ Decided with the user. Both cells have direct gated `CHR-passed` evidence:
214
227
  maps onto the guest bandwidth server on 2000 (the same SLIRP inbound path that
215
228
  already carries REST/SSH — no firewall change). The centrs client dials
216
229
  `127.0.0.1:<host port>` and proves **TCP receive**, unauth and **EC-SRP5** (its
217
- client proof verified by RouterOS's real server verifier), plus a wrong-password
218
- reject. **TCP only** — UDP would need the server's datagrams to traverse SLIRP
219
- back to the host, which this forward does not provide, so UDP client→server stays
220
- loopback/transitive.
230
+ client proof verified by RouterOS's real server verifier), a wrong-password
231
+ reject, **TCP multi-connection fan-out** (`connection-count=4`), and **UDP
232
+ receive/both**. The UDP server→client return needs no UDP forward: it rides the
233
+ guest→host SLIRP gateway (`10.0.2.2:clientUdpPort`, the same path the server cell
234
+ uses), which works because the client socket is left unconnected (#86, #88).
221
235
 
222
236
  The fast/deterministic backstop under both is **centrs client ↔ centrs server
223
237
  loopback** (`127.0.0.1`, injected sockets) covering the full client↔server matrix,
@@ -226,32 +240,43 @@ EC-SRP5 both roles, and the option-grammar rejects.
226
240
  while implementing the EC-SRP5 framing and the server-side verifier; it is **not**
227
241
  an integration test (it cannot run in CI) and **not** part of the long-term plan.
228
242
 
229
- **Honest grounding caveat:** both cells are now directly CHR-passed. The remaining
230
- transitive-only paths are **UDP client→CHR-server** (the SLIRP reverse-NAT path the
231
- TCP forward does not cover) and TCP **multi-connection** fan-out — both deferred,
232
- not yet gated.
243
+ **Honest grounding caveat:** both cells are now directly CHR-passed, **including
244
+ TCP multi-connection fan-out** — `test/integration/btest-client.test.ts` gates a
245
+ centrs client opening `connection-count=4` against a real RouterOS
246
+ `/tool/bandwidth-server`; RouterOS accepts all 3 secondary joins and data flows on
247
+ every connection (`activeConnections: 4`). The *throughput increase* multiple
248
+ streams give is a WAN/latency property and is not observable over the near-zero-
249
+ latency SLIRP loopback, so the gate asserts the fan-out, not a higher number (the
250
+ per-connection drive is asserted deterministically by the loopback unit test).
251
+ **UDP client receive/both is now CHR-gated too** — the server→client return rides
252
+ the guest→host SLIRP gateway, no UDP forward needed (#88). The remaining fan-out
253
+ edge is **authenticated** multi-connection (capturing the EC-SRP5 post-auth token),
254
+ still single-stream.
233
255
 
234
256
  ## Open questions
235
257
 
236
- - **JSON streaming is deliberately deferred.** btest does not adopt the `stream`
237
- command's NDJSON-stream-of-envelopes contract yet; v1 streams `text` / `csv` and
238
- returns a single summary envelope for `json` / `yaml`. Revisit a streaming-JSON
239
- shape — ideally shared with `stream` — once that contract is settled.
240
- - **UDP receive/both through SLIRP is unproven.** Before the integration suite
241
- relies on server→guest UDP, a **one-shot CHR smoke test** must confirm the path
242
- works — i.e. that the RouterOS client originates an initial UDP/NAT probe that
243
- opens the SLIRP reverse mapping. Until then the gated UDP coverage is
244
- **transmit** (guest→host); TCP already covers both directions.
245
- - **UDP client→CHR-server and TCP multi-connection are not yet gated.** The direct
246
- client→CHR-server gate (`test/integration/btest-client.test.ts`) is **TCP only**;
247
- a UDP variant needs the server's datagrams to traverse SLIRP back to the host
248
- (the reverse-NAT path), and `connection-count > 1` needs the parallel-stream
249
- fan-out. Both remain loopback/transitive until gated. `btest.exe` (wine) is the
250
- coding-time grounding peer, not CI.
258
+ - **JSON streaming is deliberately deferred.** btest does not adopt
259
+ `api --stream`'s NDJSON-stream-of-envelopes contract yet; v1 streams `text` /
260
+ `csv` and returns a single summary envelope for `json` / `yaml`. Revisit a
261
+ streaming-JSON shape — ideally shared with `api --stream` — once it has settled.
262
+ - **UDP client receive/both is gated (#88).** The client cell
263
+ (`test/integration/btest-client.test.ts`) lands real UDP server→client throughput:
264
+ the return rides the guest→host SLIRP gateway (`10.0.2.2:clientUdpPort`), needing
265
+ no UDP forward and no quickchr change — it works because the client socket is left
266
+ unconnected (#86).
267
+ - **Only the server-cell host→guest UDP direction stays unproven.** The server cell
268
+ gates UDP *transmit* (guest→host, which SLIRP NATs cleanly); the reverse (centrs
269
+ server → CHR client, host→guest) would need a UDP hostfwd for the data ports, so it
270
+ is not yet asserted. `btest.exe` (wine) is the coding-time grounding peer, not CI.
271
+ - **Authenticated multi-connection stays single-stream.** Fan-out negotiates the
272
+ session token from the unauthenticated OK; capturing the EC-SRP5 post-auth token
273
+ to fan out authenticated tests is a follow-up (#103). centrs warns
274
+ (`routeros/btest-connection-count-single-stream`) when the realized connection
275
+ count falls short of the request. The **server-side** secondary-accept is its own
276
+ follow-up (#100).
251
277
 
252
278
  ## Out of scope (v1)
253
279
 
254
- Legacy pre-6.43 double-MD5 auth; a UDP client→CHR-server gated test (TCP is gated;
255
- UDP needs SLIRP reverse-NAT); the ≥3-router "test-through" topology RouterOS
280
+ Legacy pre-6.43 double-MD5 auth; the ≥3-router "test-through" topology RouterOS
256
281
  recommends for measuring a *transit* device (centrs is an endpoint peer); TUI/proxy
257
282
  btest frontends (later, over the stable core).
@@ -15,17 +15,19 @@ matrix's honest grounding split (`docs/MATRIX.md`, "Peer measurement (`btest`)")
15
15
  loss / direction against the CHR's own `/tool/bandwidth-test` status output
16
16
  (CHR 7.23.1, `bun run test:integration`). Examples **4** (wrong-credentials) and
17
17
  **5** (multi-connection TCP) are **designed but not yet gated** — the auth-reject
18
- path is covered by the loopback unit test, and the multi-stream fan-out is a
19
- documented follow-up — so they are not part of the asserted run.
18
+ path is covered by the loopback unit test, and example 5 is the **server-side**
19
+ secondary-accept (distinct from the now-gated **client** fan-out in example 11),
20
+ still a documented follow-up — so they are not part of the asserted run.
20
21
  - **Gated `CHR-passed` (client cell): centrs client → CHR bandwidth-server** —
21
22
  examples **6** (TCP receive) and **8** (EC-SRP5 client role + wrong-password
22
23
  reject) are *also* asserted directly against a real RouterOS
23
24
  `/tool/bandwidth-server`, over a host→guest `tcp:2000` forward
24
25
  (`test/integration/btest-client.test.ts`, CHR 7.23.1). This is what makes the
25
26
  **client** cell `CHR-passed`: centrs's EC-SRP5 **client proof** is verified by
26
- RouterOS's own server verifier. TCP only — the EC-SRP5 example is run over TCP
27
- receive here (the loopback variant below uses UDP); UDP client→server stays
28
- loopback (README, Open questions).
27
+ RouterOS's own server verifier. Example **11** (TCP **multi-connection**
28
+ fan-out), a TCP `direction=both` #85 regression guard, and **UDP receive/both**
29
+ (#88 — the server→client return over the guest→host SLIRP gateway) are gated in
30
+ the same suite.
29
31
  - **Unit / loopback (client cell + codec)** — examples 6–10. centrs client ↔
30
32
  centrs server on `127.0.0.1` with injected sockets; the EC-SRP5 both-roles math
31
33
  and the pre-socket option-grammar rejects (`test/unit/btest.test.ts`). These are
@@ -113,12 +115,13 @@ contains a `transport/auth-failed` entry (the EC-SRP5 proof did not verify) and
113
115
  `data.sessions` is empty — no session opened, no throughput. The CHR side reports
114
116
  an authentication failure.
115
117
 
116
- ### 5. Multi-connection TCP transmit
118
+ ### 5. Multi-connection TCP transmit (server-side accept)
117
119
 
118
- > *Designed, not yet gated* — the server's parallel-stream fan-out for
119
- > `connection-count > 1` is not built yet (the session token is negotiated, but
120
- > secondary TCP joins are not served). Documented follow-up; not part of the
121
- > asserted run.
120
+ > *Designed, not yet gated* — this is the **server-side** fan-out: centrs's
121
+ > `btest server` accepting parallel secondary joins from a client. The session
122
+ > token is negotiated but centrs's server does not yet serve secondary TCP joins.
123
+ > Distinct from the **client** fan-out (example 11), which **is** implemented and
124
+ > gated. Tracked as a follow-up (#100); not part of the asserted run.
122
125
 
123
126
  ```bash
124
127
  centrs btest server --authenticate=false --bind 0.0.0.0 --duration 20s --format json
@@ -188,3 +191,25 @@ centrs btest client $SRV --protocol udp --local-udp-tx-size 99999
188
191
  `ok: false`, `error.code = "validation/option"` (UDP size must be `28..64000`), no
189
192
  socket opened. The same gate rejects `connection-count` outside `1..255`,
190
193
  `interval` outside `20ms..5s`, and (server) `max-sessions` outside `1..1000`.
194
+
195
+ ## centrs client TCP multi-connection (gated; client cell)
196
+
197
+ ### 11. Multi-connection TCP receive fan-out
198
+
199
+ ```bash
200
+ centrs btest client <router> --protocol tcp --direction receive --connection-count 4 --duration 4s --format json
201
+ ```
202
+
203
+ Against a real RouterOS `/tool/bandwidth-server` (`authenticate=no`), centrs reads
204
+ the session token from the primary's OK and opens the 3 extra TCP data
205
+ connections, each presenting the grounded join `[token:u16 BE][0x02][0 …]`. The
206
+ summary is `ok: true` with `data.activeConnections: 4` and non-zero aggregate
207
+ throughput — real RouterOS accepted all 3 secondary joins and data flowed on every
208
+ connection (the per-connection drive is also asserted deterministically by the
209
+ loopback unit test). The *throughput increase* multiple streams provide is a
210
+ high-latency/WAN-link property and is **not** observable over the near-zero-latency
211
+ SLIRP loopback (a single TCP stream already saturates it), so the gate asserts the
212
+ fan-out itself, not a higher number. Gated in
213
+ `test/integration/btest-client.test.ts` (CHR 7.23.1). An **authenticated**
214
+ `--connection-count > 1` run instead stays single-stream (`activeConnections: 1`)
215
+ and carries a `routeros/btest-connection-count-single-stream` warning.