@tikoci/centrs 0.1.9-next.22 → 0.1.9-next.24
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 +106 -0
- package/GLOSSARY.txt +38 -0
- package/README.md +2 -2
- package/commands/api/AGENTS.md +18 -1
- package/commands/api/README.md +10 -6
- package/commands/api/examples.md +1 -1
- package/commands/execute/README.md +26 -6
- package/commands/execute/examples.md +43 -5
- package/commands/explain/README.md +873 -182
- package/commands/explain/examples.md +447 -38
- package/commands/retrieve/README.md +13 -2
- package/commands/retrieve/examples.md +30 -1
- package/commands/transfer/README.md +8 -4
- package/docs/CLI.md +19 -5
- package/docs/CONSTITUTION.md +18 -9
- package/docs/MATRIX.md +51 -26
- package/docs/errors/cmr/caller-policy-not-enforced.md +5 -3
- package/docs/errors/routeros/permission-denied.md +24 -0
- package/docs/errors/transport/edge-auth-failed.md +22 -0
- package/docs/errors/transport/write-outcome-unknown.md +3 -0
- package/docs/errors/usage/invalid-cursor.md +20 -0
- package/docs/errors/validation/unknown-path.md +9 -0
- package/package.json +5 -2
- package/src/api.ts +34 -39
- package/src/cli/explain.ts +206 -47
- package/src/core/envelope.ts +10 -3
- package/src/core/error-catalog.ts +15 -0
- package/src/core/execute-command.ts +53 -0
- package/src/core/fanout.ts +12 -2
- package/src/core/inspect.ts +4 -5
- package/src/core/routeros-errors.ts +25 -0
- package/src/execute-cmr.ts +2 -8
- package/src/execute.ts +48 -20
- package/src/explain/args.ts +181 -15
- package/src/explain/blocks.ts +3 -4
- package/src/explain/brace-slots.ts +5 -0
- package/src/explain/catalog.ts +13 -6
- package/src/explain/defects.ts +1 -0
- package/src/explain/group-faults.ts +1060 -0
- package/src/explain/highlight-projection.ts +10 -8
- package/src/explain/operators.ts +22 -20
- package/src/explain/pathresolve.ts +102 -20
- package/src/explain/quoted-string.ts +41 -6
- package/src/explain/run-tokens.ts +223 -0
- package/src/explain/scope-brace.ts +319 -7
- package/src/explain/segment.ts +177 -4
- package/src/explain/spaced-args.ts +116 -0
- package/src/explain/symbols.ts +163 -7
- package/src/explain/transport.ts +13 -0
- package/src/explain/values.ts +55 -4
- package/src/explain/verbsplit.ts +14 -188
- package/src/explain/write.ts +8 -1
- package/src/explain-live-facets.ts +739 -0
- package/src/explain-live-types.ts +490 -0
- package/src/explain-live.ts +593 -0
- package/src/explain.ts +772 -60
- package/src/index.ts +6 -0
- package/src/offline-gate.ts +30 -9
- package/src/protocols/adapter.ts +88 -10
- package/src/protocols/btest-session.ts +30 -10
- package/src/protocols/mac-telnet-console.ts +16 -0
- package/src/protocols/mac-telnet.ts +38 -10
- package/src/protocols/native-api.ts +13 -12
- package/src/retrieve-follow.ts +2 -1
- package/src/retrieve-sample.ts +2 -1
- package/src/retrieve-wait.ts +10 -6
- package/src/retrieve.ts +77 -27
- package/src/transfer.ts +38 -13
package/src/retrieve.ts
CHANGED
|
@@ -455,12 +455,10 @@ export async function runResolvedRetrieve(
|
|
|
455
455
|
await inspectRetrievePath(resolved, backend),
|
|
456
456
|
backend,
|
|
457
457
|
);
|
|
458
|
-
const validation
|
|
459
|
-
|
|
460
|
-
source: "live /console/inspect request=child+completion",
|
|
461
|
-
result: "passed",
|
|
458
|
+
const validation = retrieveValidationPassed(
|
|
459
|
+
"live /console/inspect request=child+completion",
|
|
462
460
|
availableAttributes,
|
|
463
|
-
|
|
461
|
+
);
|
|
464
462
|
trace.validation = validation;
|
|
465
463
|
const envelope = buildSuccessEnvelope(
|
|
466
464
|
resolved,
|
|
@@ -533,14 +531,26 @@ export async function validateRetrieveRead(
|
|
|
533
531
|
return {
|
|
534
532
|
inspection,
|
|
535
533
|
query,
|
|
536
|
-
validation:
|
|
537
|
-
|
|
538
|
-
source: availableAttributes
|
|
534
|
+
validation: retrieveValidationPassed(
|
|
535
|
+
availableAttributes
|
|
539
536
|
? "live /console/inspect request=child+completion"
|
|
540
537
|
: "live /console/inspect request=child",
|
|
541
|
-
result: "passed",
|
|
542
538
|
availableAttributes,
|
|
543
|
-
|
|
539
|
+
),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** retrieve's gate is the device stage alone; a path read has no CLI string to analyze. */
|
|
544
|
+
function retrieveValidationPassed(
|
|
545
|
+
source: string,
|
|
546
|
+
availableAttributes: readonly string[] | undefined,
|
|
547
|
+
): EnvelopeValidationMeta {
|
|
548
|
+
return {
|
|
549
|
+
enabled: true,
|
|
550
|
+
source,
|
|
551
|
+
result: "passed",
|
|
552
|
+
availableAttributes,
|
|
553
|
+
stages: [{ stage: "device", source, result: "passed" }],
|
|
544
554
|
};
|
|
545
555
|
}
|
|
546
556
|
|
|
@@ -625,29 +635,20 @@ const RETRIEVE_DEVICE_STAGE_SOURCE = "live /console/inspect";
|
|
|
625
635
|
* an inspect answer: `validation/*`, and the singleton `--query` refusal
|
|
626
636
|
* (`usage/conflicting-flags`). A `transport/*` or `auth/*` failure means the
|
|
627
637
|
* device never answered; a `routeros/*` error is a device answer that is not
|
|
628
|
-
* a verdict; `internal/*` is a centrs defect.
|
|
638
|
+
* a verdict; `internal/*` is a centrs defect. A `wait/*` error ends a `--wait`
|
|
639
|
+
* whose gate never settled; one that settled reports its own result instead
|
|
640
|
+
* (#537). Those five report no `result`.
|
|
629
641
|
*/
|
|
630
642
|
function unsettledValidationMeta(
|
|
631
643
|
resolved: ResolvedRetrieveRequest,
|
|
632
644
|
error: CentrsError,
|
|
633
645
|
): EnvelopeValidationMeta {
|
|
646
|
+
if (stoppedBeforeGateAnswer(error.code)) {
|
|
647
|
+
return notReachedValidationMeta(resolved, error.code);
|
|
648
|
+
}
|
|
634
649
|
if (!resolved.validate.value) {
|
|
635
650
|
return { enabled: false, source: "disabled" };
|
|
636
651
|
}
|
|
637
|
-
if (/^(transport|auth|routeros|internal)\//.test(error.code)) {
|
|
638
|
-
return {
|
|
639
|
-
enabled: true,
|
|
640
|
-
source: RETRIEVE_DEVICE_STAGE_SOURCE,
|
|
641
|
-
stages: [
|
|
642
|
-
{
|
|
643
|
-
stage: "device",
|
|
644
|
-
source: RETRIEVE_DEVICE_STAGE_SOURCE,
|
|
645
|
-
result: "skipped",
|
|
646
|
-
reason: notReachedReason(error.code),
|
|
647
|
-
},
|
|
648
|
-
],
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
652
|
return {
|
|
652
653
|
enabled: true,
|
|
653
654
|
source: RETRIEVE_DEVICE_STAGE_SOURCE,
|
|
@@ -662,6 +663,37 @@ function unsettledValidationMeta(
|
|
|
662
663
|
};
|
|
663
664
|
}
|
|
664
665
|
|
|
666
|
+
/** Whether an error code stops a run without being a gate answer (GH#435, #537). */
|
|
667
|
+
export function stoppedBeforeGateAnswer(code: string): boolean {
|
|
668
|
+
return /^(transport|auth|routeros|internal|wait)\//.test(code);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* `meta.validation` when the gate was never reached. `cause` is the error code
|
|
673
|
+
* that stopped it, or `interrupted` for a caller's cancellation, which has no
|
|
674
|
+
* code (#537).
|
|
675
|
+
*/
|
|
676
|
+
export function notReachedValidationMeta(
|
|
677
|
+
resolved: ResolvedRetrieveRequest,
|
|
678
|
+
cause: string,
|
|
679
|
+
): EnvelopeValidationMeta {
|
|
680
|
+
if (!resolved.validate.value) {
|
|
681
|
+
return { enabled: false, source: "disabled" };
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
enabled: true,
|
|
685
|
+
source: RETRIEVE_DEVICE_STAGE_SOURCE,
|
|
686
|
+
stages: [
|
|
687
|
+
{
|
|
688
|
+
stage: "device",
|
|
689
|
+
source: RETRIEVE_DEVICE_STAGE_SOURCE,
|
|
690
|
+
result: "skipped",
|
|
691
|
+
reason: notReachedReason(cause),
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
665
697
|
export function renderRetrieveEnvelope(
|
|
666
698
|
envelope: RetrieveEnvelope,
|
|
667
699
|
format: RetrieveOutputFormat,
|
|
@@ -1499,8 +1531,7 @@ export async function inspectRetrievePath(
|
|
|
1499
1531
|
throw new CentrsError({
|
|
1500
1532
|
code: "validation/unknown-path",
|
|
1501
1533
|
summary: `RouterOS path ${resolved.path} does not expose a retrieve command.`,
|
|
1502
|
-
remediation:
|
|
1503
|
-
"Check the slash-prefixed RouterOS path, or use a known readable path such as `/system/resource`, `/system/identity`, `/ip/address`, or `/interface`.",
|
|
1534
|
+
remediation: retrieveUnknownPathRemediation(resolved.path),
|
|
1504
1535
|
context: {
|
|
1505
1536
|
path: resolved.path,
|
|
1506
1537
|
validationSource: "/console/inspect request=child",
|
|
@@ -1526,6 +1557,25 @@ export async function inspectRetrievePath(
|
|
|
1526
1557
|
};
|
|
1527
1558
|
}
|
|
1528
1559
|
|
|
1560
|
+
/**
|
|
1561
|
+
* The `validation/unknown-path` remediation for a retrieve read. One path
|
|
1562
|
+
* earns a targeted hint: `/system/routerboard` exists only on MikroTik
|
|
1563
|
+
* hardware, so its absence on CHR and x86 systems is expected rather than a
|
|
1564
|
+
* typo — and the caller's likely want, the device's license ID, lives at
|
|
1565
|
+
* `/system/license` instead (`system-id` on CHR, `software-id` on x86; manual
|
|
1566
|
+
* "RouterOS License Keys", CLI Reference `/system/license`). The example
|
|
1567
|
+
* reads the whole object because a projected attribute would fail on one
|
|
1568
|
+
* platform or the other. The hint is static on purpose: on hardware the menu
|
|
1569
|
+
* always validates, so this branch only fires where the menu is genuinely
|
|
1570
|
+
* absent, and no extra round trip is needed to say so.
|
|
1571
|
+
*/
|
|
1572
|
+
export function retrieveUnknownPathRemediation(path: string): string {
|
|
1573
|
+
if (path.replace(/\/$/, "").toLowerCase() === "/system/routerboard") {
|
|
1574
|
+
return "This device exposes no /system/routerboard menu — expected on CHR and x86 systems, which have no routerboard. For this device's license ID, read /system/license instead: `centrs retrieve <router> /system/license` (system-id on CHR, software-id on x86).";
|
|
1575
|
+
}
|
|
1576
|
+
return "Check the slash-prefixed RouterOS path, or use a known readable path such as `/system/resource`, `/system/identity`, `/ip/address`, or `/interface`.";
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1529
1579
|
/**
|
|
1530
1580
|
* Inspect the menu's attribute names and reject requested ones it lacks with
|
|
1531
1581
|
* `validation/unknown-attribute`. Shared by one-shot retrieve and `--follow`.
|
package/src/transfer.ts
CHANGED
|
@@ -1401,13 +1401,26 @@ function buildSuccessEnvelope(
|
|
|
1401
1401
|
data,
|
|
1402
1402
|
warnings,
|
|
1403
1403
|
tips: [],
|
|
1404
|
-
meta: metaFromResolved(
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
:
|
|
1409
|
-
|
|
1410
|
-
|
|
1404
|
+
meta: metaFromResolved(
|
|
1405
|
+
resolved,
|
|
1406
|
+
operationData,
|
|
1407
|
+
!hasExistenceProbe(resolved)
|
|
1408
|
+
? { enabled: false }
|
|
1409
|
+
: resolved.validate.value
|
|
1410
|
+
? {
|
|
1411
|
+
enabled: true,
|
|
1412
|
+
source: TRANSFER_DEVICE_STAGE_SOURCE,
|
|
1413
|
+
result: "passed",
|
|
1414
|
+
stages: [
|
|
1415
|
+
{
|
|
1416
|
+
stage: "device",
|
|
1417
|
+
source: TRANSFER_DEVICE_STAGE_SOURCE,
|
|
1418
|
+
result: "passed",
|
|
1419
|
+
},
|
|
1420
|
+
],
|
|
1421
|
+
}
|
|
1422
|
+
: { enabled: false, source: "disabled", result: "skipped" },
|
|
1423
|
+
),
|
|
1411
1424
|
};
|
|
1412
1425
|
}
|
|
1413
1426
|
|
|
@@ -1491,6 +1504,16 @@ function asTransferError(error: unknown): CentrsError {
|
|
|
1491
1504
|
* see which host, user and credential sources were tried. Secrets never enter
|
|
1492
1505
|
* it — auth meta is `username` + `passwordProvided` only.
|
|
1493
1506
|
*/
|
|
1507
|
+
const TRANSFER_DEVICE_STAGE_SOURCE = "device /file existence probe";
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* `list` reads `/file` and has no precondition probe, so it is not a gate:
|
|
1511
|
+
* like `explain`, it reports `{ enabled: false }` and no verdict (#535).
|
|
1512
|
+
*/
|
|
1513
|
+
function hasExistenceProbe(resolved: ResolvedTransferRequest): boolean {
|
|
1514
|
+
return resolved.verb !== "list";
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1494
1517
|
export function buildTransferErrorEnvelopeFromResolved(
|
|
1495
1518
|
resolved: ResolvedTransferRequest,
|
|
1496
1519
|
error: unknown,
|
|
@@ -1509,12 +1532,14 @@ export function buildTransferErrorEnvelopeFromResolved(
|
|
|
1509
1532
|
remote: resolved.remote,
|
|
1510
1533
|
local: resolved.local,
|
|
1511
1534
|
}),
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1535
|
+
hasExistenceProbe(resolved)
|
|
1536
|
+
? {
|
|
1537
|
+
enabled: resolved.validate.value,
|
|
1538
|
+
source: resolved.validate.value
|
|
1539
|
+
? TRANSFER_DEVICE_STAGE_SOURCE
|
|
1540
|
+
: "disabled",
|
|
1541
|
+
}
|
|
1542
|
+
: { enabled: false },
|
|
1518
1543
|
),
|
|
1519
1544
|
};
|
|
1520
1545
|
}
|