@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/GLOSSARY.txt +38 -0
  3. package/README.md +2 -2
  4. package/commands/api/AGENTS.md +18 -1
  5. package/commands/api/README.md +10 -6
  6. package/commands/api/examples.md +1 -1
  7. package/commands/execute/README.md +26 -6
  8. package/commands/execute/examples.md +43 -5
  9. package/commands/explain/README.md +873 -182
  10. package/commands/explain/examples.md +447 -38
  11. package/commands/retrieve/README.md +13 -2
  12. package/commands/retrieve/examples.md +30 -1
  13. package/commands/transfer/README.md +8 -4
  14. package/docs/CLI.md +19 -5
  15. package/docs/CONSTITUTION.md +18 -9
  16. package/docs/MATRIX.md +51 -26
  17. package/docs/errors/cmr/caller-policy-not-enforced.md +5 -3
  18. package/docs/errors/routeros/permission-denied.md +24 -0
  19. package/docs/errors/transport/edge-auth-failed.md +22 -0
  20. package/docs/errors/transport/write-outcome-unknown.md +3 -0
  21. package/docs/errors/usage/invalid-cursor.md +20 -0
  22. package/docs/errors/validation/unknown-path.md +9 -0
  23. package/package.json +5 -2
  24. package/src/api.ts +34 -39
  25. package/src/cli/explain.ts +206 -47
  26. package/src/core/envelope.ts +10 -3
  27. package/src/core/error-catalog.ts +15 -0
  28. package/src/core/execute-command.ts +53 -0
  29. package/src/core/fanout.ts +12 -2
  30. package/src/core/inspect.ts +4 -5
  31. package/src/core/routeros-errors.ts +25 -0
  32. package/src/execute-cmr.ts +2 -8
  33. package/src/execute.ts +48 -20
  34. package/src/explain/args.ts +181 -15
  35. package/src/explain/blocks.ts +3 -4
  36. package/src/explain/brace-slots.ts +5 -0
  37. package/src/explain/catalog.ts +13 -6
  38. package/src/explain/defects.ts +1 -0
  39. package/src/explain/group-faults.ts +1060 -0
  40. package/src/explain/highlight-projection.ts +10 -8
  41. package/src/explain/operators.ts +22 -20
  42. package/src/explain/pathresolve.ts +102 -20
  43. package/src/explain/quoted-string.ts +41 -6
  44. package/src/explain/run-tokens.ts +223 -0
  45. package/src/explain/scope-brace.ts +319 -7
  46. package/src/explain/segment.ts +177 -4
  47. package/src/explain/spaced-args.ts +116 -0
  48. package/src/explain/symbols.ts +163 -7
  49. package/src/explain/transport.ts +13 -0
  50. package/src/explain/values.ts +55 -4
  51. package/src/explain/verbsplit.ts +14 -188
  52. package/src/explain/write.ts +8 -1
  53. package/src/explain-live-facets.ts +739 -0
  54. package/src/explain-live-types.ts +490 -0
  55. package/src/explain-live.ts +593 -0
  56. package/src/explain.ts +772 -60
  57. package/src/index.ts +6 -0
  58. package/src/offline-gate.ts +30 -9
  59. package/src/protocols/adapter.ts +88 -10
  60. package/src/protocols/btest-session.ts +30 -10
  61. package/src/protocols/mac-telnet-console.ts +16 -0
  62. package/src/protocols/mac-telnet.ts +38 -10
  63. package/src/protocols/native-api.ts +13 -12
  64. package/src/retrieve-follow.ts +2 -1
  65. package/src/retrieve-sample.ts +2 -1
  66. package/src/retrieve-wait.ts +10 -6
  67. package/src/retrieve.ts +77 -27
  68. 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: EnvelopeValidationMeta = {
459
- enabled: true,
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
- enabled: true,
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. Those four report no `result`.
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(resolved, operationData, {
1405
- enabled: resolved.validate.value,
1406
- source: resolved.validate.value
1407
- ? "device /file existence probe"
1408
- : "disabled",
1409
- result: resolved.validate.value ? "passed" : "skipped",
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
- enabled: resolved.validate.value,
1514
- source: resolved.validate.value
1515
- ? "device /file existence probe"
1516
- : "disabled",
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
  }