@tikoci/centrs 0.1.3 → 0.1.6
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 +348 -0
- package/GLOSSARY.txt +252 -0
- package/LICENSE +21 -0
- package/README.md +44 -12
- package/commands/AGENTS.md +14 -5
- package/commands/api/AGENTS.md +13 -1
- package/commands/api/README.md +56 -26
- package/commands/api/examples.md +46 -1
- 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 +77 -36
- package/commands/execute/examples.md +120 -10
- package/commands/explain/README.md +2756 -0
- package/commands/explain/examples.md +702 -0
- package/commands/mcp/README.md +7 -4
- package/commands/mcp/examples.md +2 -2
- package/commands/retrieve/README.md +25 -27
- package/commands/retrieve/examples.md +16 -0
- package/commands/terminal/examples.md +7 -1
- package/commands/transfer/README.md +28 -20
- package/commands/transfer/examples.md +29 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +138 -30
- package/docs/MATRIX.md +92 -18
- 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/syntax.md +29 -4
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +56 -10
- package/src/api-fanout.ts +46 -18
- package/src/api.ts +351 -121
- package/src/cli/api.ts +26 -7
- package/src/cli/btest.ts +19 -3
- 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/terminal.ts +1 -1
- package/src/cli/transfer.ts +23 -6
- package/src/cli.ts +15 -2
- package/src/core/envelope.ts +58 -2
- package/src/core/error-catalog.ts +42 -1
- package/src/core/execute-command.ts +148 -0
- package/src/core/inspect.ts +36 -4
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/core/version.ts +4 -0
- package/src/core/yaml.ts +86 -0
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +47 -18
- package/src/execute.ts +339 -307
- package/src/explain/arg-tokens.ts +151 -0
- package/src/explain/args.ts +1749 -0
- package/src/explain/blocks.ts +175 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +39 -0
- package/src/explain/catalog.ts +1343 -0
- package/src/explain/coordinates.ts +488 -0
- package/src/explain/defects.ts +218 -0
- package/src/explain/escape-tokens.ts +77 -0
- package/src/explain/highlight-projection.ts +308 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +668 -0
- package/src/explain/operator-tokens.ts +316 -0
- package/src/explain/operators.ts +465 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1569 -0
- package/src/explain/quoted-string.ts +331 -0
- package/src/explain/scope-brace.ts +461 -0
- package/src/explain/segment.ts +1165 -0
- package/src/explain/separator.ts +187 -0
- package/src/explain/split-owner.ts +194 -0
- package/src/explain/string-tokens.ts +77 -0
- package/src/explain/symbol-values.ts +556 -0
- package/src/explain/symbols.ts +1617 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +514 -0
- package/src/explain/value-tokens.ts +83 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +955 -0
- package/src/explain/write.ts +856 -0
- package/src/explain.ts +2602 -0
- package/src/index.ts +225 -6
- package/src/mcp/server.ts +2 -1
- package/src/offline-gate.ts +148 -0
- 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 +27 -13
- package/src/retrieve.ts +82 -77
- package/src/settings.ts +2 -1
- package/src/transfer-fanout.ts +43 -26
- package/src/transfer.ts +88 -45
package/docs/CONSTITUTION.md
CHANGED
|
@@ -9,15 +9,22 @@ This file is short on purpose. Resist the urge to grow it.
|
|
|
9
9
|
|
|
10
10
|
## What centrs is
|
|
11
11
|
|
|
12
|
-
A typed library
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
A typed library for RouterOS analysis and **validated** device access, with
|
|
13
|
+
CLI/MCP/TUI/proxy frontends and structured, actionable diagnostics. The
|
|
14
|
+
TypeScript API is the root surface; everything else is an adapter over it.
|
|
15
|
+
The initial consumers are other TIKOCI tools and developers building integrations.
|
|
16
16
|
|
|
17
17
|
The product is *not* "REST/SSH/native API wrappers." The product is
|
|
18
18
|
**validation + structured envelopes + identity resolution** on top of those
|
|
19
19
|
transports. Without those, this is a worse `curl`.
|
|
20
20
|
|
|
21
|
+
`explain` is shared analysis across transports and a useful offline capability
|
|
22
|
+
in its own right. It exposes source structure, facts, uncertainty, and evidence
|
|
23
|
+
without executing the input. Those facts can support future diagnostics in
|
|
24
|
+
other commands' envelopes; that integration is not implemented by declaring
|
|
25
|
+
this boundary. Analysis findings do not grant execution permission or replace
|
|
26
|
+
the runners' validation gates.
|
|
27
|
+
|
|
21
28
|
centrs also speaks a small set of MikroTik **peer protocols** that are *not*
|
|
22
29
|
RouterOS-command transports — currently the **bandwidth test** (`btest`), which
|
|
23
30
|
mirrors `/tool/bandwidth-test` and `/tool/bandwidth-server`. This is a
|
|
@@ -29,29 +36,69 @@ friendly errors, but the "validate a RouterOS-shaped command" gate below does
|
|
|
29
36
|
|
|
30
37
|
## Validation is the product
|
|
31
38
|
|
|
32
|
-
Every RouterOS-shaped
|
|
33
|
-
re-validate-server-side.
|
|
39
|
+
Every RouterOS-shaped operation sent to a device goes through canonicalize →
|
|
40
|
+
validate → run → re-validate-server-side. Analysis-only `explain` never runs
|
|
41
|
+
the analyzed input; offline analysis opens no connection. Validation on the
|
|
42
|
+
runners is not optional polish.
|
|
43
|
+
|
|
44
|
+
The `validate` step itself has **two stages, in this order** (GH#354):
|
|
45
|
+
|
|
46
|
+
1. **Offline** — the same corpus-gated analyzer `explain` publishes, run over
|
|
47
|
+
the input with no connection (`src/offline-gate.ts`). A rejection is a byte
|
|
48
|
+
span and no round trip; the protocol adapter is never dialed. This stage runs
|
|
49
|
+
on every transport, for `execute` and for `api`'s `/execute` script mode.
|
|
50
|
+
2. **Device** — the RouterOS-side probe below: `:put [:parse "…"]` and/or
|
|
51
|
+
`/console/inspect request=child`, depending on the command shape and the
|
|
52
|
+
transport.
|
|
53
|
+
|
|
54
|
+
The stages are not interchangeable and neither subsumes the other. Offline has
|
|
55
|
+
no per-menu schema (decision 3 — no offline schema snapshot), so it cannot see
|
|
56
|
+
an unknown attribute; the device probe is version-fragile (GH#343), so where it
|
|
57
|
+
is unsafe the offline stage is what keeps the gate from degrading to nothing. A
|
|
58
|
+
clean offline pass is **necessary, never sufficient** — `explain` publishes
|
|
59
|
+
`runtimeAcceptance: "not-proven"` for exactly this reason. An offline `warn` is
|
|
60
|
+
an ABSTENTION and passes through to the device stage; only a `fail` rejects.
|
|
61
|
+
|
|
62
|
+
`meta.validation.stages[]` reports what each stage concluded, including a stage
|
|
63
|
+
that was skipped and why. A reader must always be able to tell "the device
|
|
64
|
+
accepted it" from "the device was never asked".
|
|
65
|
+
|
|
66
|
+
`--validate=false` disables **both** stages — one flag, one meaning. A bypass
|
|
67
|
+
that covered only the device stage would leave a caller stranded behind a
|
|
68
|
+
stage-1 defect with no escape at all, and the analyzer is demonstrably wrong in
|
|
69
|
+
both directions today (GH#355 is a live false accept, GH#351 a live false
|
|
70
|
+
reject). That is a reason for the flag to reach both stages, not a license to
|
|
71
|
+
use it: the rule below still holds — a wrong rejection is fixed in the
|
|
72
|
+
validator, with CHR evidence.
|
|
34
73
|
|
|
35
74
|
- `retrieve` and other read-shaped calls validate against
|
|
36
75
|
`/console/inspect request=syntax path=...,print` (path joined by commas) and
|
|
37
76
|
attribute inspection. The path syntax (commas, no leading slash, last token
|
|
38
77
|
is the verb) is part of the validator contract.
|
|
39
78
|
- `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
|
-
|
|
79
|
+
a semantic gate. Grounded on CHR 7.23.3 (GH#230), **`:parse` never throws** —
|
|
80
|
+
it returns a script value whose printed form carries the verdict. For a bare
|
|
81
|
+
menu path a clean parse prints `(evl /canonical/path)` with abbreviations
|
|
82
|
+
expanded; for script-shaped input (e.g. `:put …`, `:global …`, `where …`) the
|
|
83
|
+
accepted return echoes the script body and may contain arbitrary text such as
|
|
84
|
+
`expected`. Rejection forms are `(evl bad parameter <name> (line N column M)
|
|
85
|
+
/path)`, `(<%% bad command name <name> (line N column M) …)`, and the
|
|
86
|
+
line-anchored `expected … (line N column M)` (spellings: `expected end of
|
|
87
|
+
command`, `expected command name`, `expected input value`) / `syntax error
|
|
88
|
+
(line N column M)`. That text rides the **HTTP-200 `ret` value** (REST,
|
|
89
|
+
`as-string`), the native `as-string` `ret`, and the console's printed output
|
|
90
|
+
alike — so a single `:put [:parse ...]` covers both the syntax and the
|
|
91
|
+
**unknown-attribute** gate on every transport. centrs sends `as-string` and now
|
|
92
|
+
reads the `ret`/printed value (the previous gate ignored it, so it rejected
|
|
93
|
+
nothing). Because the accepted return echoes the body, the matcher must anchor
|
|
94
|
+
on the diagnostic shape (the `(<%%` wrapper, or a trailing `(line N column M)`)
|
|
95
|
+
and never on a bare English word — `expected` is also a real
|
|
96
|
+
`/ip/firewall/connection` field name.
|
|
97
|
+
That unknown-attribute check is **name-level only**: `:parse` does not validate
|
|
98
|
+
parameter *values* (`/ip/address/add address=not-an-ip` parses clean). So a
|
|
99
|
+
clean parse is necessary, not sufficient, and value-type plus the remaining
|
|
100
|
+
semantic validation stay distinct steps — `/console/inspect` or the server's
|
|
101
|
+
own re-validation on the write round-trip.
|
|
55
102
|
- **Peer-measurement commands (`btest`) are exempt from this gate** — they issue
|
|
56
103
|
no RouterOS command, so they validate *option-grammar* + the **EC-SRP5** auth
|
|
57
104
|
exchange instead (the envelope, identity-resolution, and friendly-error
|
|
@@ -260,6 +307,43 @@ falls in `--near`'s radius **or** `--bbox`.
|
|
|
260
307
|
by a distinct `--new-group` flag on `devices add`/`set`, so record selection and
|
|
261
308
|
field-setting never collide on one flag.
|
|
262
309
|
|
|
310
|
+
#### Resolution providers
|
|
311
|
+
|
|
312
|
+
Target resolution has **two independent axes**, and centrs keeps them separate so
|
|
313
|
+
the resolver is generalized **once**, not per source (the umbrella plan is #174):
|
|
314
|
+
|
|
315
|
+
- **Axis A — selection** ("which devices"): the target-selection grammar above.
|
|
316
|
+
- **Axis B — connection-fact origin** ("where host/port/auth for a member come
|
|
317
|
+
from"): what a resolved member yields. Today: a **CDB record** or an ad-hoc
|
|
318
|
+
**literal** (+ the `__default__` ladder). Tomorrow: a live descriptor or an
|
|
319
|
+
inventory entry.
|
|
320
|
+
|
|
321
|
+
Sources fall into two kinds, and the distinction is normative:
|
|
322
|
+
|
|
323
|
+
- **Selector-source** — participates in the Axis-A grammar. **CDB** (now), and
|
|
324
|
+
**TikTOML** next (#137). Multiple selector-sources union into one selection;
|
|
325
|
+
the split-brain precedence between them is #137's problem, not resolved here.
|
|
326
|
+
- **Named-live-provider** — addressed *only* by an explicit named flag, **never**
|
|
327
|
+
matched by a selector; fanned out by repeating the flag. Because it owns an
|
|
328
|
+
**ephemeral** endpoint it also **bypasses** the settings/`__default__` ladder and
|
|
329
|
+
**conflicts** with `--host`/`--port`/`--username`/`--password`/`--ssh-key`. These
|
|
330
|
+
are properties of *live/ephemeral* providers, not of any one provider. **quickchr**
|
|
331
|
+
(`--quickchr <name>`, #134) is the first. A named-live-provider selection is
|
|
332
|
+
**exclusive** (v1): mixing it with `<router>` positionals or selector flags is
|
|
333
|
+
`usage/conflicting-flags` — one flag stays single-target, repeating it fans
|
|
334
|
+
out. Relaxing exclusivity later is additive.
|
|
335
|
+
|
|
336
|
+
Connection facts resolve into one neutral **per-service endpoint map**
|
|
337
|
+
(`src/resolver/service-endpoint.ts`: `via → ServiceEndpoint`, each carrying
|
|
338
|
+
availability, host, port, TLS, auth, and provenance). A **selector-source** is the
|
|
339
|
+
*degenerate single-endpoint* case — one record materialized lazily once `--via` is
|
|
340
|
+
known. A **named-live-provider** populates the map directly, since it genuinely
|
|
341
|
+
exposes different facts per transport. Providers must **not** be flattened to a
|
|
342
|
+
single host/port: that shape is the contract quickchr's descriptor and TikTOML's
|
|
343
|
+
`services` block both map onto, and flattening it forces exactly the re-plumb #174
|
|
344
|
+
exists to prevent. A `--via` whose service key a provider does not supply is a
|
|
345
|
+
typed unsupported-provider error, never a silent fallback.
|
|
346
|
+
|
|
263
347
|
### MCP surface: the CDB is the allowlist
|
|
264
348
|
|
|
265
349
|
The MCP frontend (`commands/mcp/`) is an adapter over this core, not a new set of
|
|
@@ -352,22 +436,38 @@ default), so key auth is the normal sftp path.
|
|
|
352
436
|
|
|
353
437
|
## Done definition
|
|
354
438
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
439
|
+
Done requires executable evidence for the capability being claimed. Device
|
|
440
|
+
operations and live analysis require **green CHR integration tests** against a
|
|
441
|
+
real RouterOS CHR booted by `@tikoci/quickchr`, via `bun run test:integration`.
|
|
442
|
+
Code existing or unit tests passing cannot establish live behavior. This is
|
|
443
|
+
the **normative** done definition; `AGENTS.md` and
|
|
359
444
|
`.github/instructions/done-definition.instructions.md` point here (the latter
|
|
360
445
|
carries the Copilot-workflow procedure for satisfying it).
|
|
361
446
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
447
|
+
Offline analysis is `verified` when its applicable command examples and
|
|
448
|
+
contract checks pass without a router, its RouterOS semantic claims have
|
|
449
|
+
committed CHR-grounded evidence, and the command's baseline acceptance criteria
|
|
450
|
+
are satisfied. New or changed RouterOS semantics still require live grounding
|
|
451
|
+
with accepted and rejected controls plus a reproducible capture/replay path;
|
|
452
|
+
offline tests do not establish new device facts. Changes to shared execution
|
|
453
|
+
or transport behavior still require `bun run test:integration`. The offline
|
|
454
|
+
call itself never needs a router merely to satisfy a workflow label.
|
|
455
|
+
|
|
456
|
+
`docs/MATRIX.md` owns capability status and the selected work track. A
|
|
457
|
+
capability's acceptance criteria — like `explain`'s Offline baseline
|
|
458
|
+
acceptance — live in its `commands/<name>/README.md`; GitHub issues own the
|
|
459
|
+
tasks and dependencies that satisfy those criteria, not the criteria
|
|
460
|
+
themselves. Umbrella issues index that work rather than replacing capability
|
|
461
|
+
status. Status advances only with matching evidence in the same change.
|
|
462
|
+
Record test names, fixture provenance, and CHR versions where applicable.
|
|
463
|
+
Offline verification never advances a live protocol cell to `CHR-passed`.
|
|
366
464
|
|
|
367
465
|
## Boundaries
|
|
368
466
|
|
|
369
467
|
centrs does:
|
|
370
468
|
|
|
469
|
+
- Analyze RouterOS source independently of a connection, preserving ordered
|
|
470
|
+
structure, source locations, evidence, and ambiguous/unknown readings.
|
|
371
471
|
- Resolve devices/groups from explicit input, env, CDB, MNDP cache.
|
|
372
472
|
- Choose protocols per the table above and configure them.
|
|
373
473
|
- Validate before execute; re-validate server-side immediately before run.
|
|
@@ -380,7 +480,9 @@ centrs does:
|
|
|
380
480
|
|
|
381
481
|
centrs does not:
|
|
382
482
|
|
|
383
|
-
- Replace RouterOS syntax with high-level
|
|
483
|
+
- Replace RouterOS syntax with high-level configuration helpers. Normalized
|
|
484
|
+
representations that preserve its semantics are in scope; reconstructing
|
|
485
|
+
final device state or predicting network behavior is not an offline parse claim.
|
|
384
486
|
- Treat passive discovery (MNDP) as authoritative inventory.
|
|
385
487
|
- Run write-shaped operations without an explicit target and validation policy.
|
|
386
488
|
- Make generated output the hand-edited source of truth.
|
|
@@ -440,3 +542,9 @@ canonicalization only, never the structured-mode predicate. The gate contract,
|
|
|
440
542
|
its pinning test, and the parser-vendoring preconditions are documented in
|
|
441
543
|
`commands/execute/README.md` and the `canonicalizeExecuteCommand` doc-comment
|
|
442
544
|
(`src/execute.ts`).
|
|
545
|
+
|
|
546
|
+
Richer schema-free analysis must distinguish **ambiguous** (multiple plausible
|
|
547
|
+
readings) from **unknown** (no safe reading) and abstain in both cases rather than
|
|
548
|
+
inventing a command, path, symbol class, or transport mapping. These explain-only
|
|
549
|
+
verdicts never widen or reinterpret the execution gate's `mode` or `writeShaped`
|
|
550
|
+
decision.
|
package/docs/MATRIX.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Matrix
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This file owns **capability status and the selected work track**. The
|
|
4
|
+
command × protocol grid covers live operations; offline analysis, frontends,
|
|
5
|
+
and peer measurement have separate entries below. A capability's acceptance
|
|
6
|
+
criteria live in its `commands/<name>/README.md`; GitHub issues own the tasks
|
|
7
|
+
and dependencies that satisfy them.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
Start with [Current priority](#current-priority), then the command's README,
|
|
10
|
+
examples, and linked issue. Verification follows `docs/CONSTITUTION.md` →
|
|
11
|
+
Done definition. A completed implementation slice is not proof that the whole
|
|
12
|
+
capability is complete.
|
|
9
13
|
|
|
10
14
|
## Cell states
|
|
11
15
|
|
|
@@ -15,10 +19,15 @@ work. See `docs/CONSTITUTION.md` for the cell-state definitions.
|
|
|
15
19
|
| `not-started` | No code and no design |
|
|
16
20
|
| `designed` | `commands/<name>/README.md` describes intent and flags |
|
|
17
21
|
| `coded` | Implementation exists in `src/` |
|
|
22
|
+
| `verified` | Offline capability meets its command README's baseline acceptance criteria in full (examples, contract checks, measured/consumer gates, and CHR-grounded evidence). |
|
|
18
23
|
| `CHR-passed` | Every example in `commands/<name>/examples.md` is green on CHR |
|
|
19
24
|
|
|
20
25
|
A cell advances only with the matching evidence in the same change.
|
|
21
26
|
|
|
27
|
+
`verified` applies to offline capabilities; it does not advance a live protocol
|
|
28
|
+
cell. Existing `CHR-passed` fixture-backed `devices`/`settings` entries retain
|
|
29
|
+
their command-specific evidence contracts.
|
|
30
|
+
|
|
22
31
|
## Grid
|
|
23
32
|
|
|
24
33
|
| Command | rest-api | native-api | ssh | mac-telnet | snmp | mndp | romon | winbox-terminal |
|
|
@@ -31,13 +40,14 @@ A cell advances only with the matching evidence in the same change.
|
|
|
31
40
|
| devices | — | — | — | — | — | — | — | — |
|
|
32
41
|
| discover | — | — | — | — | — | `CHR-passed` | — | — |
|
|
33
42
|
| check | `designed` | `designed` | `designed` | `designed` | `not-started` | `not-started` | `not-started` | `not-started` |
|
|
43
|
+
| explain | `designed` | `designed` | — | — | — | — | — | — |
|
|
34
44
|
| settings | — | — | — | — | — | — | — | — |
|
|
35
45
|
|
|
36
46
|
## Status pointers
|
|
37
47
|
|
|
38
48
|
Detailed command contracts live in `commands/<name>/README.md`; example evidence
|
|
39
|
-
lives in `commands/<name>/examples.md` and the named
|
|
40
|
-
|
|
49
|
+
lives in `commands/<name>/examples.md` and the named tests. Keep task history
|
|
50
|
+
and implementation sequencing in the linked issues.
|
|
41
51
|
|
|
42
52
|
- `devices` is transport-less, so its row stays `—`; command state is
|
|
43
53
|
`CHR-passed` by fixture-backed integration tests in `test/integration/devices.test.ts`.
|
|
@@ -59,6 +69,13 @@ section short enough that the grid remains the status surface.
|
|
|
59
69
|
`winbox-terminal` stay `not-started`. Its per-host fan-out is the machinery the
|
|
60
70
|
IP-scan discovery (#149) will iterate; the L2-default timing evidence (#136) is
|
|
61
71
|
recorded in the README before the cells advance past `designed`.
|
|
72
|
+
- `explain`'s two `designed` protocol cells are the future live probes
|
|
73
|
+
(`/console/inspect` + `:parse`, #236). The live grammar
|
|
74
|
+
(`explain <router> '<input>'`) is parsed and refused with
|
|
75
|
+
`usage/not-implemented`, never degraded to an offline run. Offline analysis
|
|
76
|
+
has its own capability entry below. The generated structure tables
|
|
77
|
+
`src/explain/menus.ts` and `src/explain/catalog.ts` keep analysis offline;
|
|
78
|
+
their ownership and regeneration contracts live in `commands/explain/README.md`.
|
|
62
79
|
- `transfer / ssh` means the SFTP-backed transfer method. Deferred file-transfer
|
|
63
80
|
methods such as `scp`, `fetch`, and `ftp` are tracked in
|
|
64
81
|
`commands/transfer/README.md`; `fetch` is not a grid column.
|
|
@@ -71,9 +88,20 @@ section short enough that the grid remains the status surface.
|
|
|
71
88
|
- Transport wire-format and delivery caveats belong in `src/protocols/*.ts`
|
|
72
89
|
module headers. Do not expand this file with per-packet or per-router findings.
|
|
73
90
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
### Offline analysis
|
|
92
|
+
|
|
93
|
+
This capability opens no protocol connection. It is usable independently and
|
|
94
|
+
provides shared analysis for future integrations across the command grid.
|
|
95
|
+
|
|
96
|
+
| Capability | State | Contract and evidence | Work |
|
|
97
|
+
| ---------- | ----- | --------------------- | ---- |
|
|
98
|
+
| explain / offline baseline | `verified` | `commands/explain/README.md` → Offline baseline acceptance; offline examples in `test/unit/explain.test.ts`, focused `test/unit/explain-*` suites, and CHR captures under `test/fixtures/explain/`. The token partition is scored against device `highlight` by `test/unit/explain-highlight-agreement.test.ts` and `test/fixtures/explain/highlight-agreement.json`. The public entry is `@tikoci/centrs/explain`; its dependency boundary and executed browser-consumer proof are gated by `test/unit/explain-browser-entry.test.ts` (`bun run explain:browser-consumer`). #322's fixed-size 16× depth guard and deterministic structural-work guards close the bounded-execution criterion. | #90 indexes correctness, token agreement (#264/#263), consumer verification, and bounded execution (#322). |
|
|
99
|
+
|
|
100
|
+
The CLI/library expose structure, diagnostics, token partitions, symbols, and
|
|
101
|
+
flow-sensitive value facts. `verified` records the offline acceptance evidence;
|
|
102
|
+
it is not a parser-coverage score and does not advance either live protocol cell.
|
|
103
|
+
Live probes (#236), MCP response alignment (#223), and future configuration
|
|
104
|
+
policies are distinct work, not prerequisites for using offline analysis.
|
|
77
105
|
|
|
78
106
|
### Transport-base readiness
|
|
79
107
|
|
|
@@ -89,13 +117,13 @@ single home for wire-format, auth, and delivery facts.
|
|
|
89
117
|
|
|
90
118
|
### Frontend surfaces (orthogonal to the command grid)
|
|
91
119
|
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
Frontends adapt the shared analysis and device-operation core and carry their
|
|
121
|
+
own state, tracked here:
|
|
94
122
|
|
|
95
123
|
| Surface | State | Spec | Notes |
|
|
96
124
|
| ------- | ----- | ---- | ----- |
|
|
97
125
|
| 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`
|
|
126
|
+
| 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
127
|
| mcp | `CHR-passed` | `commands/mcp/` | Scoped-verb stdio MCP server; details in `commands/mcp/README.md` and `test/integration/mcp.test.ts`. |
|
|
100
128
|
| tui | `not-started` | `src/tui.ts` | Stub. |
|
|
101
129
|
| proxy | `not-started` | `src/webproxy.ts` | Stub. |
|
|
@@ -120,11 +148,57 @@ its own capability axis and is exempt from the RouterOS command-validation gate.
|
|
|
120
148
|
Wire, auth, TCP/UDP, and CI caveats live in `commands/btest/README.md` and the
|
|
121
149
|
`src/protocols/btest*.ts` / `src/protocols/ec-srp5.ts` module headers.
|
|
122
150
|
|
|
123
|
-
##
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
151
|
+
## Current priority
|
|
152
|
+
|
|
153
|
+
**Tightening validation as a whole** is the selected track, in the three-step
|
|
154
|
+
order agreed on [#236](https://github.com/tikoci/centrs/issues/236):
|
|
155
|
+
|
|
156
|
+
1. **Done** — fix the offline false rejects so the analyzer can be trusted as a
|
|
157
|
+
gate (#347/#348, shipped in #349; the measured corpus false-reject count went
|
|
158
|
+
4 → 1).
|
|
159
|
+
2. **Done** — front the `execute` / `api` device preflight with the offline
|
|
160
|
+
analyzer (#354). The gate is now two-stage; the contract is in
|
|
161
|
+
[`docs/CONSTITUTION.md` → Validation is the product](CONSTITUTION.md#validation-is-the-product).
|
|
162
|
+
3. **Next** — **live `explain` (#236)**, as stage two of a gate that now exists.
|
|
163
|
+
Start with [the #90 task index](https://github.com/tikoci/centrs/issues/90),
|
|
164
|
+
then follow [`commands/explain/README.md`](../commands/explain/README.md) and
|
|
165
|
+
the live examples in
|
|
166
|
+
[`commands/explain/examples.md`](../commands/explain/examples.md).
|
|
167
|
+
|
|
168
|
+
The offline baseline stays `verified`: #264 closed the measured token vocabulary
|
|
169
|
+
and #322 closed the final bounded-execution gap, including the public depth-axis
|
|
170
|
+
guard.
|
|
171
|
+
|
|
172
|
+
The live pass is the device-backed cross-check on the offline result, not a
|
|
173
|
+
replacement for it. It adds read-only `/console/inspect` and `:parse` evidence,
|
|
174
|
+
the describe/completion facets, and the live `observedType`/`schemaType`
|
|
175
|
+
producers owned with #225. REST and native-api cells advance only when their
|
|
176
|
+
applicable examples pass on CHR via `bun run test:integration`.
|
|
177
|
+
|
|
178
|
+
Open offline follow-ups remain explicit but do not reopen the verified baseline.
|
|
179
|
+
Issue #211 retains the fail-closed unlisted-path decision; #272 owns
|
|
180
|
+
non-authoritative `.scratch/` citation cleanup. #350 (the broad optional `=`,
|
|
181
|
+
schema-dependent and so belonging to step 3), #351 (an unresolvable menu that
|
|
182
|
+
abstains at 2-deep nesting but hard-fails at 3-deep) and #355 (`'` is not a
|
|
183
|
+
RouterOS token — a false ACCEPT the corpus join cannot see) are bounded analyzer
|
|
184
|
+
defects that step 2 now surfaces through the `execute`/`api` gate. #336 and #342 are complete: the
|
|
185
|
+
corpus is repinned to three *complete* builds (long-term 7.23.5, stable 7.24.2,
|
|
186
|
+
development 7.25beta3), and the operator sweep and highlight slice are re-swept
|
|
187
|
+
onto those same builds — plus 7.21.5 retained as an age anchor — so the operator
|
|
188
|
+
axis and the token partition are scored on the same firmware. The #335
|
|
189
|
+
source refresh pins both architectures of those versions beside the 7.10.2 /
|
|
190
|
+
7.16 history anchors and accepts the current CLI-Reference drift. Issue #223 is
|
|
191
|
+
a separate MCP response migration. If live evidence finds a wrong offline
|
|
192
|
+
assertion, give that defect a bounded issue and matching CHR-grounded controls
|
|
193
|
+
rather than folding it silently into the live implementation.
|
|
194
|
+
|
|
195
|
+
Unfinished SNMP, RoMON, TUI, or other protocol/frontend cells do not preempt this
|
|
196
|
+
track. Change this selection when the maintainer selects another track.
|
|
197
|
+
|
|
198
|
+
### Protocol backlog order
|
|
199
|
+
|
|
200
|
+
Retained for protocol work when selected; this is not a dependency chain for
|
|
201
|
+
offline analysis. Per-item detail lives in the linked `commands/<name>/`.
|
|
128
202
|
|
|
129
203
|
1. **retrieve / rest-api** — `CHR-passed` (`commands/retrieve/`).
|
|
130
204
|
2. **CDB resolution + metadata overrides** — `CHR-passed` (`commands/devices/`).
|
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.
|