@tikoci/centrs 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/GLOSSARY.txt +213 -0
  3. package/LICENSE +21 -0
  4. package/README.md +36 -8
  5. package/commands/AGENTS.md +7 -1
  6. package/commands/api/README.md +19 -18
  7. package/commands/api/examples.md +15 -0
  8. package/commands/btest/README.md +21 -32
  9. package/commands/check/README.md +6 -1
  10. package/commands/discover/README.md +5 -11
  11. package/commands/execute/README.md +41 -29
  12. package/commands/execute/examples.md +41 -3
  13. package/commands/explain/README.md +1716 -0
  14. package/commands/explain/examples.md +606 -0
  15. package/commands/retrieve/README.md +25 -27
  16. package/commands/retrieve/examples.md +16 -0
  17. package/commands/transfer/README.md +28 -20
  18. package/commands/transfer/examples.md +29 -0
  19. package/docs/CLI.md +353 -0
  20. package/docs/CONSTITUTION.md +66 -15
  21. package/docs/MATRIX.md +38 -1
  22. package/docs/errors/README.md +3 -0
  23. package/docs/errors/quickchr/machine-not-found.md +22 -0
  24. package/docs/errors/quickchr/machine-stopped.md +20 -0
  25. package/docs/errors/quickchr/package-unavailable.md +27 -0
  26. package/docs/errors/quickchr/unsupported-via.md +35 -0
  27. package/docs/errors/quickchr/unsupported.md +35 -0
  28. package/docs/errors/routeros/version-unsupported.md +26 -0
  29. package/docs/errors/transport/provider-trust.md +29 -0
  30. package/docs/errors/usage/stdin-ignored.md +28 -0
  31. package/docs/errors/validation/unknown-attribute.md +37 -4
  32. package/package.json +45 -10
  33. package/src/api-fanout.ts +29 -14
  34. package/src/api.ts +97 -54
  35. package/src/cli/api.ts +25 -6
  36. package/src/cli/btest.ts +18 -2
  37. package/src/cli/execute.ts +25 -6
  38. package/src/cli/explain.ts +503 -0
  39. package/src/cli/retrieve.ts +33 -1
  40. package/src/cli/selection.ts +71 -11
  41. package/src/cli/settings.ts +2 -2
  42. package/src/cli/transfer.ts +23 -6
  43. package/src/cli.ts +15 -2
  44. package/src/core/envelope.ts +5 -2
  45. package/src/core/error-catalog.ts +40 -0
  46. package/src/core/preflight.ts +306 -0
  47. package/src/core/routeros-string.ts +24 -0
  48. package/src/errors.ts +1 -0
  49. package/src/execute-fanout.ts +29 -14
  50. package/src/execute.ts +114 -63
  51. package/src/explain/arg-tokens.ts +114 -0
  52. package/src/explain/args.ts +1403 -0
  53. package/src/explain/blocks.ts +132 -0
  54. package/src/explain/brace-slots.ts +143 -0
  55. package/src/explain/brace-tokens.ts +38 -0
  56. package/src/explain/catalog.ts +1345 -0
  57. package/src/explain/coordinates.ts +329 -0
  58. package/src/explain/defects.ts +217 -0
  59. package/src/explain/is-known-menu.ts +17 -0
  60. package/src/explain/menus.ts +664 -0
  61. package/src/explain/operator-tokens.ts +310 -0
  62. package/src/explain/operators.ts +458 -0
  63. package/src/explain/path-tokens.ts +150 -0
  64. package/src/explain/pathresolve.ts +1324 -0
  65. package/src/explain/quoted-string.ts +241 -0
  66. package/src/explain/scope-brace.ts +308 -0
  67. package/src/explain/segment.ts +757 -0
  68. package/src/explain/string-tokens.ts +67 -0
  69. package/src/explain/symbol-values.ts +520 -0
  70. package/src/explain/symbols.ts +1597 -0
  71. package/src/explain/token-ranges.ts +72 -0
  72. package/src/explain/transport.ts +497 -0
  73. package/src/explain/value-tokens.ts +80 -0
  74. package/src/explain/values.ts +357 -0
  75. package/src/explain/verbs.ts +64 -0
  76. package/src/explain/verbsplit.ts +813 -0
  77. package/src/explain/write.ts +815 -0
  78. package/src/explain.ts +2152 -0
  79. package/src/index.ts +206 -4
  80. package/src/protocols/mac-telnet-console.ts +61 -17
  81. package/src/resolver/facts.ts +1 -1
  82. package/src/resolver/index.ts +23 -4
  83. package/src/resolver/quickchr-provider.ts +597 -0
  84. package/src/resolver/selection.ts +99 -12
  85. package/src/resolver/service-endpoint.ts +155 -0
  86. package/src/resolver/target.ts +1 -1
  87. package/src/retrieve-fanout.ts +26 -12
  88. package/src/retrieve.ts +81 -18
  89. package/src/transfer-fanout.ts +42 -25
  90. package/src/transfer.ts +87 -44
@@ -37,21 +37,29 @@ re-validate-server-side. Validation is not optional polish.
37
37
  attribute inspection. The path syntax (commas, no leading slash, last token
38
38
  is the verb) is part of the validator contract.
39
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).
40
+ a semantic gate. Grounded on CHR 7.23.3 (GH#230), **`:parse` never throws** —
41
+ it returns a script value whose printed form carries the verdict. For a bare
42
+ menu path a clean parse prints `(evl /canonical/path)` with abbreviations
43
+ expanded; for script-shaped input (e.g. `:put …`, `:global …`, `where …`) the
44
+ accepted return echoes the script body and may contain arbitrary text such as
45
+ `expected`. Rejection forms are `(evl bad parameter <name> (line N column M)
46
+ /path)`, `(<%% bad command name <name> (line N column M) …)`, and the
47
+ line-anchored `expected … (line N column M)` (spellings: `expected end of
48
+ command`, `expected command name`, `expected input value`) / `syntax error
49
+ (line N column M)`. That text rides the **HTTP-200 `ret` value** (REST,
50
+ `as-string`), the native `as-string` `ret`, and the console's printed output
51
+ alike — so a single `:put [:parse ...]` covers both the syntax and the
52
+ **unknown-attribute** gate on every transport. centrs sends `as-string` and now
53
+ reads the `ret`/printed value (the previous gate ignored it, so it rejected
54
+ nothing). Because the accepted return echoes the body, the matcher must anchor
55
+ on the diagnostic shape (the `(<%%` wrapper, or a trailing `(line N column M)`)
56
+ and never on a bare English word — `expected` is also a real
57
+ `/ip/firewall/connection` field name.
58
+ That unknown-attribute check is **name-level only**: `:parse` does not validate
59
+ parameter *values* (`/ip/address/add address=not-an-ip` parses clean). So a
60
+ clean parse is necessary, not sufficient, and value-type plus the remaining
61
+ semantic validation stay distinct steps — `/console/inspect` or the server's
62
+ own re-validation on the write round-trip.
55
63
  - **Peer-measurement commands (`btest`) are exempt from this gate** — they issue
56
64
  no RouterOS command, so they validate *option-grammar* + the **EC-SRP5** auth
57
65
  exchange instead (the envelope, identity-resolution, and friendly-error
@@ -260,6 +268,43 @@ falls in `--near`'s radius **or** `--bbox`.
260
268
  by a distinct `--new-group` flag on `devices add`/`set`, so record selection and
261
269
  field-setting never collide on one flag.
262
270
 
271
+ #### Resolution providers
272
+
273
+ Target resolution has **two independent axes**, and centrs keeps them separate so
274
+ the resolver is generalized **once**, not per source (the umbrella plan is #174):
275
+
276
+ - **Axis A — selection** ("which devices"): the target-selection grammar above.
277
+ - **Axis B — connection-fact origin** ("where host/port/auth for a member come
278
+ from"): what a resolved member yields. Today: a **CDB record** or an ad-hoc
279
+ **literal** (+ the `__default__` ladder). Tomorrow: a live descriptor or an
280
+ inventory entry.
281
+
282
+ Sources fall into two kinds, and the distinction is normative:
283
+
284
+ - **Selector-source** — participates in the Axis-A grammar. **CDB** (now), and
285
+ **TikTOML** next (#137). Multiple selector-sources union into one selection;
286
+ the split-brain precedence between them is #137's problem, not resolved here.
287
+ - **Named-live-provider** — addressed *only* by an explicit named flag, **never**
288
+ matched by a selector; fanned out by repeating the flag. Because it owns an
289
+ **ephemeral** endpoint it also **bypasses** the settings/`__default__` ladder and
290
+ **conflicts** with `--host`/`--port`/`--username`/`--password`/`--ssh-key`. These
291
+ are properties of *live/ephemeral* providers, not of any one provider. **quickchr**
292
+ (`--quickchr <name>`, #134) is the first. A named-live-provider selection is
293
+ **exclusive** (v1): mixing it with `<router>` positionals or selector flags is
294
+ `usage/conflicting-flags` — one flag stays single-target, repeating it fans
295
+ out. Relaxing exclusivity later is additive.
296
+
297
+ Connection facts resolve into one neutral **per-service endpoint map**
298
+ (`src/resolver/service-endpoint.ts`: `via → ServiceEndpoint`, each carrying
299
+ availability, host, port, TLS, auth, and provenance). A **selector-source** is the
300
+ *degenerate single-endpoint* case — one record materialized lazily once `--via` is
301
+ known. A **named-live-provider** populates the map directly, since it genuinely
302
+ exposes different facts per transport. Providers must **not** be flattened to a
303
+ single host/port: that shape is the contract quickchr's descriptor and TikTOML's
304
+ `services` block both map onto, and flattening it forces exactly the re-plumb #174
305
+ exists to prevent. A `--via` whose service key a provider does not supply is a
306
+ typed unsupported-provider error, never a silent fallback.
307
+
263
308
  ### MCP surface: the CDB is the allowlist
264
309
 
265
310
  The MCP frontend (`commands/mcp/`) is an adapter over this core, not a new set of
@@ -440,3 +485,9 @@ canonicalization only, never the structured-mode predicate. The gate contract,
440
485
  its pinning test, and the parser-vendoring preconditions are documented in
441
486
  `commands/execute/README.md` and the `canonicalizeExecuteCommand` doc-comment
442
487
  (`src/execute.ts`).
488
+
489
+ Richer schema-free analysis must distinguish **ambiguous** (multiple plausible
490
+ readings) from **unknown** (no safe reading) and abstain in both cases rather than
491
+ inventing a command, path, symbol class, or transport mapping. These explain-only
492
+ verdicts never widen or reinterpret the execution gate's `mode` or `writeShaped`
493
+ decision.
package/docs/MATRIX.md CHANGED
@@ -31,6 +31,7 @@ A cell advances only with the matching evidence in the same change.
31
31
  | devices | — | — | — | — | — | — | — | — |
32
32
  | discover | — | — | — | — | — | `CHR-passed` | — | — |
33
33
  | check | `designed` | `designed` | `designed` | `designed` | `not-started` | `not-started` | `not-started` | `not-started` |
34
+ | explain | `designed` | `designed` | — | — | — | — | — | — |
34
35
  | settings | — | — | — | — | — | — | — | — |
35
36
 
36
37
  ## Status pointers
@@ -59,6 +60,42 @@ section short enough that the grid remains the status surface.
59
60
  `winbox-terminal` stay `not-started`. Its per-host fan-out is the machinery the
60
61
  IP-scan discovery (#149) will iterate; the L2-default timing evidence (#136) is
61
62
  recorded in the README before the cells advance past `designed`.
63
+ - `explain` is `designed` (`commands/explain/README.md`): the explain →
64
+ validate → run knowledge tier — canonical structure, LSP-like
65
+ spans/diagnostics, transport classification (api-able vs execute, `curl`
66
+ rendering), and live schema/completion facts. Its offline mode is the
67
+ canonicalizer only and opens no protocol connection (evidence is
68
+ unit/fixture tests); the
69
+ live probes (`/console/inspect` + `:parse`) ride rest-api/native-api, so
70
+ those two cells are `designed`. The surface was decided in the 2026-07-19
71
+ design round (#90) and the spec is **ratified** on the phase-0 grounding
72
+ lab (#185, ratification PR #187). Phase 0.5 has since promoted the ratified
73
+ lab logic into the library — `src/explain/{segment,blocks,coordinates,defects,
74
+ pathresolve,verbsplit,write,symbols,menus,verbs,args,values,brace-slots}.ts`
75
+ with frozen fixtures under `test/fixtures/explain/` — and phase 1 has composed
76
+ them into one analysis and envelope in `src/explain.ts` (`explainCommand` /
77
+ `explainEnvelope`, #202a). **`centrs explain '<input>'` is a real command as
78
+ of #202b** (`src/cli/explain.ts`), and #202c adds statement argument reading,
79
+ the closed Q8 transport classifier, equivalent centrs invocations, and
80
+ opt-in `--curl`. All offline examples, including 1, 2, 6 and 23, run green in
81
+ `test/unit/explain.test.ts`. The row nonetheless stays `designed`: offline
82
+ mode occupies no protocol cell, and the two `designed` cells are the live
83
+ `/console/inspect` + `:parse` probes, which are phase 2. The live grammar
84
+ (`explain <router> '<input>'`) is parsed and refused with
85
+ `usage/not-implemented` rather than degraded to an offline run. Cells advance
86
+ only when the live probes land and their examples run green per the done rule.
87
+ `src/explain/menus.ts` and `src/explain/catalog.ts` are the **generated**
88
+ files in that set — the RouterOS menu-vs-command table (#207, from pinned
89
+ restraml typed trees) and the path catalog (#228, those trees unioned with
90
+ MikroTik's published CLI Reference, carrying kind, per-entry provenance and
91
+ the published applicability gate). Regenerate with `bun run explain:menus` /
92
+ `bun run explain:catalog`; both are drift-checked in QA. They keep the offline
93
+ analyzer offline; do not hand-edit them. The catalog's **command axis is read**
94
+ by `pathresolve.ts`, `verbsplit.ts` and `write.ts` (#228 step 2); #235 also
95
+ reads its menu/settings kind, unioned with `menus.ts` behind
96
+ `src/explain/is-known-menu.ts`, for both a context-applied relative bare path
97
+ and an absolute one. Both changes are inside the offline canonicalizer, so
98
+ this grid is unchanged by them.
62
99
  - `transfer / ssh` means the SFTP-backed transfer method. Deferred file-transfer
63
100
  methods such as `scp`, `fetch`, and `ftp` are tracked in
64
101
  `commands/transfer/README.md`; `fetch` is not a grid column.
@@ -95,7 +132,7 @@ core and carry their own state, tracked here:
95
132
  | Surface | State | Spec | Notes |
96
133
  | ------- | ----- | ---- | ----- |
97
134
  | ts-api | `CHR-passed` | `src/index.ts` | Root TypeScript library surface; everything else adapts it. |
98
- | cli | `coded` | `src/cli/` | `retrieve`/`execute`/`transfer`/`terminal`/`devices`/`discover`/`btest` wired. |
135
+ | cli | `coded` | `src/cli/` | Every command in `cliCommands` (`src/cli.ts`) is wired: `retrieve`/`execute`/`api`/`transfer`/`terminal`/`explain`/`devices`/`discover`/`btest`/`mcp`/`settings`, plus the `upload`/`download` shortcuts. |
99
136
  | mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; details in `commands/mcp/README.md` and `test/integration/mcp.test.ts`. |
100
137
  | tui | `not-started` | `src/tui.ts` | Stub. |
101
138
  | proxy | `not-started` | `src/webproxy.ts` | Stub. |
@@ -47,6 +47,9 @@ Codes are slash-namespaced `family/slug`. The families and where they originate:
47
47
  - `input/*` — malformed caller input (paths, MACs, arguments).
48
48
  - `internal/*` — centrs bugs / unreachable states.
49
49
  - `mndp/*` — MNDP wire codec and listener (`src/data/mndp.ts`).
50
+ - `quickchr/*` — the quickchr named-live-provider (`--quickchr <name>`): optional
51
+ package availability, descriptor version support, and machine lookup/liveness
52
+ (`src/resolver/quickchr-provider.ts`).
50
53
  - `routeros/*` — RouterOS-surfaced faults mapped by the grounded vocabulary in
51
54
  `src/core/routeros-errors.ts` (REST `detail` and native-api `!trap`, identical
52
55
  on CHR 7.23).
@@ -0,0 +1,22 @@
1
+ # `quickchr/machine-not-found`
2
+
3
+ No quickchr machine is registered under the name passed to `--quickchr <name>`.
4
+
5
+ ## Typical trigger
6
+
7
+ The name does not match any machine quickchr knows about — a typo, a machine
8
+ that was never created, or one that has been removed. centrs raises this both
9
+ when `QuickCHR.get(name)` returns nothing and when the descriptor call reports
10
+ quickchr's own `MACHINE_NOT_FOUND`.
11
+
12
+ ## Fix
13
+
14
+ - List the registered machines with `quickchr list` and pass an existing name.
15
+ - Create/start the machine first if it does not exist yet (`quickchr start
16
+ <name>`).
17
+ - Names are exact — check for typos and case.
18
+
19
+ If the machine exists but is not running, you will see
20
+ [`quickchr/machine-stopped`](./machine-stopped.md) instead.
21
+
22
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers.
@@ -0,0 +1,20 @@
1
+ # `quickchr/machine-stopped`
2
+
3
+ The named quickchr machine exists but is not running, so it has no live
4
+ connection facts (host, forwarded ports, credentials) for centrs to dial.
5
+
6
+ ## Typical trigger
7
+
8
+ `--quickchr <name>` named a real machine, but its VM is stopped. quickchr only
9
+ knows the forwarded loopback ports and running-state auth while the machine is
10
+ up, so a stopped machine cannot yield a `ServiceEndpointMap`.
11
+
12
+ ## Fix
13
+
14
+ - Start it: `quickchr start <name>`, then retry `--quickchr <name>`.
15
+ - Confirm state with `quickchr list` / `quickchr inspect <name>`.
16
+
17
+ This is a per-target failure: in a fan-out over several `--quickchr` targets,
18
+ one stopped machine fails only its own row, not the whole run.
19
+
20
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers.
@@ -0,0 +1,27 @@
1
+ # `quickchr/package-unavailable`
2
+
3
+ The optional `@tikoci/quickchr` package is not installed, so a `--quickchr
4
+ <name>` target cannot be resolved into connection facts.
5
+
6
+ ## Typical trigger
7
+
8
+ You passed `--quickchr <name>` (or otherwise resolved a quickchr target) in an
9
+ install that does not have `@tikoci/quickchr`. centrs declares it as an
10
+ **optional dependency** and loads it through a runtime import, so a normal
11
+ install without it works for every non-quickchr path and only fails here.
12
+
13
+ ## Fix
14
+
15
+ - Install the package: `bun add @tikoci/quickchr` (0.4.5+ — 0.4.4 introduced
16
+ the descriptor v1 API but advertises dead TLS endpoints on a stock CHR;
17
+ see quickchr#95).
18
+ - Or bypass quickchr entirely and target the device directly with `--host`
19
+ (plus `--username`/`--password`/`--ssh-key` or a CDB record).
20
+
21
+ This code is raised **only** when the module is genuinely not found. An
22
+ installed package that throws while loading reports
23
+ [`quickchr/unsupported`](./unsupported.md) instead, so the two are never
24
+ confused.
25
+
26
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
27
+ the named-live-provider contract.
@@ -0,0 +1,35 @@
1
+ # `quickchr/unsupported-via`
2
+
3
+ The quickchr machine resolved fine, but the chosen `--via` has no usable
4
+ service on it. The descriptor is per-service; centrs reads exactly the
5
+ endpoint for the chosen protocol and never falls back to another transport.
6
+
7
+ ## Typical trigger
8
+
9
+ Any of:
10
+
11
+ - The `--via` is a protocol the descriptor does not forward. quickchr v1
12
+ forwards `rest-api`, `native-api`, and `ssh` only — `mac-telnet`, `romon`,
13
+ `btest`, and `winbox-terminal` are never available on a quickchr target.
14
+ - The service exists but quickchr marked it `available: false` (the error
15
+ carries the provider's `unavailableReason`).
16
+ - `--via ssh` (or `transfer --via sftp`/`scp`): the SSH endpoint reports no
17
+ batch-capable auth mode (`batchModes` is empty), so a non-interactive
18
+ handoff is impossible. centrs fails typed here rather than prompt for a
19
+ password or silently pick another transport.
20
+
21
+ ## Fix
22
+
23
+ - Pick one of the machine's live services; the error's `availableServices`
24
+ context lists them (typically `--via rest-api` / `--via native-api` /
25
+ `--via ssh`).
26
+ - For the SSH/sftp gate, verify key-based login works from quickchr's side
27
+ (`quickchr ssh <name>`); once quickchr has a verified key, the descriptor
28
+ advertises `private-key` in `batchModes` and centrs will use it.
29
+ - If the machine should be forwarding the service but is not, inspect it with
30
+ `quickchr inspect <name>`.
31
+ - To use a transport quickchr does not forward, target the device directly
32
+ (`--host`/CDB record) instead of `--quickchr`.
33
+
34
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
35
+ why a provider's missing service is a typed error, never a silent fallback.
@@ -0,0 +1,35 @@
1
+ # `quickchr/unsupported`
2
+
3
+ The installed `@tikoci/quickchr` cannot give centrs a usable descriptor: it is
4
+ too old to expose the descriptor API, it failed to load, or it emitted a
5
+ descriptor centrs cannot read.
6
+
7
+ ## Typical trigger
8
+
9
+ Any of:
10
+
11
+ - The installed quickchr predates the `QuickCHR.get(name).descriptor()` API
12
+ (before 0.4.4), so the descriptor entry point is missing.
13
+ - The package is installed but throws while loading (a broken/partial install),
14
+ which is a real fault — distinct from
15
+ [`quickchr/package-unavailable`](./package-unavailable.md), which means "not
16
+ installed at all".
17
+ - The descriptor is malformed or pre-v1: no numeric `descriptorVersion`, no
18
+ `services` map, or missing `name`/`version`/`arch`. centrs rejects it rather
19
+ than crash on a later field access or silently treat `services` as empty
20
+ (which would hide a provider contract violation).
21
+ - The descriptor version is **newer** than centrs supports
22
+ (`descriptorVersion` > 1); a newer quickchr speaks a schema this centrs
23
+ predates.
24
+
25
+ ## Fix
26
+
27
+ - Upgrade quickchr to a release that emits descriptor v1: `bun add
28
+ @tikoci/quickchr` (0.4.5+ — 0.4.4 has the API but a descriptor endpoint
29
+ bug, quickchr#95).
30
+ - If the descriptor looks wrong, inspect the machine directly with `quickchr
31
+ inspect <name>` — it may be in a transient state.
32
+ - If the descriptor is newer than centrs supports, upgrade centrs.
33
+
34
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers for
35
+ the descriptor contract and the supported version boundary.
@@ -0,0 +1,26 @@
1
+ # `routeros/version-unsupported`
2
+
3
+ The device's RouterOS version does not provide the operation this command
4
+ needs. centrs pre-flighted the generated command against the device's known
5
+ version (a live read, or the stored CDB `version=` fact) and refused it before
6
+ touching the router, instead of letting the device return a raw protocol
7
+ error.
8
+
9
+ Distinct from `routeros/unsupported-capability`, where the chosen *protocol*
10
+ cannot carry the operation at all regardless of device version.
11
+
12
+ ## Context fields
13
+
14
+ - `capability` — stable slug of the missing operation.
15
+ - `version` — the device version that was checked.
16
+ - `versionSource` — `live`, `cdb-fact`, or `caller`. A `cdb-fact` version is
17
+ stale-by-nature; it was recorded by `discover --save` or `devices --check`.
18
+ - `supported` — the version windows where RouterOS provides the operation
19
+ (inclusive `min`, optional exclusive `maxExclusive` for backport windows).
20
+
21
+ ## Fix
22
+
23
+ Upgrade RouterOS to one of the versions listed in `supported`. If the device
24
+ was already upgraded and the check used a stale stored fact
25
+ (`versionSource: "cdb-fact"`), refresh the record's `version=` comment fact
26
+ and retry.
@@ -0,0 +1,29 @@
1
+ # `transport/provider-trust`
2
+
3
+ A warning (not an error): the target came from a named-live-provider
4
+ (`--quickchr <name>`), and centrs relaxed peer verification to use its
5
+ endpoint.
6
+
7
+ ## Why this happens
8
+
9
+ quickchr's descriptor prefers the VM's TLS forwards (`https` / `api-ssl`),
10
+ whose certificate is the CHR's own self-signed one, and its SSH endpoint's
11
+ host key is regenerated per VM. The descriptor carries no trust material, so
12
+ strict verification would reject every quickchr target and force `--insecure`
13
+ ceremony onto every lab call.
14
+
15
+ Instead, centrs treats the endpoint as **trusted by provenance**: quickchr
16
+ vouches for a loopback-forwarded port (`127.0.0.1:<port>`) of a VM the user
17
+ owns, so peer verification is relaxed for exactly that connection — with
18
+ `provider` provenance on `meta.settings.insecure` and this warning on the
19
+ envelope, never silently.
20
+
21
+ ## When to care
22
+
23
+ - If this warning appears on a target you did **not** select with
24
+ `--quickchr`, that is a bug — report it.
25
+ - The relaxation never applies to CDB, literal, or env-resolved targets;
26
+ those still require an explicit `--insecure` (which raises
27
+ [`transport/insecure-trust`](./insecure-trust.md) instead).
28
+
29
+ See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) → Resolution providers.
@@ -0,0 +1,28 @@
1
+ # `usage/stdin-ignored`
2
+
3
+ stdin was redirected but the input came from the positional argument, so
4
+ anything piped in was not analyzed.
5
+
6
+ This is a **warning**, not an error: the analysis ran and its result is exactly
7
+ what the arguments asked for. What the warning reports is that a second possible
8
+ input source was present and lost.
9
+
10
+ ## Fix
11
+
12
+ Name the source you meant:
13
+
14
+ ```bash
15
+ cat script.rsc | centrs explain --file - # analyze stdin
16
+ centrs explain '/ip/route print' # analyze the positional
17
+ ```
18
+
19
+ The live form spells the same thing (`… | centrs explain edge1 --file -`) and is
20
+ already parsed, but it is refused with
21
+ [`usage/not-implemented`](not-implemented.md) until the phase-2 live probes
22
+ land — so it is not a fix you can apply today.
23
+
24
+ `explain` reads ambient stdin only when no positional could be the input, so a
25
+ positional always wins. It cannot check whether the pipe actually carries bytes
26
+ without consuming fd 0, which is why the collision is reported rather than
27
+ resolved. See [`commands/explain/README.md`](https://github.com/tikoci/centrs/blob/main/commands/explain/README.md)
28
+ for the positional grammar.
@@ -1,9 +1,42 @@
1
1
  # `validation/unknown-attribute`
2
2
 
3
- An attribute is not valid for the path/verb per /console/inspect.
3
+ An attribute or argument is not valid for the requested RouterOS operation.
4
+ The validator rejects the request before RouterOS executes it.
5
+
6
+ ## Context keys
7
+
8
+ Context keys vary by validation mechanism. Callers must not assume every key
9
+ below is present on every `validation/unknown-attribute` rejection.
10
+
11
+ | Key | Type | Notes |
12
+ | --- | ---- | ----- |
13
+ | `parameter` | `string` | First offending name. Present on retrieve and inspect-gated rejections, and on console `:parse` when RouterOS reports `bad parameter <name>`. |
14
+ | `requestedAttributes` | `string[]` | Full requested name list. Retrieve and inspect-gated API/execute validation only. |
15
+ | `availableAttributes` | `string[]` | Names exposed for the path/verb. Retrieve and inspect-gated API/execute validation only. |
16
+ | `path` | `string` | Slash-prefixed RouterOS path (for example `/ip/address`). Retrieve and inspect-gated API/execute validation only. |
17
+ | `verb` | `string` | Verb when applicable (`print`, `add`, `set`, …). Inspect-gated API/execute validation only; retrieve is path-only. |
18
+ | `validationSource` | `string` | Validation provenance. Inspect-gated API/execute and console `:parse` only; retrieve does not currently emit it. |
19
+ | `command` | `string` | Original CLI command. Console `:parse` only. |
20
+ | `detail` | `string` | Raw RouterOS parse diagnostic. Console `:parse` only. |
21
+
22
+ Console `:parse` may also put RouterOS' byte location in the top-level
23
+ `error.position` field. On older RouterOS versions, an unknown name can produce
24
+ only a generic syntax diagnostic; that is reported as `validation/syntax`
25
+ because no parameter identity is available.
26
+
27
+ Provenance:
28
+
29
+ - `src/api.ts:988,1033` — REST/native `validateApiRequest` (print vs add/set branches)
30
+ - `src/execute.ts:742` — REST `validateRestExecute`
31
+ - `src/retrieve.ts:233` — REST `retrieve` attribute projection
32
+ - `src/protocols/mac-telnet-console.ts:674` — console `:parse` (`bad parameter <name>`) over mac-telnet/ssh
33
+ - `src/core/routeros-errors.ts:128` — `!trap`/`detail` `unknown parameter <name>` (routeros/unknown-attribute sibling)
4
34
 
5
35
  ## Fix
6
36
 
7
- See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
8
- contract. This stub will be expanded with the typical trigger and remediation
9
- for `validation/unknown-attribute`.
37
+ For `retrieve` and inspect-gated API/execute validation, check the name against
38
+ `/console/inspect`; `retrieve --list-attributes` lists the available properties.
39
+ For console `:parse`, compare `context.parameter` with the RouterOS parameters
40
+ for `context.command`; `context.detail` preserves the device's raw diagnostic.
41
+ Use `--validate=false` only when intentionally probing an undocumented RouterOS
42
+ edge.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/centrs",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "main": "src/index.ts",
5
5
  "module": "src/index.ts",
6
6
  "repository": {
@@ -9,11 +9,11 @@
9
9
  },
10
10
  "homepage": "https://github.com/tikoci/centrs#readme",
11
11
  "bugs": "https://github.com/tikoci/centrs/issues",
12
- "packageManager": "bun@1.3.13",
12
+ "packageManager": "bun@1.3.14",
13
+ "license": "MIT",
13
14
  "devDependencies": {
14
- "@biomejs/biome": "2.5.2",
15
+ "@biomejs/biome": "2.5.7",
15
16
  "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
16
- "@tikoci/quickchr": "^0.4.2",
17
17
  "@types/bun": "^1.3.13",
18
18
  "cloc": "^2.6.0-cloc",
19
19
  "cspell": "^10.0.0",
@@ -22,7 +22,9 @@
22
22
  "secretlint": "^13.0.2",
23
23
  "typedoc": "^0.28.19"
24
24
  },
25
- "optionalDependencies": {},
25
+ "optionalDependencies": {
26
+ "@tikoci/quickchr": "^0.4.7"
27
+ },
26
28
  "peerDependencies": {
27
29
  "typescript": "^6.0.3"
28
30
  },
@@ -45,22 +47,24 @@
45
47
  "files": [
46
48
  "src/",
47
49
  "commands/",
50
+ "docs/CLI.md",
48
51
  "docs/CONSTITUTION.md",
49
52
  "docs/MATRIX.md",
50
53
  "docs/errors/",
51
54
  "README.md",
52
55
  "CHANGELOG.md",
53
56
  "SECURITY.md",
57
+ "LICENSE",
54
58
  "GLOSSARY.txt"
55
59
  ],
56
60
  "scripts": {
57
- "ci": "bun run lint && bun run test && bun run build",
58
- "git:pre-commit": "bun run lint",
61
+ "ci": "bun run lint:ci && bun run test && bun run build",
62
+ "git:pre-commit": "bun run lint:git:commit",
59
63
  "git:pre-push": "bun run lint:git:push",
60
64
  "lint": "bun run lint:biome && bun run lint:typecheck",
61
- "lint:git:commit": "bun run lint",
62
- "lint:git:push": "bun run lint:ci && bun run test && bun run build",
63
- "lint:ci": "bun run lint && bun run lint:markdownlint && bun run lint:cspell && bun run lint:secretlint",
65
+ "lint:git:commit": "bun run lint:biome",
66
+ "lint:git:push": "bun run lint:ci",
67
+ "lint:ci": "bun run lint && bun run docs:cli:check && bun run explain:value-census:readme:check && bun run explain:token-census:readme:check && bun run explain:operator-readme:check && bun run lint:markdownlint && bun run lint:cspell && bun run lint:secretlint",
64
68
  "lint:cspell": "cspell lint . --gitignore",
65
69
  "lint:cspell:all": "cspell lint . --no-gitignore",
66
70
  "lint:markdownlint": "markdownlint-cli2 \"**/*.md\" \"!.scratch/**\"",
@@ -82,9 +86,40 @@
82
86
  "test:ci": "bun test --coverage",
83
87
  "test:all": "bun run test:ci",
84
88
  "check:agent": "bun run scripts/agent-check.ts",
89
+ "corpus:fetch": "bun run scripts/corpus-fetch.ts",
85
90
  "build": "bun build src/index.ts src/cli.ts src/mcp.ts src/tui.ts src/webproxy.ts src/protocols/index.ts --outdir dist --target bun",
86
91
  "build:doc:api": "typedoc --out docs/api src/index.ts",
92
+ "docs:cli": "bun run scripts/gen-cli-docs.ts",
93
+ "docs:cli:check": "bun run scripts/gen-cli-docs.ts --check",
87
94
  "docs:errors": "bun run scripts/gen-error-pages.ts",
95
+ "explain:catalog": "bun run scripts/gen-explain-catalog.ts",
96
+ "explain:catalog:check": "bun run scripts/gen-explain-catalog.ts --check",
97
+ "explain:corpus-census": "bun run scripts/explain-corpus-census.ts",
98
+ "explain:highlight-slice": "bun run scripts/explain-highlight-slice.ts",
99
+ "explain:menus": "bun run scripts/gen-explain-menus.ts",
100
+ "explain:operator-census": "bun run scripts/explain-operator-census.ts",
101
+ "explain:operator-census:check": "bun run scripts/explain-operator-census.ts --check",
102
+ "explain:operator-readme": "bun run scripts/explain-operator-slice.ts --readme",
103
+ "explain:operator-readme:check": "bun run scripts/explain-operator-slice.ts --readme --check",
104
+ "explain:operator-slice": "bun run scripts/explain-operator-slice.ts",
105
+ "explain:probe:brace-slots": "bun run scripts/probes/explain-brace-slot-sweep.ts",
106
+ "explain:probe:escapes": "bun run scripts/probes/explain-escape-sweep.ts",
107
+ "explain:probe:highlight-recapture": "bun run scripts/probes/explain-highlight-recapture.ts",
108
+ "explain:probe:operators": "bun run scripts/probes/explain-operator-sweep.ts",
109
+ "explain:probe:symbol-anchors": "bun run scripts/probes/explain-symbol-anchors.ts",
110
+ "explain:probe:symbol-arms-score": "bun run scripts/probes/explain-symbol-arms-score.ts",
111
+ "explain:probe:symbol-bad-sigil-replay": "bun run scripts/probes/explain-symbol-bad-sigil-replay.ts",
112
+ "explain:probe:symbol-classes-replay": "bun run scripts/probes/explain-symbol-classes-replay.ts",
113
+ "explain:probe:version-floors": "bun run scripts/probes/explain-version-floors.ts",
114
+ "explain:menus:check": "bun run scripts/gen-explain-menus.ts --check",
115
+ "explain:value-census": "bun run scripts/explain-value-census.ts",
116
+ "explain:value-census:check": "bun run scripts/explain-value-census.ts --check",
117
+ "explain:value-census:readme": "bun run scripts/explain-value-census.ts --readme",
118
+ "explain:value-census:readme:check": "bun run scripts/explain-value-census.ts --readme --check",
119
+ "explain:token-census": "bun run scripts/explain-token-census.ts",
120
+ "explain:token-census:check": "bun run scripts/explain-token-census.ts --check",
121
+ "explain:token-census:readme": "bun run scripts/explain-token-census.ts --readme",
122
+ "explain:token-census:readme:check": "bun run scripts/explain-token-census.ts --readme --check",
88
123
  "security": "bun run lint:secretlint && bun run lint:markdownlint"
89
124
  },
90
125
  "type": "module",