@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
|
@@ -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,34 @@
|
|
|
1
1
|
# `validation/syntax`
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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).
|
|
@@ -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.6",
|
|
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,13 +22,16 @@
|
|
|
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
|
},
|
|
29
31
|
"exports": {
|
|
30
32
|
".": "./src/index.ts",
|
|
31
33
|
"./cli": "./src/cli.ts",
|
|
34
|
+
"./explain": "./src/explain.ts",
|
|
32
35
|
"./mcp": "./src/mcp.ts",
|
|
33
36
|
"./tui": "./src/tui.ts",
|
|
34
37
|
"./webproxy": "./src/webproxy.ts",
|
|
@@ -45,22 +48,24 @@
|
|
|
45
48
|
"files": [
|
|
46
49
|
"src/",
|
|
47
50
|
"commands/",
|
|
51
|
+
"docs/CLI.md",
|
|
48
52
|
"docs/CONSTITUTION.md",
|
|
49
53
|
"docs/MATRIX.md",
|
|
50
54
|
"docs/errors/",
|
|
51
55
|
"README.md",
|
|
52
56
|
"CHANGELOG.md",
|
|
53
57
|
"SECURITY.md",
|
|
58
|
+
"LICENSE",
|
|
54
59
|
"GLOSSARY.txt"
|
|
55
60
|
],
|
|
56
61
|
"scripts": {
|
|
57
|
-
"ci": "bun run lint && bun run test && bun run build",
|
|
58
|
-
"git:pre-commit": "bun run lint",
|
|
62
|
+
"ci": "bun run lint:ci && bun run test && bun run build",
|
|
63
|
+
"git:pre-commit": "bun run lint:git:commit",
|
|
59
64
|
"git:pre-push": "bun run lint:git:push",
|
|
60
65
|
"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",
|
|
66
|
+
"lint:git:commit": "bun run lint:biome",
|
|
67
|
+
"lint:git:push": "bun run lint:ci",
|
|
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",
|
|
64
69
|
"lint:cspell": "cspell lint . --gitignore",
|
|
65
70
|
"lint:cspell:all": "cspell lint . --no-gitignore",
|
|
66
71
|
"lint:markdownlint": "markdownlint-cli2 \"**/*.md\" \"!.scratch/**\"",
|
|
@@ -82,9 +87,50 @@
|
|
|
82
87
|
"test:ci": "bun test --coverage",
|
|
83
88
|
"test:all": "bun run test:ci",
|
|
84
89
|
"check:agent": "bun run scripts/agent-check.ts",
|
|
90
|
+
"corpus:fetch": "bun run scripts/corpus-fetch.ts",
|
|
85
91
|
"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
92
|
"build:doc:api": "typedoc --out docs/api src/index.ts",
|
|
93
|
+
"docs:cli": "bun run scripts/gen-cli-docs.ts",
|
|
94
|
+
"docs:cli:check": "bun run scripts/gen-cli-docs.ts --check",
|
|
87
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",
|
|
101
|
+
"explain:catalog": "bun run scripts/gen-explain-catalog.ts",
|
|
102
|
+
"explain:catalog:check": "bun run scripts/gen-explain-catalog.ts --check",
|
|
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",
|
|
108
|
+
"explain:highlight-slice": "bun run scripts/explain-highlight-slice.ts",
|
|
109
|
+
"explain:menus": "bun run scripts/gen-explain-menus.ts",
|
|
110
|
+
"explain:operator-census": "bun run scripts/explain-operator-census.ts",
|
|
111
|
+
"explain:operator-census:check": "bun run scripts/explain-operator-census.ts --check",
|
|
112
|
+
"explain:operator-readme": "bun run scripts/explain-operator-slice.ts --readme",
|
|
113
|
+
"explain:operator-readme:check": "bun run scripts/explain-operator-slice.ts --readme --check",
|
|
114
|
+
"explain:operator-slice": "bun run scripts/explain-operator-slice.ts",
|
|
115
|
+
"explain:probe:brace-slots": "bun run scripts/probes/explain-brace-slot-sweep.ts",
|
|
116
|
+
"explain:probe:escapes": "bun run scripts/probes/explain-escape-sweep.ts",
|
|
117
|
+
"explain:probe:highlight-recapture": "bun run scripts/probes/explain-highlight-recapture.ts",
|
|
118
|
+
"explain:probe:operators": "bun run scripts/probes/explain-operator-sweep.ts",
|
|
119
|
+
"explain:probe:symbol-anchors": "bun run scripts/probes/explain-symbol-anchors.ts",
|
|
120
|
+
"explain:probe:symbol-arms-score": "bun run scripts/probes/explain-symbol-arms-score.ts",
|
|
121
|
+
"explain:probe:symbol-bad-sigil-replay": "bun run scripts/probes/explain-symbol-bad-sigil-replay.ts",
|
|
122
|
+
"explain:probe:symbol-classes-replay": "bun run scripts/probes/explain-symbol-classes-replay.ts",
|
|
123
|
+
"explain:probe:version-floors": "bun run scripts/probes/explain-version-floors.ts",
|
|
124
|
+
"explain:menus:check": "bun run scripts/gen-explain-menus.ts --check",
|
|
125
|
+
"explain:value-census": "bun run scripts/explain-value-census.ts",
|
|
126
|
+
"explain:value-census:check": "bun run scripts/explain-value-census.ts --check",
|
|
127
|
+
"explain:value-census:readme": "bun run scripts/explain-value-census.ts --readme",
|
|
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",
|
|
130
|
+
"explain:token-census": "bun run scripts/explain-token-census.ts",
|
|
131
|
+
"explain:token-census:check": "bun run scripts/explain-token-census.ts --check",
|
|
132
|
+
"explain:token-census:readme": "bun run scripts/explain-token-census.ts --readme",
|
|
133
|
+
"explain:token-census:readme:check": "bun run scripts/explain-token-census.ts --readme --check",
|
|
88
134
|
"security": "bun run lint:secretlint && bun run lint:markdownlint"
|
|
89
135
|
},
|
|
90
136
|
"type": "module",
|
package/src/api-fanout.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `ApiEnvelope`s into the locked `FanoutData` shape (`src/core/envelope.ts`).
|
|
8
8
|
*
|
|
9
9
|
* Boundaries (see `commands/api/README.md`, `docs/CONSTITUTION.md`):
|
|
10
|
-
* - The CDB is loaded ONCE (`
|
|
10
|
+
* - The CDB is loaded ONCE (`expandSelection`); a cdb member is resolved
|
|
11
11
|
* with its pre-resolved record, so `resolveCdb`'s `__default__` synthetic
|
|
12
12
|
* fallback never collides ad-hoc literals.
|
|
13
13
|
* - A `--default` / `__default__` member is guarded from being dialed as the
|
|
@@ -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,19 +50,19 @@ 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";
|
|
55
57
|
import {
|
|
56
|
-
|
|
57
|
-
type CdbSelectionMember,
|
|
58
|
-
type CdbSelectionResolveInput,
|
|
59
|
-
expandCdbSelection,
|
|
58
|
+
expandSelection,
|
|
60
59
|
isDefaultRecordTarget,
|
|
61
60
|
loadEnvFileDefaults,
|
|
61
|
+
type SelectionExpansion,
|
|
62
|
+
type SelectionMember,
|
|
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 {
|
|
@@ -70,6 +71,8 @@ export interface ApiSelectionSummary {
|
|
|
70
71
|
all: boolean;
|
|
71
72
|
default: boolean;
|
|
72
73
|
positionals: readonly string[];
|
|
74
|
+
/** quickchr machine names (`--quickchr`, repeatable; exclusive of the rest). */
|
|
75
|
+
quickchr?: readonly string[];
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
export interface ApiFanoutOperationMeta {
|
|
@@ -92,10 +95,10 @@ export type ApiFanoutErrorEnvelope =
|
|
|
92
95
|
export interface ApiFanoutInternals {
|
|
93
96
|
expand?: (
|
|
94
97
|
selection: TargetSelection,
|
|
95
|
-
input:
|
|
98
|
+
input: SelectionResolveInput,
|
|
96
99
|
env: Record<string, string | undefined>,
|
|
97
100
|
config?: Record<string, string | undefined>,
|
|
98
|
-
) => Promise<
|
|
101
|
+
) => Promise<SelectionExpansion>;
|
|
99
102
|
execute?: (resolved: ResolvedApiRequest) => Promise<ApiSuccessEnvelope>;
|
|
100
103
|
sleep?: (ms: number) => Promise<void>;
|
|
101
104
|
}
|
|
@@ -113,6 +116,7 @@ function summarizeSelection(selection: TargetSelection): ApiSelectionSummary {
|
|
|
113
116
|
all: selection.all,
|
|
114
117
|
default: selection.default,
|
|
115
118
|
positionals: selection.positionals,
|
|
119
|
+
quickchr: selection.quickchr,
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -120,7 +124,7 @@ function summarizeSelection(selection: TargetSelection): ApiSelectionSummary {
|
|
|
120
124
|
function fanoutVia(
|
|
121
125
|
request: ApiRequest,
|
|
122
126
|
env: Record<string, string | undefined>,
|
|
123
|
-
expansion:
|
|
127
|
+
expansion: SelectionExpansion,
|
|
124
128
|
): RouterOsProtocol {
|
|
125
129
|
// Deliberately CLI/env only, not `config` — see the note above this
|
|
126
130
|
// function's caller.
|
|
@@ -139,7 +143,7 @@ function fanoutVia(
|
|
|
139
143
|
return anyNative ? "native-api" : "rest-api";
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
function memberTargetMeta(member:
|
|
146
|
+
function memberTargetMeta(member: SelectionMember): {
|
|
143
147
|
input?: string;
|
|
144
148
|
identity?: string;
|
|
145
149
|
recordIndex?: number;
|
|
@@ -151,6 +155,9 @@ function memberTargetMeta(member: CdbSelectionMember): {
|
|
|
151
155
|
recordIndex: member.recordIndex,
|
|
152
156
|
};
|
|
153
157
|
}
|
|
158
|
+
if (member.kind === "quickchr") {
|
|
159
|
+
return { input: member.name, identity: member.name };
|
|
160
|
+
}
|
|
154
161
|
return { input: member.input };
|
|
155
162
|
}
|
|
156
163
|
|
|
@@ -163,7 +170,7 @@ function memberTargetMeta(member: CdbSelectionMember): {
|
|
|
163
170
|
*/
|
|
164
171
|
function relabelAdhocMember(
|
|
165
172
|
envelope: ApiEnvelope,
|
|
166
|
-
member:
|
|
173
|
+
member: SelectionMember,
|
|
167
174
|
): ApiEnvelope {
|
|
168
175
|
if (member.kind !== "literal") {
|
|
169
176
|
return envelope;
|
|
@@ -209,7 +216,7 @@ export async function apiFanout(
|
|
|
209
216
|
assertApiProtocolSupported(pinnedVia);
|
|
210
217
|
}
|
|
211
218
|
|
|
212
|
-
const expand = internals.expand ??
|
|
219
|
+
const expand = internals.expand ?? expandSelection;
|
|
213
220
|
const expansion = await expand(
|
|
214
221
|
selection,
|
|
215
222
|
{
|
|
@@ -240,10 +247,15 @@ export async function apiFanout(
|
|
|
240
247
|
await assertFanoutWriteConfirmed(request, requestSummary, method, expansion);
|
|
241
248
|
|
|
242
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>();
|
|
243
255
|
const sleep = internals.sleep ?? defaultFanoutSleep;
|
|
244
256
|
|
|
245
257
|
const targets = await runFanout<
|
|
246
|
-
|
|
258
|
+
SelectionMember,
|
|
247
259
|
ResolvedApiRequest,
|
|
248
260
|
ApiEnvelope
|
|
249
261
|
>({
|
|
@@ -267,6 +279,15 @@ export async function apiFanout(
|
|
|
267
279
|
{ cdbResolution: member.resolution, config },
|
|
268
280
|
);
|
|
269
281
|
}
|
|
282
|
+
if (member.kind === "quickchr") {
|
|
283
|
+
// The live descriptor resolves here, per member — a stopped/unknown
|
|
284
|
+
// machine is an inner per-target failure, not an expansion error.
|
|
285
|
+
return resolveApiRequest(
|
|
286
|
+
{ ...request, targetInput: undefined, quickchr: member.name },
|
|
287
|
+
env,
|
|
288
|
+
{ config },
|
|
289
|
+
);
|
|
290
|
+
}
|
|
270
291
|
return resolveApiRequest({ ...request, targetInput: member.input }, env, {
|
|
271
292
|
config,
|
|
272
293
|
});
|
|
@@ -277,11 +298,18 @@ export async function apiFanout(
|
|
|
277
298
|
target: memberTargetMeta(member),
|
|
278
299
|
warnings: member.kind === "cdb" ? [...member.resolution.warnings] : [],
|
|
279
300
|
}),
|
|
280
|
-
execute: async (resolved, member) =>
|
|
281
|
-
|
|
301
|
+
execute: async (resolved, member) => {
|
|
302
|
+
const trace: ValidationTrace = {};
|
|
303
|
+
traces.set(resolved, trace);
|
|
304
|
+
return relabelAdhocMember(await execute(resolved, trace), member);
|
|
305
|
+
},
|
|
282
306
|
onExecuteError: (resolved, member, error) =>
|
|
283
307
|
relabelAdhocMember(
|
|
284
|
-
buildApiErrorEnvelopeFromResolved(
|
|
308
|
+
buildApiErrorEnvelopeFromResolved(
|
|
309
|
+
resolved,
|
|
310
|
+
error,
|
|
311
|
+
traces.get(resolved),
|
|
312
|
+
),
|
|
285
313
|
member,
|
|
286
314
|
),
|
|
287
315
|
sleep,
|
|
@@ -302,7 +330,7 @@ async function assertFanoutWriteConfirmed(
|
|
|
302
330
|
request: ApiRequest,
|
|
303
331
|
summary: ApiRequestSummary,
|
|
304
332
|
method: string,
|
|
305
|
-
expansion:
|
|
333
|
+
expansion: SelectionExpansion,
|
|
306
334
|
): Promise<void> {
|
|
307
335
|
if (!summary.write || request.yes) {
|
|
308
336
|
return;
|
|
@@ -329,7 +357,7 @@ async function assertFanoutWriteConfirmed(
|
|
|
329
357
|
interface ApiFanoutSuccessInput {
|
|
330
358
|
summary: FanoutSummary;
|
|
331
359
|
targets: readonly ApiEnvelope[];
|
|
332
|
-
warnings:
|
|
360
|
+
warnings: SelectionExpansion["warnings"];
|
|
333
361
|
via: RouterOsProtocol | null;
|
|
334
362
|
concurrency: number;
|
|
335
363
|
requestSummary: ApiRequestSummary;
|