@intentius/chant 0.37.2 → 0.39.0

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 (130) hide show
  1. package/dist/build.d.ts +21 -0
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/commands/check-lexicon-mcp.d.ts +44 -0
  5. package/dist/cli/commands/check-lexicon-mcp.d.ts.map +1 -0
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts +57 -0
  7. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -0
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/handlers/emulator.d.ts.map +1 -1
  10. package/dist/cli/handlers/graph.d.ts.map +1 -1
  11. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  12. package/dist/cli/handlers/run-client.d.ts.map +1 -1
  13. package/dist/cli/handlers/search.d.ts +30 -1
  14. package/dist/cli/handlers/search.d.ts.map +1 -1
  15. package/dist/cli/main.d.ts.map +1 -1
  16. package/dist/cli/mcp/server.d.ts +26 -2
  17. package/dist/cli/mcp/server.d.ts.map +1 -1
  18. package/dist/cli/plugins.d.ts +20 -0
  19. package/dist/cli/plugins.d.ts.map +1 -1
  20. package/dist/codegen/registry.d.ts +23 -0
  21. package/dist/codegen/registry.d.ts.map +1 -0
  22. package/dist/components/sandbox/driver.d.ts.map +1 -1
  23. package/dist/composite.d.ts +23 -4
  24. package/dist/composite.d.ts.map +1 -1
  25. package/dist/deep-observation.d.ts +11 -0
  26. package/dist/deep-observation.d.ts.map +1 -1
  27. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  28. package/dist/graph-declared.d.ts.map +1 -1
  29. package/dist/graph-ir.d.ts +17 -3
  30. package/dist/graph-ir.d.ts.map +1 -1
  31. package/dist/graph-refs.d.ts +24 -0
  32. package/dist/graph-refs.d.ts.map +1 -1
  33. package/dist/kubectl-context.d.ts.map +1 -1
  34. package/dist/lexicon-config.d.ts +61 -0
  35. package/dist/lexicon-config.d.ts.map +1 -0
  36. package/dist/lexicon.d.ts +85 -37
  37. package/dist/lexicon.d.ts.map +1 -1
  38. package/dist/lifecycle/deep-diff.d.ts +11 -0
  39. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  40. package/dist/lifecycle/digest.d.ts.map +1 -1
  41. package/dist/lifecycle/identity.d.ts +52 -0
  42. package/dist/lifecycle/identity.d.ts.map +1 -0
  43. package/dist/lifecycle/observe.d.ts +5 -0
  44. package/dist/lifecycle/observe.d.ts.map +1 -1
  45. package/dist/lifecycle/replay.d.ts.map +1 -1
  46. package/dist/lifecycle/types.d.ts +30 -0
  47. package/dist/lifecycle/types.d.ts.map +1 -1
  48. package/dist/live-endpoint.d.ts +21 -22
  49. package/dist/live-endpoint.d.ts.map +1 -1
  50. package/dist/managed-fields.d.ts +11 -0
  51. package/dist/managed-fields.d.ts.map +1 -1
  52. package/dist/op/emulator-freshness.d.ts +44 -0
  53. package/dist/op/emulator-freshness.d.ts.map +1 -0
  54. package/dist/op/emulator-lifecycle.d.ts +36 -0
  55. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  56. package/dist/op/index.d.ts +4 -2
  57. package/dist/op/index.d.ts.map +1 -1
  58. package/dist/ownership.d.ts +33 -0
  59. package/dist/ownership.d.ts.map +1 -1
  60. package/dist/serializer.d.ts +15 -0
  61. package/dist/serializer.d.ts.map +1 -1
  62. package/package.json +1 -1
  63. package/src/audit/catalog.test.ts +58 -6
  64. package/src/build.ts +24 -0
  65. package/src/cli/commands/build.ts +10 -0
  66. package/src/cli/commands/check-lexicon-doc-drift.test.ts +73 -0
  67. package/src/cli/commands/check-lexicon-mcp.test.ts +93 -0
  68. package/src/cli/commands/check-lexicon-mcp.ts +103 -0
  69. package/src/cli/commands/check-lexicon-plugin.test.ts +149 -0
  70. package/src/cli/commands/check-lexicon-plugin.ts +115 -0
  71. package/src/cli/commands/check-lexicon.ts +176 -26
  72. package/src/cli/handlers/components.test.ts +17 -0
  73. package/src/cli/handlers/components.ts +1 -1
  74. package/src/cli/handlers/emulator.ts +12 -8
  75. package/src/cli/handlers/graph.test.ts +71 -12
  76. package/src/cli/handlers/graph.ts +46 -5
  77. package/src/cli/handlers/lifecycle.test.ts +25 -4
  78. package/src/cli/handlers/lifecycle.ts +19 -3
  79. package/src/cli/handlers/run-client.ts +3 -1
  80. package/src/cli/handlers/search-kind.test.ts +45 -0
  81. package/src/cli/handlers/search.ts +102 -4
  82. package/src/cli/main.ts +32 -10
  83. package/src/cli/mcp/server.test.ts +82 -0
  84. package/src/cli/mcp/server.ts +40 -5
  85. package/src/cli/param-flag-scope.test.ts +69 -0
  86. package/src/cli/plugins.test.ts +33 -1
  87. package/src/cli/plugins.ts +55 -0
  88. package/src/codegen/registry.test.ts +56 -0
  89. package/src/codegen/registry.ts +69 -0
  90. package/src/components/SPRAWL-VALIDATION.md +5 -5
  91. package/src/components/sandbox/driver.test.ts +27 -0
  92. package/src/components/sandbox/driver.ts +12 -0
  93. package/src/composite.ts +33 -4
  94. package/src/deep-observation.ts +11 -0
  95. package/src/discovery/sandbox/driver.test.ts +34 -0
  96. package/src/discovery/sandbox/driver.ts +19 -0
  97. package/src/graph-declared.test.ts +86 -0
  98. package/src/graph-declared.ts +14 -2
  99. package/src/graph-ir.ts +32 -8
  100. package/src/graph-refs.test.ts +56 -0
  101. package/src/graph-refs.ts +37 -1
  102. package/src/kubectl-context.ts +4 -1
  103. package/src/lexicon-config.test.ts +111 -0
  104. package/src/lexicon-config.ts +92 -0
  105. package/src/lexicon-doc-coverage.test.ts +128 -0
  106. package/src/lexicon-seams.test.ts +113 -0
  107. package/src/lexicon.ts +88 -38
  108. package/src/lifecycle/deep-diff.test.ts +48 -1
  109. package/src/lifecycle/deep-diff.ts +16 -0
  110. package/src/lifecycle/digest.test.ts +81 -0
  111. package/src/lifecycle/digest.ts +34 -3
  112. package/src/lifecycle/identity.test.ts +39 -0
  113. package/src/lifecycle/identity.ts +61 -0
  114. package/src/lifecycle/observe.test.ts +75 -1
  115. package/src/lifecycle/observe.ts +28 -2
  116. package/src/lifecycle/replay.test.ts +251 -0
  117. package/src/lifecycle/replay.ts +67 -19
  118. package/src/lifecycle/types.ts +26 -0
  119. package/src/live-endpoint.test.ts +51 -12
  120. package/src/live-endpoint.ts +32 -33
  121. package/src/managed-fields.test.ts +50 -0
  122. package/src/managed-fields.ts +25 -6
  123. package/src/meta/peer-deps.test.ts +111 -14
  124. package/src/op/emulator-declaration.test.ts +63 -0
  125. package/src/op/emulator-freshness.test.ts +135 -0
  126. package/src/op/emulator-freshness.ts +102 -0
  127. package/src/op/emulator-lifecycle.ts +49 -0
  128. package/src/op/index.ts +4 -2
  129. package/src/ownership.ts +41 -0
  130. package/src/serializer.ts +16 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * How far behind an emulator's pinned image is (#1345, generalizing #808 T2).
3
+ *
4
+ * This began as `lexicons/fly/src/emulator-freshness.ts`, checking fly's two
5
+ * pins against their GitHub releases. The other three emulators — Floci for aws,
6
+ * floci-az, floci-gcp — ran `:latest`, so there was nothing to be behind and
7
+ * nothing to check: an image could change underneath a passing local test suite
8
+ * with no record in the repo of what moved.
9
+ *
10
+ * With the pin and its upstream declared on {@link EmulatorSpec}, the check
11
+ * covers every emulator any lexicon ships, and a new one is included by
12
+ * declaring `upstream` rather than by editing a list here.
13
+ *
14
+ * Advisory, never gating. Per the bump policy (#808) a pin moves when a
15
+ * consuming test needs the newer emulator, not because a release happened.
16
+ */
17
+ import type { EmulatorSpec } from "./emulator-lifecycle.js";
18
+ export interface FreshnessResult {
19
+ /** The emulator's container name, e.g. `chant-mudflaps`. */
20
+ name: string;
21
+ /** Pinned version, no leading `v`. */
22
+ pinned: string;
23
+ /** Latest released version, no leading `v`. */
24
+ latest: string;
25
+ /** True when the latest release is newer than the pin. */
26
+ behind: boolean;
27
+ }
28
+ /** The version tag of an image ref (`ghcr.io/x/mudflaps:0.3.1` → `0.3.1`). */
29
+ export declare function parseVersion(image: string): string;
30
+ /** Compare pinned against latest as dotted numeric versions. */
31
+ export declare function compare(name: string, pinned: string, latest: string): FreshnessResult;
32
+ /** The latest release tag for a repo, via the GitHub REST API. */
33
+ export declare function latestRelease(repo: string, fetchImpl?: typeof fetch): Promise<string>;
34
+ /**
35
+ * Check every spec that declares an upstream. A spec without one is skipped
36
+ * rather than reported — an emulator built from a local image has no release
37
+ * feed to be behind.
38
+ */
39
+ export declare function checkFreshness(specs: readonly EmulatorSpec[], fetchImpl?: typeof fetch): Promise<FreshnessResult[]>;
40
+ /** One-line human summary of a result. */
41
+ export declare function formatResult(r: FreshnessResult): string;
42
+ /** Emulator images that carry no version tag — `:latest` or none at all. */
43
+ export declare function unpinned(specs: readonly EmulatorSpec[]): EmulatorSpec[];
44
+ //# sourceMappingURL=emulator-freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulator-freshness.d.ts","sourceRoot":"","sources":["../../src/op/emulator-freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAMD,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAcrF;AAED,kEAAkE;AAClE,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlG;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,eAAe,EAAE,CAAC,CAO5B;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAIvD;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,EAAE,CAKvE"}
@@ -16,6 +16,20 @@ export interface EmulatorSpec {
16
16
  ready?: (healthBody: string) => boolean;
17
17
  /** Extra `docker run` args inserted before the image (e.g. a socket mount). */
18
18
  runArgs?: readonly string[];
19
+ /**
20
+ * Where {@link image} is published, so how far behind the pin is can be
21
+ * answered (#1345).
22
+ *
23
+ * fly pinned its two emulators and tracked their freshness; aws, azure and gcp
24
+ * ran `floci/*:latest`, which is the drift a pin exists to stop — a local test
25
+ * suite that passes today and fails tomorrow because an image moved underneath
26
+ * it, with nothing in the repo recording what changed. Declaring the upstream
27
+ * makes the check general instead of one lexicon's private tooling.
28
+ */
29
+ upstream?: {
30
+ /** `owner/repo` whose latest GitHub release names the current version. */
31
+ repo: string;
32
+ };
19
33
  }
20
34
  /**
21
35
  * A lexicon's local-emulator capability (#920) — what `chant emulator` needs to
@@ -28,6 +42,28 @@ export interface EmulatorCapability {
28
42
  spec: EmulatorSpec;
29
43
  env(endpoint: string): Record<string, string>;
30
44
  }
45
+ /**
46
+ * What a plugin declares: one emulator, or several (#1345).
47
+ *
48
+ * fly ships two — mudflaps for the Machines API and spritzer for Sprites — and
49
+ * a single-spec field could describe only one of them, so both stayed
50
+ * unreachable from `chant emulator` while the repo's docs presented them as
51
+ * first-class local targets.
52
+ */
53
+ export type EmulatorDeclaration = EmulatorCapability | readonly EmulatorCapability[];
54
+ /** Every emulator a plugin declares, normalized to a list. */
55
+ export declare function emulatorsOf(declaration: EmulatorDeclaration | undefined): readonly EmulatorCapability[];
56
+ /**
57
+ * The env vars whose value *is* the endpoint, as opposed to the credentials and
58
+ * region an emulator also needs (#1345).
59
+ *
60
+ * Derived by asking `env()` for a sentinel and keeping the keys that carry it,
61
+ * so a lexicon states the mapping once in the place it already states it. The
62
+ * alternative — `LEXICON_ENDPOINT_ENV_VAR`, a hand-maintained map in core — held
63
+ * two of the four lexicons that have one, and its module doc asserted azure had
64
+ * none while `describe-resources.ts` read `AZURE_ENDPOINT_URL` on every call.
65
+ */
66
+ export declare function endpointEnvVars(capability: EmulatorCapability): string[];
31
67
  /** Per-call overrides for {@link EmulatorLifecycle.up} / `runCommand`. */
32
68
  export interface EmulatorUpArgs {
33
69
  name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"emulator-lifecycle.d.ts","sourceRoot":"","sources":["../../src/op/emulator-lifecycle.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;IAC1C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CA2EvE"}
1
+ {"version":3,"file":"emulator-lifecycle.d.ts","sourceRoot":"","sources":["../../src/op/emulator-lifecycle.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE;QACT,0EAA0E;QAC1E,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,SAAS,kBAAkB,EAAE,CAAC;AAErF,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,WAAW,EAAE,mBAAmB,GAAG,SAAS,GAAG,SAAS,kBAAkB,EAAE,CAGvG;AAKD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAIxE;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;IAC1C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CA2EvE"}
@@ -1,8 +1,10 @@
1
1
  export { Op, phase, activity, gate, build, kubectlApply, helmInstall, waitForStack, gitlabPipeline, lifecycleSnapshot, shell, teardown, k3dUp, k3dDown, flociUp, flociDown, flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck, azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate, spriteCreate, spriteExec, spriteCheckpoint, spriteRestore, listCheckpoints, spriteDestroy, spriteWriteFile, spriteReadFile, spriteListDir, spriteRemove, spriteApplyNetworkPolicy, spriteApplyServices, spriteTaskCreate, spriteTaskRefresh, spriteTaskRelease, spritesUp, spritesDown } from "./builders.js";
2
2
  export { OpResource } from "./resource.js";
3
3
  export { safeHeartbeat, sleep } from "./activity-runtime.js";
4
- export { emulatorLifecycle } from "./emulator-lifecycle.js";
5
- export type { EmulatorSpec, EmulatorCapability, EmulatorUpArgs, EmulatorLifecycle } from "./emulator-lifecycle.js";
4
+ export { emulatorLifecycle, emulatorsOf, endpointEnvVars } from "./emulator-lifecycle.js";
5
+ export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorUpArgs, EmulatorLifecycle } from "./emulator-lifecycle.js";
6
+ export { checkFreshness, compare, formatResult, latestRelease, parseVersion, unpinned } from "./emulator-freshness.js";
7
+ export type { FreshnessResult } from "./emulator-freshness.js";
6
8
  export type { OpConfig, PhaseDefinition, StepDefinition, ActivityStep, GateStep } from "./types.js";
7
9
  export { discoverOps } from "./discover.js";
8
10
  export type { DiscoveredOp, OpDiscoveryResult } from "./discover.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/op/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EACzE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EACtF,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAC3D,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EACrG,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EACzF,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAC5D,wBAAwB,EAAE,mBAAmB,EAC7C,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EACtD,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAChH,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAClH,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/op/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EACzE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EACtF,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAC3D,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EACrG,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EACzF,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAC5D,wBAAwB,EAAE,mBAAmB,EAC7C,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EACtD,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrI,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACpH,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAClH,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
@@ -78,4 +78,37 @@ export declare function tagArrayToMap(tags: ReadonlyArray<{
78
78
  * undefined when the managed-by marker is absent.
79
79
  */
80
80
  export declare function readOwnership(tagsOrLabels: Record<string, unknown> | undefined, keys: ChannelKeys): OwnershipMarker | undefined;
81
+ /**
82
+ * A read path that can resolve an ownership verdict from the marker (#1348).
83
+ *
84
+ * Per-path rather than per-lexicon because the answer genuinely differs by
85
+ * path: aws stamps tags at synthesis and reads them on the deep observation and
86
+ * on live export, but `describeResources` is sourced from
87
+ * `describe-stack-resources`, which returns no tags at all — so an `owned: true`
88
+ * thin read against aws can only answer `unknown`.
89
+ */
90
+ export type OwnershipReadPath = "describeResources" | "observeResourcesDeep" | "exportResources";
91
+ /**
92
+ * Where a lexicon can stamp and read chant's ownership marker (#1348).
93
+ *
94
+ * `ResourceMetadata.ownership` documents an obligation — a lexicon with no
95
+ * marker channel on a path must stamp `unknown` rather than degrade silently,
96
+ * because the change set never escalates `unknown` to a delete — and that
97
+ * obligation had no type, no declaration, and no check. A caller could not
98
+ * learn whether `owned: true` was answerable except by asking and reading a
99
+ * warning on stderr afterwards, which is invisible to `lifecycle plan`, which
100
+ * is where the wrong delete gets proposed.
101
+ *
102
+ * Absent means the lexicon has no marker channel at all: every verdict it
103
+ * returns must be `unknown`. Declaring one is a claim the conformance suite
104
+ * checks — on a declared path, verdicts must be `owned` or `foreign`.
105
+ */
106
+ export interface OwnershipChannel {
107
+ /** The provider-native keys this lexicon stamps into. */
108
+ readonly keys: ChannelKeys;
109
+ /** The read paths that resolve a verdict from the marker. */
110
+ readonly reads: readonly OwnershipReadPath[];
111
+ }
112
+ /** Whether this lexicon resolves a real verdict on `path`, or can only say `unknown`. */
113
+ export declare function resolvesOwnershipOn(channel: OwnershipChannel | undefined, path: OwnershipReadPath): boolean;
81
114
  //# sourceMappingURL=ownership.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../src/ownership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,0BAA0B,UAAU,CAAC;AAElD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,eAAe,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,OAAO,CAGT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,OAAO,GAAG,SAAS,CAErB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,aAAa,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,SAAS,GACjE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,eAAe,GAAG,SAAS,CAQ7B"}
1
+ {"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../src/ownership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,0BAA0B,UAAU,CAAC;AAElD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,eAAe,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,OAAO,CAGT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,OAAO,GAAG,SAAS,CAErB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,aAAa,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,SAAS,GACjE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,IAAI,EAAE,WAAW,GAChB,eAAe,GAAG,SAAS,CAQ7B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG,sBAAsB,GAAG,iBAAiB,CAAC;AAEjG;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC9C;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAET"}
@@ -46,6 +46,21 @@ export interface Serializer {
46
46
  * Prefix used for rules in this lexicon
47
47
  */
48
48
  rulePrefix: string;
49
+ /**
50
+ * Further id families this lexicon owns, beyond {@link rulePrefix} (#1349).
51
+ *
52
+ * The prefix exists so ids do not collide when several lexicons are loaded
53
+ * together — forgejo wraps github's rules as `WFJ-GHA0xx` for exactly that
54
+ * reason. It was declared and checked by nothing, and k8s quietly shipped
55
+ * five `ARGO0xx` checks outside its own `WK8`.
56
+ *
57
+ * A second family is sometimes right: Argo CD is a distinct product surface
58
+ * that happens to be covered by the k8s lexicon, and renaming published ids
59
+ * would break every `chant-disable ARGO001` in the wild. Declaring it keeps
60
+ * the collision guarantee while allowing the split — an undeclared family is
61
+ * a tier-1 failure.
62
+ */
63
+ extraRulePrefixes?: readonly string[];
49
64
  /**
50
65
  * Serializes the entities to a string representation
51
66
  * @param entities - Map of entity name to Declarable entity
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;OAKG;IACH,SAAS,CACP,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,OAAO,CAAC,EAAE,aAAa,EAAE,EACzB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,gBAAgB,CAAC;IAE7B;;;;;OAKG;IACH,iBAAiB,CAAC,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC;CACpD"}
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEtC;;;;;OAKG;IACH,SAAS,CACP,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,OAAO,CAAC,EAAE,aAAa,EAAE,EACzB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,gBAAgB,CAAC;IAE7B;;;;;OAKG;IACH,iBAAiB,CAAC,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC;CACpD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.37.2",
3
+ "version": "0.39.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -1,10 +1,24 @@
1
1
  import { describe, test, expect } from "vitest";
2
+ import { readdirSync } from "fs";
3
+ import { join } from "path";
2
4
  import { RULE_CATALOG, RULE_CATEGORY, auditRule, resolveAuditCatalog } from "./catalog";
3
5
  import { loadPlugins } from "../cli/plugins";
4
6
 
5
- const AUDIT_LEXICONS = ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm"];
6
-
7
- /** All post-synth check ids the audit can actually surface, from the lexicons. */
7
+ /**
8
+ * Every lexicon in the repo, enumerated rather than listed (#1346).
9
+ *
10
+ * This was nine hardcoded names. It missed fountain, which contributes a
11
+ * catalog, and could not notice fly and temporal, which did not — so their six
12
+ * post-synth checks reached `chant audit` with no title, tier, fix kind, or
13
+ * category, and the guard meant to catch exactly that was blind to them. A
14
+ * thirteenth lexicon would have been invisible to it on arrival.
15
+ */
16
+ const AUDIT_LEXICONS = readdirSync(join(__dirname, "../../../../lexicons"), { withFileTypes: true })
17
+ .filter((e) => e.isDirectory())
18
+ .map((e) => e.name)
19
+ .sort();
20
+
21
+ /** Post-synth check ids — what the catalog is required to cover. */
8
22
  async function realCheckIds(): Promise<Set<string>> {
9
23
  const plugins = await loadPlugins(AUDIT_LEXICONS);
10
24
  const ids = new Set<string>();
@@ -16,6 +30,27 @@ async function realCheckIds(): Promise<Set<string>> {
16
30
  return ids;
17
31
  }
18
32
 
33
+ /**
34
+ * Every rule id a lexicon ships, post-synth or lint — what a catalog entry is
35
+ * allowed to name.
36
+ *
37
+ * Wider than {@link realCheckIds} on purpose. A catalog must cover the
38
+ * post-synth checks, because those are what `chant audit` surfaces, but an
39
+ * entry for a lint rule is not stale: fountain documents FTN001, a lint rule,
40
+ * because a reader hitting it wants the same metadata. Requiring the reverse —
41
+ * an entry for every lint rule in every lexicon — would be a much larger claim
42
+ * than the audit makes.
43
+ */
44
+ async function shippedRuleIds(): Promise<Set<string>> {
45
+ const plugins = await loadPlugins(AUDIT_LEXICONS);
46
+ const ids = new Set<string>();
47
+ for (const plugin of plugins) {
48
+ for (const check of plugin.postSynthChecks?.() ?? []) ids.add(check.id);
49
+ for (const rule of plugin.lintRules?.() ?? []) ids.add(rule.id);
50
+ }
51
+ return ids;
52
+ }
53
+
19
54
  /** The effective catalog the auditor uses — core static + every lexicon's contributed entries (#687). */
20
55
  const aggregate = () => resolveAuditCatalog(AUDIT_LEXICONS);
21
56
 
@@ -26,12 +61,29 @@ describe("RULE_CATALOG (aggregated: core static + lexicon-contributed, #687)", (
26
61
  expect(missing).toEqual([]);
27
62
  });
28
63
 
29
- test("has no stale entries that aren't real checks", async () => {
30
- const [real, catalog] = [await realCheckIds(), await aggregate()];
31
- const stale = Object.keys(catalog).filter((id) => !real.has(id)).sort();
64
+ test("has no stale entries that aren't real rules", async () => {
65
+ const [shipped, catalog] = [await shippedRuleIds(), await aggregate()];
66
+ const stale = Object.keys(catalog)
67
+ .filter((id) => !shipped.has(id))
68
+ // Core's own cross-cutting ids are not any one lexicon's to ship.
69
+ .filter((id) => !(id in RULE_CATALOG))
70
+ .sort();
32
71
  expect(stale).toEqual([]);
33
72
  });
34
73
 
74
+ test("every lexicon that ships post-synth checks contributes a catalog for them", async () => {
75
+ const plugins = await loadPlugins(AUDIT_LEXICONS);
76
+ const uncatalogued = plugins
77
+ .map((plugin) => {
78
+ const checks = (plugin.postSynthChecks?.() ?? []).map((c) => c.id);
79
+ const catalog = plugin.auditCatalog?.() ?? {};
80
+ const missing = checks.filter((id) => !(id in catalog) && !(id in RULE_CATALOG));
81
+ return missing.length > 0 ? `${plugin.name}: ${missing.join(", ")}` : undefined;
82
+ })
83
+ .filter((entry): entry is string => entry !== undefined);
84
+ expect(uncatalogued).toEqual([]);
85
+ });
86
+
35
87
  test("every entry has a title, remediation, and valid tier/fixKind/category", async () => {
36
88
  for (const [id, m] of Object.entries(await aggregate())) {
37
89
  expect(m.id, `${id} id matches key`).toBe(id);
package/src/build.ts CHANGED
@@ -185,6 +185,14 @@ export interface BuildOptions {
185
185
  */
186
186
  lexicons?: readonly string[];
187
187
 
188
+ /**
189
+ * chant #1442 — lexicon name → the version of the plugin that served it.
190
+ * Recorded on {@link BuildResult.lexiconVersions} so a build digest can say
191
+ * WHAT interpreted the declarations, not only what was declared. The CLI
192
+ * populates this from `options.plugins`; `build()` only carries it.
193
+ */
194
+ lexiconVersions?: Readonly<Record<string, string>>;
195
+
188
196
  /**
189
197
  * chant #1045 Phase 2 — opt-in: run-fallback files (or, when {@link fold}
190
198
  * isn't set, every file) execute together, isolated, in one sandboxed
@@ -230,6 +238,21 @@ export interface BuildResult {
230
238
  */
231
239
  foldDecisions: FoldDecision[];
232
240
 
241
+ /**
242
+ * chant #1442 — lexicon name → the version of the plugin that served this
243
+ * build, passed through verbatim from {@link BuildOptions.lexiconVersions}.
244
+ *
245
+ * The other half of what a build digest needs. `hashProps` fingerprints the
246
+ * declaration; this records what turned it into output. A lexicon is a
247
+ * generated artifact pinned to an upstream spec, so a bump can change
248
+ * emitted output with no source change at all — and without this the two
249
+ * builds are indistinguishable.
250
+ *
251
+ * Empty when the caller supplied no plugins (`build()` used as a library,
252
+ * and most tests).
253
+ */
254
+ lexiconVersions: Record<string, string>;
255
+
233
256
  /**
234
257
  * This build's resolved build-time parameters (#1064) — the build
235
258
  * provenance record for `params.<name>` values, alongside the existing
@@ -717,6 +740,7 @@ async function buildFromDiscoveryResult(
717
740
  manifest,
718
741
  sourceFileCount: discoveryResult.sourceFiles.length,
719
742
  foldDecisions: discoveryResult.foldDecisions,
743
+ lexiconVersions: { ...(options?.lexiconVersions ?? {}) },
720
744
  buildParams: options?.buildParams ?? [],
721
745
  };
722
746
  }
@@ -3,6 +3,7 @@ import { loadChantConfigUpward, resolveOwnershipMarker, resolveFoldEnabled, reso
3
3
  import { resolveCliBuildParams } from "../build-params-cli";
4
4
  import type { Serializer, SerializerResult } from "../../serializer";
5
5
  import type { LexiconPlugin } from "../../lexicon";
6
+ import { resolveLexiconVersions } from "../plugins";
6
7
  import { runPostSynthChecks } from "../../lint/post-synth";
7
8
  import { applyConfiguredSeverity } from "../../lint/config";
8
9
  import { loadPolicyChecks } from "../../lint/policy";
@@ -242,6 +243,14 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
242
243
  // ../plugins.ts and fold-import.ts's `lexiconPackageName`.
243
244
  const lexicons = options.plugins?.map((plugin) => plugin.name) ?? [];
244
245
 
246
+ // chant #1442 — which lexicon VERSION interpreted each declaration. The
247
+ // declaration is fingerprinted by `hashProps`; the thing that turned it into
248
+ // output was not recorded at all, so a lexicon bump that changed emitted
249
+ // output left the build digest identical. Captured here because this is the
250
+ // layer that knows which lexicons were loaded — `build()` is given only
251
+ // their names.
252
+ const lexiconVersions = resolveLexiconVersions(lexicons);
253
+
245
254
  // Run the build
246
255
  const result = await build(infraPath, options.serializers, undefined, {
247
256
  ownership,
@@ -250,6 +259,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
250
259
  sandbox,
251
260
  intrinsics,
252
261
  lexicons,
262
+ lexiconVersions,
253
263
  buildParams: paramsResolution.provenance,
254
264
  });
255
265
 
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The completeness checklist and the tool it describes must agree (#1343).
3
+ *
4
+ * They did not. The doc listed 16/16/14 checks where the tool ran 18/14/9 —
5
+ * seven rows described checks that do not exist anywhere in `check-lexicon.ts`
6
+ * (`default-labels.test.ts`, `coverage.test.ts`, the three `import/*` tests,
7
+ * `exportResources()` implemented, and the ownership marker), and two real
8
+ * tier-1 checks were documented nowhere. The two missing ones were the damaging
9
+ * direction: an author reading the checklist would have believed live export and
10
+ * ownership marking were verified somewhere, and nothing verifies either.
11
+ *
12
+ * A hand-maintained parallel description of a tool drifts. This test makes the
13
+ * doc's first column the check's own `name`, so drift is a build failure rather
14
+ * than something a reviewer has to notice.
15
+ */
16
+
17
+ import { describe, test, expect } from "vitest";
18
+ import { readFileSync } from "fs";
19
+ import { join } from "path";
20
+ import { checkLexicon } from "./check-lexicon";
21
+
22
+ const CHECKLIST = join(
23
+ __dirname,
24
+ "../../../../../docs/src/content/docs/lexicon-authoring/completeness-checklist.mdx",
25
+ );
26
+
27
+ /** The check names listed under `## Tier <n>` in the checklist. */
28
+ function documentedChecks(markdown: string, tier: 1 | 2 | 3): string[] {
29
+ const section = markdown.split(new RegExp(`^## Tier ${tier}\\b`, "m"))[1];
30
+ if (section === undefined) throw new Error(`no "## Tier ${tier}" section in the checklist`);
31
+ const body = section.split(/^## /m)[0];
32
+ const names: string[] = [];
33
+ for (const line of body.split("\n")) {
34
+ // A table row whose first cell is a backticked check name.
35
+ const match = /^\|\s*`(.+?)`\s*\|/.exec(line);
36
+ if (match) names.push(match[1]);
37
+ }
38
+ return names;
39
+ }
40
+
41
+ describe("the completeness checklist matches check-lexicon (#1343)", () => {
42
+ const markdown = readFileSync(CHECKLIST, "utf-8");
43
+
44
+ // aws is the reference lexicon; the set of checks run is the same for any
45
+ // directory, since every check is pushed unconditionally.
46
+ const resultPromise = checkLexicon(join(__dirname, "../../../../../lexicons/aws"));
47
+
48
+ for (const tier of [1, 2, 3] as const) {
49
+ test(`tier ${tier} documents exactly the checks the tool runs`, async () => {
50
+ const executed = (await resultPromise).items.filter((i) => i.tier === tier).map((i) => i.name);
51
+ const documented = documentedChecks(markdown, tier);
52
+
53
+ const undocumented = executed.filter((n) => !documented.includes(n));
54
+ const phantom = documented.filter((n) => !executed.includes(n));
55
+
56
+ expect({ undocumented, phantom }).toEqual({ undocumented: [], phantom: [] });
57
+ expect(documented).toHaveLength(executed.length);
58
+ });
59
+ }
60
+
61
+ test("the checklist lists the checks in the order the tool reports them", async () => {
62
+ for (const tier of [1, 2, 3] as const) {
63
+ const executed = (await resultPromise).items.filter((i) => i.tier === tier).map((i) => i.name);
64
+ expect(documentedChecks(markdown, tier)).toEqual(executed);
65
+ }
66
+ });
67
+
68
+ test("no tier section is empty — a parsing regression would silently pass the set comparison", () => {
69
+ for (const tier of [1, 2, 3] as const) {
70
+ expect(documentedChecks(markdown, tier).length).toBeGreaterThan(0);
71
+ }
72
+ });
73
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { mkdtempSync, writeFileSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { mcpNameViolations, lexiconNameFor } from "./check-lexicon-mcp";
6
+
7
+ const tools = (...names: string[]) => names.map((name) => ({ name }));
8
+ const resources = (...uris: string[]) => uris.map((uri) => ({ uri }));
9
+
10
+ describe("mcpNameViolations (#1341)", () => {
11
+ test("a bare tool name is fine — core supplies the namespace", () => {
12
+ expect(mcpNameViolations("gitlab", tools("migrate"), [])).toEqual([]);
13
+ });
14
+
15
+ test("a self-prefixed tool name is fine — core does not double it", () => {
16
+ expect(mcpNameViolations("gitlab", tools("gitlab:diff"), [])).toEqual([]);
17
+ });
18
+
19
+ test("a name carrying another lexicon's prefix is a violation", () => {
20
+ // This is the shape the doubling bug produced: `gitlab:gitlab:diff`.
21
+ const found = mcpNameViolations("git", tools("gitlab:diff"), []);
22
+ expect(found).toHaveLength(1);
23
+ expect(found[0]).toContain("git:gitlab:diff");
24
+ });
25
+
26
+ test("an uppercase or spaced tool name is a violation", () => {
27
+ expect(mcpNameViolations("aws", tools("Diff Resources"), [])).toHaveLength(1);
28
+ expect(mcpNameViolations("aws", tools("Diff"), [])).toHaveLength(1);
29
+ });
30
+
31
+ test("hyphenated verbs are allowed", () => {
32
+ expect(mcpNameViolations("github", tools("github:workflow-yaml"), [])).toEqual([]);
33
+ });
34
+
35
+ test("a bare resource path is fine", () => {
36
+ expect(mcpNameViolations("aws", [], resources("examples/s3-bucket"))).toEqual([]);
37
+ });
38
+
39
+ test("the colon form the shared catalog helper emits is fine", () => {
40
+ expect(mcpNameViolations("aws", [], resources("aws:resource-catalog"))).toEqual([]);
41
+ });
42
+
43
+ test("the chant://lexicon/<name>/ form the authoring docs taught is fine once normalized", () => {
44
+ expect(mcpNameViolations("azure", [], resources("chant://lexicon/azure/catalog"))).toEqual([]);
45
+ });
46
+
47
+ test("a uri carrying a foreign scheme is a violation", () => {
48
+ // azure shipped `chant://azure/chant://lexicon/azure/catalog` this way when
49
+ // the lexicon name in the uri did not match the lexicon being registered.
50
+ const found = mcpNameViolations("aws", [], resources("chant://lexicon/azure/catalog"));
51
+ expect(found).toHaveLength(1);
52
+ expect(found[0]).toContain("chant://aws/chant://lexicon/azure/catalog");
53
+ });
54
+
55
+ test("an already-registered uri passes through", () => {
56
+ expect(mcpNameViolations("aws", [], resources("chant://aws/catalog"))).toEqual([]);
57
+ });
58
+
59
+ test("tools and resources are reported together", () => {
60
+ expect(mcpNameViolations("aws", tools("Bad Name"), resources("chant://other/x"))).toHaveLength(2);
61
+ });
62
+
63
+ test("a lexicon contributing nothing has nothing to violate", () => {
64
+ expect(mcpNameViolations("fly", [], [])).toEqual([]);
65
+ });
66
+ });
67
+
68
+ describe("lexiconNameFor", () => {
69
+ function fixture(pkg?: Record<string, unknown>): string {
70
+ const dir = mkdtempSync(join(tmpdir(), "chant-mcp-name-"));
71
+ mkdirSync(dir, { recursive: true });
72
+ if (pkg) writeFileSync(join(dir, "package.json"), JSON.stringify(pkg));
73
+ return dir;
74
+ }
75
+
76
+ test("reads the lexicon name out of the package name", () => {
77
+ expect(lexiconNameFor(fixture({ name: "@intentius/chant-lexicon-aws" }))).toBe("aws");
78
+ });
79
+
80
+ test("handles a hyphenated lexicon name", () => {
81
+ expect(lexiconNameFor(fixture({ name: "@intentius/chant-lexicon-my-cloud" }))).toBe("my-cloud");
82
+ });
83
+
84
+ test("falls back to the directory name when package.json is missing", () => {
85
+ const dir = fixture();
86
+ expect(lexiconNameFor(dir)).toBe(dir.split("/").pop());
87
+ });
88
+
89
+ test("falls back when the package name is not a lexicon package", () => {
90
+ const dir = fixture({ name: "@intentius/chant" });
91
+ expect(lexiconNameFor(dir)).toBe(dir.split("/").pop());
92
+ });
93
+ });