@tikoci/centrs 0.1.0
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.
- package/CHANGELOG.md +207 -0
- package/GLOSSARY.txt +420 -0
- package/README.md +196 -0
- package/SECURITY.md +30 -0
- package/commands/AGENTS.md +50 -0
- package/commands/btest/README.md +257 -0
- package/commands/btest/examples.md +190 -0
- package/commands/check/README.md +59 -0
- package/commands/devices/README.md +405 -0
- package/commands/devices/examples.md +488 -0
- package/commands/discover/README.md +170 -0
- package/commands/discover/examples.md +98 -0
- package/commands/execute/README.md +208 -0
- package/commands/execute/examples.md +353 -0
- package/commands/mcp/README.md +260 -0
- package/commands/mcp/examples.md +158 -0
- package/commands/retrieve/README.md +209 -0
- package/commands/retrieve/examples.md +306 -0
- package/commands/settings/README.md +69 -0
- package/commands/stream/README.md +70 -0
- package/commands/terminal/README.md +177 -0
- package/commands/terminal/examples.md +144 -0
- package/commands/transfer/README.md +315 -0
- package/commands/transfer/examples.md +368 -0
- package/docs/CONSTITUTION.md +417 -0
- package/docs/MATRIX.md +224 -0
- package/docs/errors/README.md +74 -0
- package/docs/errors/auth/failed.md +9 -0
- package/docs/errors/cdb/already-exists.md +9 -0
- package/docs/errors/cdb/created.md +9 -0
- package/docs/errors/cdb/decrypt-failed.md +9 -0
- package/docs/errors/cdb/empty-group.md +9 -0
- package/docs/errors/cdb/invalid-option.md +9 -0
- package/docs/errors/cdb/invalid-record.md +9 -0
- package/docs/errors/cdb/not-found-target.md +9 -0
- package/docs/errors/cdb/not-found.md +9 -0
- package/docs/errors/cdb/override-applied.md +9 -0
- package/docs/errors/cdb/parse-failed.md +9 -0
- package/docs/errors/cdb/password-not-needed.md +9 -0
- package/docs/errors/cdb/password-required.md +9 -0
- package/docs/errors/cdb/reserved-key.md +9 -0
- package/docs/errors/cdb/reserved-option.md +9 -0
- package/docs/errors/cdb/target-not-registered.md +9 -0
- package/docs/errors/cdb/unknown-field.md +9 -0
- package/docs/errors/cdb/unknown-option.md +9 -0
- package/docs/errors/cdb/write-not-permitted.md +9 -0
- package/docs/errors/discover/broadcast-unavailable.md +9 -0
- package/docs/errors/discover/failed.md +9 -0
- package/docs/errors/identity/ambiguous.md +9 -0
- package/docs/errors/identity/no-match.md +9 -0
- package/docs/errors/input/invalid-command.md +9 -0
- package/docs/errors/input/invalid-match.md +9 -0
- package/docs/errors/input/invalid-path.md +9 -0
- package/docs/errors/input/invalid-routeros-path.md +9 -0
- package/docs/errors/input/invalid-target.md +9 -0
- package/docs/errors/input/local-file-not-found.md +9 -0
- package/docs/errors/input/mac-address.md +9 -0
- package/docs/errors/input/max-results-exceeded.md +9 -0
- package/docs/errors/internal/devices-failed.md +9 -0
- package/docs/errors/internal/mcp-start.md +9 -0
- package/docs/errors/internal/mcp-tool.md +9 -0
- package/docs/errors/internal/native-api-length.md +9 -0
- package/docs/errors/internal/unhandled.md +9 -0
- package/docs/errors/internal/unreachable.md +9 -0
- package/docs/errors/mndp/encode-failed.md +9 -0
- package/docs/errors/mndp/listen-failed.md +9 -0
- package/docs/errors/mndp/malformed.md +9 -0
- package/docs/errors/routeros/api-fatal.md +9 -0
- package/docs/errors/routeros/api-protocol.md +9 -0
- package/docs/errors/routeros/api-trap.md +9 -0
- package/docs/errors/routeros/btest-protocol.md +12 -0
- package/docs/errors/routeros/btest-too-many-sessions.md +9 -0
- package/docs/errors/routeros/command-failed.md +14 -0
- package/docs/errors/routeros/ec-srp5-protocol.md +11 -0
- package/docs/errors/routeros/error.md +9 -0
- package/docs/errors/routeros/invalid-value.md +14 -0
- package/docs/errors/routeros/mac-telnet-error.md +9 -0
- package/docs/errors/routeros/mac-telnet-not-ready.md +9 -0
- package/docs/errors/routeros/mac-telnet-protocol.md +9 -0
- package/docs/errors/routeros/mac-telnet-unsupported-auth.md +9 -0
- package/docs/errors/routeros/protocol-not-implemented.md +9 -0
- package/docs/errors/routeros/request-failed.md +9 -0
- package/docs/errors/routeros/session-closed.md +13 -0
- package/docs/errors/routeros/unknown-attribute.md +13 -0
- package/docs/errors/routeros/unknown-path.md +15 -0
- package/docs/errors/routeros/unsupported-capability.md +9 -0
- package/docs/errors/settings/invalid-boolean.md +9 -0
- package/docs/errors/settings/invalid-format.md +9 -0
- package/docs/errors/settings/invalid-integer.md +9 -0
- package/docs/errors/settings/invalid-timeout.md +9 -0
- package/docs/errors/settings/invalid-via.md +9 -0
- package/docs/errors/settings/unsafe-protocol-blocked.md +9 -0
- package/docs/errors/target/mac-not-in-arp.md +14 -0
- package/docs/errors/target/mac-required.md +23 -0
- package/docs/errors/target/mac-unresolved.md +17 -0
- package/docs/errors/target/unresolved.md +9 -0
- package/docs/errors/transport/auth-failed.md +9 -0
- package/docs/errors/transport/auto-method.md +9 -0
- package/docs/errors/transport/capability-unsupported.md +22 -0
- package/docs/errors/transport/checksum-unavailable.md +9 -0
- package/docs/errors/transport/connection-closed.md +9 -0
- package/docs/errors/transport/connection-refused.md +9 -0
- package/docs/errors/transport/dns.md +9 -0
- package/docs/errors/transport/host-key-mismatch.md +9 -0
- package/docs/errors/transport/incomplete-transfer.md +9 -0
- package/docs/errors/transport/insecure-trust.md +9 -0
- package/docs/errors/transport/local-tool-missing.md +11 -0
- package/docs/errors/transport/network.md +9 -0
- package/docs/errors/transport/timeout.md +9 -0
- package/docs/errors/transport/tls-certificate.md +9 -0
- package/docs/errors/transport/unreachable.md +9 -0
- package/docs/errors/transport/unsupported-operation.md +9 -0
- package/docs/errors/usage/confirmation-required.md +9 -0
- package/docs/errors/usage/conflicting-flags.md +9 -0
- package/docs/errors/usage/invalid-concurrency.md +9 -0
- package/docs/errors/usage/missing-group.md +9 -0
- package/docs/errors/usage/not-implemented.md +9 -0
- package/docs/errors/usage/target-exists.md +9 -0
- package/docs/errors/usage/timeout-out-of-range.md +9 -0
- package/docs/errors/validation/not-implemented.md +9 -0
- package/docs/errors/validation/option.md +9 -0
- package/docs/errors/validation/syntax.md +9 -0
- package/docs/errors/validation/unknown-attribute.md +9 -0
- package/docs/errors/validation/unknown-path.md +9 -0
- package/package.json +96 -0
- package/src/AGENTS.md +7 -0
- package/src/btest.ts +906 -0
- package/src/cli/btest.ts +455 -0
- package/src/cli/common.ts +53 -0
- package/src/cli/devices.ts +527 -0
- package/src/cli/discover.ts +186 -0
- package/src/cli/execute.ts +333 -0
- package/src/cli/mcp.ts +109 -0
- package/src/cli/retrieve.ts +383 -0
- package/src/cli/terminal.ts +303 -0
- package/src/cli/transfer.ts +471 -0
- package/src/cli.ts +115 -0
- package/src/core/envelope.ts +221 -0
- package/src/core/error-catalog.ts +469 -0
- package/src/core/routeros-errors.ts +268 -0
- package/src/data/AGENTS.md +11 -0
- package/src/data/dude-db.ts +0 -0
- package/src/data/mndp-cache.ts +132 -0
- package/src/data/mndp.ts +405 -0
- package/src/data/winbox-cdb-write.ts +200 -0
- package/src/data/winbox-cdb.ts +913 -0
- package/src/devices-schema.ts +107 -0
- package/src/devices.ts +1717 -0
- package/src/discover.ts +802 -0
- package/src/errors.ts +189 -0
- package/src/execute.ts +1350 -0
- package/src/index.ts +353 -0
- package/src/mcp/config.ts +66 -0
- package/src/mcp/resources.ts +98 -0
- package/src/mcp/safety.ts +119 -0
- package/src/mcp/server.ts +225 -0
- package/src/mcp/tools.ts +657 -0
- package/src/mcp.ts +33 -0
- package/src/protocols/AGENTS.md +14 -0
- package/src/protocols/adapter.ts +866 -0
- package/src/protocols/btest-session.ts +1493 -0
- package/src/protocols/btest.ts +447 -0
- package/src/protocols/ec-srp5.ts +466 -0
- package/src/protocols/index.ts +118 -0
- package/src/protocols/mac-telnet-console.ts +655 -0
- package/src/protocols/mac-telnet.ts +1299 -0
- package/src/protocols/mtwei.ts +47 -0
- package/src/protocols/native-api.ts +796 -0
- package/src/protocols/sftp.ts +427 -0
- package/src/protocols/ssh.ts +297 -0
- package/src/resolver/cdb.ts +580 -0
- package/src/resolver/comment-kv.ts +360 -0
- package/src/resolver/index.ts +74 -0
- package/src/resolver/mac.ts +227 -0
- package/src/resolver/settings.ts +228 -0
- package/src/resolver/target.ts +442 -0
- package/src/retrieve-fanout.ts +533 -0
- package/src/retrieve.ts +1215 -0
- package/src/terminal.ts +444 -0
- package/src/transfer.ts +1554 -0
- package/src/tui.ts +22 -0
- package/src/webproxy.ts +16 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Constitution
|
|
2
|
+
|
|
3
|
+
The load-bearing rules for `centrs`. Everything else (per-command READMEs, the
|
|
4
|
+
matrix, code, tests) must be consistent with this file. If something here is
|
|
5
|
+
wrong, fix it here first, then propagate. If a rule needs an exception, add the
|
|
6
|
+
exception here — not in scattered prose.
|
|
7
|
+
|
|
8
|
+
This file is short on purpose. Resist the urge to grow it.
|
|
9
|
+
|
|
10
|
+
## What centrs is
|
|
11
|
+
|
|
12
|
+
A typed library, with CLI/MCP/TUI/proxy frontends, that reaches RouterOS over
|
|
13
|
+
multiple protocols and gives the caller **validated** access plus structured,
|
|
14
|
+
actionable diagnostics. The TypeScript API is the root surface; everything else
|
|
15
|
+
is an adapter over it.
|
|
16
|
+
|
|
17
|
+
The product is *not* "REST/SSH/native API wrappers." The product is
|
|
18
|
+
**validation + structured envelopes + identity resolution** on top of those
|
|
19
|
+
transports. Without those, this is a worse `curl`.
|
|
20
|
+
|
|
21
|
+
centrs also speaks a small set of MikroTik **peer protocols** that are *not*
|
|
22
|
+
RouterOS-command transports — currently the **bandwidth test** (`btest`), which
|
|
23
|
+
mirrors `/tool/bandwidth-test` and `/tool/bandwidth-server`. This is a
|
|
24
|
+
deliberate, separate capability axis (*measurement*, not "run a validated
|
|
25
|
+
command"): centrs still gives structured envelopes, identity resolution, and
|
|
26
|
+
friendly errors, but the "validate a RouterOS-shaped command" gate below does
|
|
27
|
+
**not** apply to it (see "Validation is the product" and Protocol selection's
|
|
28
|
+
`measure` row).
|
|
29
|
+
|
|
30
|
+
## Validation is the product
|
|
31
|
+
|
|
32
|
+
Every RouterOS-shaped call goes through canonicalize → validate → run →
|
|
33
|
+
re-validate-server-side. Validation is not optional polish.
|
|
34
|
+
|
|
35
|
+
- `retrieve` and other read-shaped calls validate against
|
|
36
|
+
`/console/inspect request=syntax path=...,print` (path joined by commas) and
|
|
37
|
+
attribute inspection. The path syntax (commas, no leading slash, last token
|
|
38
|
+
is the verb) is part of the validator contract.
|
|
39
|
+
- `execute` and other CLI-shaped calls validate with `:put [:parse "..."]` plus
|
|
40
|
+
a semantic gate. **How `:parse` surfaces an error is transport-specific**
|
|
41
|
+
(grounded on CHR 7.23.1): `:parse` *does* reject an unknown attribute
|
|
42
|
+
(`bad parameter <name>`) and malformed CLI (`syntax error`), but over REST that
|
|
43
|
+
text rides the **HTTP-200 `ret` value** (no error status), and over the
|
|
44
|
+
**native API** `:put [:parse]` returns an opaque `*NN` handle that reveals
|
|
45
|
+
nothing at all. centrs's REST/native syntax gate runs `:parse` but does not
|
|
46
|
+
read the `ret`, so for those transports the unknown-attribute catch comes from
|
|
47
|
+
the separate **`/console/inspect`** semantic gate (or the server's own
|
|
48
|
+
write re-validation); the `:parse` round-trip there mainly backstops transport
|
|
49
|
+
errors and the local quote preflight. Over **mac-telnet** the interactive
|
|
50
|
+
console *prints* the `:parse` result, so centrs reads it and a single console
|
|
51
|
+
`:parse` covers both syntax and the unknown-attribute (semantic) gate — no
|
|
52
|
+
`/console/inspect` table needed. In every case a clean parse is necessary, not
|
|
53
|
+
sufficient on its own; semantic validation is a distinct, transport-appropriate
|
|
54
|
+
step (`/console/inspect`, the console `:parse` text, or server re-validation).
|
|
55
|
+
- **Peer-measurement commands (`btest`) are exempt from this gate** — they issue
|
|
56
|
+
no RouterOS command, so they validate *option-grammar* + the **EC-SRP5** auth
|
|
57
|
+
exchange instead (the envelope, identity-resolution, and friendly-error
|
|
58
|
+
contract still hold; only the RouterOS-command gate is absent). The grammar
|
|
59
|
+
rules are in `commands/btest/README.md`.
|
|
60
|
+
- A failing validator is a real result. Surface it with the structured error
|
|
61
|
+
envelope below — do not bypass.
|
|
62
|
+
|
|
63
|
+
There is no `update` verb: generalizing RouterOS add/set/remove into one abstract
|
|
64
|
+
write would need too many heuristics, so writes ride `execute`'s
|
|
65
|
+
canonicalize → validate → run path while `retrieve` stays read-only (see Protocol
|
|
66
|
+
selection).
|
|
67
|
+
|
|
68
|
+
**Disabling validation to make a call work is forbidden.** If the validator
|
|
69
|
+
rejects something a real router accepts, the validator is wrong; fix the
|
|
70
|
+
validator with CHR evidence. If a feature only passes its integration test
|
|
71
|
+
with `validate=false`, the feature is not done.
|
|
72
|
+
|
|
73
|
+
The default is `validate=true`. `--validate=false` exists as an escape hatch
|
|
74
|
+
for probing undocumented edges, never as a workaround for a centrs bug.
|
|
75
|
+
|
|
76
|
+
## Result envelope
|
|
77
|
+
|
|
78
|
+
Every API/CLI call returns the same shape, regardless of transport:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
{
|
|
82
|
+
ok: boolean, // true if the operation succeeded end-to-end
|
|
83
|
+
data?: unknown, // the payload (array of records, single object, etc.)
|
|
84
|
+
warnings?: Warning[], // non-fatal anomalies (stale cache, unused --cdb-password, etc.)
|
|
85
|
+
tips?: Tip[], // advice, not anomalies (no username set, no CDB found, …)
|
|
86
|
+
error?: CentrsError, // present iff ok=false
|
|
87
|
+
meta: {
|
|
88
|
+
target: { ... }, // resolved target + provenance (where each field came from)
|
|
89
|
+
via: RouterOsProtocol, // chosen protocol id
|
|
90
|
+
settings: { ... }, // settings winners + sources (env / cli / cdb / default)
|
|
91
|
+
validation?: { ... }, // validator name + result, if validation ran
|
|
92
|
+
timing?: { ... }, // request/response durations
|
|
93
|
+
truncated?: { ... }, // populated when --max-bytes/--max-rows clipped output
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Notes:
|
|
99
|
+
|
|
100
|
+
- "Success with footnotes" is supported: `ok: true` may carry warnings.
|
|
101
|
+
- `warnings` and `tips` are both always present (`[]` when empty) so consumers
|
|
102
|
+
never branch on existence. They are distinct on purpose: a **warning** is a
|
|
103
|
+
non-fatal anomaly about *this* result (stale cache, unused `--cdb-password`); a
|
|
104
|
+
**tip** is advice that is explicitly *not* an error or anomaly (no username
|
|
105
|
+
set, no CDB found, consider a default device). The "this is not a problem,
|
|
106
|
+
just a pointer" signal is the whole reason tips are a separate channel rather
|
|
107
|
+
than folded into warnings. A `Tip` has the same shape as a warning
|
|
108
|
+
(`{ code, message, fix? }`) with `tip/*` codes and a `details_url`.
|
|
109
|
+
- CLI render must be lossless against the JSON envelope; `--format json` and
|
|
110
|
+
`--format yaml` are aliases that select serialization, not different shapes.
|
|
111
|
+
Text mode renders tips under a `Tips:` footer, distinct from warnings. This
|
|
112
|
+
lossless rule applies to **stdout** only: **stderr** carries progress and
|
|
113
|
+
interactive prompts (discover scan progress, write/ambiguity confirmation) and
|
|
114
|
+
is exempt — use it sparingly, since most operations are fast.
|
|
115
|
+
- `data` is the only field whose *shape* varies by command. Everything else —
|
|
116
|
+
including `warnings` and `tips` — is invariant in shape; only contents differ.
|
|
117
|
+
|
|
118
|
+
## Error model
|
|
119
|
+
|
|
120
|
+
Errors are typed values, not thrown strings. Every error has:
|
|
121
|
+
|
|
122
|
+
- `code` — slash-namespaced, RouterOS-style, no spaces. Example:
|
|
123
|
+
`routeros/unsupported-path`, `transport/connection-refused`,
|
|
124
|
+
`validation/unknown-attribute`, `cdb/decrypt-failed`.
|
|
125
|
+
- `message` — one human sentence. No stack trace.
|
|
126
|
+
- `fix` — one human sentence describing the next step the caller should take.
|
|
127
|
+
- `details_url` — pointer to the canonical **per-code** page (the URL scheme is
|
|
128
|
+
in `docs/errors/README.md`).
|
|
129
|
+
- `cause?` — structured sub-error, when relevant (RouterOS nova error,
|
|
130
|
+
transport detail). Stack traces and raw exception text are summarized, not
|
|
131
|
+
embedded raw.
|
|
132
|
+
- `position?` — RouterOS's `(line N column M)` parse location, when the fault
|
|
133
|
+
carries one (console `:parse` rejections do). `column` is RouterOS's
|
|
134
|
+
authoritative 1-based **byte** offset — the console is not Unicode-aware — so
|
|
135
|
+
it is carried verbatim, never re-derived into a character index.
|
|
136
|
+
- `redactable_fields?` — names of fields the bug-report renderer must redact.
|
|
137
|
+
|
|
138
|
+
Credentials and private key material are always redactable. Private key paths are
|
|
139
|
+
configuration values and may appear in `meta.settings`; redact them only when an
|
|
140
|
+
error also carries sensitive key material or a caller explicitly marks the path
|
|
141
|
+
as sensitive.
|
|
142
|
+
|
|
143
|
+
The bug-report renderer is invoked **inline** via `--bug-report` on any command —
|
|
144
|
+
there is no separate `bug-report` verb. It re-renders the just-produced envelope
|
|
145
|
+
with `redactable_fields` stripped and each error's `details_url` kept, so a
|
|
146
|
+
shareable report needs no extra capture step (the envelope is already rich enough
|
|
147
|
+
to be the report).
|
|
148
|
+
|
|
149
|
+
Two error sources must be visually distinguishable:
|
|
150
|
+
|
|
151
|
+
1. **centrs errors** — bad usage, validator rejection, transport plumbing.
|
|
152
|
+
2. **RouterOS errors** — surfaced by the router (HTTP 4xx/5xx, parse refusal,
|
|
153
|
+
nova error). Map to a normalized `routeros/*` code, preserving the original
|
|
154
|
+
string.
|
|
155
|
+
|
|
156
|
+
RouterOS faults map to a normalized `routeros/*` code (preserving the original
|
|
157
|
+
string) via a CHR-grounded table shared by REST `detail` and native `!trap`
|
|
158
|
+
(`src/core/routeros-errors.ts`). Every error's `details_url` is the stable
|
|
159
|
+
`https://tikoci.github.io/centrs/errors/<code>`. The mapping shape, the
|
|
160
|
+
ground-on-CHR-evidence rule, and the URL-scheme rules live in
|
|
161
|
+
`docs/errors/README.md`.
|
|
162
|
+
|
|
163
|
+
## Settings precedence
|
|
164
|
+
|
|
165
|
+
Lowest to highest priority:
|
|
166
|
+
|
|
167
|
+
1. Built-in defaults
|
|
168
|
+
2. Config file: `${XDG_CONFIG_HOME:-~/.config}/tikoci/centrs.env` — optional
|
|
169
|
+
dotenv-style `CENTRS_*=value` **global** defaults (centrs-only preferences
|
|
170
|
+
with no per-device home; **not** an inventory or credential store — per-device
|
|
171
|
+
settings stay in the CDB). Applied as fallbacks, never injected into
|
|
172
|
+
`process.env`. The `centrs settings` command is its front-end; the file
|
|
173
|
+
mechanics, `--skip-env-file`, and the settings-key vocabulary live in
|
|
174
|
+
`commands/settings/README.md`.
|
|
175
|
+
3. CDB comment-kv metadata
|
|
176
|
+
4. Environment variables (`CENTRS_*`)
|
|
177
|
+
5. CLI flags / API call args
|
|
178
|
+
|
|
179
|
+
`meta.settings` reports the winning source for each resolved setting.
|
|
180
|
+
|
|
181
|
+
### Identity and CDB
|
|
182
|
+
|
|
183
|
+
The `<router>` argument may be an IP, a DNS name, a MAC, or an **identity**
|
|
184
|
+
(the device's `/system/identity`); centrs resolves it through the CDB (then
|
|
185
|
+
through ARP / MNDP-derived metadata as fallbacks, once implemented). Two
|
|
186
|
+
principles are load-bearing:
|
|
187
|
+
|
|
188
|
+
- **Record identity is the `(target, user)` pair** — matching WinBox, the same
|
|
189
|
+
address saved under a different user is a second record, not an update.
|
|
190
|
+
`group` is a flat attribute, never part of the key.
|
|
191
|
+
- **The CDB is both the device datastore and the credential store.** The WinBox
|
|
192
|
+
CDB file at its well-known location *is* the inventory; there is no separate
|
|
193
|
+
cache. centrs-specific meaning is overlaid through an allowlist of comment-kv keys —
|
|
194
|
+
the lookup keys `identity=`/`mac=`/`ip=` (the one sanctioned exception to "the
|
|
195
|
+
comment is free text") plus override keys like `via`/`port`/`ssh-key`; every
|
|
196
|
+
other comment token stays inert. **Anything expressible in the CDB must also
|
|
197
|
+
be expressible via env/CLI/API**, so tests and ad-hoc use never require one.
|
|
198
|
+
`dude.db` import is out of scope (→ `tikoci/donny`).
|
|
199
|
+
|
|
200
|
+
The lookup-key grammar, the CDB resolution and ambiguity rules (`--match`,
|
|
201
|
+
`identity/ambiguous`, `--prefer-family`), the comment-kv allowlist, encrypted-CDB
|
|
202
|
+
handling, and the atomic write strategy live in `commands/devices/README.md` —
|
|
203
|
+
`devices` is the registry command and the CDB's only writer.
|
|
204
|
+
|
|
205
|
+
#### Default-device record (`__default__`)
|
|
206
|
+
|
|
207
|
+
A reserved CDB record (`target=__default__`) supplies fallback metadata +
|
|
208
|
+
credentials, **per field**, for a resolved device whose own record leaves them
|
|
209
|
+
unset. Precedence is per-field: per-call args → env (`CENTRS_*`) → matched device
|
|
210
|
+
record → `__default__` → built-in default. It is **core** (honored by CLI, API,
|
|
211
|
+
and MCP) and **never widens the MCP allowlist**: on MCP an unregistered target is
|
|
212
|
+
still `cdb/target-not-registered`, and `__default__` only fills *missing* creds
|
|
213
|
+
for an already-registered device. Create/edit it like any record; mechanics and
|
|
214
|
+
examples are in `commands/devices/README.md`.
|
|
215
|
+
|
|
216
|
+
Group selectors (e.g. `--group prod-edge`) target CDB groups so a single
|
|
217
|
+
`retrieve`/`execute`/etc. fans out to multiple routers. Group output shape
|
|
218
|
+
must round-trip through the same envelope.
|
|
219
|
+
|
|
220
|
+
#### Target selection grammar
|
|
221
|
+
|
|
222
|
+
One grammar across every non-terminal command. A call selects targets from any
|
|
223
|
+
mix of: `<router>` positionals (one or more), repeatable `--group <name>`,
|
|
224
|
+
`--all` (every CDB record), `--default` (the `__default__` record), and
|
|
225
|
+
repeatable `--where <attr>=<value>` — a **device-class** selector that matches
|
|
226
|
+
CDB-stored facts/comment-kv (e.g. `--where board=RB5009`), AND-combined across
|
|
227
|
+
repeats. The union is **de-duped by CDB record index** and run through the same
|
|
228
|
+
per-target pipeline; the `data` array is reassembled in resolved record order,
|
|
229
|
+
not completion order, so repeated runs diff cleanly. Destructive multi-target
|
|
230
|
+
operations (e.g. `devices remove` across a group) require `--force`. `terminal`
|
|
231
|
+
and single-session `stream` are not fan-out surfaces and reject N>1 with
|
|
232
|
+
`usage/fanout-not-supported`.
|
|
233
|
+
|
|
234
|
+
`--where` filters *which devices* by CDB-stored facts; keep it distinct from
|
|
235
|
+
`retrieve`/`execute`'s `--query`/`--filter`, which filter *RouterOS rows* in the
|
|
236
|
+
response. Two layers, two flag families.
|
|
237
|
+
|
|
238
|
+
`--group` is purely a **selector** in this grammar; the group *field* is written
|
|
239
|
+
by a distinct `--new-group` flag on `devices add`/`set`, so record selection and
|
|
240
|
+
field-setting never collide on one flag.
|
|
241
|
+
|
|
242
|
+
### MCP surface: the CDB is the allowlist
|
|
243
|
+
|
|
244
|
+
The MCP frontend (`commands/mcp/`) is an adapter over this core, not a new set of
|
|
245
|
+
RouterOS semantics. Two boundaries are load-bearing:
|
|
246
|
+
|
|
247
|
+
- **Authorization is the CDB allowlist.** RouterOS-facing tools resolve targets
|
|
248
|
+
through the CDB only; an unregistered target is rejected
|
|
249
|
+
(`cdb/target-not-registered`), and `__default__` never widens the allowlist (see
|
|
250
|
+
Identity and CDB). The off-by-default `--allow-adhoc-targets` flag is reserved
|
|
251
|
+
for future inline-target flows — today no RouterOS-facing tool exposes an inline
|
|
252
|
+
host+credential schema, so the CDB allowlist is the only active target source.
|
|
253
|
+
- **Transport is stdio only.** HTTP/remote access is the proxy surface's job
|
|
254
|
+
(`src/webproxy.ts`), which fronts the same CDB; the MCP server grows no listener
|
|
255
|
+
of its own. One surface owns network exposure.
|
|
256
|
+
|
|
257
|
+
The scoped tool surface (verbs mirroring the CLI/API, never one tool per RouterOS
|
|
258
|
+
command), the per-device `mcp=ro|rw` + `confirm: true` write gate, the
|
|
259
|
+
`centrs://devices` / `centrs://errors` resources, and the full safety model are in
|
|
260
|
+
`commands/mcp/README.md`.
|
|
261
|
+
|
|
262
|
+
## Protocol selection
|
|
263
|
+
|
|
264
|
+
Per-operation preferences, downgrade order in parens:
|
|
265
|
+
|
|
266
|
+
| Operation | Preferred | Downgrade order |
|
|
267
|
+
| --------- | --------- | --------------- |
|
|
268
|
+
| retrieve | rest-api, native-api; snmp for OID/MIB reads (future) | rest-api, native-api |
|
|
269
|
+
| stream | native-api, ssh | (REST cannot follow — 60s cap; bounded or rejected) |
|
|
270
|
+
| execute | native-api → rest-api → mac-telnet | native-api, rest-api, mac-telnet, ssh, romon, winbox-terminal |
|
|
271
|
+
| terminal | ssh | mac-telnet (L2 only when ssh fails or MAC given) |
|
|
272
|
+
| transfer | size/direction-aware: rest-api/native-api for ≤60 KB writes & all reads (chunked), sftp for large uploads | sftp ⇄ rest/native by size; scp · fetch · ftp explicit-only |
|
|
273
|
+
| discover | mndp | (not a transport for command operations) |
|
|
274
|
+
| measure | btest | (explicit-only — no downgrade) |
|
|
275
|
+
|
|
276
|
+
Rules:
|
|
277
|
+
|
|
278
|
+
- **Read vs write:** `retrieve` is read-only (rest-api/native-api); `execute` is
|
|
279
|
+
the read **and** write surface — writes (add/set/remove) ride `execute`, and
|
|
280
|
+
there is no `update` operation.
|
|
281
|
+
- **Never silently downgrade across `--via`.** If the caller pinned a transport
|
|
282
|
+
that cannot do the operation, error out with a `transport/*` code.
|
|
283
|
+
Auto-selection (no `--via`) may downgrade *within* the table, but every hop is
|
|
284
|
+
reported in `meta.warnings` with its reason.
|
|
285
|
+
- **Execute vs retrieve surfaces:** native-api, rest-api, ssh, mac-telnet (and
|
|
286
|
+
later RoMON / WinBox Terminal) are execute surfaces; SNMP is **retrieve-only**.
|
|
287
|
+
mac-telnet is the default execute path for an unresolved-MAC target — IP-level
|
|
288
|
+
access needs explicit ARP opt-in.
|
|
289
|
+
- **`measure` (btest) is explicit-only** — never in the auto-select/downgrade
|
|
290
|
+
chain, never a substitute, and no other command rides it.
|
|
291
|
+
- Per-command selection **depth** lives in the command READMEs: `stream`'s
|
|
292
|
+
follow/NDJSON contract and the REST-cannot-follow constraint
|
|
293
|
+
(`commands/stream/README.md`), `transfer`'s size/direction-aware method
|
|
294
|
+
selection and the explicit-only `scp`/`ftp`/`fetch`
|
|
295
|
+
(`commands/transfer/README.md`), SNMP's MIB-cache
|
|
296
|
+
(`commands/retrieve/README.md`), and btest's port/auth
|
|
297
|
+
(`commands/btest/README.md`).
|
|
298
|
+
|
|
299
|
+
REST-specific constraint: RouterOS REST has a 60-second hard timeout. Do not
|
|
300
|
+
let `--timeout` exceed 60s when `via=rest-api`; reject with a clear error.
|
|
301
|
+
Other transports may accept longer timeouts.
|
|
302
|
+
|
|
303
|
+
### Transport trust (TLS / SSH host keys)
|
|
304
|
+
|
|
305
|
+
One opt-out across every transport: `--insecure` (`CENTRS_INSECURE`, CDB
|
|
306
|
+
`insecure=` comment-kv). Default is **verify**; `--insecure` disables peer
|
|
307
|
+
verification and adds a `transport/insecure-trust` warning to the envelope so the
|
|
308
|
+
downgrade is always visible. Because RouterOS ships **self-signed** certificates
|
|
309
|
+
and host keys, the *default posture per transport follows that transport's
|
|
310
|
+
ecosystem norm* rather than one literal value:
|
|
311
|
+
|
|
312
|
+
- **TLS** (REST `https`, native-api `api-ssl`) **verifies** by default; a
|
|
313
|
+
self-signed cert fails with `transport/tls-certificate` whose remediation names
|
|
314
|
+
`--insecure`. (Both transports honor the same knob — previously native-api
|
|
315
|
+
silently accepted any cert; that is fixed.)
|
|
316
|
+
- **SSH host keys** (sftp/scp) default to **trust-on-first-use**
|
|
317
|
+
(`StrictHostKeyChecking=accept-new` into the user's `known_hosts`): a new key is
|
|
318
|
+
accepted, a *changed* key fails with `transport/host-key-mismatch`. `--insecure`
|
|
319
|
+
disables the check entirely. TOFU is the universal SSH norm, so the SSH default
|
|
320
|
+
is laxer than the TLS default by design.
|
|
321
|
+
|
|
322
|
+
SSH identity selection: the `sshKey` setting (`--ssh-key`/`CENTRS_SSH_KEY`/CDB
|
|
323
|
+
`ssh-key=`) carries a private-key **path only** — never key material; when unset,
|
|
324
|
+
the host SSH agent / `~/.ssh/config` is used. RouterOS refuses password login once
|
|
325
|
+
an SSH key is set for a user (`password-authentication=yes-if-no-key`, the
|
|
326
|
+
default), so key auth is the normal sftp path.
|
|
327
|
+
|
|
328
|
+
## Done definition
|
|
329
|
+
|
|
330
|
+
A feature is done when its **CHR integration test is green** against a real
|
|
331
|
+
RouterOS CHR booted by `@tikoci/quickchr`. Not when code exists. Not when unit
|
|
332
|
+
tests pass. Not when the spec says so. This is the **normative** done
|
|
333
|
+
definition; `AGENTS.md` and
|
|
334
|
+
`.github/instructions/done-definition.instructions.md` point here (the latter
|
|
335
|
+
carries the Copilot-workflow procedure for satisfying it).
|
|
336
|
+
|
|
337
|
+
`docs/MATRIX.md` is the single source of truth for *what* is done and defines the
|
|
338
|
+
cell states (`not-started` / `designed` / `coded` / `CHR-passed`). A cell
|
|
339
|
+
advances only with the matching evidence in the same change, and the commit that
|
|
340
|
+
advances it must include the test name and CHR version.
|
|
341
|
+
|
|
342
|
+
## Boundaries
|
|
343
|
+
|
|
344
|
+
centrs does:
|
|
345
|
+
|
|
346
|
+
- Resolve devices/groups from explicit input, env, CDB, MNDP cache.
|
|
347
|
+
- Choose protocols per the table above and configure them.
|
|
348
|
+
- Validate before execute; re-validate server-side immediately before run.
|
|
349
|
+
- Return structured envelopes with provenance and warnings.
|
|
350
|
+
- Use the WinBox CDB at its well-known location as the device datastore and
|
|
351
|
+
cache; overlay centrs metadata via comment-kv and groups.
|
|
352
|
+
- Speak MikroTik peer-measurement protocols on explicit request — the bandwidth
|
|
353
|
+
test (`btest`), as both client and server — with the same envelope / identity /
|
|
354
|
+
error contract (but not the RouterOS-command validation gate).
|
|
355
|
+
|
|
356
|
+
centrs does not:
|
|
357
|
+
|
|
358
|
+
- Replace RouterOS syntax with high-level abstractions.
|
|
359
|
+
- Treat passive discovery (MNDP) as authoritative inventory.
|
|
360
|
+
- Run write-shaped operations without an explicit target and validation policy.
|
|
361
|
+
- Make generated output the hand-edited source of truth.
|
|
362
|
+
|
|
363
|
+
### Network listeners
|
|
364
|
+
|
|
365
|
+
centrs opens a network listener in exactly two sanctioned places: the **proxy**
|
|
366
|
+
surface (HTTP / remote access; `src/webproxy.ts`) and the **btest server**
|
|
367
|
+
(`centrs btest server`, TCP/UDP port 2000). The btest server is an **explicit,
|
|
368
|
+
foreground, user-invoked** command — not an always-on daemon, and not the proxy.
|
|
369
|
+
It **binds loopback by default** (`--bind` to expose), **requires auth by
|
|
370
|
+
default** (`authenticate=yes`, mirroring RouterOS), and is security-sensitive
|
|
371
|
+
surface: treat credentials as redactable (`SECURITY.md`). The MCP server stays
|
|
372
|
+
stdio-only and grows no listener of its own.
|
|
373
|
+
|
|
374
|
+
## Invariants
|
|
375
|
+
|
|
376
|
+
- One settings vocabulary across API/CLI/MCP/TUI/proxy.
|
|
377
|
+
- Friendly, structured errors are part of the API contract — not optional polish.
|
|
378
|
+
- Help text, source reporting, redaction, and bug-report envelopes are
|
|
379
|
+
shared-core, not per-frontend.
|
|
380
|
+
- Integration tests prefer real CHR via `quickchr` over elaborate mocks when
|
|
381
|
+
behavior depends on RouterOS.
|
|
382
|
+
- Generated docs are preferred when the source of truth is code, CLI metadata,
|
|
383
|
+
or schema.
|
|
384
|
+
|
|
385
|
+
## Adjacent projects (grounding sources)
|
|
386
|
+
|
|
387
|
+
- `tikoci/rosetta` — RouterOS docs / RAG.
|
|
388
|
+
- `tikoci/restraml` — REST schema and inspect output.
|
|
389
|
+
- `tikoci/m2ir` — protocol IR (WinBox, etc.), and the home for the
|
|
390
|
+
declarative "schema-as-data" approach to representing protocol internals
|
|
391
|
+
(consult it when WinBox-terminal / RoMON / heavy protocol work lands, rather
|
|
392
|
+
than hand-rolling protocol knowledge in centrs). Not an accessible source for
|
|
393
|
+
the RouterOS error-string vocabulary; ground error mappings on live CHR
|
|
394
|
+
strings.
|
|
395
|
+
- `tikoci/lsp-routeros-ts` — canonicalization, parse-validation patterns.
|
|
396
|
+
- `tikoci/vscode-tikbook` — peer RouterOS front-end (notebook UX); a source for
|
|
397
|
+
run-time error-string mapping (faults that pass `:parse` but fail at run).
|
|
398
|
+
- `tikoci/quickchr` — CHR-backed integration test harness.
|
|
399
|
+
|
|
400
|
+
When one of these owns a question, defer to it instead of restating here.
|
|
401
|
+
|
|
402
|
+
External reference implementations to compare centrs protocol/transport work
|
|
403
|
+
against (SSH patterns, native-API behavior, CDB format, WinBox framing) are kept
|
|
404
|
+
as bibliography entries in `GLOSSARY.txt` — e.g. `netmiko`,
|
|
405
|
+
`terraform-provider-routeros`, `librouteros-api`, `RouterOS_Tools`,
|
|
406
|
+
`Winbox_Protocol_Dissector`.
|
|
407
|
+
|
|
408
|
+
### Canonicalizer ownership
|
|
409
|
+
|
|
410
|
+
centrs owns the **script-vs-structured execution gate** — the load-bearing
|
|
411
|
+
discriminator for which validation runs and whether the write-confirmation prompt
|
|
412
|
+
fires; widening what counts as `structured` is a product regression. The shared,
|
|
413
|
+
pure command canonicalizer that `rosetta` / `lsp-routeros-ts` publish is for
|
|
414
|
+
canonicalization only, never the structured-mode predicate. The gate contract,
|
|
415
|
+
its pinning test, and the parser-vendoring preconditions are documented in
|
|
416
|
+
`commands/execute/README.md` and the `canonicalizeExecuteCommand` doc-comment
|
|
417
|
+
(`src/execute.ts`).
|
package/docs/MATRIX.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Matrix
|
|
2
|
+
|
|
3
|
+
The product is a 2D grid of commands × protocols. This file is the **only**
|
|
4
|
+
source of truth for what is done. No alpha gates, no milestones, no roadmap
|
|
5
|
+
prose.
|
|
6
|
+
|
|
7
|
+
Pick the highest-priority cell that is not `CHR-passed`. That is the next
|
|
8
|
+
work. See `docs/CONSTITUTION.md` for the cell-state definitions.
|
|
9
|
+
|
|
10
|
+
## Cell states
|
|
11
|
+
|
|
12
|
+
| State | Meaning |
|
|
13
|
+
| -------------- | --------------------------------------------------------------- |
|
|
14
|
+
| `—` | Not applicable / out of scope for this protocol |
|
|
15
|
+
| `not-started` | No code and no design |
|
|
16
|
+
| `designed` | `commands/<name>/README.md` describes intent and flags |
|
|
17
|
+
| `coded` | Implementation exists in `src/` |
|
|
18
|
+
| `CHR-passed` | Every example in `commands/<name>/examples.md` is green on CHR |
|
|
19
|
+
|
|
20
|
+
A cell advances only with the matching evidence in the same change.
|
|
21
|
+
|
|
22
|
+
## Grid
|
|
23
|
+
|
|
24
|
+
| Command | rest-api | native-api | ssh | mac-telnet | snmp | mndp | romon | winbox-terminal |
|
|
25
|
+
| -------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ---------------- |
|
|
26
|
+
| retrieve | `CHR-passed` | `CHR-passed` | — | — | `not-started` | — | — | — |
|
|
27
|
+
| stream | — | `designed` | `designed` | — | — | — | — | — |
|
|
28
|
+
| execute | `CHR-passed` | `CHR-passed` | `CHR-passed` | `CHR-passed` | — | — | `not-started` | `not-started` |
|
|
29
|
+
| terminal | — | — | `CHR-passed` | `CHR-passed` | — | — | — | — |
|
|
30
|
+
| transfer | `CHR-passed` | `CHR-passed` | `CHR-passed` | — | — | — | — | — |
|
|
31
|
+
| devices | — | — | — | — | — | — | — | — |
|
|
32
|
+
| discover | — | — | — | — | — | `CHR-passed` | — | — |
|
|
33
|
+
| check | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` | `not-started` |
|
|
34
|
+
| settings | — | — | — | — | — | — | — | — |
|
|
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)
|
|
111
|
+
|
|
112
|
+
The grid above tracks command cells. The transports those cells ride are
|
|
113
|
+
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.
|
|
151
|
+
|
|
152
|
+
### Frontend surfaces (orthogonal to the command grid)
|
|
153
|
+
|
|
154
|
+
The grid tracks the core (commands × protocols). Frontends are adapters over that
|
|
155
|
+
core and carry their own state, tracked here:
|
|
156
|
+
|
|
157
|
+
| Surface | State | Spec | Notes |
|
|
158
|
+
| ------- | ----- | ---- | ----- |
|
|
159
|
+
| api (TS) | `CHR-passed` | `src/index.ts` | Root surface; everything else adapts it. |
|
|
160
|
+
| 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. |
|
|
162
|
+
| tui | `not-started` | `src/tui.ts` | Stub. |
|
|
163
|
+
| proxy | `not-started` | `src/webproxy.ts` | Stub. |
|
|
164
|
+
|
|
165
|
+
A surface advances to `CHR-passed` only when every example in its spec
|
|
166
|
+
(`commands/<surface>/examples.md`, where one exists) is green via
|
|
167
|
+
`bun run test:integration`. The `mcp` surface's tool list, CDB-as-allowlist safety
|
|
168
|
+
model, and CHR test shape (examples 1–10, the `mcp=rw` + `confirm:true` write gate)
|
|
169
|
+
are documented in `commands/mcp/README.md` and `commands/mcp/examples.md`.
|
|
170
|
+
|
|
171
|
+
### Peer measurement (`btest`) — orthogonal to the command grid
|
|
172
|
+
|
|
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).
|
|
179
|
+
|
|
180
|
+
| Mode | State | Evidence |
|
|
181
|
+
| -------------- | ------------ | -------- |
|
|
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.
|
|
195
|
+
|
|
196
|
+
## Priority order
|
|
197
|
+
|
|
198
|
+
Do not start a later item until the earlier cell or dependency checkpoint has
|
|
199
|
+
matching evidence. The grid above is the live status; per-item detail lives in
|
|
200
|
+
the linked `commands/<name>/`.
|
|
201
|
+
|
|
202
|
+
1. **retrieve / rest-api** — `CHR-passed`. The shakedown for everything else (`commands/retrieve/`).
|
|
203
|
+
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`.
|
|
206
|
+
5. **devices** (CDB mutation + provenance) — `CHR-passed` (`commands/devices/`).
|
|
207
|
+
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.
|
|
210
|
+
9. **RoMON / WinBox Terminal for execute** — `not-started` (see Open questions).
|
|
211
|
+
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.
|
|
214
|
+
|
|
215
|
+
## Open questions (decisions needed before the affected cell can advance)
|
|
216
|
+
|
|
217
|
+
| Question | Affects cell | Notes |
|
|
218
|
+
| -------- | ------------ | ----- |
|
|
219
|
+
| RoMON / WinBox Terminal validation and CI | execute / romon, execute / winbox-terminal | Lower priority than mac-telnet; need reference tooling and typed failure mapping before advancing. |
|
|
220
|
+
|
|
221
|
+
Recently closed (folded into the specs): SNMP MIB-cache policy → `commands/retrieve/README.md`; bug-report rendering (inline `--bug-report` flag, no separate verb) → `docs/CONSTITUTION.md`; L2-in-CI for mac-telnet/MNDP (quickchr `socket-connect` host-side L2 capture; `socket-mcast` is macOS-broken) → `commands/discover/README.md` + `commands/execute/README.md`.
|
|
222
|
+
|
|
223
|
+
When a question is answered, fold the answer into the relevant
|
|
224
|
+
`commands/<name>/README.md` or `docs/CONSTITUTION.md`, then delete the row.
|