@tikoci/centrs 0.1.2 → 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 (82) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/GLOSSARY.txt +71 -0
  3. package/README.md +24 -8
  4. package/SECURITY.md +3 -3
  5. package/commands/api/README.md +27 -9
  6. package/commands/api/examples.md +109 -6
  7. package/commands/check/README.md +371 -53
  8. package/commands/check/examples.md +390 -0
  9. package/commands/devices/README.md +177 -37
  10. package/commands/devices/examples.md +148 -0
  11. package/commands/execute/README.md +39 -4
  12. package/commands/execute/examples.md +90 -6
  13. package/commands/retrieve/README.md +36 -50
  14. package/commands/retrieve/examples.md +40 -9
  15. package/commands/settings/README.md +286 -22
  16. package/commands/settings/examples.md +408 -0
  17. package/commands/terminal/README.md +6 -0
  18. package/commands/transfer/README.md +32 -2
  19. package/commands/transfer/examples.md +66 -0
  20. package/docs/CONSTITUTION.md +36 -11
  21. package/docs/MATRIX.md +68 -172
  22. package/docs/errors/README.md +2 -1
  23. package/docs/errors/cdb/empty-selection.md +19 -0
  24. package/docs/errors/input/incomplete-gps.md +9 -0
  25. package/docs/errors/input/invalid-altitude.md +9 -0
  26. package/docs/errors/input/invalid-bbox.md +9 -0
  27. package/docs/errors/input/invalid-coordinate.md +9 -0
  28. package/docs/errors/input/invalid-radius.md +9 -0
  29. package/docs/errors/internal/settings-failed.md +9 -0
  30. package/docs/errors/settings/consequential-value.md +26 -0
  31. package/docs/errors/settings/invalid-boolean.md +21 -3
  32. package/docs/errors/settings/invalid-format.md +18 -3
  33. package/docs/errors/settings/invalid-integer.md +18 -3
  34. package/docs/errors/settings/invalid-timeout.md +20 -3
  35. package/docs/errors/settings/invalid-via.md +22 -3
  36. package/docs/errors/settings/reserved-key.md +29 -0
  37. package/docs/errors/settings/skip-env-file-active.md +25 -0
  38. package/docs/errors/settings/unknown-key.md +25 -0
  39. package/docs/errors/usage/fanout-not-supported.md +16 -0
  40. package/package.json +6 -6
  41. package/src/api-fanout.ts +454 -0
  42. package/src/api.ts +91 -19
  43. package/src/cli/api.ts +135 -11
  44. package/src/cli/btest.ts +20 -2
  45. package/src/cli/common.ts +11 -0
  46. package/src/cli/devices.ts +235 -6
  47. package/src/cli/discover.ts +3 -1
  48. package/src/cli/execute.ts +107 -6
  49. package/src/cli/mcp.ts +8 -1
  50. package/src/cli/retrieve.ts +135 -90
  51. package/src/cli/selection.ts +200 -0
  52. package/src/cli/settings.ts +286 -0
  53. package/src/cli/terminal.ts +22 -7
  54. package/src/cli/transfer.ts +171 -47
  55. package/src/cli.ts +7 -0
  56. package/src/core/error-catalog.ts +55 -0
  57. package/src/core/fanout.ts +143 -2
  58. package/src/devices.ts +212 -20
  59. package/src/discover.ts +7 -2
  60. package/src/execute-fanout.ts +438 -0
  61. package/src/execute.ts +120 -23
  62. package/src/index.ts +105 -1
  63. package/src/mcp/config.ts +12 -2
  64. package/src/mcp/tools.ts +137 -2
  65. package/src/protocols/adapter.ts +38 -18
  66. package/src/protocols/index.ts +11 -5
  67. package/src/resolver/cdb.ts +10 -2
  68. package/src/resolver/comment-kv.ts +58 -4
  69. package/src/resolver/config-file.ts +136 -0
  70. package/src/resolver/facts.ts +78 -0
  71. package/src/resolver/geo.ts +453 -0
  72. package/src/resolver/index.ts +46 -0
  73. package/src/resolver/mac.ts +12 -3
  74. package/src/resolver/selection.ts +304 -0
  75. package/src/resolver/settings.ts +54 -5
  76. package/src/resolver/target.ts +20 -2
  77. package/src/retrieve-fanout.ts +225 -133
  78. package/src/retrieve.ts +39 -7
  79. package/src/settings.ts +1078 -0
  80. package/src/terminal.ts +11 -1
  81. package/src/transfer-fanout.ts +527 -0
  82. package/src/transfer.ts +61 -11
package/docs/MATRIX.md CHANGED
@@ -30,137 +30,62 @@ A cell advances only with the matching evidence in the same change.
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 *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`.
79
-
80
- `transfer` is `CHR-passed` for `rest-api`/`native-api` and for **sftp** (the
81
- `ssh` column's first method): `src/transfer.ts` + `src/cli/transfer.ts`
82
- (size/direction-aware method selection, leading-slash normalization, the
83
- `print`-probe existence guard, `--verify`) over the shared `FileBackend` seam,
84
- with the SFTP client in `src/protocols/sftp.ts` (host OpenSSH `sftp` subsystem;
85
- `scp` deferred). `test/integration/transfer.test.ts` is green on real CHR 7.23.1
86
- (110 assertions): the rest + native round-trip, the native `N1`–`N4` mirror, the
87
- sftp `S1`–`S5` round-trip, the stdin/stdout forms via the subprocess harness
88
- (examples 8–10), and example 17 (chunked REST read of an sftp-seeded >60 KB file).
89
- The method-selection grammar, the SFTP-vs-SCP rationale, the `--verify` behavior
90
- (sftp trusts the transfer guarantee; rest/native re-read the `/file` size), and the
91
- deferred `scp`/`fetch`/`ftp` methods are documented in
92
- `commands/transfer/README.md`.
93
-
94
- `execute / ssh` is `CHR-passed` (the second SSH consumer): a per-command batch
95
- client (`SshExecClient` in `src/protocols/ssh.ts` — one `ssh user@host "<cmd>"`
96
- per command, sharing `sftp.ts`'s host-`ssh` plumbing) behind the `SshExecAdapter`
97
- console transport (execute-only, like mac-telnet; structured reads/inspect
98
- unsupported). It reuses the mac-telnet `:put [:parse …]` gate verbatim. Green via
99
- `test/integration/execute-ssh.test.ts` (S1–S4) + `test/unit/ssh.test.ts`; the
100
- no-PTY / clean-output / `:parse` wire grounding lives in the `src/protocols/ssh.ts`
101
- header and `commands/execute/README.md`.
102
-
103
- `terminal / ssh` is `CHR-passed` (the third and last SSH consumer): it execs the
104
- host `ssh` with inherited stdio (`buildSshTerminalArgv` in `src/terminal.ts`,
105
- reusing `sshCommonOptions`) and lets the OS relay RouterOS's already-clean no-PTY
106
- console — no screen emulation. No `-t` is passed (`-tt` hangs RouterOS);
107
- `runTerminal` returns `ssh`'s exit code. A host target defaults to ssh, a MAC
108
- target to mac-telnet. Green via `test/integration/terminal-ssh.test.ts` (TS1/TS2),
109
- with argv unit-tested in `test/unit/terminal.test.ts`. **All three SSH cells
110
- (transfer/execute/terminal) are `CHR-passed`.** The no-PTY / exit-code semantics are documented in
111
- `src/terminal.ts`; the no-multi-line-brace limitation is in
112
- `commands/terminal/README.md`.
113
-
114
- `fetch` (centrs-as-HTTP-server + `/tool/fetch`) is a **deferred, explicit-only
115
- method within the rest-api/native-api cells**, not a grid column — it needs
116
- inbound reachability (router → centrs) so it is never auto-selected.
117
-
118
- `settings` is `designed` and transport-less (like `devices`), so its grid row
119
- stays `—`. `commands/settings/README.md` describes the `centrs.env` + `__default__`
120
- front-end (interactive first-time setup plus `settings get/set/reset/print`); no
121
- code yet.
122
-
123
- ### 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
124
79
 
125
80
  The grid above tracks command cells. The transports those cells ride are
126
81
  implemented and grounded in their `src/protocols/*.ts` **module headers** — the
127
- single home for wire-format, auth, and delivery facts (one fact, one place).
128
- Status here is terse; the module header carries the detail.
129
-
130
- - **native-api** (`src/protocols/native-api.ts`): word/sentence codec, streaming
131
- reader, login (post-6.43 plaintext + legacy MD5 challenge), tagged command
132
- multiplexing, typed error mapping. Live cell status is in the grid (`retrieve`,
133
- `execute`, and `transfer` over `native-api` are all `CHR-passed`). Covered by
134
- `test/unit/native-api.test.ts`, `test/integration/native-api.test.ts`
135
- (transport), and `test/integration/native-api-retrieve.test.ts` (command).
136
- - **mac-telnet** (`src/protocols/mac-telnet.ts` + `mtwei.ts` +
137
- `mac-telnet-console.ts` + `MacTelnetAdapter` in `adapter.ts`): packet/control
138
- codec, session state machine, both auth methods (classic MD5 + MTWEI EC-SRP),
139
- the interactive-console reader, and the UDP datagram transport with route
140
- discovery. The deep findings — auth (MTWEI-required / MD5-refused /
141
- `END_AUTH`≠success), console (ANSI terminal-size probe, ~10s prime stall,
142
- one-time license gate, `:put [:parse]` syntax+semantic gate, silent writes),
143
- reliability (byte-counter retransmit + empty-ACK keepalive), and the
144
- real-device UDP-delivery facts (real egress MAC required; broadcast-only reply;
145
- per-NIC directed-broadcast reach via `discoverMacTelnetRoute`; bind `0.0.0.0`)
146
- — are documented in those module headers. Two cells are `CHR-passed`:
147
- - **`execute / mac-telnet`**: `executeEnvelope` (resolver → `MacTelnetAdapter` →
148
- UDP transport) runs reads, REST-verified writes, and the validation-reject
149
- path. Green via `test/integration/mac-telnet-console.test.ts` (examples
150
- 19–21); transport/auth alone via `test/integration/mac-telnet.test.ts` (MTWEI
151
- login + MD5 refusal). Unit: `test/unit/{mtwei,mac-telnet,mac-telnet-console}.test.ts`.
152
- - **`terminal / mac-telnet`**: the interactive relay over the same console
153
- reader (`MacTelnetConsole.attachInteractive`), orchestrated in
154
- `src/terminal.ts` over an injectable `TerminalIo`, reusing the execute
155
- resolver and the shared `resolveMacTelnetRoute`. Green via
156
- `test/integration/terminal-mac-telnet.test.ts` (T1–T3) driving the real
157
- `centrs terminal` binary through the subprocess harness.
158
- - **Evidence caveat:** the CHR integration runs over the quickchr
159
- `socket-connect` **L2 bridge** (`test/integration/mactelnet-l2-bridge.ts`), so
160
- the real `createUdpMacTelnetTransport` egress/broadcast path is *not* exercised
161
- in CI — the RB1100AHx4 / RouterOS 7.24beta1 real-device run grounded the
162
- UDP-delivery facts (and caught the synthetic-source-MAC break). A full-TTY
163
- `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`. |
164
89
 
165
90
  ### Frontend surfaces (orthogonal to the command grid)
166
91
 
@@ -169,9 +94,9 @@ core and carry their own state, tracked here:
169
94
 
170
95
  | Surface | State | Spec | Notes |
171
96
  | ------- | ----- | ---- | ----- |
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.) |
97
+ | ts-api | `CHR-passed` | `src/index.ts` | Root TypeScript library surface; everything else adapts it. |
173
98
  | cli | `coded` | `src/cli/` | `retrieve`/`execute`/`transfer`/`terminal`/`devices`/`discover`/`btest` wired. |
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. |
99
+ | mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; details in `commands/mcp/README.md` and `test/integration/mcp.test.ts`. |
175
100
  | tui | `not-started` | `src/tui.ts` | Stub. |
176
101
  | proxy | `not-started` | `src/webproxy.ts` | Stub. |
177
102
 
@@ -183,46 +108,17 @@ are documented in `commands/mcp/README.md` and `commands/mcp/examples.md`.
183
108
 
184
109
  ### Peer measurement (`btest`) — orthogonal to the command grid
185
110
 
186
- `btest` is the MikroTik **bandwidth test** (peer measurement, TCP/UDP port 2000)
187
- — *not* a RouterOS-command transport, so it sits outside the command×protocol
188
- grid as its own capability axis (`measure` in `src/protocols/index.ts`), exempt
189
- from the `:parse` / `/console/inspect` gate. v1 scope (decided with the user):
190
- **both** client and server, **EC-SRP5 + unauthenticated**, **TCP and UDP** (legacy
191
- 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.
192
114
 
193
115
  | Mode | State | Evidence |
194
116
  | -------------- | ------------ | -------- |
195
- | 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`. |
196
- | 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`). |
197
-
198
- The wire codec, the EC-SRP5 session/handshake (shared core byte-identical with
199
- mac-telnet's MTWEI, plus the net-new server role), the TCP/UDP data engines, the
200
- orchestrator + CLI, and the honest grounding caveat (the server gets direct CHR
201
- evidence over the QEMU SLIRP gateway `10.0.2.2`; the client gets direct CHR
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.
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.
226
122
 
227
123
  ## Priority order
228
124
 
@@ -230,18 +126,18 @@ Do not start a later item until the earlier cell or dependency checkpoint has
230
126
  matching evidence. The grid above is the live status; per-item detail lives in
231
127
  the linked `commands/<name>/`.
232
128
 
233
- 1. **retrieve / rest-api** — `CHR-passed`. The shakedown for everything else (`commands/retrieve/`).
129
+ 1. **retrieve / rest-api** — `CHR-passed` (`commands/retrieve/`).
234
130
  2. **CDB resolution + metadata overrides** — `CHR-passed` (`commands/devices/`).
235
- 3. **CDB groups + fanout** — `CHR-passed` (`commands/devices/`, Fanout).
236
- 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/`).
237
133
  5. **devices** (CDB mutation + provenance) — `CHR-passed` (`commands/devices/`).
238
134
  6. **retrieve / snmp** — `not-started` (future: SNMP OID/MIB reads + MikroTik MIB cache).
239
- 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`).
240
- 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`.
241
137
  9. **RoMON / WinBox Terminal for execute** — `not-started` (see Open questions).
242
138
  10. **discover / mndp** — `CHR-passed` (`commands/discover/`).
243
- 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`.
244
- 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`.
245
141
 
246
142
  ## Open questions (decisions needed before the affected cell can advance)
247
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,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`.
@@ -0,0 +1,25 @@
1
+ # `settings/skip-env-file-active`
2
+
3
+ `settings print` ran under --skip-env-file; the shown centrs.env values would not apply to other commands run in the same environment.
4
+
5
+ ## Fix
6
+
7
+ This is an informational warning, not a failure — `centrs settings print`
8
+ still returns `ok: true` with the file's real contents. It appears only when
9
+ `--skip-env-file` (or `CENTRS_SKIP_ENV_FILE`) was active on that same
10
+ `print` invocation, as a reminder that the flag doesn't apply to `settings`
11
+ itself: `settings` always reads and writes the real `centrs.env` file
12
+ regardless of `--skip-env-file`, since inspecting/editing the file is this
13
+ command's whole job (see the module doc comment at the top of
14
+ `src/settings.ts`).
15
+
16
+ What the warning is actually flagging is every *other* command: if
17
+ `--skip-env-file`/`CENTRS_SKIP_ENV_FILE` is also active when you run
18
+ `retrieve`/`execute`/`transfer`/`api` in that same shell or environment,
19
+ those commands' resolvers skip loading `centrs.env` as a settings source —
20
+ so the values `print` just showed you would not apply there. There's
21
+ nothing to fix; either drop `--skip-env-file` if you want other commands to
22
+ see these values, or ignore the warning if the isolation is intentional. See
23
+ example 6 in `commands/settings/examples.md`, and
24
+ [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Settings precedence) for how
25
+ the config tier fits into the precedence ladder.