@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
package/docs/MATRIX.md CHANGED
@@ -24,130 +24,68 @@ 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` | — | — | — | — | — |
31
31
  | devices | — | — | — | — | — | — | — | — |
32
32
  | discover | — | — | — | — | — | `CHR-passed` | — | — |
33
- | check | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` |
33
+ | check | `designed` | `designed` | `designed` | `designed` | `not-started` | `not-started` | `not-started` | `not-started` |
34
34
  | settings | — | — | — | — | — | — | — | — |
35
35
 
36
- `devices` does not use a transport in the protocol sense, so its grid row stays
37
- `—`; its cell state is `CHR-passed`. `devices` performs no network IO, so that
38
- evidence is a fixture-backed `bun run test:integration` run
39
- (`test/integration/devices.test.ts`, every example in
40
- `commands/devices/examples.md`) against an in-test CDB (open + encrypted), not a
41
- booted CHR. The implemented surface (read subset, CDB mutation, lookup-key
42
- resolution, `(target, user)` identity, ambiguity / `--match`, profile sentinels,
43
- verb aliases, `__default__`, `tips[]`, provenance/override) and the one open item
44
- (ARP test scheme) are documented in `commands/devices/README.md`.
45
-
46
- `discover` is `CHR-passed`: the MNDP codec (`src/data/mndp.ts`), the TTL neighbor
47
- cache (`src/data/mndp-cache.ts`), the UDP listener, and `discover --save`
48
- (through `devices`' atomic write layer) live in `src/discover.ts`. The real-L2
49
- receive/decode/save path is green on RouterOS CHR 7.23.1 via
50
- `test/integration/discover.test.ts` (the quickchr `socket-connect` L2 bridge,
51
- `test/integration/mndp-l2-bridge.ts`; examples 1, 2, 4); the network-independent
52
- codec/cache/listener/`--save` paths stay unit-tested (examples 3, 5, 6, 7). The
53
- wire format, the de-dupe rule (`target`-keyed today, MAC-keyed is the decided
54
- target), TTL/timeout defaults, the live board-TLV finding (short board id vs the
55
- verbose REST `board-name`), and the L2 validation policy are documented in
56
- `commands/discover/README.md`.
57
-
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`).
66
-
67
- `transfer` is `CHR-passed` for `rest-api`/`native-api` and for **sftp** (the
68
- `ssh` column's first method): `src/transfer.ts` + `src/cli/transfer.ts`
69
- (size/direction-aware method selection, leading-slash normalization, the
70
- `print`-probe existence guard, `--verify`) over the shared `FileBackend` seam,
71
- with the SFTP client in `src/protocols/sftp.ts` (host OpenSSH `sftp` subsystem;
72
- `scp` deferred). `test/integration/transfer.test.ts` is green on real CHR 7.23.1
73
- (110 assertions): the rest + native round-trip, the native `N1`–`N4` mirror, the
74
- sftp `S1`–`S5` round-trip, the stdin/stdout forms via the subprocess harness
75
- (examples 8–10), and example 17 (chunked REST read of an sftp-seeded >60 KB file).
76
- The method-selection grammar, the SFTP-vs-SCP rationale, the `--verify` behavior
77
- (sftp trusts the transfer guarantee; rest/native re-read the `/file` size), and the
78
- deferred `scp`/`fetch`/`ftp` methods are documented in
79
- `commands/transfer/README.md`.
80
-
81
- `execute / ssh` is `CHR-passed` (the second SSH consumer): a per-command batch
82
- client (`SshExecClient` in `src/protocols/ssh.ts` — one `ssh user@host "<cmd>"`
83
- per command, sharing `sftp.ts`'s host-`ssh` plumbing) behind the `SshExecAdapter`
84
- console transport (execute-only, like mac-telnet; structured reads/inspect
85
- unsupported). It reuses the mac-telnet `:put [:parse …]` gate verbatim. Green via
86
- `test/integration/execute-ssh.test.ts` (S1–S4) + `test/unit/ssh.test.ts`; the
87
- no-PTY / clean-output / `:parse` wire grounding lives in the `src/protocols/ssh.ts`
88
- header and `commands/execute/README.md`.
89
-
90
- `terminal / ssh` is `CHR-passed` (the third and last SSH consumer): it execs the
91
- host `ssh` with inherited stdio (`buildSshTerminalArgv` in `src/terminal.ts`,
92
- reusing `sshCommonOptions`) and lets the OS relay RouterOS's already-clean no-PTY
93
- console — no screen emulation. No `-t` is passed (`-tt` hangs RouterOS);
94
- `runTerminal` returns `ssh`'s exit code. A host target defaults to ssh, a MAC
95
- target to mac-telnet. Green via `test/integration/terminal-ssh.test.ts` (TS1/TS2),
96
- with argv unit-tested in `test/unit/terminal.test.ts`. **All three SSH cells
97
- (transfer/execute/terminal) are `CHR-passed`.** The no-PTY / exit-code semantics are documented in
98
- `src/terminal.ts`; the no-multi-line-brace limitation is in
99
- `commands/terminal/README.md`.
100
-
101
- `fetch` (centrs-as-HTTP-server + `/tool/fetch`) is a **deferred, explicit-only
102
- method within the rest-api/native-api cells**, not a grid column — it needs
103
- inbound reachability (router → centrs) so it is never auto-selected.
104
-
105
- `settings` is `designed` and transport-less (like `devices`), so its grid row
106
- stays `—`. `commands/settings/README.md` describes the `centrs.env` + `__default__`
107
- front-end (interactive first-time setup plus `settings get/set/reset/print`); no
108
- code yet.
109
-
110
- ### Transport-base readiness (below the command grid)
36
+ ## Status pointers
37
+
38
+ Detailed command contracts live in `commands/<name>/README.md`; example evidence
39
+ lives in `commands/<name>/examples.md` and the named integration tests. Keep this
40
+ section short enough that the grid remains the status surface.
41
+
42
+ - `devices` is transport-less, so its row stays `—`; command state is
43
+ `CHR-passed` by fixture-backed integration tests in `test/integration/devices.test.ts`.
44
+ - `settings` is transport-less, so its row stays `—`; command state is
45
+ `CHR-passed` by fixture-backed integration tests in `test/integration/settings.test.ts`.
46
+ - `retrieve`, `api`, `execute`, and `transfer` share the target-selection and
47
+ fan-out contracts from `docs/CONSTITUTION.md`; command-specific boundaries and
48
+ evidence are in their command READMEs and integration tests.
49
+ - There is no `update` command. RouterOS CLI-shaped writes ride `execute`; the
50
+ structured operation surface is `api`.
51
+ - `api --stream` absorbs the former `stream`/`tail` surface; native-api stream
52
+ details are in `commands/api/README.md` and `src/protocols/native-api.ts`.
53
+ - `discover / mndp` is grounded by `commands/discover/README.md`,
54
+ `src/data/mndp.ts`, `src/data/mndp-cache.ts`, and
55
+ `test/integration/discover.test.ts`.
56
+ - `check` is `designed` (`commands/check/README.md`): a reach + health battery
57
+ whose reads ride the execute-capable transports (rest-api/native-api/ssh/
58
+ mac-telnet), so those four cells are `designed`; `snmp` and `romon`/
59
+ `winbox-terminal` stay `not-started`. Its per-host fan-out is the machinery the
60
+ IP-scan discovery (#149) will iterate; the L2-default timing evidence (#136) is
61
+ recorded in the README before the cells advance past `designed`.
62
+ - `transfer / ssh` means the SFTP-backed transfer method. Deferred file-transfer
63
+ methods such as `scp`, `fetch`, and `ftp` are tracked in
64
+ `commands/transfer/README.md`; `fetch` is not a grid column.
65
+ - SSH grounding for `execute` and `terminal` lives in
66
+ `commands/execute/README.md`, `commands/terminal/README.md`,
67
+ `src/protocols/ssh.ts`, `src/protocols/sftp.ts`, and `src/terminal.ts`.
68
+ - MAC-Telnet grounding for `execute` and `terminal` lives in
69
+ `commands/execute/README.md`, `commands/terminal/README.md`, and the
70
+ `src/protocols/mac-telnet*.ts` module headers.
71
+ - Transport wire-format and delivery caveats belong in `src/protocols/*.ts`
72
+ module headers. Do not expand this file with per-packet or per-router findings.
73
+
74
+ Beyond the grid: post-`CHR-passed` hardening, ergonomics, and research work is
75
+ tracked in GitHub issues (`dry-july` and successors). Those issues may point back
76
+ to this matrix for current status, but they are not additional cell states.
77
+
78
+ ### Transport-base readiness
111
79
 
112
80
  The grid above tracks command cells. The transports those cells ride are
113
81
  implemented and grounded in their `src/protocols/*.ts` **module headers** — the
114
- single home for wire-format, auth, and delivery facts (one fact, one place).
115
- Status here is terse; the module header carries the detail.
116
-
117
- - **native-api** (`src/protocols/native-api.ts`): word/sentence codec, streaming
118
- reader, login (post-6.43 plaintext + legacy MD5 challenge), tagged command
119
- multiplexing, typed error mapping. Live cell status is in the grid (`retrieve`,
120
- `execute`, and `transfer` over `native-api` are all `CHR-passed`). Covered by
121
- `test/unit/native-api.test.ts`, `test/integration/native-api.test.ts`
122
- (transport), and `test/integration/native-api-retrieve.test.ts` (command).
123
- - **mac-telnet** (`src/protocols/mac-telnet.ts` + `mtwei.ts` +
124
- `mac-telnet-console.ts` + `MacTelnetAdapter` in `adapter.ts`): packet/control
125
- codec, session state machine, both auth methods (classic MD5 + MTWEI EC-SRP),
126
- the interactive-console reader, and the UDP datagram transport with route
127
- discovery. The deep findings — auth (MTWEI-required / MD5-refused /
128
- `END_AUTH`≠success), console (ANSI terminal-size probe, ~10s prime stall,
129
- one-time license gate, `:put [:parse]` syntax+semantic gate, silent writes),
130
- reliability (byte-counter retransmit + empty-ACK keepalive), and the
131
- real-device UDP-delivery facts (real egress MAC required; broadcast-only reply;
132
- per-NIC directed-broadcast reach via `discoverMacTelnetRoute`; bind `0.0.0.0`)
133
- — are documented in those module headers. Two cells are `CHR-passed`:
134
- - **`execute / mac-telnet`**: `executeEnvelope` (resolver → `MacTelnetAdapter` →
135
- UDP transport) runs reads, REST-verified writes, and the validation-reject
136
- path. Green via `test/integration/mac-telnet-console.test.ts` (examples
137
- 19–21); transport/auth alone via `test/integration/mac-telnet.test.ts` (MTWEI
138
- login + MD5 refusal). Unit: `test/unit/{mtwei,mac-telnet,mac-telnet-console}.test.ts`.
139
- - **`terminal / mac-telnet`**: the interactive relay over the same console
140
- reader (`MacTelnetConsole.attachInteractive`), orchestrated in
141
- `src/terminal.ts` over an injectable `TerminalIo`, reusing the execute
142
- resolver and the shared `resolveMacTelnetRoute`. Green via
143
- `test/integration/terminal-mac-telnet.test.ts` (T1–T3) driving the real
144
- `centrs terminal` binary through the subprocess harness.
145
- - **Evidence caveat:** the CHR integration runs over the quickchr
146
- `socket-connect` **L2 bridge** (`test/integration/mactelnet-l2-bridge.ts`), so
147
- the real `createUdpMacTelnetTransport` egress/broadcast path is *not* exercised
148
- in CI — the RB1100AHx4 / RouterOS 7.24beta1 real-device run grounded the
149
- UDP-delivery facts (and caught the synthetic-source-MAC break). A full-TTY
150
- `terminal` test (real PTY via `script(1)`/node-pty) is likewise deferred.
82
+ single home for wire-format, auth, and delivery facts.
83
+
84
+ | Transport | Status pointer |
85
+ | --------- | -------------- |
86
+ | native-api | Used by `retrieve`, `api`, `execute`, and `transfer`; wire facts in `src/protocols/native-api.ts`. |
87
+ | ssh / sftp | Used by `transfer`, `execute`, and `terminal`; facts in `src/protocols/ssh.ts`, `src/protocols/sftp.ts`, and `src/terminal.ts`. |
88
+ | mac-telnet | Used by `execute` and `terminal`; facts in `src/protocols/mac-telnet.ts`, `src/protocols/mac-telnet-console.ts`, `src/protocols/mtwei.ts`, and `src/protocols/adapter.ts`. |
151
89
 
152
90
  ### Frontend surfaces (orthogonal to the command grid)
153
91
 
@@ -156,9 +94,9 @@ core and carry their own state, tracked here:
156
94
 
157
95
  | Surface | State | Spec | Notes |
158
96
  | ------- | ----- | ---- | ----- |
159
- | api (TS) | `CHR-passed` | `src/index.ts` | Root surface; everything else adapts it. |
97
+ | ts-api | `CHR-passed` | `src/index.ts` | Root TypeScript library surface; everything else adapts it. |
160
98
  | cli | `coded` | `src/cli/` | `retrieve`/`execute`/`transfer`/`terminal`/`devices`/`discover`/`btest` wired. |
161
- | 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. |
99
+ | mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; details in `commands/mcp/README.md` and `test/integration/mcp.test.ts`. |
162
100
  | tui | `not-started` | `src/tui.ts` | Stub. |
163
101
  | proxy | `not-started` | `src/webproxy.ts` | Stub. |
164
102
 
@@ -170,28 +108,17 @@ are documented in `commands/mcp/README.md` and `commands/mcp/examples.md`.
170
108
 
171
109
  ### Peer measurement (`btest`) — orthogonal to the command grid
172
110
 
173
- `btest` is the MikroTik **bandwidth test** (peer measurement, TCP/UDP port 2000)
174
- — *not* a RouterOS-command transport, so it sits outside the command×protocol
175
- grid as its own capability axis (`measure` in `src/protocols/index.ts`), exempt
176
- from the `:parse` / `/console/inspect` gate. v1 scope (decided with the user):
177
- **both** client and server, **EC-SRP5 + unauthenticated**, **TCP and UDP** (legacy
178
- pre-6.43 double-MD5 is out of scope).
111
+ `btest` is the MikroTik bandwidth test (peer measurement, TCP/UDP port 2000),
112
+ not a RouterOS-command transport. It sits outside the command×protocol grid as
113
+ its own capability axis and is exempt from the RouterOS command-validation gate.
179
114
 
180
115
  | Mode | State | Evidence |
181
116
  | -------------- | ------------ | -------- |
182
- | btest / server | `CHR-passed` | CHR `/tool/bandwidth-test` client → centrs server (TCP+UDP, unauth + EC-SRP5), CHR 7.23.1, `test/integration/btest.test.ts`. |
183
- | btest / client | `CHR-passed` | centrs client → CHR `/tool/bandwidth-server` over a host→guest `tcp:2000` forward — unauth + **EC-SRP5 client proof verified by real RouterOS** + wrong-pass reject (TCP receive), CHR 7.23.1, `test/integration/btest-client.test.ts`; plus client↔server loopback (`test/unit/btest-*.test.ts`). |
184
-
185
- The wire codec, the EC-SRP5 session/handshake (shared core byte-identical with
186
- mac-telnet's MTWEI, plus the net-new server role), the TCP/UDP data engines, the
187
- orchestrator + CLI, and the honest grounding caveat (the server gets direct CHR
188
- 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.
117
+ | btest / server | `CHR-passed` | `test/integration/btest.test.ts`; details in `commands/btest/README.md`. |
118
+ | btest / client | `CHR-passed` | `test/integration/btest-client.test.ts`; details in `commands/btest/README.md`. |
119
+
120
+ Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
121
+ `src/protocols/btest*.ts` / `src/protocols/ec-srp5.ts` module headers.
195
122
 
196
123
  ## Priority order
197
124
 
@@ -199,18 +126,18 @@ Do not start a later item until the earlier cell or dependency checkpoint has
199
126
  matching evidence. The grid above is the live status; per-item detail lives in
200
127
  the linked `commands/<name>/`.
201
128
 
202
- 1. **retrieve / rest-api** — `CHR-passed`. The shakedown for everything else (`commands/retrieve/`).
129
+ 1. **retrieve / rest-api** — `CHR-passed` (`commands/retrieve/`).
203
130
  2. **CDB resolution + metadata overrides** — `CHR-passed` (`commands/devices/`).
204
- 3. **CDB groups + fanout** — `CHR-passed` (`commands/devices/`, Fanout).
205
- 4. **execute / native-api + rest-api** — `CHR-passed` (`commands/execute/`). The single write path; there is no `update`.
131
+ 3. **CDB groups + fan-out** — `CHR-passed` (`commands/devices/`, fan-out core).
132
+ 4. **execute / native-api + rest-api** — `CHR-passed` (`commands/execute/`).
206
133
  5. **devices** (CDB mutation + provenance) — `CHR-passed` (`commands/devices/`).
207
134
  6. **retrieve / snmp** — `not-started` (future: SNMP OID/MIB reads + MikroTik MIB cache).
208
- 7. **ssh** for transfer/execute/terminal — **all three `CHR-passed`** (landed transfer-first; the `transfer`/`execute`/`terminal` READMEs + `src/protocols/ssh.ts` and `src/protocols/sftp.ts`).
209
- 8. **mac-telnet** for execute/terminal — both `CHR-passed` (`src/protocols/mac-telnet.ts` + `src/protocols/mac-telnet-console.ts`). Optional polish: the ~10s prime-latency fix and a full-TTY terminal test under a real PTY.
135
+ 7. **ssh** for transfer/execute/terminal — all three `CHR-passed`.
136
+ 8. **mac-telnet** for execute/terminal — both `CHR-passed`.
210
137
  9. **RoMON / WinBox Terminal for execute** — `not-started` (see Open questions).
211
138
  10. **discover / mndp** — `CHR-passed` (`commands/discover/`).
212
- 11. **MCP, TUI, proxy** — frontends over the stable core. **MCP** is `CHR-passed` for Phase 1 + the first Phase 2 CDB mutation (`commands/mcp/`); next small step is the device-free `centrs mcp --list-tools` manifest dump (bench token-footprint measurement). MCP stays **stdio-only** (HTTP is the proxy's job). **TUI / proxy** `not-started`.
213
- 12. **btest (peer measurement)** — server **and** client both `CHR-passed` (`commands/btest/`). Own protocol axis; shares EC-SRP5 with mac-telnet, so it can proceed in parallel.
139
+ 11. **MCP, TUI, proxy** — MCP `CHR-passed`; TUI/proxy `not-started`.
140
+ 12. **btest (peer measurement)** — server and client both `CHR-passed`.
214
141
 
215
142
  ## Open questions (decisions needed before the affected cell can advance)
216
143
 
@@ -50,7 +50,8 @@ Codes are slash-namespaced `family/slug`. The families and where they originate:
50
50
  - `routeros/*` — RouterOS-surfaced faults mapped by the grounded vocabulary in
51
51
  `src/core/routeros-errors.ts` (REST `detail` and native-api `!trap`, identical
52
52
  on CHR 7.23).
53
- - `settings/*` — settings/value parsing (`src/resolver/settings.ts`).
53
+ - `settings/*` — settings/value parsing (`src/resolver/settings.ts`) and the
54
+ `centrs settings` command's own registry/refusal rules (`src/settings.ts`).
54
55
  - `target/*` — turning a `<router>` into a transport endpoint.
55
56
  - `transport/*` — connection, TLS, DNS, and timeout plumbing.
56
57
  - `usage/*` — how a command was invoked (arity, conflicting flags, confirmation).
@@ -0,0 +1,19 @@
1
+ # `cdb/empty-selection`
2
+
3
+ The target selection matched no CDB records.
4
+
5
+ ## Fix
6
+
7
+ A fan-out selection involving `--where` / `--all` / `--default` / positionals (on
8
+ its own or mixed with `--group`) resolved to zero targets. This is reported as a
9
+ warning on an `ok: true` fan-out envelope with `summary { total: 0, ok: 0, failed:
10
+ 0 }`, not a hard failure — there was simply nothing to run.
11
+
12
+ A **group-only** empty (bare `--group`, no other selector) is reported as
13
+ [`cdb/empty-group`](empty-group.md) instead, naming the group that matched nothing;
14
+ this page covers every other empty selection.
15
+
16
+ Check the selector: run `centrs devices list` (and `centrs devices groups`) to see
17
+ the registered records, groups, and the facts a `--where <attr>=<value>` can match.
18
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Target selection) for the
19
+ selector grammar.
@@ -0,0 +1,9 @@
1
+ # `input/incomplete-gps`
2
+
3
+ lat/lon (or a --gps tuple) were not given as a complete pair.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `input/incomplete-gps`.
@@ -0,0 +1,9 @@
1
+ # `input/invalid-altitude`
2
+
3
+ The altitude is not a number, or altitude-type is not MSL/AGL.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `input/invalid-altitude`.
@@ -0,0 +1,9 @@
1
+ # `input/invalid-bbox`
2
+
3
+ The --bbox is not four lat-first corners with south<=north and west<=east.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `input/invalid-bbox`.
@@ -0,0 +1,9 @@
1
+ # `input/invalid-coordinate`
2
+
3
+ The latitude or longitude is not a number in its valid range.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `input/invalid-coordinate`.
@@ -0,0 +1,9 @@
1
+ # `input/invalid-radius`
2
+
3
+ The --near radius is not a number with an optional m/km/mi/ft suffix.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `input/invalid-radius`.
@@ -0,0 +1,9 @@
1
+ # `internal/settings-failed`
2
+
3
+ The settings command failed with an unexpected internal error.
4
+
5
+ ## Fix
6
+
7
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
+ contract. This stub will be expanded with the typical trigger and remediation
9
+ for `internal/settings-failed`.
@@ -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,26 @@
1
+ # `settings/consequential-value`
2
+
3
+ `settings set` accepted a syntactically valid value with a real, non-obvious consequence (e.g. a global --insecure default, or a transfer-via value gated elsewhere).
4
+
5
+ ## Fix
6
+
7
+ This is a warning, not a failure — the envelope is still `ok: true` and the
8
+ value is written. `centrs settings set` emits it for exactly two keys today
9
+ (the `warn` callback on the matching `SettingsKeyDef` in `src/settings.ts`):
10
+
11
+ - `insecure` set to any truthy spelling (`true`, `yes`, `on`, `1`) makes
12
+ every command run without an explicit `--insecure`/`--no-insecure`
13
+ override trust RouterOS's self-signed TLS certificate or unknown SSH host
14
+ key globally, not just for one invocation. See the `insecure` row in
15
+ `commands/settings/README.md`'s "Known settings keys" table.
16
+ - `transfer-via` set to `ftp` is accepted here — `ftp` is a syntactically
17
+ valid `CENTRS_TRANSFER_VIA` value — but `transfer` still rejects it at
18
+ call time with [`settings/unsafe-protocol-blocked`](unsafe-protocol-blocked.md)
19
+ unless `ALLOW_UNSAFE_PROTOCOLS` also includes `ftp`. The warning exists so
20
+ the write doesn't silently look like it "worked" when the value won't
21
+ actually take effect anywhere else yet.
22
+
23
+ There is nothing to fix in the sense of retrying with different input — the
24
+ value you asked for was written as requested. Run `centrs settings set
25
+ insecure false` or pick a different `transfer-via` value if the consequence
26
+ wasn't intended. See example 24 in `commands/settings/examples.md`.
@@ -4,6 +4,24 @@ A boolean-like setting received a non-boolean value.
4
4
 
5
5
  ## Fix
6
6
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `settings/invalid-boolean`.
7
+ `centrs settings set <attr> <value>` throws this for the three boolean-typed
8
+ managed keys — `mcp-allow-adhoc`, `validate`, `insecure` — when `<value>`
9
+ isn't one of the spellings the shared parser (`parseBoolean` in
10
+ `src/resolver/settings.ts`) accepts: `true`/`false`, `yes`/`no`, `on`/`off`,
11
+ or `1`/`0`, case-insensitively. Anything else — `maybe`, `2`, an empty
12
+ string — fails with a remediation listing the accepted spellings.
13
+
14
+ This isn't a `settings`-only rule: `parseBoolean` backs every `CENTRS_*`
15
+ boolean setting across the app, so the same error and message shape appears
16
+ whenever any command resolves a boolean env var to an unrecognized value,
17
+ not just through `settings set`.
18
+
19
+ One canonicalization detail worth knowing even though it isn't what triggers
20
+ this error: once a boolean value *does* validate, `settings set` always
21
+ writes the canonical `1` or `0` to `centrs.env`, never `true`/`false` —
22
+ `CENTRS_MCP_ALLOW_ADHOC`'s reader in `src/mcp/config.ts` checks the literal
23
+ string `"1"` rather than reusing `parseBoolean`, so writing `1`/`0`
24
+ uniformly is what satisfies every reader. Retry `set` with one of the
25
+ accepted spellings above; see
26
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Settings precedence) for how
27
+ this value feeds into the resolver chain.
@@ -4,6 +4,21 @@ An unsupported output format was requested.
4
4
 
5
5
  ## Fix
6
6
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `settings/invalid-format`.
7
+ `centrs settings set format <value>` throws this when `<value>` isn't one of
8
+ `text`, `json`, or `yaml` — the same `{text, json, yaml}` set every
9
+ command's own `--format` flag (and `CENTRS_FORMAT`) validates against
10
+ (`retrieveOutputFormats` and its `execute`/`transfer`/`api` counterparts).
11
+ `centrs settings set format xml` is the canonical trigger; see example 14 in
12
+ `commands/settings/examples.md`. A failed `set` never partially writes — the
13
+ file is unchanged.
14
+
15
+ `format` is the one managed key without a single universal built-in
16
+ default: `retrieve`/`execute`/`transfer` default to `text`, but `api`
17
+ defaults to `json`. Because of that, `centrs settings print`/`get format`
18
+ can't report one scalar default when the key is unset — it reports a
19
+ `perCommandDefault` map (`{ retrieve: "text", execute: "text", transfer:
20
+ "text", api: "json" }`) instead, so this is the one key where "unset" looks
21
+ different in the output shape than every other key.
22
+
23
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Settings precedence) for
24
+ where `format` sits in the resolver chain once a value is set.
@@ -4,6 +4,21 @@ An integer setting received a non-integer or out-of-range value.
4
4
 
5
5
  ## Fix
6
6
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `settings/invalid-integer`.
7
+ `centrs settings set max-results <value>` and `centrs settings set port
8
+ <value>` both throw this when `<value>` fails the shared positive-integer
9
+ rule (`resolveOptionalIntegerSetting` in `src/resolver/settings.ts`):
10
+ `Number.isInteger(n) && n > 0`. `0`, negative numbers (`-1`), and
11
+ non-numeric strings (`abc`) are all rejected the same way — `centrs settings
12
+ set max-results 0` is the canonical trigger (example 20 in
13
+ `commands/settings/examples.md`).
14
+
15
+ This validator isn't specific to `settings` — `resolveOptionalIntegerSetting`
16
+ is the shared helper every `CENTRS_*` integer setting resolves through
17
+ (`max-results`, `port`, and any future integer key), so the same code and
18
+ message shape appear whenever an integer env var, CLI flag, or `centrs.env`
19
+ line resolves to a non-positive or non-numeric value anywhere in the app,
20
+ not only through `settings set`.
21
+
22
+ A failed `set` never partially writes; re-run with a positive integer. See
23
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Settings precedence) for how
24
+ this value feeds into the resolver chain.
@@ -4,6 +4,23 @@ A timeout setting received an invalid value.
4
4
 
5
5
  ## Fix
6
6
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `settings/invalid-timeout`.
7
+ `centrs settings set timeout <value>` throws this when `<value>` doesn't
8
+ match the shared duration grammar (`parseDuration` in
9
+ `src/resolver/settings.ts`): a bare integer (milliseconds) or an integer
10
+ with a `ms`/`s`/`m` suffix — `500ms`, `5s`, `1m`. Anything else, like
11
+ `not-a-duration` (example 22 in `commands/settings/examples.md`), fails.
12
+ `settings.ts` layers one more rule on top for `timeout` specifically: even a
13
+ syntactically valid duration that parses to `0` is rejected — a zero timeout
14
+ isn't a meaningful value to set.
15
+
16
+ Like the other typed-value codes here, this isn't `settings`-exclusive:
17
+ `parseDuration` backs `CENTRS_TIMEOUT` and every command's own `--timeout`
18
+ flag, so the same failure shape shows up anywhere a duration string is
19
+ resolved, not just through `settings set`.
20
+
21
+ One write detail worth knowing: `settings set timeout` writes the
22
+ **canonical parsed milliseconds**, not the raw suffixed string you typed —
23
+ `centrs settings set timeout 5s` writes `CENTRS_TIMEOUT=5000` to
24
+ `centrs.env`, not `CENTRS_TIMEOUT=5s`. See
25
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Settings precedence) for how
26
+ this value feeds into the resolver chain.
@@ -4,6 +4,25 @@ An unsupported protocol identifier was supplied to --via.
4
4
 
5
5
  ## Fix
6
6
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `settings/invalid-via`.
7
+ This one code covers two different value sets, because `settings` reuses it
8
+ for two different keys. `centrs settings set via <value>` requires one of
9
+ the protocol identifiers in `plannedProtocols` (`src/protocols/index.ts`):
10
+ `rest-api`, `native-api`, `ssh`, `snmp`, `mndp`, `mac-telnet`, `romon`,
11
+ `winbox-terminal` — `centrs settings set via carrier-pigeon` is the
12
+ canonical trigger (example 21 in `commands/settings/examples.md`).
13
+ `centrs settings set transfer-via <value>` reuses the same error code but
14
+ validates against a completely different set: `auto`, `rest`/`rest-api`,
15
+ `native`/`native-api`, `sftp`, `scp`, `fetch`, `ftp`. Unlike `CENTRS_VIA`,
16
+ which has a per-device `via=` comment-kv counterpart, `CENTRS_TRANSFER_VIA`
17
+ has none — `transfer` reads only the env var.
18
+
19
+ The same validation backs `retrieve`/`execute`/`api`'s own `--via` flag more
20
+ broadly, so the identical code and message shape appear there too, not only
21
+ through `settings set`.
22
+
23
+ `transfer-via ftp` is syntactically valid against the `transfer-via` set
24
+ above — it does **not** trigger this error — but it does trigger a separate
25
+ warning, [`settings/consequential-value`](consequential-value.md), because
26
+ `transfer` still rejects `ftp` at call time unless `ALLOW_UNSAFE_PROTOCOLS`
27
+ also opts in. See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Protocol
28
+ selection) for the full `via` vocabulary.
@@ -0,0 +1,29 @@
1
+ # `settings/reserved-key`
2
+
3
+ `settings set`/`reset` refused a credential-shaped or self-referential CENTRS_* key.
4
+
5
+ ## Fix
6
+
7
+ `centrs settings set <attr> <value>` and `centrs settings reset <attr>` refuse
8
+ five keys outright: `password`, `username`, `cdb-password`, `skip-env-file`,
9
+ and `run-fast-integration` (the `settingsRefusedKeys` list in
10
+ `src/settings.ts`). Each is refused for a different reason, not a blanket
11
+ policy: `password`/`username` are credential-shaped and belong on the
12
+ `__default__` CDB record — run `centrs devices add __default__ --user …
13
+ --password …` instead of duplicating a secret into a second, unencrypted
14
+ file. `cdb-password` is the CDB's own decrypt password; writing it beside the
15
+ encrypted CDB it unlocks would defeat the encryption, so pass
16
+ `--cdb-password` per invocation or export `CENTRS_CDB_PASSWORD` in the
17
+ calling shell instead. `skip-env-file` is self-referential — a line in
18
+ `centrs.env` that says "ignore this file" is a no-op by construction, since
19
+ the file has to be read to find it. `run-fast-integration` is a CI/test
20
+ signal `bun run test:integration` sets, never a human preference.
21
+
22
+ `settings get`/`settings print` still work read-only on all five keys — this
23
+ error is specific to the write path (`set`/`reset`). A read of `cdb-password`
24
+ or `password` redacts the value rather than refusing outright; see
25
+ [`settings/unknown-key`](unknown-key.md) for the different case of a token
26
+ that isn't a recognized setting at all. See examples 17 and 29 in
27
+ `commands/settings/examples.md`, and
28
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Identity and CDB) for why
29
+ credentials route through `devices` rather than `centrs.env`.