@tikoci/centrs 0.1.2 → 0.1.4

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 (134) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/GLOSSARY.txt +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +55 -11
  5. package/SECURITY.md +3 -3
  6. package/commands/AGENTS.md +7 -1
  7. package/commands/api/README.md +46 -27
  8. package/commands/api/examples.md +124 -6
  9. package/commands/btest/README.md +21 -32
  10. package/commands/check/README.md +376 -53
  11. package/commands/check/examples.md +390 -0
  12. package/commands/devices/README.md +177 -37
  13. package/commands/devices/examples.md +148 -0
  14. package/commands/discover/README.md +5 -11
  15. package/commands/execute/README.md +73 -26
  16. package/commands/execute/examples.md +131 -9
  17. package/commands/explain/README.md +1716 -0
  18. package/commands/explain/examples.md +606 -0
  19. package/commands/retrieve/README.md +55 -71
  20. package/commands/retrieve/examples.md +56 -9
  21. package/commands/settings/README.md +286 -22
  22. package/commands/settings/examples.md +408 -0
  23. package/commands/terminal/README.md +6 -0
  24. package/commands/transfer/README.md +57 -19
  25. package/commands/transfer/examples.md +95 -0
  26. package/docs/CLI.md +353 -0
  27. package/docs/CONSTITUTION.md +102 -26
  28. package/docs/MATRIX.md +106 -173
  29. package/docs/errors/README.md +5 -1
  30. package/docs/errors/cdb/empty-selection.md +19 -0
  31. package/docs/errors/input/incomplete-gps.md +9 -0
  32. package/docs/errors/input/invalid-altitude.md +9 -0
  33. package/docs/errors/input/invalid-bbox.md +9 -0
  34. package/docs/errors/input/invalid-coordinate.md +9 -0
  35. package/docs/errors/input/invalid-radius.md +9 -0
  36. package/docs/errors/internal/settings-failed.md +9 -0
  37. package/docs/errors/quickchr/machine-not-found.md +22 -0
  38. package/docs/errors/quickchr/machine-stopped.md +20 -0
  39. package/docs/errors/quickchr/package-unavailable.md +27 -0
  40. package/docs/errors/quickchr/unsupported-via.md +35 -0
  41. package/docs/errors/quickchr/unsupported.md +35 -0
  42. package/docs/errors/routeros/version-unsupported.md +26 -0
  43. package/docs/errors/settings/consequential-value.md +26 -0
  44. package/docs/errors/settings/invalid-boolean.md +21 -3
  45. package/docs/errors/settings/invalid-format.md +18 -3
  46. package/docs/errors/settings/invalid-integer.md +18 -3
  47. package/docs/errors/settings/invalid-timeout.md +20 -3
  48. package/docs/errors/settings/invalid-via.md +22 -3
  49. package/docs/errors/settings/reserved-key.md +29 -0
  50. package/docs/errors/settings/skip-env-file-active.md +25 -0
  51. package/docs/errors/settings/unknown-key.md +25 -0
  52. package/docs/errors/transport/provider-trust.md +29 -0
  53. package/docs/errors/usage/fanout-not-supported.md +16 -0
  54. package/docs/errors/usage/stdin-ignored.md +28 -0
  55. package/docs/errors/validation/unknown-attribute.md +37 -4
  56. package/package.json +49 -14
  57. package/src/api-fanout.ts +469 -0
  58. package/src/api.ts +171 -56
  59. package/src/cli/api.ts +156 -13
  60. package/src/cli/btest.ts +38 -4
  61. package/src/cli/common.ts +11 -0
  62. package/src/cli/devices.ts +235 -6
  63. package/src/cli/discover.ts +3 -1
  64. package/src/cli/execute.ts +129 -9
  65. package/src/cli/explain.ts +503 -0
  66. package/src/cli/mcp.ts +8 -1
  67. package/src/cli/retrieve.ts +167 -90
  68. package/src/cli/selection.ts +260 -0
  69. package/src/cli/settings.ts +286 -0
  70. package/src/cli/terminal.ts +22 -7
  71. package/src/cli/transfer.ts +192 -51
  72. package/src/cli.ts +22 -2
  73. package/src/core/envelope.ts +5 -2
  74. package/src/core/error-catalog.ts +95 -0
  75. package/src/core/fanout.ts +143 -2
  76. package/src/core/preflight.ts +306 -0
  77. package/src/core/routeros-string.ts +24 -0
  78. package/src/devices.ts +212 -20
  79. package/src/discover.ts +7 -2
  80. package/src/errors.ts +1 -0
  81. package/src/execute-fanout.ts +453 -0
  82. package/src/execute.ts +216 -68
  83. package/src/explain/arg-tokens.ts +114 -0
  84. package/src/explain/args.ts +1403 -0
  85. package/src/explain/blocks.ts +132 -0
  86. package/src/explain/brace-slots.ts +143 -0
  87. package/src/explain/brace-tokens.ts +38 -0
  88. package/src/explain/catalog.ts +1345 -0
  89. package/src/explain/coordinates.ts +329 -0
  90. package/src/explain/defects.ts +217 -0
  91. package/src/explain/is-known-menu.ts +17 -0
  92. package/src/explain/menus.ts +664 -0
  93. package/src/explain/operator-tokens.ts +310 -0
  94. package/src/explain/operators.ts +458 -0
  95. package/src/explain/path-tokens.ts +150 -0
  96. package/src/explain/pathresolve.ts +1324 -0
  97. package/src/explain/quoted-string.ts +241 -0
  98. package/src/explain/scope-brace.ts +308 -0
  99. package/src/explain/segment.ts +757 -0
  100. package/src/explain/string-tokens.ts +67 -0
  101. package/src/explain/symbol-values.ts +520 -0
  102. package/src/explain/symbols.ts +1597 -0
  103. package/src/explain/token-ranges.ts +72 -0
  104. package/src/explain/transport.ts +497 -0
  105. package/src/explain/value-tokens.ts +80 -0
  106. package/src/explain/values.ts +357 -0
  107. package/src/explain/verbs.ts +64 -0
  108. package/src/explain/verbsplit.ts +813 -0
  109. package/src/explain/write.ts +815 -0
  110. package/src/explain.ts +2152 -0
  111. package/src/index.ts +307 -1
  112. package/src/mcp/config.ts +12 -2
  113. package/src/mcp/tools.ts +137 -2
  114. package/src/protocols/adapter.ts +38 -18
  115. package/src/protocols/index.ts +11 -5
  116. package/src/protocols/mac-telnet-console.ts +61 -17
  117. package/src/resolver/cdb.ts +10 -2
  118. package/src/resolver/comment-kv.ts +58 -4
  119. package/src/resolver/config-file.ts +136 -0
  120. package/src/resolver/facts.ts +78 -0
  121. package/src/resolver/geo.ts +453 -0
  122. package/src/resolver/index.ts +65 -0
  123. package/src/resolver/mac.ts +12 -3
  124. package/src/resolver/quickchr-provider.ts +597 -0
  125. package/src/resolver/selection.ts +391 -0
  126. package/src/resolver/service-endpoint.ts +155 -0
  127. package/src/resolver/settings.ts +54 -5
  128. package/src/resolver/target.ts +21 -3
  129. package/src/retrieve-fanout.ts +239 -133
  130. package/src/retrieve.ts +120 -25
  131. package/src/settings.ts +1078 -0
  132. package/src/terminal.ts +11 -1
  133. package/src/transfer-fanout.ts +544 -0
  134. package/src/transfer.ts +133 -40
package/docs/MATRIX.md CHANGED
@@ -30,137 +30,99 @@ 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
+ | explain | `designed` | `designed` | — | — | — | — | — | — |
34
35
  | settings | — | — | — | — | — | — | — | — |
35
36
 
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)
37
+ ## Status pointers
38
+
39
+ Detailed command contracts live in `commands/<name>/README.md`; example evidence
40
+ lives in `commands/<name>/examples.md` and the named integration tests. Keep this
41
+ section short enough that the grid remains the status surface.
42
+
43
+ - `devices` is transport-less, so its row stays `—`; command state is
44
+ `CHR-passed` by fixture-backed integration tests in `test/integration/devices.test.ts`.
45
+ - `settings` is transport-less, so its row stays `—`; command state is
46
+ `CHR-passed` by fixture-backed integration tests in `test/integration/settings.test.ts`.
47
+ - `retrieve`, `api`, `execute`, and `transfer` share the target-selection and
48
+ fan-out contracts from `docs/CONSTITUTION.md`; command-specific boundaries and
49
+ evidence are in their command READMEs and integration tests.
50
+ - There is no `update` command. RouterOS CLI-shaped writes ride `execute`; the
51
+ structured operation surface is `api`.
52
+ - `api --stream` absorbs the former `stream`/`tail` surface; native-api stream
53
+ details are in `commands/api/README.md` and `src/protocols/native-api.ts`.
54
+ - `discover / mndp` is grounded by `commands/discover/README.md`,
55
+ `src/data/mndp.ts`, `src/data/mndp-cache.ts`, and
56
+ `test/integration/discover.test.ts`.
57
+ - `check` is `designed` (`commands/check/README.md`): a reach + health battery
58
+ whose reads ride the execute-capable transports (rest-api/native-api/ssh/
59
+ mac-telnet), so those four cells are `designed`; `snmp` and `romon`/
60
+ `winbox-terminal` stay `not-started`. Its per-host fan-out is the machinery the
61
+ IP-scan discovery (#149) will iterate; the L2-default timing evidence (#136) is
62
+ recorded in the README before the cells advance past `designed`.
63
+ - `explain` is `designed` (`commands/explain/README.md`): the explain →
64
+ validate → run knowledge tier — canonical structure, LSP-like
65
+ spans/diagnostics, transport classification (api-able vs execute, `curl`
66
+ rendering), and live schema/completion facts. Its offline mode is the
67
+ canonicalizer only and opens no protocol connection (evidence is
68
+ unit/fixture tests); the
69
+ live probes (`/console/inspect` + `:parse`) ride rest-api/native-api, so
70
+ those two cells are `designed`. The surface was decided in the 2026-07-19
71
+ design round (#90) and the spec is **ratified** on the phase-0 grounding
72
+ lab (#185, ratification PR #187). Phase 0.5 has since promoted the ratified
73
+ lab logic into the library — `src/explain/{segment,blocks,coordinates,defects,
74
+ pathresolve,verbsplit,write,symbols,menus,verbs,args,values,brace-slots}.ts`
75
+ with frozen fixtures under `test/fixtures/explain/` — and phase 1 has composed
76
+ them into one analysis and envelope in `src/explain.ts` (`explainCommand` /
77
+ `explainEnvelope`, #202a). **`centrs explain '<input>'` is a real command as
78
+ of #202b** (`src/cli/explain.ts`), and #202c adds statement argument reading,
79
+ the closed Q8 transport classifier, equivalent centrs invocations, and
80
+ opt-in `--curl`. All offline examples, including 1, 2, 6 and 23, run green in
81
+ `test/unit/explain.test.ts`. The row nonetheless stays `designed`: offline
82
+ mode occupies no protocol cell, and the two `designed` cells are the live
83
+ `/console/inspect` + `:parse` probes, which are phase 2. The live grammar
84
+ (`explain <router> '<input>'`) is parsed and refused with
85
+ `usage/not-implemented` rather than degraded to an offline run. Cells advance
86
+ only when the live probes land and their examples run green per the done rule.
87
+ `src/explain/menus.ts` and `src/explain/catalog.ts` are the **generated**
88
+ files in that set — the RouterOS menu-vs-command table (#207, from pinned
89
+ restraml typed trees) and the path catalog (#228, those trees unioned with
90
+ MikroTik's published CLI Reference, carrying kind, per-entry provenance and
91
+ the published applicability gate). Regenerate with `bun run explain:menus` /
92
+ `bun run explain:catalog`; both are drift-checked in QA. They keep the offline
93
+ analyzer offline; do not hand-edit them. The catalog's **command axis is read**
94
+ by `pathresolve.ts`, `verbsplit.ts` and `write.ts` (#228 step 2); #235 also
95
+ reads its menu/settings kind, unioned with `menus.ts` behind
96
+ `src/explain/is-known-menu.ts`, for both a context-applied relative bare path
97
+ and an absolute one. Both changes are inside the offline canonicalizer, so
98
+ this grid is unchanged by them.
99
+ - `transfer / ssh` means the SFTP-backed transfer method. Deferred file-transfer
100
+ methods such as `scp`, `fetch`, and `ftp` are tracked in
101
+ `commands/transfer/README.md`; `fetch` is not a grid column.
102
+ - SSH grounding for `execute` and `terminal` lives in
103
+ `commands/execute/README.md`, `commands/terminal/README.md`,
104
+ `src/protocols/ssh.ts`, `src/protocols/sftp.ts`, and `src/terminal.ts`.
105
+ - MAC-Telnet grounding for `execute` and `terminal` lives in
106
+ `commands/execute/README.md`, `commands/terminal/README.md`, and the
107
+ `src/protocols/mac-telnet*.ts` module headers.
108
+ - Transport wire-format and delivery caveats belong in `src/protocols/*.ts`
109
+ module headers. Do not expand this file with per-packet or per-router findings.
110
+
111
+ Beyond the grid: post-`CHR-passed` hardening, ergonomics, and research work is
112
+ tracked in GitHub issues (`dry-july` and successors). Those issues may point back
113
+ to this matrix for current status, but they are not additional cell states.
114
+
115
+ ### Transport-base readiness
124
116
 
125
117
  The grid above tracks command cells. The transports those cells ride are
126
118
  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.
119
+ single home for wire-format, auth, and delivery facts.
120
+
121
+ | Transport | Status pointer |
122
+ | --------- | -------------- |
123
+ | native-api | Used by `retrieve`, `api`, `execute`, and `transfer`; wire facts in `src/protocols/native-api.ts`. |
124
+ | ssh / sftp | Used by `transfer`, `execute`, and `terminal`; facts in `src/protocols/ssh.ts`, `src/protocols/sftp.ts`, and `src/terminal.ts`. |
125
+ | 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
126
 
165
127
  ### Frontend surfaces (orthogonal to the command grid)
166
128
 
@@ -169,9 +131,9 @@ core and carry their own state, tracked here:
169
131
 
170
132
  | Surface | State | Spec | Notes |
171
133
  | ------- | ----- | ---- | ----- |
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.) |
173
- | 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. |
134
+ | ts-api | `CHR-passed` | `src/index.ts` | Root TypeScript library surface; everything else adapts it. |
135
+ | cli | `coded` | `src/cli/` | Every command in `cliCommands` (`src/cli.ts`) is wired: `retrieve`/`execute`/`api`/`transfer`/`terminal`/`explain`/`devices`/`discover`/`btest`/`mcp`/`settings`, plus the `upload`/`download` shortcuts. |
136
+ | mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; details in `commands/mcp/README.md` and `test/integration/mcp.test.ts`. |
175
137
  | tui | `not-started` | `src/tui.ts` | Stub. |
176
138
  | proxy | `not-started` | `src/webproxy.ts` | Stub. |
177
139
 
@@ -183,46 +145,17 @@ are documented in `commands/mcp/README.md` and `commands/mcp/examples.md`.
183
145
 
184
146
  ### Peer measurement (`btest`) — orthogonal to the command grid
185
147
 
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).
148
+ `btest` is the MikroTik bandwidth test (peer measurement, TCP/UDP port 2000),
149
+ not a RouterOS-command transport. It sits outside the command×protocol grid as
150
+ its own capability axis and is exempt from the RouterOS command-validation gate.
192
151
 
193
152
  | Mode | State | Evidence |
194
153
  | -------------- | ------------ | -------- |
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.
154
+ | btest / server | `CHR-passed` | `test/integration/btest.test.ts`; details in `commands/btest/README.md`. |
155
+ | btest / client | `CHR-passed` | `test/integration/btest-client.test.ts`; details in `commands/btest/README.md`. |
156
+
157
+ Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
158
+ `src/protocols/btest*.ts` / `src/protocols/ec-srp5.ts` module headers.
226
159
 
227
160
  ## Priority order
228
161
 
@@ -230,18 +163,18 @@ Do not start a later item until the earlier cell or dependency checkpoint has
230
163
  matching evidence. The grid above is the live status; per-item detail lives in
231
164
  the linked `commands/<name>/`.
232
165
 
233
- 1. **retrieve / rest-api** — `CHR-passed`. The shakedown for everything else (`commands/retrieve/`).
166
+ 1. **retrieve / rest-api** — `CHR-passed` (`commands/retrieve/`).
234
167
  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`.
168
+ 3. **CDB groups + fan-out** — `CHR-passed` (`commands/devices/`, fan-out core).
169
+ 4. **execute / native-api + rest-api** — `CHR-passed` (`commands/execute/`).
237
170
  5. **devices** (CDB mutation + provenance) — `CHR-passed` (`commands/devices/`).
238
171
  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.
172
+ 7. **ssh** for transfer/execute/terminal — all three `CHR-passed`.
173
+ 8. **mac-telnet** for execute/terminal — both `CHR-passed`.
241
174
  9. **RoMON / WinBox Terminal for execute** — `not-started` (see Open questions).
242
175
  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.
176
+ 11. **MCP, TUI, proxy** — MCP `CHR-passed`; TUI/proxy `not-started`.
177
+ 12. **btest (peer measurement)** — server and client both `CHR-passed`.
245
178
 
246
179
  ## Open questions (decisions needed before the affected cell can advance)
247
180
 
@@ -47,10 +47,14 @@ Codes are slash-namespaced `family/slug`. The families and where they originate:
47
47
  - `input/*` — malformed caller input (paths, MACs, arguments).
48
48
  - `internal/*` — centrs bugs / unreachable states.
49
49
  - `mndp/*` — MNDP wire codec and listener (`src/data/mndp.ts`).
50
+ - `quickchr/*` — the quickchr named-live-provider (`--quickchr <name>`): optional
51
+ package availability, descriptor version support, and machine lookup/liveness
52
+ (`src/resolver/quickchr-provider.ts`).
50
53
  - `routeros/*` — RouterOS-surfaced faults mapped by the grounded vocabulary in
51
54
  `src/core/routeros-errors.ts` (REST `detail` and native-api `!trap`, identical
52
55
  on CHR 7.23).
53
- - `settings/*` — settings/value parsing (`src/resolver/settings.ts`).
56
+ - `settings/*` — settings/value parsing (`src/resolver/settings.ts`) and the
57
+ `centrs settings` command's own registry/refusal rules (`src/settings.ts`).
54
58
  - `target/*` — turning a `<router>` into a transport endpoint.
55
59
  - `transport/*` — connection, TLS, DNS, and timeout plumbing.
56
60
  - `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
+ # `quickchr/machine-not-found`
2
+
3
+ No quickchr machine is registered under the name passed to `--quickchr <name>`.
4
+
5
+ ## Typical trigger
6
+
7
+ The name does not match any machine quickchr knows about — a typo, a machine
8
+ that was never created, or one that has been removed. centrs raises this both
9
+ when `QuickCHR.get(name)` returns nothing and when the descriptor call reports
10
+ quickchr's own `MACHINE_NOT_FOUND`.
11
+
12
+ ## Fix
13
+
14
+ - List the registered machines with `quickchr list` and pass an existing name.
15
+ - Create/start the machine first if it does not exist yet (`quickchr start
16
+ <name>`).
17
+ - Names are exact — check for typos and case.
18
+
19
+ If the machine exists but is not running, you will see
20
+ [`quickchr/machine-stopped`](./machine-stopped.md) instead.
21
+
22
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers.
@@ -0,0 +1,20 @@
1
+ # `quickchr/machine-stopped`
2
+
3
+ The named quickchr machine exists but is not running, so it has no live
4
+ connection facts (host, forwarded ports, credentials) for centrs to dial.
5
+
6
+ ## Typical trigger
7
+
8
+ `--quickchr <name>` named a real machine, but its VM is stopped. quickchr only
9
+ knows the forwarded loopback ports and running-state auth while the machine is
10
+ up, so a stopped machine cannot yield a `ServiceEndpointMap`.
11
+
12
+ ## Fix
13
+
14
+ - Start it: `quickchr start <name>`, then retry `--quickchr <name>`.
15
+ - Confirm state with `quickchr list` / `quickchr inspect <name>`.
16
+
17
+ This is a per-target failure: in a fan-out over several `--quickchr` targets,
18
+ one stopped machine fails only its own row, not the whole run.
19
+
20
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers.
@@ -0,0 +1,27 @@
1
+ # `quickchr/package-unavailable`
2
+
3
+ The optional `@tikoci/quickchr` package is not installed, so a `--quickchr
4
+ <name>` target cannot be resolved into connection facts.
5
+
6
+ ## Typical trigger
7
+
8
+ You passed `--quickchr <name>` (or otherwise resolved a quickchr target) in an
9
+ install that does not have `@tikoci/quickchr`. centrs declares it as an
10
+ **optional dependency** and loads it through a runtime import, so a normal
11
+ install without it works for every non-quickchr path and only fails here.
12
+
13
+ ## Fix
14
+
15
+ - Install the package: `bun add @tikoci/quickchr` (0.4.5+ — 0.4.4 introduced
16
+ the descriptor v1 API but advertises dead TLS endpoints on a stock CHR;
17
+ see quickchr#95).
18
+ - Or bypass quickchr entirely and target the device directly with `--host`
19
+ (plus `--username`/`--password`/`--ssh-key` or a CDB record).
20
+
21
+ This code is raised **only** when the module is genuinely not found. An
22
+ installed package that throws while loading reports
23
+ [`quickchr/unsupported`](./unsupported.md) instead, so the two are never
24
+ confused.
25
+
26
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
27
+ the named-live-provider contract.
@@ -0,0 +1,35 @@
1
+ # `quickchr/unsupported-via`
2
+
3
+ The quickchr machine resolved fine, but the chosen `--via` has no usable
4
+ service on it. The descriptor is per-service; centrs reads exactly the
5
+ endpoint for the chosen protocol and never falls back to another transport.
6
+
7
+ ## Typical trigger
8
+
9
+ Any of:
10
+
11
+ - The `--via` is a protocol the descriptor does not forward. quickchr v1
12
+ forwards `rest-api`, `native-api`, and `ssh` only — `mac-telnet`, `romon`,
13
+ `btest`, and `winbox-terminal` are never available on a quickchr target.
14
+ - The service exists but quickchr marked it `available: false` (the error
15
+ carries the provider's `unavailableReason`).
16
+ - `--via ssh` (or `transfer --via sftp`/`scp`): the SSH endpoint reports no
17
+ batch-capable auth mode (`batchModes` is empty), so a non-interactive
18
+ handoff is impossible. centrs fails typed here rather than prompt for a
19
+ password or silently pick another transport.
20
+
21
+ ## Fix
22
+
23
+ - Pick one of the machine's live services; the error's `availableServices`
24
+ context lists them (typically `--via rest-api` / `--via native-api` /
25
+ `--via ssh`).
26
+ - For the SSH/sftp gate, verify key-based login works from quickchr's side
27
+ (`quickchr ssh <name>`); once quickchr has a verified key, the descriptor
28
+ advertises `private-key` in `batchModes` and centrs will use it.
29
+ - If the machine should be forwarding the service but is not, inspect it with
30
+ `quickchr inspect <name>`.
31
+ - To use a transport quickchr does not forward, target the device directly
32
+ (`--host`/CDB record) instead of `--quickchr`.
33
+
34
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
35
+ why a provider's missing service is a typed error, never a silent fallback.
@@ -0,0 +1,35 @@
1
+ # `quickchr/unsupported`
2
+
3
+ The installed `@tikoci/quickchr` cannot give centrs a usable descriptor: it is
4
+ too old to expose the descriptor API, it failed to load, or it emitted a
5
+ descriptor centrs cannot read.
6
+
7
+ ## Typical trigger
8
+
9
+ Any of:
10
+
11
+ - The installed quickchr predates the `QuickCHR.get(name).descriptor()` API
12
+ (before 0.4.4), so the descriptor entry point is missing.
13
+ - The package is installed but throws while loading (a broken/partial install),
14
+ which is a real fault — distinct from
15
+ [`quickchr/package-unavailable`](./package-unavailable.md), which means "not
16
+ installed at all".
17
+ - The descriptor is malformed or pre-v1: no numeric `descriptorVersion`, no
18
+ `services` map, or missing `name`/`version`/`arch`. centrs rejects it rather
19
+ than crash on a later field access or silently treat `services` as empty
20
+ (which would hide a provider contract violation).
21
+ - The descriptor version is **newer** than centrs supports
22
+ (`descriptorVersion` > 1); a newer quickchr speaks a schema this centrs
23
+ predates.
24
+
25
+ ## Fix
26
+
27
+ - Upgrade quickchr to a release that emits descriptor v1: `bun add
28
+ @tikoci/quickchr` (0.4.5+ — 0.4.4 has the API but a descriptor endpoint
29
+ bug, quickchr#95).
30
+ - If the descriptor looks wrong, inspect the machine directly with `quickchr
31
+ inspect <name>` — it may be in a transient state.
32
+ - If the descriptor is newer than centrs supports, upgrade centrs.
33
+
34
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
35
+ the descriptor contract and the supported version boundary.
@@ -0,0 +1,26 @@
1
+ # `routeros/version-unsupported`
2
+
3
+ The device's RouterOS version does not provide the operation this command
4
+ needs. centrs pre-flighted the generated command against the device's known
5
+ version (a live read, or the stored CDB `version=` fact) and refused it before
6
+ touching the router, instead of letting the device return a raw protocol
7
+ error.
8
+
9
+ Distinct from `routeros/unsupported-capability`, where the chosen *protocol*
10
+ cannot carry the operation at all regardless of device version.
11
+
12
+ ## Context fields
13
+
14
+ - `capability` — stable slug of the missing operation.
15
+ - `version` — the device version that was checked.
16
+ - `versionSource` — `live`, `cdb-fact`, or `caller`. A `cdb-fact` version is
17
+ stale-by-nature; it was recorded by `discover --save` or `devices --check`.
18
+ - `supported` — the version windows where RouterOS provides the operation
19
+ (inclusive `min`, optional exclusive `maxExclusive` for backport windows).
20
+
21
+ ## Fix
22
+
23
+ Upgrade RouterOS to one of the versions listed in `supported`. If the device
24
+ was already upgraded and the check used a stale stored fact
25
+ (`versionSource: "cdb-fact"`), refresh the record's `version=` comment fact
26
+ and retry.