@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.
- package/CHANGELOG.md +39 -0
- package/GLOSSARY.txt +213 -0
- package/LICENSE +21 -0
- package/README.md +36 -8
- package/commands/AGENTS.md +7 -1
- package/commands/api/README.md +19 -18
- package/commands/api/examples.md +15 -0
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +6 -1
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +41 -29
- package/commands/execute/examples.md +41 -3
- package/commands/explain/README.md +1716 -0
- package/commands/explain/examples.md +606 -0
- package/commands/retrieve/README.md +25 -27
- package/commands/retrieve/examples.md +16 -0
- package/commands/transfer/README.md +28 -20
- package/commands/transfer/examples.md +29 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +66 -15
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -0
- package/docs/errors/quickchr/machine-not-found.md +22 -0
- package/docs/errors/quickchr/machine-stopped.md +20 -0
- package/docs/errors/quickchr/package-unavailable.md +27 -0
- package/docs/errors/quickchr/unsupported-via.md +35 -0
- package/docs/errors/quickchr/unsupported.md +35 -0
- package/docs/errors/routeros/version-unsupported.md +26 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +45 -10
- package/src/api-fanout.ts +29 -14
- package/src/api.ts +97 -54
- package/src/cli/api.ts +25 -6
- package/src/cli/btest.ts +18 -2
- package/src/cli/execute.ts +25 -6
- package/src/cli/explain.ts +503 -0
- package/src/cli/retrieve.ts +33 -1
- package/src/cli/selection.ts +71 -11
- package/src/cli/settings.ts +2 -2
- package/src/cli/transfer.ts +23 -6
- package/src/cli.ts +15 -2
- package/src/core/envelope.ts +5 -2
- package/src/core/error-catalog.ts +40 -0
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +29 -14
- package/src/execute.ts +114 -63
- package/src/explain/arg-tokens.ts +114 -0
- package/src/explain/args.ts +1403 -0
- package/src/explain/blocks.ts +132 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +38 -0
- package/src/explain/catalog.ts +1345 -0
- package/src/explain/coordinates.ts +329 -0
- package/src/explain/defects.ts +217 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +664 -0
- package/src/explain/operator-tokens.ts +310 -0
- package/src/explain/operators.ts +458 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1324 -0
- package/src/explain/quoted-string.ts +241 -0
- package/src/explain/scope-brace.ts +308 -0
- package/src/explain/segment.ts +757 -0
- package/src/explain/string-tokens.ts +67 -0
- package/src/explain/symbol-values.ts +520 -0
- package/src/explain/symbols.ts +1597 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +497 -0
- package/src/explain/value-tokens.ts +80 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +813 -0
- package/src/explain/write.ts +815 -0
- package/src/explain.ts +2152 -0
- package/src/index.ts +206 -4
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/facts.ts +1 -1
- package/src/resolver/index.ts +23 -4
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +99 -12
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/target.ts +1 -1
- package/src/retrieve-fanout.ts +26 -12
- package/src/retrieve.ts +81 -18
- package/src/transfer-fanout.ts +42 -25
- package/src/transfer.ts +87 -44
package/docs/CONSTITUTION.md
CHANGED
|
@@ -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.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`:parse` covers both syntax and the
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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`
|
|
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. |
|
package/docs/errors/README.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
"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.
|
|
12
|
+
"packageManager": "bun@1.3.14",
|
|
13
|
+
"license": "MIT",
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"@biomejs/biome": "2.5.
|
|
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
|
|
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",
|