@tikoci/centrs 0.1.4 → 0.1.7

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 (85) hide show
  1. package/CHANGELOG.md +417 -1
  2. package/GLOSSARY.txt +40 -1
  3. package/README.md +16 -8
  4. package/commands/AGENTS.md +7 -4
  5. package/commands/api/AGENTS.md +13 -1
  6. package/commands/api/README.md +50 -10
  7. package/commands/api/examples.md +100 -2
  8. package/commands/devices/README.md +10 -1
  9. package/commands/devices/examples.md +33 -2
  10. package/commands/execute/README.md +68 -25
  11. package/commands/execute/examples.md +168 -7
  12. package/commands/explain/README.md +1206 -155
  13. package/commands/explain/examples.md +99 -3
  14. package/commands/mcp/README.md +9 -5
  15. package/commands/mcp/examples.md +2 -2
  16. package/commands/retrieve/examples.md +20 -0
  17. package/commands/terminal/examples.md +7 -1
  18. package/docs/CLI.md +3 -3
  19. package/docs/CONSTITUTION.md +83 -21
  20. package/docs/MATRIX.md +89 -52
  21. package/docs/errors/cdb/create-failed.md +20 -0
  22. package/docs/errors/cdb/file-exists.md +23 -0
  23. package/docs/errors/input/invalid-target.md +25 -3
  24. package/docs/errors/routeros/invalid-value.md +3 -2
  25. package/docs/errors/transport/cancel-unacknowledged.md +24 -0
  26. package/docs/errors/validation/syntax.md +29 -4
  27. package/package.json +13 -2
  28. package/src/api-fanout.ts +17 -4
  29. package/src/api.ts +346 -75
  30. package/src/cli/api.ts +1 -1
  31. package/src/cli/btest.ts +1 -1
  32. package/src/cli/devices.ts +16 -3
  33. package/src/cli/explain.ts +1 -1
  34. package/src/cli/terminal.ts +1 -1
  35. package/src/core/envelope.ts +53 -0
  36. package/src/core/error-catalog.ts +16 -1
  37. package/src/core/execute-command.ts +455 -0
  38. package/src/core/inspect.ts +58 -26
  39. package/src/core/routeros-errors.ts +66 -0
  40. package/src/core/version.ts +4 -0
  41. package/src/core/yaml.ts +86 -0
  42. package/src/data/winbox-cdb-write.ts +22 -8
  43. package/src/devices.ts +142 -28
  44. package/src/execute-fanout.ts +19 -5
  45. package/src/execute.ts +247 -295
  46. package/src/explain/arg-tokens.ts +62 -25
  47. package/src/explain/args.ts +398 -52
  48. package/src/explain/blocks.ts +54 -11
  49. package/src/explain/brace-tokens.ts +4 -3
  50. package/src/explain/catalog.ts +42 -44
  51. package/src/explain/coordinates.ts +159 -0
  52. package/src/explain/defects.ts +2 -1
  53. package/src/explain/escape-tokens.ts +77 -0
  54. package/src/explain/highlight-projection.ts +308 -0
  55. package/src/explain/menus.ts +9 -5
  56. package/src/explain/operator-tokens.ts +13 -7
  57. package/src/explain/operators.ts +27 -20
  58. package/src/explain/path-tokens.ts +1 -1
  59. package/src/explain/pathresolve.ts +358 -113
  60. package/src/explain/quoted-string.ts +110 -20
  61. package/src/explain/scope-brace.ts +175 -22
  62. package/src/explain/segment.ts +429 -21
  63. package/src/explain/separator.ts +187 -0
  64. package/src/explain/split-owner.ts +194 -0
  65. package/src/explain/string-tokens.ts +14 -4
  66. package/src/explain/symbol-values.ts +209 -173
  67. package/src/explain/symbols.ts +21 -1
  68. package/src/explain/transport.ts +17 -0
  69. package/src/explain/value-tokens.ts +15 -12
  70. package/src/explain/verbsplit.ts +165 -23
  71. package/src/explain/write.ts +47 -6
  72. package/src/explain.ts +522 -72
  73. package/src/index.ts +21 -2
  74. package/src/mcp/server.ts +2 -1
  75. package/src/mcp/tools.ts +1 -1
  76. package/src/offline-gate.ts +148 -0
  77. package/src/protocols/adapter.ts +12 -1
  78. package/src/protocols/mac-telnet-console.ts +6 -4
  79. package/src/protocols/native-api.ts +29 -3
  80. package/src/resolver/target.ts +32 -12
  81. package/src/retrieve-fanout.ts +1 -1
  82. package/src/retrieve.ts +18 -63
  83. package/src/settings.ts +2 -1
  84. package/src/transfer-fanout.ts +1 -1
  85. package/src/transfer.ts +1 -1
@@ -0,0 +1,23 @@
1
+ # `cdb/file-exists`
2
+
3
+ `devices init` found a CDB already at the resolved location and wrote nothing.
4
+
5
+ This is a warning on a successful result, not an error. Like `git init`,
6
+ `centrs devices init` never overwrites an existing CDB. When the file there
7
+ already loads as a CDB, `init` succeeds with `data.created: false` and
8
+ `data.recordCount` set to the number of records it holds, and leaves the file
9
+ byte-for-byte untouched. A bootstrap script can therefore run `init`
10
+ unconditionally.
11
+
12
+ A file at that path that does not load as a CDB is not reported here. `init`
13
+ fails with the load error instead (`cdb/parse-failed`, or
14
+ `cdb/password-required` for an encrypted CDB without `--cdb-password`), and
15
+ still leaves the file alone.
16
+
17
+ ## Fix
18
+
19
+ Usually nothing: the CDB you asked for exists.
20
+
21
+ - To see what it holds: `centrs devices list --cdb-file PATH`.
22
+ - To start fresh, pick a different path (`centrs devices init --cdb-file
23
+ NEW_PATH`), or move the existing file aside yourself; centrs never deletes it.
@@ -2,8 +2,30 @@
2
2
 
3
3
  The target string is invalid for the requested operation.
4
4
 
5
+ A command's `<router>` is looked up in the CDB first: its `target`, or an
6
+ `identity=` / `mac=` / `ip=` comment lookup key. When nothing matches, centrs
7
+ treats the string as a host. This error means it is not a valid host either,
8
+ or it is a URL that carries a path. Common causes:
9
+
10
+ - **`quickchr:NAME`**: there is no `quickchr:` target prefix. A quickchr VM is
11
+ selected with a flag: `--quickchr NAME`.
12
+ - **A typo or stray characters**, such as spaces or an extra `:` (`a b`,
13
+ `bad:host:name`).
14
+ - **A base URL with a path**, such as `http://192.0.2.1/rest`. The RouterOS
15
+ menu path is a separate argument.
16
+
17
+ The rejected string is in the error's `context.target`.
18
+
5
19
  ## Fix
6
20
 
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 `input/invalid-target`.
21
+ Pass one of the accepted target forms:
22
+
23
+ | Form | Example |
24
+ | ---- | ------- |
25
+ | CDB target or lookup key | `edge1` (see `centrs devices list`) |
26
+ | IP address or hostname, optional `:port` | `192.0.2.1`, `router.example:8080` |
27
+ | Base URL without a path | `https://192.0.2.1:8443` |
28
+ | quickchr VM | `--quickchr NAME` (a flag, not a prefix) |
29
+
30
+ With an explicit `--cdb-file` and no `__default__` fallback record, a string
31
+ that matches no record stops earlier with `cdb/not-found-target`.
@@ -1,12 +1,13 @@
1
1
  # `routeros/invalid-value`
2
2
 
3
- RouterOS rejected the value supplied for an argument. The offending argument
4
- name is captured in `context.argument`.
3
+ RouterOS rejected a supplied value. When RouterOS names the offending argument,
4
+ it is captured in `context.argument`.
5
5
 
6
6
  ## Typical RouterOS strings
7
7
 
8
8
  - `invalid value for argument <name>`
9
9
  - `invalid value of <name>`
10
+ - `input does not match ...`
10
11
 
11
12
  ## Fix
12
13
 
@@ -0,0 +1,24 @@
1
+ # `transport/cancel-unacknowledged`
2
+
3
+ RouterOS did not acknowledge a stream's /cancel within the timeout; centrs ended
4
+ the stream and closed the session locally.
5
+
6
+ ## When it happens
7
+
8
+ This is a **warning** on the summary envelope of `centrs api … --stream`, not a
9
+ failure. The stream stopped for its normal reason (`--duration` elapsed, or
10
+ Ctrl-C), and centrs sent RouterOS `/cancel` for the `/listen`. A cooperating
11
+ router answers with an `interrupted` trap and `!done` within milliseconds. This
12
+ one did not answer within `--timeout`, so centrs stopped waiting, closed the
13
+ native API session and finished.
14
+
15
+ The summary's `stopReason` is still the reason the stream stopped. The warning
16
+ records that the router never confirmed the cancel, so the stop was local.
17
+
18
+ ## Fix
19
+
20
+ - Usually nothing: the stream ended when you asked, and the session is closed.
21
+ - If it repeats against one router, the API connection is likely stalled (a
22
+ router under load, or a firewall or NAT device holding the TCP session).
23
+ Check the router's CPU and the path to it before relying on long-lived
24
+ streams there.
@@ -1,9 +1,34 @@
1
1
  # `validation/syntax`
2
2
 
3
- RouterOS rejected the command syntax during the :parse gate.
3
+ The command syntax was rejected by the validation gate — offline analysis, or
4
+ the RouterOS `:parse` probe.
5
+
6
+ The gate has two stages
7
+ ([`docs/CONSTITUTION.md` → Validation is the product](../../CONSTITUTION.md#validation-is-the-product)),
8
+ and `error.context.validationStage` says which one spoke:
9
+
10
+ - **`"offline"`** — centrs rejected the input before any connection was opened,
11
+ and `meta.validation.stages[]` shows the device stage `skipped`: the router was
12
+ never asked. The **corpus-gated analyzer** is the whole of this stage, and
13
+ `error.context.validationSource` names it. `error.context.span` carries the
14
+ offending byte range (end-exclusive) and `error.context.diagnostics[]` the
15
+ analyzer's own `explain/<analyzer>/<slug>` codes.
16
+
17
+ A quote-balance supplement used to sit beside it for the unterminated `"` or
18
+ `'` the analyzer passed, reporting no span. It is gone (GH#355): the analyzer
19
+ rejects a bare `'` itself now, with the byte span, and the supplement's own
20
+ model of `'` as a delimiter was false-rejecting input RouterOS accepts.
21
+ - **absent** — RouterOS itself rejected it, through `:put [:parse "…"]`. When the
22
+ device reported a `(line N column M)`, it is on `error.position`, which is
23
+ RouterOS's authoritative 1-based **byte** column and is never synthesized by
24
+ centrs. An offline rejection never carries `error.position`.
4
25
 
5
26
  ## Fix
6
27
 
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/syntax`.
28
+ Run `centrs explain '<command>'` to see the offending bytes in context, fix the
29
+ RouterOS CLI syntax — quotes and bracketed expressions most often — and retry.
30
+
31
+ `--validate=false` bypasses **both** stages. Use it only to probe a RouterOS edge
32
+ centrs reads wrong, never to work around a rejection you believe is correct: if
33
+ the validator rejects something a real router accepts, the validator is the bug
34
+ (constitution: validation is the product).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/centrs",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "main": "src/index.ts",
5
5
  "module": "src/index.ts",
6
6
  "repository": {
@@ -31,6 +31,7 @@
31
31
  "exports": {
32
32
  ".": "./src/index.ts",
33
33
  "./cli": "./src/cli.ts",
34
+ "./explain": "./src/explain.ts",
34
35
  "./mcp": "./src/mcp.ts",
35
36
  "./tui": "./src/tui.ts",
36
37
  "./webproxy": "./src/webproxy.ts",
@@ -64,7 +65,7 @@
64
65
  "lint": "bun run lint:biome && bun run lint:typecheck",
65
66
  "lint:git:commit": "bun run lint:biome",
66
67
  "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",
68
+ "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:highlight-agreement:readme:check && bun run explain:operator-readme:check && bun run explain:bracket-equals:readme:check && bun run lint:markdownlint && bun run lint:cspell && bun run lint:secretlint",
68
69
  "lint:cspell": "cspell lint . --gitignore",
69
70
  "lint:cspell:all": "cspell lint . --no-gitignore",
70
71
  "lint:markdownlint": "markdownlint-cli2 \"**/*.md\" \"!.scratch/**\"",
@@ -92,9 +93,18 @@
92
93
  "docs:cli": "bun run scripts/gen-cli-docs.ts",
93
94
  "docs:cli:check": "bun run scripts/gen-cli-docs.ts --check",
94
95
  "docs:errors": "bun run scripts/gen-error-pages.ts",
96
+ "explain:bracket-equals": "bun run scripts/explain-bracket-equals.ts",
97
+ "explain:bracket-equals:check": "bun run scripts/explain-bracket-equals.ts --check",
98
+ "explain:bracket-equals:readme": "bun run scripts/explain-bracket-equals.ts --readme",
99
+ "explain:bracket-equals:readme:check": "bun run scripts/explain-bracket-equals.ts --readme --check",
100
+ "explain:browser-consumer": "bun run scripts/explain-browser-consumer.ts",
95
101
  "explain:catalog": "bun run scripts/gen-explain-catalog.ts",
96
102
  "explain:catalog:check": "bun run scripts/gen-explain-catalog.ts --check",
97
103
  "explain:corpus-census": "bun run scripts/explain-corpus-census.ts",
104
+ "explain:highlight-agreement": "bun run scripts/explain-highlight-agreement.ts",
105
+ "explain:highlight-agreement:check": "bun run scripts/explain-highlight-agreement.ts --check",
106
+ "explain:highlight-agreement:readme": "bun run scripts/explain-highlight-agreement.ts --readme",
107
+ "explain:highlight-agreement:readme:check": "bun run scripts/explain-highlight-agreement.ts --readme --check",
98
108
  "explain:highlight-slice": "bun run scripts/explain-highlight-slice.ts",
99
109
  "explain:menus": "bun run scripts/gen-explain-menus.ts",
100
110
  "explain:operator-census": "bun run scripts/explain-operator-census.ts",
@@ -116,6 +126,7 @@
116
126
  "explain:value-census:check": "bun run scripts/explain-value-census.ts --check",
117
127
  "explain:value-census:readme": "bun run scripts/explain-value-census.ts --readme",
118
128
  "explain:value-census:readme:check": "bun run scripts/explain-value-census.ts --readme --check",
129
+ "explain:arg-reach": "bun run scripts/explain-arg-reach-sweep.ts",
119
130
  "explain:token-census": "bun run scripts/explain-token-census.ts",
120
131
  "explain:token-census:check": "bun run scripts/explain-token-census.ts --check",
121
132
  "explain:token-census:readme": "bun run scripts/explain-token-census.ts --readme",
package/src/api-fanout.ts CHANGED
@@ -39,6 +39,7 @@ import type {
39
39
  CentrsSuccessEnvelope,
40
40
  FanoutData,
41
41
  FanoutSummary,
42
+ ValidationTrace,
42
43
  } from "./core/envelope.ts";
43
44
  import {
44
45
  buildFanoutEnvelope,
@@ -49,6 +50,7 @@ import {
49
50
  runFanout,
50
51
  summarizeFanout,
51
52
  } from "./core/fanout.ts";
53
+ import { toYaml } from "./core/yaml.ts";
52
54
  import { CentrsError, serializeCentrsError } from "./errors.ts";
53
55
  import { promptForWriteConfirmation } from "./execute.ts";
54
56
  import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
@@ -61,7 +63,6 @@ import {
61
63
  type SelectionResolveInput,
62
64
  type TargetSelection,
63
65
  } from "./resolver/index.ts";
64
- import { toYaml } from "./retrieve.ts";
65
66
 
66
67
  /** Compact description of the selector that produced the fan-out. */
67
68
  export interface ApiSelectionSummary {
@@ -246,6 +247,11 @@ export async function apiFanout(
246
247
  await assertFanoutWriteConfirmed(request, requestSummary, method, expansion);
247
248
 
248
249
  const execute = internals.execute ?? runResolvedApi;
250
+ // Per-target validation trace. `execute` and `onExecuteError` are separate
251
+ // callbacks that share only the resolved request, so the request object is
252
+ // the key: without it a per-target error envelope would reconstruct
253
+ // `meta.validation` instead of reporting the gate that actually ran (GH#354).
254
+ const traces = new WeakMap<ResolvedApiRequest, ValidationTrace>();
249
255
  const sleep = internals.sleep ?? defaultFanoutSleep;
250
256
 
251
257
  const targets = await runFanout<
@@ -292,11 +298,18 @@ export async function apiFanout(
292
298
  target: memberTargetMeta(member),
293
299
  warnings: member.kind === "cdb" ? [...member.resolution.warnings] : [],
294
300
  }),
295
- execute: async (resolved, member) =>
296
- relabelAdhocMember(await execute(resolved), member),
301
+ execute: async (resolved, member) => {
302
+ const trace: ValidationTrace = {};
303
+ traces.set(resolved, trace);
304
+ return relabelAdhocMember(await execute(resolved, trace), member);
305
+ },
297
306
  onExecuteError: (resolved, member, error) =>
298
307
  relabelAdhocMember(
299
- buildApiErrorEnvelopeFromResolved(resolved, error),
308
+ buildApiErrorEnvelopeFromResolved(
309
+ resolved,
310
+ error,
311
+ traces.get(resolved),
312
+ ),
300
313
  member,
301
314
  ),
302
315
  sleep,