@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,488 @@
|
|
|
1
|
+
# devices — examples
|
|
2
|
+
|
|
3
|
+
Each numbered example is an executable spec. `devices` performs no network
|
|
4
|
+
IO, so its integration tests run against a fixture CDB rather than a live
|
|
5
|
+
CHR. The fixtures are built in-test from the known CDB primitives and written
|
|
6
|
+
into a temporary directory at runtime (open and encrypted, encrypted password
|
|
7
|
+
`centrs-test`); there are no checked-in `.cdb` files. See
|
|
8
|
+
`test/integration/devices.test.ts`.
|
|
9
|
+
|
|
10
|
+
Writes operate on a copy of the fixture per test; the source bytes are never
|
|
11
|
+
mutated in place. `$CDB` is the per-test path.
|
|
12
|
+
|
|
13
|
+
> The decided `devices` redesign is fully implemented and exercised below:
|
|
14
|
+
> lookup-key resolution (`identity=`/`mac=`/`ip=`, examples 32–34), the broadened
|
|
15
|
+
> `--match` selectors (`user=`/`target=`, examples 6/35/36), the symmetric
|
|
16
|
+
> `add`/`set` model (`edit` reserved for the interactive editor, example 37), the
|
|
17
|
+
> `(target, user)` record identity (example 36), the `--profile-none`/
|
|
18
|
+
> `--profile-own` sentinels (example 38), the `tips[]` channel (example 40), and
|
|
19
|
+
> the CLI verb aliases (example 41). The `__default__` fallback record is
|
|
20
|
+
> resolver-level (unit-tested in `test/unit/resolver.test.ts`). Do not reconcile
|
|
21
|
+
> the README back to these examples; reconcile forward.
|
|
22
|
+
|
|
23
|
+
## list
|
|
24
|
+
|
|
25
|
+
### 1. List all targets
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
centrs devices list --cdb-file $CDB
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Envelope: `ok: true`, `data` is an array of `{ target, recordType, group?,
|
|
32
|
+
user, sources }`. No `meta.timing.request`.
|
|
33
|
+
|
|
34
|
+
### 2. List a single group
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
centrs devices list --cdb-file $CDB --group prod-edge
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`data` contains only members whose `group="prod-edge"`. Empty array is a
|
|
41
|
+
valid success.
|
|
42
|
+
|
|
43
|
+
### 3. List with no CDB present
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
centrs devices list --cdb-file /tmp/does-not-exist.cdb
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Errors with `cdb/not-found`. `details_url` populated.
|
|
50
|
+
|
|
51
|
+
## show
|
|
52
|
+
|
|
53
|
+
### 4. Show by exact target
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
centrs devices show 192.0.2.5 --cdb-file $CDB
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`data` is one entry. `meta.target.sources` maps every resolved field to
|
|
60
|
+
`cdb` or `default`. `meta.target.cdbRecordIndex` is the entry's index.
|
|
61
|
+
|
|
62
|
+
### 5. Show ambiguous target (non-TTY)
|
|
63
|
+
|
|
64
|
+
The CDB holds two entries with the same `target` (`2001:db8::5`) — the same
|
|
65
|
+
host saved once as an `ipAdmin` record and once as an `ipUser` record. With no
|
|
66
|
+
disambiguator and no interactive prompt available, `show` refuses to guess.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
centrs devices show 2001:db8::5 --cdb-file $CDB </dev/null
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`identity/ambiguous`. `error.context.matches` is an array of
|
|
73
|
+
`{ cdbRecordIndex, target, recordType }`, one per colliding entry.
|
|
74
|
+
|
|
75
|
+
### 6. Show with --match disambiguation
|
|
76
|
+
|
|
77
|
+
`--match <record-type>` selects among duplicate targets by record type (one of
|
|
78
|
+
the `winBoxCdbRecordType` names: `ipAdmin`, `ipUser`, `macTarget`, …).
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
centrs devices show 2001:db8::5 --cdb-file $CDB --match ipUser
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`ok: true`. The returned entry is the `ipUser` record; `meta.target.resolvedTarget`
|
|
85
|
+
is `2001:db8::5`. A `--match` token that names no colliding entry errors with
|
|
86
|
+
`identity/no-match`; an unknown record-type token errors with `input/invalid-match`.
|
|
87
|
+
|
|
88
|
+
### 7. Show --explain dumps raw record
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
centrs devices show 192.0.2.5 --cdb-file $CDB --explain
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`data.record` is the raw `WinBoxCdbRecord` (tag/marker/tcode/value). Useful
|
|
95
|
+
for diagnosing unknown-field preservation.
|
|
96
|
+
|
|
97
|
+
## groups
|
|
98
|
+
|
|
99
|
+
### 8. List distinct groups with counts
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
centrs devices groups --cdb-file $CDB
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`data` is an array of `{ group: string, members: number }`, sorted by name.
|
|
106
|
+
|
|
107
|
+
### 9. Groups --members expands to membership
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
centrs devices groups --cdb-file $CDB --members
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`data` is an array of `{ group, members: [{ target, recordType }] }`.
|
|
114
|
+
|
|
115
|
+
## add
|
|
116
|
+
|
|
117
|
+
### 10. Add an ipAdmin entry
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
centrs devices add 198.51.100.10 --user admin --password secret \
|
|
121
|
+
--group prod-edge --comment "site=NYC via=ssh" --cdb-file $CDB
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Envelope: `ok: true`. Re-reading the CDB shows the entry. Backup file
|
|
125
|
+
`$CDB.bak.<iso8601>` exists.
|
|
126
|
+
|
|
127
|
+
### 11. Add the same (target, user) without --force
|
|
128
|
+
|
|
129
|
+
Record identity is `(target, user)`, so a collision requires the **same user**
|
|
130
|
+
(`192.0.2.5` is saved under `admin`).
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
centrs devices add 192.0.2.5 --user admin --password other --cdb-file $CDB
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Errors with `cdb/already-exists`. No file mutation. No backup written.
|
|
137
|
+
|
|
138
|
+
### 12. Replace the same (target, user) with --force
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
centrs devices add 192.0.2.5 --user admin --password new --force --cdb-file $CDB
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`ok: true`. Backup written. The previous entry's unknown fields are
|
|
145
|
+
preserved on the replacement record; `meta.warnings` contains a
|
|
146
|
+
`cdb/unknown-field` entry listing the preserved tags.
|
|
147
|
+
|
|
148
|
+
### 13. Add against encrypted CDB without password
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
centrs devices add 198.51.100.20 --user u --password p \
|
|
152
|
+
--cdb-file $CDB_ENCRYPTED
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Errors with `cdb/password-required` (load fails before any mutation).
|
|
156
|
+
|
|
157
|
+
### 14. Add against encrypted CDB with password
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
centrs devices add 198.51.100.20 --user u --password p \
|
|
161
|
+
--cdb-file $CDB_ENCRYPTED --cdb-password centrs-test
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`ok: true`. The write layer decrypts → mutates → re-encrypts under the same
|
|
165
|
+
password with a fresh salt, then atomically renames over the original. The
|
|
166
|
+
backup (`.bak.<timestamp>`) is the verbatim prior ciphertext.
|
|
167
|
+
|
|
168
|
+
## set (first-class fields)
|
|
169
|
+
|
|
170
|
+
`set` modifies an existing record. First-class fields change via flags; comment
|
|
171
|
+
override/lookup keys change via `k=v` positionals (next section). `add` takes the
|
|
172
|
+
same flags + positionals — the only difference is existence (`add` creates,
|
|
173
|
+
`set` requires the record to exist).
|
|
174
|
+
|
|
175
|
+
### 15. Set credentials (first-class fields)
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
centrs devices set 192.0.2.5 --user admin2 --password new --cdb-file $CDB
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`ok: true`. Other fields (group, profile, comment, unknowns) unchanged.
|
|
182
|
+
|
|
183
|
+
### 16. Set a missing target
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
centrs devices set 203.0.113.1 --user x --password y --cdb-file $CDB
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Errors with `cdb/not-found-target`.
|
|
190
|
+
|
|
191
|
+
## set (comment kv-soup)
|
|
192
|
+
|
|
193
|
+
### 17. Set a recognized override
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
centrs devices set 192.0.2.5 via=ssh validate=false --cdb-file $CDB
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`ok: true`. Re-running `devices show 192.0.2.5` shows
|
|
200
|
+
`meta.target.sources.via = "comment-kv"`.
|
|
201
|
+
|
|
202
|
+
### 18. Set a key that maps to a first-class CDB field
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
centrs devices set 192.0.2.5 user=hacker --cdb-file $CDB
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Errors with `cdb/reserved-key` listing the forbidden keys
|
|
209
|
+
(`user`, `password`, `group`, `profile`, `session`).
|
|
210
|
+
|
|
211
|
+
### 19. Set unknown key (lenient)
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
centrs devices set 192.0.2.5 future=maybe --cdb-file $CDB
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`ok: true` with `cdb/unknown-option` warning. The token is written to
|
|
218
|
+
`comment` verbatim.
|
|
219
|
+
|
|
220
|
+
### 20. Set unknown key with --strict
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
centrs devices set 192.0.2.5 future=maybe --strict --cdb-file $CDB
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Errors with `cdb/unknown-option`. No write.
|
|
227
|
+
|
|
228
|
+
### 21. Set value with spaces (quoting)
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
centrs devices set 192.0.2.5 'note="rack 7 row B"' --cdb-file $CDB
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`ok: true`. Re-reading parses the value back as `rack 7 row B` (one token).
|
|
235
|
+
|
|
236
|
+
## remove
|
|
237
|
+
|
|
238
|
+
### 22. Remove a single entry
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
centrs devices remove 192.0.2.5 --cdb-file $CDB
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`ok: true`. Backup written. Subsequent `devices show` returns
|
|
245
|
+
`cdb/not-found-target`.
|
|
246
|
+
|
|
247
|
+
### 23. Remove unknown target
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
centrs devices remove 203.0.113.99 --cdb-file $CDB
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`cdb/not-found-target`. No backup written.
|
|
254
|
+
|
|
255
|
+
## Provenance and overrides
|
|
256
|
+
|
|
257
|
+
### 24. CLI overrides comment-kv
|
|
258
|
+
|
|
259
|
+
CDB entry has `via=ssh` in its comment. CLI passes `--via rest-api`.
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
centrs devices show 192.0.2.5 --via rest-api --cdb-file $CDB
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`meta.target.via = "rest-api"`, `meta.target.sources.via = "cli"`.
|
|
266
|
+
`meta.warnings` includes `cdb/override-applied`.
|
|
267
|
+
|
|
268
|
+
### 25. Env overrides comment-kv but loses to CLI
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
CENTRS_VIA=native-api centrs devices show 192.0.2.5 \
|
|
272
|
+
--via rest-api --cdb-file $CDB
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`meta.target.via = "rest-api"`, `meta.target.sources.via = "cli"`.
|
|
276
|
+
|
|
277
|
+
## Fanout (resolved here, executed by other commands)
|
|
278
|
+
|
|
279
|
+
`devices` itself doesn't fan out — these tests assert the resolver behavior
|
|
280
|
+
that other commands consume.
|
|
281
|
+
|
|
282
|
+
### 26. Group expansion is deterministic
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
centrs devices list --group prod-edge --cdb-file $CDB --format json
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The order of resolved members matches CDB record order. This deterministic
|
|
289
|
+
ordering is canonical across centrs: fanout consumers reassemble their `data`
|
|
290
|
+
array in this same resolved order (not completion order) so repeated runs
|
|
291
|
+
produce stable diffs.
|
|
292
|
+
|
|
293
|
+
### 27. Unknown group
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
centrs devices list --group does-not-exist --cdb-file $CDB
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`ok: true`, `data: []`, `meta.warnings` includes `cdb/empty-group`.
|
|
300
|
+
|
|
301
|
+
### 28. Mixed positional + group dedupe (assertion via `devices show`)
|
|
302
|
+
|
|
303
|
+
The resolver's de-dupe rule: positional targets that are also group members
|
|
304
|
+
appear once. Exercised through a downstream command's test once `retrieve`
|
|
305
|
+
gains multi-target support; tracked here so the rule is anchored.
|
|
306
|
+
|
|
307
|
+
## Encrypted CDB read
|
|
308
|
+
|
|
309
|
+
### 29. Read encrypted CDB with password
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
centrs devices list --cdb-file $CDB_ENCRYPTED --cdb-password centrs-test
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`ok: true`. `meta.settings.cdbFile.kind = "cli"`,
|
|
316
|
+
`meta.settings.cdbPassword.kind = "cli"`.
|
|
317
|
+
|
|
318
|
+
### 30. Wrong password
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
centrs devices list --cdb-file $CDB_ENCRYPTED --cdb-password wrong
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`cdb/decrypt-failed`. `error.cause` includes the decoder's detected magic
|
|
325
|
+
mismatch.
|
|
326
|
+
|
|
327
|
+
## Discovery provenance
|
|
328
|
+
|
|
329
|
+
### 31. MNDP-discovered entries are marked as discovered
|
|
330
|
+
|
|
331
|
+
`discover --save` writes CDB records that `devices list` surfaces
|
|
332
|
+
with `group="discovered"` and `source=mndp` in comment-kv metadata.
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
centrs devices list --cdb-file $CDB --group discovered
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`ok: true`; each member reports CDB provenance and discovery metadata.
|
|
339
|
+
|
|
340
|
+
## Lookup-key resolution
|
|
341
|
+
|
|
342
|
+
`<router>` resolves against the `target` field **and** the `identity=` / `mac=` /
|
|
343
|
+
`ip=` comment lookup keys (see `README.md`, Identity model). MAC values compare
|
|
344
|
+
case- and separator-insensitively. These run against a dedicated lookup fixture
|
|
345
|
+
(`$CDB_LOOKUP`): one `ipAdmin` record `192.0.2.60` with
|
|
346
|
+
`comment="identity=edge1 mac=AA:BB:CC:DD:EE:11"`, and one `macTarget` record
|
|
347
|
+
`AA:BB:CC:DD:EE:22` with `comment="identity=edge2 ip=192.0.2.61"`.
|
|
348
|
+
|
|
349
|
+
### 32. Resolve by the identity= lookup key
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
centrs devices show edge1 --cdb-file $CDB_LOOKUP
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`ok: true`. The resolved entry's `target` is `192.0.2.60`;
|
|
356
|
+
`meta.target.identity` is `edge1`.
|
|
357
|
+
|
|
358
|
+
### 33. Resolve by the mac= lookup key (case/separator-insensitive)
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
centrs devices show aa-bb-cc-dd-ee-11 --cdb-file $CDB_LOOKUP
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`ok: true`. Resolves the same `192.0.2.60` record even though the typed MAC uses
|
|
365
|
+
dashes and lower case.
|
|
366
|
+
|
|
367
|
+
### 34. Resolve by the ip= lookup key
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
centrs devices show 192.0.2.61 --cdb-file $CDB_LOOKUP
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`ok: true`. Resolves the `AA:BB:CC:DD:EE:22` record; `meta.target.identity` is
|
|
374
|
+
`edge2`.
|
|
375
|
+
|
|
376
|
+
### 35. A duplicated identity= is ambiguous; --match user= pins it
|
|
377
|
+
|
|
378
|
+
A second fixture (`$CDB_DUP_IDENTITY`) holds two distinct hosts that share
|
|
379
|
+
`identity=dup` (the deliberately non-unique identity case).
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
centrs devices show dup --cdb-file $CDB_DUP_IDENTITY </dev/null
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`identity/ambiguous`; `error.context.matches` lists both records (with `user`).
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
centrs devices show dup --match user=ops --cdb-file $CDB_DUP_IDENTITY
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`ok: true`; selects the `ops` record. `--match target=<addr>` and
|
|
392
|
+
`--match <record-type>` are the other selectors.
|
|
393
|
+
|
|
394
|
+
## (target, user) identity
|
|
395
|
+
|
|
396
|
+
### 36. The same address under a different user coexists
|
|
397
|
+
|
|
398
|
+
`192.0.2.5` is saved under `admin`. Adding it under a different user is a new
|
|
399
|
+
record, not a collision.
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
centrs devices add 192.0.2.5 --user ops --password ops-pw --cdb-file $CDB
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
`ok: true`, `data.replaced: false`. The CDB now holds two records for
|
|
406
|
+
`192.0.2.5`, so the bare address is ambiguous:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
centrs devices show 192.0.2.5 --cdb-file $CDB </dev/null
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`identity/ambiguous`. `centrs devices show 192.0.2.5 --match user=ops` resolves
|
|
413
|
+
the new record; `--match user=admin` the original.
|
|
414
|
+
|
|
415
|
+
## edit (reserved for the interactive editor)
|
|
416
|
+
|
|
417
|
+
### 37. `edit` reports usage/not-implemented
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
centrs devices edit 192.0.2.5 --cdb-file $CDB
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Errors with `usage/not-implemented`. The interactive (clack/TUI) editor is not
|
|
424
|
+
built yet; use `set` for non-interactive field and metadata changes.
|
|
425
|
+
|
|
426
|
+
## Profile sentinels
|
|
427
|
+
|
|
428
|
+
### 38. Write the `<none>` / `<own>` profile sentinels
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
centrs devices set 192.0.2.5 --profile-none --cdb-file $CDB
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`ok: true`; the record's `profile` becomes `<none>`. `--profile-own` writes
|
|
435
|
+
`<own>`; `--profile <name>` writes a named profile. The three are mutually
|
|
436
|
+
exclusive (passing more than one is a usage error).
|
|
437
|
+
|
|
438
|
+
## Symmetric add (fields + k=v)
|
|
439
|
+
|
|
440
|
+
### 39. Add with bare k=v positionals
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
centrs devices add 198.51.100.30 --user admin --password p \
|
|
444
|
+
identity=spare via=ssh --cdb-file $CDB
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`ok: true`. The record's comment carries `identity=spare via=ssh`, so the device
|
|
448
|
+
is then resolvable as `spare`. A first-class field written as a positional
|
|
449
|
+
(`user=x`) is refused with `cdb/reserved-key` — use the `--user` flag.
|
|
450
|
+
|
|
451
|
+
## Tips
|
|
452
|
+
|
|
453
|
+
Tips are advice, not anomalies (see `docs/CONSTITUTION.md`, Result envelope).
|
|
454
|
+
They ride the top-level `tips[]` channel, always present (`[]` when empty),
|
|
455
|
+
and render in text mode under a `Tips:` footer.
|
|
456
|
+
|
|
457
|
+
### 40. An empty registry suggests adding a device
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
centrs devices list --cdb-file $EMPTY_CDB
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`ok: true`, `data: []`, and `tips` carries a `tip/no-devices` entry (with a
|
|
464
|
+
`fix`). `--json` includes the same `tips[]` array. `devices show` on a record
|
|
465
|
+
with no stored password emits `tip/credentials-missing` unless a `__default__`
|
|
466
|
+
record supplies fallback creds.
|
|
467
|
+
|
|
468
|
+
## Verb aliases
|
|
469
|
+
|
|
470
|
+
The RouterOS-muscle-memory aliases (`commands/AGENTS.md`, Verb vocabulary)
|
|
471
|
+
resolve silently to the canonical verb: `print`→`list`, `get`→`show`,
|
|
472
|
+
`rm`/`delete`→`remove`. Canonical names still win in help text and the usage
|
|
473
|
+
line; aliases are a CLI input affordance only (the MCP `op` enum and the API
|
|
474
|
+
functions stay canonical-only).
|
|
475
|
+
|
|
476
|
+
### 41. Aliases resolve to the canonical verb
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
centrs devices print --cdb-file $CDB # = list
|
|
480
|
+
centrs devices get 192.0.2.5 --cdb-file $CDB # = show
|
|
481
|
+
centrs devices rm 192.0.2.5 --cdb-file $CDB # = remove
|
|
482
|
+
centrs devices delete 192.0.2.5 --cdb-file $CDB # = remove
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Each alias produces the same envelope as its canonical verb — `meta.operation`
|
|
486
|
+
reports the canonical command (`list`/`show`/`remove`), never the alias. An
|
|
487
|
+
unknown verb still errors with `input/invalid-command`, and the error lists the
|
|
488
|
+
canonical verbs plus the accepted aliases.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# discover
|
|
2
|
+
|
|
3
|
+
Discover RouterOS neighbors over MNDP and optionally save them into the CDB.
|
|
4
|
+
|
|
5
|
+
Status: `CHR-passed` (see `docs/MATRIX.md`). The MNDP codec, TTL cache, UDP
|
|
6
|
+
listener, and `discover --save` are implemented; the real-L2 receive/decode/save
|
|
7
|
+
path is green against RouterOS CHR 7.23.1 via `test/integration/discover.test.ts`
|
|
8
|
+
(socket-connect bridge), and the network-independent paths stay unit-tested. The
|
|
9
|
+
matrix remains the only status surface. The `identity=`/`mac=` lookup-key writing and
|
|
10
|
+
MAC-keyed de-dupe described below are the decided target; the current `--save`
|
|
11
|
+
de-dupes on `target` and keeps identity/MAC in the inert detail — that gap moves
|
|
12
|
+
with the `devices` lookup-key work.
|
|
13
|
+
|
|
14
|
+
## Synopsis
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
centrs discover [--timeout 15s] [--save] [--group discovered] \
|
|
18
|
+
[--port 5678] [--cdb-file <path>] [--cdb-password <secret>] \
|
|
19
|
+
[--format text|json|yaml]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Intent
|
|
23
|
+
|
|
24
|
+
- MNDP is the first discovery source. It is a passive hint source, not
|
|
25
|
+
authoritative inventory.
|
|
26
|
+
- `--save` writes discovered targets into the configured CDB with provenance
|
|
27
|
+
metadata in comment kv-soup and default `group=discovered`.
|
|
28
|
+
- Saved records preserve enough metadata for later resolution: MAC, advertised
|
|
29
|
+
identity, platform, RouterOS version when present, source, and discovery
|
|
30
|
+
timestamp.
|
|
31
|
+
- Discovery never supplies credentials. A later `retrieve`, `execute`, or
|
|
32
|
+
`devices set` must still resolve credentials from CDB, env, CLI, or prompt.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
MNDP is UDP broadcast on port `5678`; each RouterOS interface answers a refresh
|
|
37
|
+
with a TLV-encoded announcement (MAC, identity, version, platform, board,
|
|
38
|
+
uptime, software-id, interface, IPv4/IPv6). `discover`:
|
|
39
|
+
|
|
40
|
+
1. Binds a UDP socket (default `0.0.0.0:5678`) with address/port reuse so
|
|
41
|
+
it can coexist with other MNDP listeners when the platform permits it.
|
|
42
|
+
2. Broadcasts a refresh request **immediately**, then every 5s, and listens for
|
|
43
|
+
the `--timeout` window. Sending the first refresh up front (rather than after
|
|
44
|
+
the initial 5s tick) is what makes a short default window viable: responders
|
|
45
|
+
reply within a round-trip instead of waiting for their own ~60s passive cycle.
|
|
46
|
+
3. Decodes each datagram with the pure codec in `src/data/mndp.ts`, tolerating
|
|
47
|
+
unknown TLV types and ignoring malformed datagrams and its own refresh echo.
|
|
48
|
+
4. Stores neighbors in an in-memory TTL cache keyed by MAC.
|
|
49
|
+
5. Returns a canonical envelope whose `data.neighbors` is sorted by MAC.
|
|
50
|
+
|
|
51
|
+
The codec is pure and the socket layer is thin and injectable, so both are
|
|
52
|
+
tested without a router (crafted packet fixtures + a loopback socket).
|
|
53
|
+
|
|
54
|
+
## Flags and defaults
|
|
55
|
+
|
|
56
|
+
| Flag | Default | Meaning |
|
|
57
|
+
| ---------------- | ------------- | ---------------------------------------------------- |
|
|
58
|
+
| `--timeout` | `15s` | Listen window before results are returned. First refresh is sent immediately. |
|
|
59
|
+
| `--save` | off | Persist discovered neighbors into the CDB. |
|
|
60
|
+
| `--group` | `discovered` | First-class CDB group assigned to saved entries. |
|
|
61
|
+
| `--port` | `5678` | UDP port to bind for MNDP. |
|
|
62
|
+
| `--cdb-file` | resolved CDB | CDB path override for `--save`. |
|
|
63
|
+
| `--cdb-password` | env/none | Decrypt an encrypted CDB; also used to re-encrypt on `--save`. |
|
|
64
|
+
| `--format` | `text` | `text`, `json`, or `yaml`; renders the same envelope. |
|
|
65
|
+
|
|
66
|
+
`--save` without `--timeout` uses the default 15s window.
|
|
67
|
+
|
|
68
|
+
Bare `centrs discover` is **read-only** — it returns the envelope and never
|
|
69
|
+
writes the (WinBox-shared) CDB; `--save` is the explicit opt-in. The registry
|
|
70
|
+
surface `centrs devices discover` is the inverse: invoking `devices` means you
|
|
71
|
+
intend to populate it, so **`--save` is implied** there, and it is the home for
|
|
72
|
+
the "set default credentials so centrs starts useful" onboarding nudge. Both
|
|
73
|
+
paths write through the `devices` atomic write layer, so "devices is the only
|
|
74
|
+
writer" holds.
|
|
75
|
+
|
|
76
|
+
## MNDP cache shape and TTL policy
|
|
77
|
+
|
|
78
|
+
(Answers the former matrix open question.)
|
|
79
|
+
|
|
80
|
+
- **Shape:** an in-memory `MndpCache` (`src/data/mndp-cache.ts`) keyed by the
|
|
81
|
+
lower-cased MAC address. MAC-less announcements fall back to a synthetic
|
|
82
|
+
`noaddr:<identity>:<interface>` key. Each entry records the latest decoded
|
|
83
|
+
neighbor plus `firstSeenAt` / `lastSeenAt` epoch-ms timestamps. The cache is
|
|
84
|
+
process-local only — the CDB stays the single durable datastore.
|
|
85
|
+
- **TTL:** entries expire `180s` (three ~60s announce cycles) after they were
|
|
86
|
+
last seen. Three cycles tolerates a couple of dropped broadcasts before a
|
|
87
|
+
still-present neighbor is forgotten. Re-observing a neighbor refreshes
|
|
88
|
+
`lastSeenAt` and keeps `firstSeenAt`. The clock is injectable for
|
|
89
|
+
deterministic tests.
|
|
90
|
+
|
|
91
|
+
## `--save` provenance and de-dupe
|
|
92
|
+
|
|
93
|
+
- **Target:** a neighbor's CDB target is its advertised IPv4 when present
|
|
94
|
+
(record type `ipAdmin`), else its MAC (record type `macTarget`). One record
|
|
95
|
+
per device — the connectable address — not a MAC record plus an IP record.
|
|
96
|
+
- **Lookup keys:** the identifiers that are *not* the target are written as
|
|
97
|
+
comment lookup keys so the device resolves by any of them (see
|
|
98
|
+
`commands/devices/README.md`, Identity model): `identity=<advertised
|
|
99
|
+
identity>` always, and `mac=` when the target is the IP. The advertised
|
|
100
|
+
identity is therefore usable directly — `centrs retrieve <identity> …` — even
|
|
101
|
+
though it is duplicative across factory-default devices.
|
|
102
|
+
- **Provenance:** the comment also carries the allowlisted `source=mndp` token
|
|
103
|
+
plus a free-form, parenthesized detail (`discovered <iso> via MNDP (platform:
|
|
104
|
+
…; board: …; version: …; interface: …; software-id: …)`). The detail is inert
|
|
105
|
+
so it preserves provenance without emitting `cdb/unknown-option` warnings.
|
|
106
|
+
- **Group:** new entries get `group=discovered` (override with `--group`).
|
|
107
|
+
- **De-dupe rule:** keyed on the **MAC**, which MNDP always carries and which is
|
|
108
|
+
globally unique — `identity` is *not* the de-dupe key, because factory-default
|
|
109
|
+
devices all report `MikroTik`. A neighbor whose MAC already names a CDB entry
|
|
110
|
+
(as the `target` of a `macTarget` record or as a `mac=` lookup key on any
|
|
111
|
+
record) is **skipped, never overwritten** — hand-curated records win. Only
|
|
112
|
+
genuinely new devices are added. Writes reuse the `devices` atomic write path
|
|
113
|
+
(`addDevice`); the CDB is reloaded between writes so each add sees prior ones.
|
|
114
|
+
- **Encrypted CDBs:** `--save` against an encrypted CDB decrypts the file under
|
|
115
|
+
the loaded password, appends new neighbors, and re-encrypts with a fresh salt
|
|
116
|
+
before the atomic rename. The backup beside the CDB is the verbatim prior
|
|
117
|
+
ciphertext. Reading an encrypted CDB stays supported.
|
|
118
|
+
- **No auto-deletion / aging.** centrs never removes or rewrites a CDB record it
|
|
119
|
+
did not just create in this run. Discovered records age via their discovery /
|
|
120
|
+
`updated=` timestamp, and staleness is surfaced as a **tip** ("N discovered
|
|
121
|
+
entries are >30d old"), never an automatic delete — deleting records from a
|
|
122
|
+
WinBox-shared file unprompted is unsafe.
|
|
123
|
+
|
|
124
|
+
## Errors
|
|
125
|
+
|
|
126
|
+
- `mndp/listen-failed` — the UDP bind failed (`EADDRINUSE`, e.g. WinBox holds
|
|
127
|
+
5678 without compatible port sharing; or `EACCES`). Remediation: stop the
|
|
128
|
+
holder, or pass a different `--port`.
|
|
129
|
+
- `mndp/malformed` — the pure codec rejects a structurally invalid packet
|
|
130
|
+
(too-short header or a TLV length that overruns the buffer). The listener
|
|
131
|
+
ignores such datagrams rather than failing the whole scan.
|
|
132
|
+
|
|
133
|
+
## L2 validation policy
|
|
134
|
+
|
|
135
|
+
Decided (2026-06-06), wired and proven (2026-06-07): the real-L2 integration
|
|
136
|
+
path for MNDP is `@tikoci/quickchr`'s host-side L2 capture. The host runs a TCP
|
|
137
|
+
server and the CHR gets a `socket-connect` NIC; QEMU streams every guest Ethernet
|
|
138
|
+
frame to the host length-prefixed (4-byte BE length + raw frame), and a frame
|
|
139
|
+
written back is injected into the guest. A small host shim
|
|
140
|
+
(`test/integration/mndp-l2-bridge.ts`) bridges that frame stream to centrs's
|
|
141
|
+
UDP/5678 listener — lifting the UDP/5678 payload out of each frame and
|
|
142
|
+
re-delivering it to the **unmodified** `discover()`/`listenMndp` path, and
|
|
143
|
+
injecting MNDP refresh frames back over the same TCP connection so RouterOS
|
|
144
|
+
answers within a round-trip — a real L2 path with no root and no native
|
|
145
|
+
raw-frame helper, while REST/native-API keep a separate user-mode NIC with
|
|
146
|
+
hostfwd. centrs runs with `sendRefresh: false` here (its own broadcast cannot
|
|
147
|
+
reach the guest through the bridge, so the shim does the L2 injection; the
|
|
148
|
+
refresh-send path stays covered by the loopback unit test). Prefer
|
|
149
|
+
`socket-connect` over `socket-mcast`: the multicast netdev is broken on macOS
|
|
150
|
+
(QEMU sets only `SO_REUSEADDR` where macOS needs `SO_REUSEPORT`; mcast works on
|
|
151
|
+
Linux/CI). Grounding: quickchr `docs/mndp.md`, `examples/mndp/`,
|
|
152
|
+
`test/lab/mndp/REPORT.md`.
|
|
153
|
+
|
|
154
|
+
`test/integration/discover.test.ts` exercises this end to end on RouterOS CHR
|
|
155
|
+
7.23.1: it boots `["user", { type: "socket-connect", port }]`, captures a real
|
|
156
|
+
MNDP announcement, decodes it with centrs's codec, cross-checks
|
|
157
|
+
`identity`/`platform`/`board`/`version`/`mac` against REST, and `--save`s a
|
|
158
|
+
`macTarget`/`group=discovered`/`source=mndp` record (examples 1, 2, 4). It runs
|
|
159
|
+
under `bun run test:integration` (gated by `CENTRS_RUN_FAST_INTEGRATION`). The
|
|
160
|
+
network-independent examples (3 port-in-use, 5 custom group, 6 de-dupe, 7
|
|
161
|
+
encrypted CDB) are CDB/bind logic that is identical regardless of where the
|
|
162
|
+
neighbor came from, so they stay validated by `test/unit/discover.test.ts`
|
|
163
|
+
against a loopback socket and crafted fixtures.
|
|
164
|
+
|
|
165
|
+
Live-router finding worth carrying into mac-telnet: MNDP's board TLV (type 12)
|
|
166
|
+
advertises the short board id (`CHR`), while REST `/system/resource` `board-name`
|
|
167
|
+
is the verbose hardware string (`CHR QEMU Standard PC (i440FX + PIIX, 1996)`)
|
|
168
|
+
that *begins with* it — so cross-checks must use `startsWith`, not equality. The
|
|
169
|
+
same `socket-connect` bridge (with its frame-injection write-back) is the L2
|
|
170
|
+
harness the mac-telnet execute/terminal cells will reuse.
|