@intentius/chant 0.124.32 → 0.124.34

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 (320) hide show
  1. package/dist/ci-jobs.d.ts +4 -0
  2. package/dist/ci-jobs.d.ts.map +1 -1
  3. package/dist/ci-jobs.js +9 -2
  4. package/dist/cli/handlers/build.d.ts.map +1 -1
  5. package/dist/cli/handlers/build.js +18 -3
  6. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  7. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  8. package/dist/cli/handlers/lifecycle-health.js +150 -0
  9. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  10. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  11. package/dist/cli/handlers/lifecycle.js +1 -1
  12. package/dist/cli/handlers/pr.d.ts +2 -0
  13. package/dist/cli/handlers/pr.d.ts.map +1 -1
  14. package/dist/cli/handlers/pr.js +124 -2
  15. package/dist/cli/handlers/promote.d.ts.map +1 -1
  16. package/dist/cli/handlers/promote.js +277 -74
  17. package/dist/cli/handlers/release.d.ts +16 -1
  18. package/dist/cli/handlers/release.d.ts.map +1 -1
  19. package/dist/cli/handlers/release.js +83 -11
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/main.js +99 -22
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +30 -1
  24. package/dist/cli/registry.d.ts +21 -5
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/components/deploy-inputs.d.ts +91 -0
  27. package/dist/components/deploy-inputs.d.ts.map +1 -0
  28. package/dist/components/deploy-inputs.js +184 -0
  29. package/dist/components/driver.d.ts.map +1 -1
  30. package/dist/components/driver.js +9 -0
  31. package/dist/components/gate-plan.d.ts +8 -0
  32. package/dist/components/gate-plan.d.ts.map +1 -1
  33. package/dist/components/gate-plan.js +6 -0
  34. package/dist/components/pr-pipeline.d.ts +33 -10
  35. package/dist/components/pr-pipeline.d.ts.map +1 -1
  36. package/dist/components/pr-pipeline.js +42 -11
  37. package/dist/components/pr-run.d.ts +1 -1
  38. package/dist/components/pr-run.d.ts.map +1 -1
  39. package/dist/components/promote-rounds.d.ts +102 -0
  40. package/dist/components/promote-rounds.d.ts.map +1 -0
  41. package/dist/components/promote-rounds.js +174 -0
  42. package/dist/components/release-compile.d.ts +37 -2
  43. package/dist/components/release-compile.d.ts.map +1 -1
  44. package/dist/components/release-compile.js +64 -8
  45. package/dist/components/release-gate.d.ts +30 -0
  46. package/dist/components/release-gate.d.ts.map +1 -1
  47. package/dist/components/release-gate.js +92 -42
  48. package/dist/components/release-promote.d.ts +17 -3
  49. package/dist/components/release-promote.d.ts.map +1 -1
  50. package/dist/components/release-promote.js +79 -18
  51. package/dist/components/verbs/bom-writer.d.ts +1 -1
  52. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  53. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  54. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  56. package/dist/components/wave-plan.d.ts +10 -0
  57. package/dist/components/wave-plan.d.ts.map +1 -1
  58. package/dist/components/wave-plan.js +32 -4
  59. package/dist/graph-declared.d.ts +4 -0
  60. package/dist/graph-declared.d.ts.map +1 -1
  61. package/dist/graph-declared.js +11 -1
  62. package/dist/graph-detail.d.ts.map +1 -1
  63. package/dist/graph-detail.js +32 -7
  64. package/dist/health.d.ts +110 -0
  65. package/dist/health.d.ts.map +1 -0
  66. package/dist/health.js +105 -0
  67. package/dist/health.schema.json +191 -0
  68. package/dist/lexicon.d.ts +27 -0
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/merge-record.d.ts +104 -0
  71. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  72. package/dist/lifecycle/merge-record.js +102 -0
  73. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  74. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  75. package/dist/lifecycle/publish-attachments.js +27 -13
  76. package/dist/lifecycle/publish-npm.d.ts +3 -2
  77. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  78. package/dist/lifecycle/publish-npm.js +4 -3
  79. package/dist/lifecycle/release-at.d.ts +40 -0
  80. package/dist/lifecycle/release-at.d.ts.map +1 -0
  81. package/dist/lifecycle/release-at.js +83 -0
  82. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  83. package/dist/lifecycle/release-cut.js +23 -12
  84. package/dist/lifecycle/release-publish.d.ts +28 -12
  85. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  86. package/dist/lifecycle/release-publish.js +29 -17
  87. package/dist/lifecycle/release-store.d.ts +25 -2
  88. package/dist/lifecycle/release-store.d.ts.map +1 -1
  89. package/dist/lifecycle/release-store.js +40 -13
  90. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  91. package/dist/lifecycle/run-ledger.js +1 -0
  92. package/dist/lint/rules/op/index.d.ts +2 -1
  93. package/dist/lint/rules/op/index.d.ts.map +1 -1
  94. package/dist/lint/rules/op/index.js +4 -2
  95. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  96. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  97. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  98. package/dist/op/activities/activity-contracts.d.ts +5 -0
  99. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  100. package/dist/op/activities/activity-contracts.js +3 -0
  101. package/dist/op/activities/shell.d.ts +38 -0
  102. package/dist/op/activities/shell.d.ts.map +1 -1
  103. package/dist/op/activities/shell.js +89 -3
  104. package/dist/op/builders.d.ts +7 -0
  105. package/dist/op/builders.d.ts.map +1 -1
  106. package/dist/op/builders.js +7 -0
  107. package/dist/op/runtime.d.ts +2 -0
  108. package/dist/op/runtime.d.ts.map +1 -1
  109. package/dist/pr-loop.d.ts +14 -3
  110. package/dist/pr-loop.d.ts.map +1 -1
  111. package/dist/pr-loop.js +15 -5
  112. package/dist/pr-report-url.d.ts +2 -2
  113. package/dist/pr-report-url.d.ts.map +1 -1
  114. package/dist/runner-start.d.ts +179 -0
  115. package/dist/runner-start.d.ts.map +1 -0
  116. package/dist/runner-start.js +277 -0
  117. package/dist/wave-runtime.mjs +236 -229
  118. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  119. package/dist/workspace/__fixtures__/contract-repo.js +13 -2
  120. package/dist/workspace/check.schema.json +88 -2
  121. package/dist/workspace/checks/ci-workflow.d.ts +17 -0
  122. package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
  123. package/dist/workspace/checks/ci-workflow.js +123 -0
  124. package/dist/workspace/checks/security.d.ts +50 -0
  125. package/dist/workspace/checks/security.d.ts.map +1 -0
  126. package/dist/workspace/checks/security.js +305 -0
  127. package/dist/workspace/checks.d.ts +2 -0
  128. package/dist/workspace/checks.d.ts.map +1 -1
  129. package/dist/workspace/checks.js +8 -0
  130. package/dist/workspace/ci-builds.d.ts +163 -0
  131. package/dist/workspace/ci-builds.d.ts.map +1 -0
  132. package/dist/workspace/ci-builds.js +315 -0
  133. package/dist/workspace/ci-green-cli.d.ts +2 -1
  134. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  135. package/dist/workspace/ci-green-cli.js +25 -5
  136. package/dist/workspace/ci-green-forge.d.ts +12 -0
  137. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  138. package/dist/workspace/ci-green-forge.js +12 -0
  139. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  140. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  141. package/dist/workspace/ci-green-workflow.js +128 -1
  142. package/dist/workspace/ci-green.d.ts +89 -4
  143. package/dist/workspace/ci-green.d.ts.map +1 -1
  144. package/dist/workspace/ci-green.js +310 -23
  145. package/dist/workspace/ci-init.d.ts +100 -0
  146. package/dist/workspace/ci-init.d.ts.map +1 -0
  147. package/dist/workspace/ci-init.js +434 -0
  148. package/dist/workspace/ci-last-green.schema.json +49 -0
  149. package/dist/workspace/ci-list.schema.json +35 -1
  150. package/dist/workspace/ci-run-index.d.ts +1 -1
  151. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  152. package/dist/workspace/ci-run-index.schema.json +1 -1
  153. package/dist/workspace/ci-run.d.ts +12 -6
  154. package/dist/workspace/ci-run.d.ts.map +1 -1
  155. package/dist/workspace/ci-run.js +72 -16
  156. package/dist/workspace/ci-runner-start.d.ts +77 -0
  157. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  158. package/dist/workspace/ci-runner-start.js +185 -0
  159. package/dist/workspace/ci-serve.d.ts +76 -7
  160. package/dist/workspace/ci-serve.d.ts.map +1 -1
  161. package/dist/workspace/ci-serve.js +153 -19
  162. package/dist/workspace/ci-workflow.d.ts +91 -0
  163. package/dist/workspace/ci-workflow.d.ts.map +1 -0
  164. package/dist/workspace/ci-workflow.js +33 -0
  165. package/dist/workspace/declaration.d.ts +35 -0
  166. package/dist/workspace/declaration.d.ts.map +1 -1
  167. package/dist/workspace/declaration.js +12 -2
  168. package/dist/workspace/declaration.schema.json +51 -0
  169. package/dist/workspace/init.js +2 -2
  170. package/dist/workspace/ls.d.ts +2 -0
  171. package/dist/workspace/ls.d.ts.map +1 -1
  172. package/dist/workspace/ls.js +1 -1
  173. package/dist/workspace/ls.schema.json +2 -1
  174. package/dist/workspace/member-pipeline.d.ts +1 -0
  175. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  176. package/dist/workspace/member-pipeline.js +2 -0
  177. package/dist/workspace/merge-record.schema.json +49 -0
  178. package/dist/workspace/pr-report.schema.json +3 -3
  179. package/dist/workspace/reason-codes.d.ts +26 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/reason-codes.js +28 -0
  182. package/dist/workspace/release-candidates.d.ts +129 -0
  183. package/dist/workspace/release-candidates.d.ts.map +1 -0
  184. package/dist/workspace/release-candidates.js +233 -0
  185. package/dist/workspace/release-candidates.schema.json +353 -0
  186. package/dist/workspace/release-workflow.d.ts +31 -0
  187. package/dist/workspace/release-workflow.d.ts.map +1 -0
  188. package/dist/workspace/release-workflow.js +150 -0
  189. package/dist/workspace/security.d.ts +40 -0
  190. package/dist/workspace/security.d.ts.map +1 -0
  191. package/dist/workspace/security.js +99 -0
  192. package/package.json +8 -1
  193. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  194. package/src/ci-jobs.ts +11 -2
  195. package/src/cli/handlers/build.test.ts +36 -0
  196. package/src/cli/handlers/build.ts +18 -3
  197. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  198. package/src/cli/handlers/lifecycle-health.ts +157 -0
  199. package/src/cli/handlers/lifecycle.ts +1 -1
  200. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  201. package/src/cli/handlers/pr-record.test.ts +201 -0
  202. package/src/cli/handlers/pr.test.ts +22 -0
  203. package/src/cli/handlers/pr.ts +127 -4
  204. package/src/cli/handlers/promote.test.ts +17 -0
  205. package/src/cli/handlers/promote.ts +296 -84
  206. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  207. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  208. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  209. package/src/cli/handlers/release.e2e.test.ts +19 -28
  210. package/src/cli/handlers/release.ts +81 -11
  211. package/src/cli/main.ts +99 -22
  212. package/src/cli/parse-args.ts +22 -1
  213. package/src/cli/registry.ts +21 -5
  214. package/src/components/deploy-inputs.test.ts +108 -0
  215. package/src/components/deploy-inputs.ts +225 -0
  216. package/src/components/driver.ts +11 -0
  217. package/src/components/gate-plan.ts +9 -0
  218. package/src/components/pr-pipeline.ts +56 -15
  219. package/src/components/pr-run.ts +1 -1
  220. package/src/components/promote-rounds.test.ts +220 -0
  221. package/src/components/promote-rounds.ts +232 -0
  222. package/src/components/release-compile.test.ts +34 -0
  223. package/src/components/release-compile.ts +89 -8
  224. package/src/components/release-gate.test.ts +47 -0
  225. package/src/components/release-gate.ts +111 -46
  226. package/src/components/release-promote.test.ts +55 -2
  227. package/src/components/release-promote.ts +93 -23
  228. package/src/components/verbs/bom-writer.ts +1 -1
  229. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  230. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  231. package/src/components/wave-plan.ts +32 -4
  232. package/src/graph-declared-exports.test.ts +40 -0
  233. package/src/graph-declared.ts +9 -1
  234. package/src/graph-detail.test.ts +57 -0
  235. package/src/graph-detail.ts +33 -7
  236. package/src/health.schema.json +191 -0
  237. package/src/health.test.ts +62 -0
  238. package/src/health.ts +178 -0
  239. package/src/lexicon.ts +41 -1
  240. package/src/lifecycle/merge-record.test.ts +111 -0
  241. package/src/lifecycle/merge-record.ts +154 -0
  242. package/src/lifecycle/publish-attachments.ts +25 -13
  243. package/src/lifecycle/publish-npm.ts +4 -3
  244. package/src/lifecycle/publish-targets.test.ts +34 -4
  245. package/src/lifecycle/release-at.test.ts +48 -0
  246. package/src/lifecycle/release-at.ts +100 -0
  247. package/src/lifecycle/release-cut.test.ts +41 -0
  248. package/src/lifecycle/release-cut.ts +22 -11
  249. package/src/lifecycle/release-publish.test.ts +43 -13
  250. package/src/lifecycle/release-publish.ts +38 -18
  251. package/src/lifecycle/release-store.ts +57 -13
  252. package/src/lifecycle/run-ledger.test.ts +13 -0
  253. package/src/lifecycle/run-ledger.ts +1 -0
  254. package/src/lint/rules/op/index.ts +4 -2
  255. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  256. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  257. package/src/op/activities/activity-contracts.ts +3 -0
  258. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  259. package/src/op/activities/shell.test.ts +44 -1
  260. package/src/op/activities/shell.ts +110 -3
  261. package/src/op/builders.ts +7 -0
  262. package/src/op/runtime.ts +2 -0
  263. package/src/op/step-console.test.ts +4 -2
  264. package/src/pr-loop.test.ts +29 -0
  265. package/src/pr-loop.ts +26 -7
  266. package/src/pr-report-url.ts +2 -2
  267. package/src/runner-start-bundle.test.ts +29 -0
  268. package/src/runner-start.test.ts +179 -0
  269. package/src/runner-start.ts +329 -0
  270. package/src/workspace/__fixtures__/contract-repo.ts +11 -2
  271. package/src/workspace/check-contract.test.ts +4 -2
  272. package/src/workspace/check.schema.json +88 -2
  273. package/src/workspace/checks/ci-workflow.test.ts +150 -0
  274. package/src/workspace/checks/ci-workflow.ts +129 -0
  275. package/src/workspace/checks/security.test.ts +138 -0
  276. package/src/workspace/checks/security.ts +305 -0
  277. package/src/workspace/checks.ts +8 -0
  278. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  279. package/src/workspace/ci-builds.test.ts +174 -0
  280. package/src/workspace/ci-builds.ts +385 -0
  281. package/src/workspace/ci-green-builds.test.ts +180 -0
  282. package/src/workspace/ci-green-cli.test.ts +54 -0
  283. package/src/workspace/ci-green-cli.ts +26 -5
  284. package/src/workspace/ci-green-forge.ts +24 -0
  285. package/src/workspace/ci-green-workflow.test.ts +84 -1
  286. package/src/workspace/ci-green-workflow.ts +139 -2
  287. package/src/workspace/ci-green.test.ts +5 -2
  288. package/src/workspace/ci-green.ts +359 -27
  289. package/src/workspace/ci-init.test.ts +159 -0
  290. package/src/workspace/ci-init.ts +445 -0
  291. package/src/workspace/ci-last-green.schema.json +49 -0
  292. package/src/workspace/ci-list.schema.json +35 -1
  293. package/src/workspace/ci-report-url.test.ts +6 -1
  294. package/src/workspace/ci-run-index.schema.json +1 -1
  295. package/src/workspace/ci-run-index.ts +1 -1
  296. package/src/workspace/ci-run.test.ts +24 -7
  297. package/src/workspace/ci-run.ts +78 -16
  298. package/src/workspace/ci-runner-start.test.ts +151 -0
  299. package/src/workspace/ci-runner-start.ts +193 -0
  300. package/src/workspace/ci-serve-builds.test.ts +86 -0
  301. package/src/workspace/ci-serve.test.ts +59 -0
  302. package/src/workspace/ci-serve.ts +177 -22
  303. package/src/workspace/ci-workflow.ts +71 -0
  304. package/src/workspace/declaration.schema.json +51 -0
  305. package/src/workspace/declaration.test.ts +17 -0
  306. package/src/workspace/declaration.ts +53 -3
  307. package/src/workspace/init.ts +2 -2
  308. package/src/workspace/ls.schema.json +2 -1
  309. package/src/workspace/ls.ts +3 -2
  310. package/src/workspace/member-pipeline.ts +2 -1
  311. package/src/workspace/merge-record.schema.json +49 -0
  312. package/src/workspace/pr-report.schema.json +3 -3
  313. package/src/workspace/reason-codes.test.ts +4 -0
  314. package/src/workspace/reason-codes.ts +28 -0
  315. package/src/workspace/release-candidates.schema.json +353 -0
  316. package/src/workspace/release-candidates.test.ts +106 -0
  317. package/src/workspace/release-candidates.ts +315 -0
  318. package/src/workspace/release-workflow.ts +167 -0
  319. package/src/workspace/security.test.ts +70 -0
  320. package/src/workspace/security.ts +118 -0
@@ -13,6 +13,14 @@
13
13
  * (`componentPlanDigest`, ./gate-plan.ts, #2574), so a wave mixing roots and
14
14
  * other components still has one digest that moves when any of them does.
15
15
  *
16
+ * The files a step without a `plan` deploys from are bound too (#4155,
17
+ * ./deploy-inputs.ts). A component with no plannable step folds them into its
18
+ * composition digest. A component with one gets a member more,
19
+ * `<component>#inputs`, over its other steps' files, so a `lambda-deploy`
20
+ * source or a Helm chart beside a `cfn-deploy` moves the wave's digest. A step
21
+ * whose content chant can't name is a hole on that member, so the plan note
22
+ * lists it as unbound.
23
+ *
16
24
  * Known limit: a step input that reads an earlier step of the same
17
25
  * component (`@Phase.field`) has no value before the component runs, so it
18
26
  * plans as `undefined`. Cross-component wiring (`stackOutput()`,
@@ -20,13 +28,18 @@
20
28
  */
21
29
  import { resolveStepInput, unresolvedWiringSources } from "./driver.js";
22
30
  import { componentPlanDigest } from "./gate-plan.js";
23
- /** A part with no entries, for a member whose planner describes no changes. */
24
- function emptyPart(member) {
31
+ import { componentDeployInputs } from "./deploy-inputs.js";
32
+ import { computePlanDigest } from "../lifecycle/plan-digest.js";
33
+ /** A part with no entries, for a member whose planner describes no changes. `unbound` steps become its holes (#4155). */
34
+ function emptyPart(member, unbound = []) {
35
+ const holes = unbound.map((u) => ({ address: `${u.phase}/${u.kind}`, type: u.kind, reason: `unbound: ${u.reason}` }));
25
36
  return {
26
- member: { member: member.member, lexicon: "chant", planner: "chant", status: "planned", planDigest: member.planDigest, holes: [] },
37
+ member: { member: member.member, lexicon: "chant", planner: "chant", status: "planned", planDigest: member.planDigest, holes },
27
38
  entries: [],
28
39
  };
29
40
  }
41
+ /** The member a planning component's other steps add (#4155): their files, under the component's name. */
42
+ export const inputsMemberName = (component) => `${component}#inputs`;
30
43
  /** Every capability step in a composition, nested phases included, gates left out. */
31
44
  export function capabilitySteps(phases) {
32
45
  const out = [];
@@ -75,17 +88,32 @@ export async function planWaveComponent(component, registry, ctx, componentOutpu
75
88
  if (planned.outputs)
76
89
  outputs = mergePlannedOutputs(outputs, planned.outputs);
77
90
  }
91
+ // #4155: the steps without a plan, which no member above covers.
92
+ const planless = (step) => !registry.has(step.kind) || registry.resolve(step.kind).plan === undefined;
78
93
  if (members.length === 0) {
94
+ const read = componentDeployInputs(component.deploy, process.cwd());
79
95
  const member = {
80
96
  member: component.name,
81
97
  planDigest: componentPlanDigest({
82
98
  environment: ctx.env,
83
99
  component,
84
100
  ...(ctx.vars ? { vars: ctx.vars } : {}),
101
+ inputs: read.inputs,
85
102
  }),
86
103
  };
87
104
  members.push(member);
88
- parts.push(emptyPart(member));
105
+ parts.push(emptyPart(member, read.unbound));
106
+ }
107
+ else {
108
+ const read = componentDeployInputs(component.deploy, process.cwd(), planless);
109
+ if (read.inputs.length > 0 || read.unbound.length > 0) {
110
+ const member = {
111
+ member: inputsMemberName(component.name),
112
+ planDigest: computePlanDigest("component-inputs", { environment: ctx.env, component: component.name, inputs: read.inputs }),
113
+ };
114
+ members.push(member);
115
+ parts.push(emptyPart(member, read.unbound));
116
+ }
89
117
  }
90
118
  return { component: component.name, members, plans, parts, ...(outputs ? { outputs } : {}) };
91
119
  }
@@ -10,6 +10,10 @@ import { type GraphIR } from "./graph-ir.js";
10
10
  * that live side, build each stack's `src` in isolation and qualify its node ids
11
11
  * and edge endpoints the same way. Stacks are merged into one graph.
12
12
  *
13
+ * Each stack's exports and imports come along with their `node` qualified the
14
+ * same way (#4202), so a workspace viewer can still match an import in another
15
+ * member to an export here once an environment is picked.
16
+ *
13
17
  * A stack without `src` contributes nothing here — it has no declared source to
14
18
  * scope to (its live nodes still show as foreign in the overlay).
15
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"graph-declared.d.ts","sourceRoot":"","sources":["../src/graph-declared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,OAAO,EAAe,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAuBlB"}
1
+ {"version":3,"file":"graph-declared.d.ts","sourceRoot":"","sources":["../src/graph-declared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,OAAO,EAAe,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CA2BlB"}
@@ -12,6 +12,10 @@ import { buildGraphIr } from "./graph-ir.js";
12
12
  * that live side, build each stack's `src` in isolation and qualify its node ids
13
13
  * and edge endpoints the same way. Stacks are merged into one graph.
14
14
  *
15
+ * Each stack's exports and imports come along with their `node` qualified the
16
+ * same way (#4202), so a workspace viewer can still match an import in another
17
+ * member to an export here once an environment is picked.
18
+ *
15
19
  * A stack without `src` contributes nothing here — it has no declared source to
16
20
  * scope to (its live nodes still show as foreign in the overlay).
17
21
  */
@@ -19,6 +23,8 @@ export async function buildDeclaredPerStack(stacks, projectPath) {
19
23
  var _a;
20
24
  const nodes = [];
21
25
  const edges = [];
26
+ const exports = [];
27
+ const imports = [];
22
28
  // Which nodes belong to which stack (#1433). This is not derived or guessed:
23
29
  // the stack is declared in config and every node here is being renamed with
24
30
  // its name on the line below. The membership was always known and thrown
@@ -37,9 +43,13 @@ export async function buildDeclaredPerStack(stacks, projectPath) {
37
43
  }
38
44
  for (const e of g.edges)
39
45
  edges.push({ ...e, from: q(e.from), to: q(e.to) });
46
+ for (const e of g.exports ?? [])
47
+ exports.push({ ...e, ...(e.node ? { node: q(e.node) } : {}) });
48
+ for (const i of g.imports ?? [])
49
+ imports.push({ ...i, node: q(i.node) });
40
50
  }
41
51
  for (const ids of Object.values(byStack))
42
52
  ids.sort();
43
53
  const groups = Object.keys(byStack).length ? { byStack } : {};
44
- return { nodes, edges, groups };
54
+ return { nodes, edges, groups, ...(exports.length ? { exports } : {}), ...(imports.length ? { imports } : {}) };
45
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-detail.d.ts","sourceRoot":"","sources":["../src/graph-detail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAExC,eAAO,MAAM,MAAM;aACjB,MAAM,EAAE,CAAC;aACT,UAAU,EAAE,CAAC;aACb,WAAW,EAAE,CAAC;aACd,UAAU,EAAE,CAAC;CACL,CAAC;AAEX,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAYpE;AA+LD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYtF"}
1
+ {"version":3,"file":"graph-detail.d.ts","sourceRoot":"","sources":["../src/graph-detail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAExC,eAAO,MAAM,MAAM;aACjB,MAAM,EAAE,CAAC;aACT,UAAU,EAAE,CAAC;aACb,WAAW,EAAE,CAAC;aACd,UAAU,EAAE,CAAC;CACL,CAAC;AAEX,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAYpE;AAyND;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYtF"}
@@ -90,27 +90,45 @@ function toDeclarables(ir) {
90
90
  /** T1 — collapse each composite instance to one node; internal edges disappear.
91
91
  * Topology view: surviving plain nodes keep only overlay paint, not properties. */
92
92
  function toComposites(ir) {
93
- const idMap = new Map();
94
- for (const n of ir.nodes)
95
- idMap.set(n.id, n.compositeInstance ?? n.id);
93
+ // A composite instance is its export name plus the composite that made it:
94
+ // an AWS FargateAlb and a GCP CloudRunServiceComposite both exported as
95
+ // `web` are two instances (#4178). One composite whose members span
96
+ // lexicons stays one instance.
97
+ const instanceKey = (n) => `${n.compositeInstance}\0${n.compositeParent ?? ""}`;
96
98
  const instances = new Map();
97
99
  const plain = [];
98
100
  for (const n of ir.nodes) {
99
101
  if (n.compositeInstance) {
100
- const arr = instances.get(n.compositeInstance) ?? [];
102
+ const key = instanceKey(n);
103
+ const arr = instances.get(key) ?? [];
101
104
  arr.push(n);
102
- instances.set(n.compositeInstance, arr);
105
+ instances.set(key, arr);
103
106
  }
104
107
  else {
105
108
  plain.push({ ...n, attrs: paintAttrs(n.attrs) });
106
109
  }
107
110
  }
111
+ // The collapsed id is the instance name, or `<instance>@<composite>` when
112
+ // two composites share the name.
113
+ const sharing = new Map();
114
+ for (const members of instances.values()) {
115
+ const inst = members[0].compositeInstance;
116
+ sharing.set(inst, (sharing.get(inst) ?? 0) + 1);
117
+ }
118
+ const collapsedId = new Map();
119
+ for (const [key, members] of instances) {
120
+ const { compositeInstance: inst, compositeParent: parent } = members[0];
121
+ collapsedId.set(key, sharing.get(inst) > 1 && parent ? `${inst}@${parent}` : inst);
122
+ }
123
+ const idMap = new Map();
124
+ for (const n of ir.nodes)
125
+ idMap.set(n.id, n.compositeInstance ? collapsedId.get(instanceKey(n)) : n.id);
108
126
  const nodes = [...plain];
109
- for (const [inst, members] of instances) {
127
+ for (const [key, members] of instances) {
110
128
  const types = new Set(members.map((m) => m.compositeParent).filter(Boolean));
111
129
  const lexicons = new Set(members.map((m) => m.lexicon));
112
130
  nodes.push({
113
- id: inst,
131
+ id: collapsedId.get(key),
114
132
  kind: types.size === 1 ? [...types][0] : "Composite",
115
133
  lexicon: lexicons.size === 1 ? [...lexicons][0] : "multi",
116
134
  attrs: { members: members.length },
@@ -163,11 +181,18 @@ function toComposites(ir) {
163
181
  if (Object.keys(byPipeline).length)
164
182
  groups.byPipeline = byPipeline;
165
183
  }
184
+ // Exports (#4178) stay at this tier, each on the node that now holds its
185
+ // producer, so a viewer can still match another member's import to it.
186
+ const exports = ir.exports?.map((x) => (x.node ? { ...x, node: idMap.get(x.node) ?? x.node } : { ...x }));
166
187
  return {
188
+ ...(ir.version !== undefined ? { version: ir.version } : {}),
167
189
  nodes,
168
190
  edges: sortEdges([...seen.values()]),
169
191
  groups,
192
+ ...(ir.meta ? { meta: ir.meta } : {}),
193
+ ...(exports && exports.length ? { exports } : {}),
170
194
  ...(imports && imports.length ? { imports } : {}),
195
+ ...(ir.pipeline ? { pipeline: ir.pipeline } : {}),
171
196
  ...(dangling && dangling.length ? { dangling } : {}),
172
197
  };
173
198
  }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Runtime health (#4177): whether a declared thing is up, read separately from
3
+ * drift.
4
+ *
5
+ * The observation family answers what is deployed (`describeResources`), what
6
+ * its properties are (`observeResourcesDeep`) and what it would do
7
+ * (`predictBehaviour`). None of them says whether a service is serving. A
8
+ * lexicon that can read that implements {@link HealthLexicon.observeHealth}
9
+ * for the types it names in `healthTypes`, and `chant lifecycle health` reports
10
+ * the answers in the {@link HEALTH_SCHEMA} shape.
11
+ *
12
+ * The verdicts follow #1089's rule. Every entity the caller asks about lands in
13
+ * exactly one place: `entities` (read, with a state), `unobserved` (the read
14
+ * failed, a hole with a reason) or `absent` (the provider says it is not
15
+ * deployed). A failed read is never `unhealthy`: `unhealthy` is something the
16
+ * provider said.
17
+ *
18
+ * Routing is not modelled. A consumer that wants rotation (a latency record
19
+ * whose health check is unhealthy is out of rotation) derives it from the
20
+ * graph's edges and these states.
21
+ */
22
+ import type { UnobservedEntity, UnobservedReason } from "./observation.js";
23
+ /** The versioned shape `chant lifecycle health --json` prints. The JSON Schema is `./health.schema.json`. */
24
+ export declare const HEALTH_SCHEMA = "chant-health/v1";
25
+ /** The `$id` of `./health.schema.json`, which the package exports as `@intentius/chant/health.schema.json`. */
26
+ export declare const HEALTH_SCHEMA_ID = "https://intentius.io/chant/schemas/health/v1/health.schema.json";
27
+ /**
28
+ * `healthy` and `unhealthy` are what the provider reported. `unknown` means the
29
+ * read succeeded and the provider's answer does not decide either way (a
30
+ * deployment still rolling, a service scaled to zero, a condition pending).
31
+ */
32
+ export type HealthState = "healthy" | "unhealthy" | "unknown";
33
+ export interface HealthEntry {
34
+ /** Entity type, e.g. `AWS::ECS::Service`. */
35
+ type: string;
36
+ state: HealthState;
37
+ /** One short line saying why, in the provider's terms. */
38
+ detail: string;
39
+ /** When chant read it (ISO 8601). */
40
+ observedAt: string;
41
+ /** When the provider last checked or changed the state, when it says (ISO 8601). */
42
+ checkedAt?: string;
43
+ }
44
+ /** What one lexicon's health read returns. */
45
+ export interface HealthReadResult {
46
+ /** Read, keyed by chant entity name, which is the graph's node id. */
47
+ entities: Record<string, HealthEntry>;
48
+ /** Could not be read. Never folded into `unhealthy`. */
49
+ unobserved: Record<string, UnobservedEntity>;
50
+ /** The provider says these are not deployed. */
51
+ absent: string[];
52
+ }
53
+ /** The options core passes a lexicon's health read. They mirror `observeResourcesDeep`'s. */
54
+ export interface HealthReadOptions {
55
+ environment: string;
56
+ buildOutput: string;
57
+ /** Only entities whose type is in the lexicon's `healthTypes`. */
58
+ entities: Map<string, {
59
+ entityType: string;
60
+ props: Record<string, unknown>;
61
+ }>;
62
+ stack?: string;
63
+ region?: string;
64
+ }
65
+ /** The two plugin members a lexicon implements to answer a health read. */
66
+ export interface HealthLexicon {
67
+ name: string;
68
+ /** Entity types {@link observeHealth} can read. Data, so core picks the entities without calling the reader. */
69
+ healthTypes?: readonly string[];
70
+ observeHealth?(options: HealthReadOptions): Promise<HealthReadResult>;
71
+ }
72
+ /**
73
+ * The entities of one lexicon a health read applies to: those whose type the
74
+ * lexicon names in `healthTypes`.
75
+ */
76
+ export declare function healthApplicable(plugin: HealthLexicon, entities: Map<string, {
77
+ entityType: string;
78
+ props: Record<string, unknown>;
79
+ }>): Map<string, {
80
+ entityType: string;
81
+ props: Record<string, unknown>;
82
+ }>;
83
+ /**
84
+ * Run one lexicon's health read. Never throws: a thrown reader reports every
85
+ * applicable entity `read-failed`. The result is made total, so an entity the
86
+ * reader forgot comes back `unobserved` rather than missing.
87
+ */
88
+ export declare function observeHealthFor(plugin: HealthLexicon, options: HealthReadOptions): Promise<HealthReadResult>;
89
+ /**
90
+ * Put every asked-about name in exactly one of the three places. A name in
91
+ * `entities` wins over the others, then `absent`, then `unobserved`; a name in
92
+ * none of them becomes `read-failed`.
93
+ */
94
+ export declare function totalHealth(names: string[], result: HealthReadResult, types?: Map<string, {
95
+ entityType: string;
96
+ }>): HealthReadResult;
97
+ /** Counts across a health read, for the summary line and `--json`. */
98
+ export interface HealthSummary {
99
+ healthy: number;
100
+ unhealthy: number;
101
+ unknown: number;
102
+ unobserved: number;
103
+ absent: number;
104
+ }
105
+ export declare function summarizeHealth(results: Iterable<HealthReadResult>): HealthSummary;
106
+ /** A health entry, for a reader building its result. */
107
+ export declare function healthEntry(type: string, state: HealthState, detail: string, observedAt: string, checkedAt?: string): HealthEntry;
108
+ /** A hole, for a reader building its result. */
109
+ export declare function healthHole(type: string, reason: UnobservedReason, detail: string): UnobservedEntity;
110
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"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"}
package/dist/health.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Runtime health (#4177): whether a declared thing is up, read separately from
3
+ * drift.
4
+ *
5
+ * The observation family answers what is deployed (`describeResources`), what
6
+ * its properties are (`observeResourcesDeep`) and what it would do
7
+ * (`predictBehaviour`). None of them says whether a service is serving. A
8
+ * lexicon that can read that implements {@link HealthLexicon.observeHealth}
9
+ * for the types it names in `healthTypes`, and `chant lifecycle health` reports
10
+ * the answers in the {@link HEALTH_SCHEMA} shape.
11
+ *
12
+ * The verdicts follow #1089's rule. Every entity the caller asks about lands in
13
+ * exactly one place: `entities` (read, with a state), `unobserved` (the read
14
+ * failed, a hole with a reason) or `absent` (the provider says it is not
15
+ * deployed). A failed read is never `unhealthy`: `unhealthy` is something the
16
+ * provider said.
17
+ *
18
+ * Routing is not modelled. A consumer that wants rotation (a latency record
19
+ * whose health check is unhealthy is out of rotation) derives it from the
20
+ * graph's edges and these states.
21
+ */
22
+ import { unobservedAll } from "./observation.js";
23
+ /** The versioned shape `chant lifecycle health --json` prints. The JSON Schema is `./health.schema.json`. */
24
+ export const HEALTH_SCHEMA = "chant-health/v1";
25
+ /** The `$id` of `./health.schema.json`, which the package exports as `@intentius/chant/health.schema.json`. */
26
+ export const HEALTH_SCHEMA_ID = "https://intentius.io/chant/schemas/health/v1/health.schema.json";
27
+ /**
28
+ * The entities of one lexicon a health read applies to: those whose type the
29
+ * lexicon names in `healthTypes`.
30
+ */
31
+ export function healthApplicable(plugin, entities) {
32
+ const types = new Set(plugin.healthTypes ?? []);
33
+ const out = new Map();
34
+ for (const [name, entity] of entities)
35
+ if (types.has(entity.entityType))
36
+ out.set(name, entity);
37
+ return out;
38
+ }
39
+ /**
40
+ * Run one lexicon's health read. Never throws: a thrown reader reports every
41
+ * applicable entity `read-failed`. The result is made total, so an entity the
42
+ * reader forgot comes back `unobserved` rather than missing.
43
+ */
44
+ export async function observeHealthFor(plugin, options) {
45
+ const names = [...options.entities.keys()];
46
+ if (names.length === 0)
47
+ return { entities: {}, unobserved: {}, absent: [] };
48
+ if (!plugin.observeHealth) {
49
+ return {
50
+ entities: {},
51
+ unobserved: unobservedAll(names, "unsupported-kind", `${plugin.name} has no health read`, options.entities),
52
+ absent: [],
53
+ };
54
+ }
55
+ let result;
56
+ try {
57
+ result = await plugin.observeHealth(options);
58
+ }
59
+ catch (err) {
60
+ const message = err instanceof Error ? err.message : String(err);
61
+ return { entities: {}, unobserved: unobservedAll(names, "read-failed", message, options.entities), absent: [] };
62
+ }
63
+ return totalHealth(names, result, options.entities);
64
+ }
65
+ /**
66
+ * Put every asked-about name in exactly one of the three places. A name in
67
+ * `entities` wins over the others, then `absent`, then `unobserved`; a name in
68
+ * none of them becomes `read-failed`.
69
+ */
70
+ export function totalHealth(names, result, types) {
71
+ const entities = {};
72
+ const unobserved = {};
73
+ const absentSet = new Set(result.absent ?? []);
74
+ const absent = [];
75
+ for (const name of names) {
76
+ const entry = result.entities?.[name];
77
+ if (entry)
78
+ entities[name] = entry;
79
+ else if (absentSet.has(name))
80
+ absent.push(name);
81
+ else if (result.unobserved?.[name])
82
+ unobserved[name] = result.unobserved[name];
83
+ else
84
+ Object.assign(unobserved, unobservedAll([name], "read-failed", "the health read returned no answer for this entity", types));
85
+ }
86
+ return { entities, unobserved, absent };
87
+ }
88
+ export function summarizeHealth(results) {
89
+ const summary = { healthy: 0, unhealthy: 0, unknown: 0, unobserved: 0, absent: 0 };
90
+ for (const r of results) {
91
+ for (const entry of Object.values(r.entities))
92
+ summary[entry.state]++;
93
+ summary.unobserved += Object.keys(r.unobserved).length;
94
+ summary.absent += r.absent.length;
95
+ }
96
+ return summary;
97
+ }
98
+ /** A health entry, for a reader building its result. */
99
+ export function healthEntry(type, state, detail, observedAt, checkedAt) {
100
+ return { type, state, detail, observedAt, ...(checkedAt ? { checkedAt } : {}) };
101
+ }
102
+ /** A hole, for a reader building its result. */
103
+ export function healthHole(type, reason, detail) {
104
+ return { type, reason, detail };
105
+ }
@@ -0,0 +1,191 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/health/v1/health.schema.json",
4
+ "title": "chant-health/v1",
5
+ "description": "What `chant lifecycle health <env> --json` prints (#4177): whether each declared resource that has a health read is up, read separately from drift. Every entity a read applies to lands in exactly one of `entities` (read, with a state), `unobserved` (the read failed, never folded into unhealthy) or `absent` (the provider says it is not deployed). Entity names are the graph's node ids. A single-stack project puts `lexicons` at the top level; a multi-stack one nests them under `stacks`. Written by the first chant release after 0.124.32. Readers ignore fields they do not know; a field is only ever added within a version.",
6
+ "type": "object",
7
+ "required": [
8
+ "schema",
9
+ "chantVersion",
10
+ "environment",
11
+ "observedAt",
12
+ "summary"
13
+ ],
14
+ "properties": {
15
+ "schema": {
16
+ "const": "chant-health/v1"
17
+ },
18
+ "chantVersion": {
19
+ "type": "string",
20
+ "description": "The chant that ran the read."
21
+ },
22
+ "environment": {
23
+ "type": "string",
24
+ "description": "The environment named on the command line."
25
+ },
26
+ "observedAt": {
27
+ "type": "string",
28
+ "description": "When the command started reading, ISO 8601 UTC."
29
+ },
30
+ "summary": {
31
+ "type": "object",
32
+ "required": [
33
+ "healthy",
34
+ "unhealthy",
35
+ "unknown",
36
+ "unobserved",
37
+ "absent"
38
+ ],
39
+ "description": "Counts over every lexicon and stack.",
40
+ "properties": {
41
+ "healthy": {
42
+ "type": "integer",
43
+ "minimum": 0
44
+ },
45
+ "unhealthy": {
46
+ "type": "integer",
47
+ "minimum": 0
48
+ },
49
+ "unknown": {
50
+ "type": "integer",
51
+ "minimum": 0
52
+ },
53
+ "unobserved": {
54
+ "type": "integer",
55
+ "minimum": 0
56
+ },
57
+ "absent": {
58
+ "type": "integer",
59
+ "minimum": 0
60
+ }
61
+ }
62
+ }
63
+ },
64
+ "oneOf": [
65
+ {
66
+ "required": [
67
+ "lexicons"
68
+ ],
69
+ "properties": {
70
+ "lexicons": {
71
+ "$ref": "#/$defs/byLexicon"
72
+ }
73
+ }
74
+ },
75
+ {
76
+ "required": [
77
+ "stacks"
78
+ ],
79
+ "properties": {
80
+ "stacks": {
81
+ "type": "object",
82
+ "description": "Per stack name, the same per-lexicon results.",
83
+ "additionalProperties": {
84
+ "$ref": "#/$defs/byLexicon"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ],
90
+ "$defs": {
91
+ "byLexicon": {
92
+ "type": "object",
93
+ "description": "Per lexicon name, that lexicon's read. A lexicon with no applicable entity is left out.",
94
+ "additionalProperties": {
95
+ "$ref": "#/$defs/read"
96
+ }
97
+ },
98
+ "read": {
99
+ "type": "object",
100
+ "required": [
101
+ "entities",
102
+ "unobserved",
103
+ "absent"
104
+ ],
105
+ "properties": {
106
+ "entities": {
107
+ "type": "object",
108
+ "additionalProperties": {
109
+ "$ref": "#/$defs/entry"
110
+ }
111
+ },
112
+ "unobserved": {
113
+ "type": "object",
114
+ "additionalProperties": {
115
+ "$ref": "#/$defs/hole"
116
+ }
117
+ },
118
+ "absent": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "string"
122
+ },
123
+ "description": "Entity names the provider says are not deployed."
124
+ }
125
+ }
126
+ },
127
+ "entry": {
128
+ "type": "object",
129
+ "required": [
130
+ "type",
131
+ "state",
132
+ "detail",
133
+ "observedAt"
134
+ ],
135
+ "properties": {
136
+ "type": {
137
+ "type": "string",
138
+ "description": "Entity type, such as AWS::ECS::Service."
139
+ },
140
+ "state": {
141
+ "enum": [
142
+ "healthy",
143
+ "unhealthy",
144
+ "unknown"
145
+ ],
146
+ "description": "healthy and unhealthy are what the provider reported. unknown means the read succeeded and the answer decides neither way: a rollout in progress, a service scaled to zero, a condition pending."
147
+ },
148
+ "detail": {
149
+ "type": "string",
150
+ "description": "One short line saying why, in the provider's terms."
151
+ },
152
+ "observedAt": {
153
+ "type": "string",
154
+ "description": "When chant read it, ISO 8601."
155
+ },
156
+ "checkedAt": {
157
+ "type": "string",
158
+ "description": "When the provider last checked or changed the state, when it says, ISO 8601."
159
+ }
160
+ }
161
+ },
162
+ "hole": {
163
+ "type": "object",
164
+ "required": [
165
+ "reason"
166
+ ],
167
+ "description": "A read that failed. Never counted as unhealthy.",
168
+ "properties": {
169
+ "type": {
170
+ "type": "string"
171
+ },
172
+ "reason": {
173
+ "enum": [
174
+ "read-failed",
175
+ "no-credentials",
176
+ "no-binding",
177
+ "unsupported-kind",
178
+ "filtered"
179
+ ]
180
+ },
181
+ "detail": {
182
+ "type": "string"
183
+ },
184
+ "queried": {
185
+ "type": "string",
186
+ "description": "The address the read was issued against, when the lexicon reports it."
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
package/dist/lexicon.d.ts CHANGED
@@ -41,6 +41,7 @@ export type { CommandGroup, CommandGroupCommand, CommandGroupContext } from "./c
41
41
  export type { ReferenceCatalog, IdentityRule, RefRule } from "./graph-refs.js";
42
42
  export type { IREdge } from "./graph-ir.js";
43
43
  export type { DescribeResourcesResult, ObservationResult, NormalizedObservation, UnobservedEntity, UnobservedReason, } from "./observation.js";
44
+ export type { HealthEntry, HealthReadOptions, HealthReadResult, HealthState, } from "./health.js";
44
45
  export type { DescribeIdentityOptions, DescribeIdentityResult, ResolvedIdentity, UnresolvedIdentity, IdentityRow, IdentityStatus, } from "./identity.js";
45
46
  export type { Disruption, DisruptionQuery, DisruptionVerdict, DisruptionClassifier, } from "./lifecycle/disruption.js";
46
47
  export type { DeepObservationResult, DeepResourceObservation, NormalizedDeepObservation, DeepNormalizationHooks, DeepNode, DeepArrayElement, DeepSide, } from "./deep-observation.js";
@@ -589,6 +590,15 @@ export type OpTrigger = {
589
590
  } | {
590
591
  kind: "push";
591
592
  branches?: string[];
593
+ /**
594
+ * Which pushes run the Op (#4169), for an Op a `ci.green` phase names
595
+ * with `build` and the tick reads from chant runs. `every`: each
596
+ * commit, in a run of its own that no later push cancels. `newest`:
597
+ * one run at a time, the newest push waiting and the ones between
598
+ * dropped, plus a run at each `ci/build/<sha>` tag the tick or `ci run
599
+ * --at` pushes. Absent: one run at a time per Op, as before.
600
+ */
601
+ build?: "every" | "newest";
592
602
  };
593
603
  /**
594
604
  * One step a generated Op job runs between the checkout and the
@@ -1617,6 +1627,23 @@ export interface LexiconPlugin {
1617
1627
  /** Restrict to chant-owned resources (#119). A lexicon with no marker channel on this path says so. */
1618
1628
  owned?: boolean;
1619
1629
  }): Promise<DeepObservationResult>;
1630
+ /**
1631
+ * Entity types {@link observeHealth} reads (#4177). Data, not a method, so
1632
+ * core picks the entities a health read applies to without calling it.
1633
+ */
1634
+ healthTypes?: readonly string[];
1635
+ /**
1636
+ * Whether each declared entity of a {@link healthTypes} type is up (#4177),
1637
+ * read separately from drift: a service's running count, a health check's
1638
+ * checkers, a revision's running state, a Ready condition.
1639
+ *
1640
+ * Every entity in `options.entities` lands in exactly one of `entities`,
1641
+ * `unobserved` or `absent`. A read that failed is `unobserved` with a reason,
1642
+ * never `unhealthy`: `unhealthy` is something the provider said. Throwing is
1643
+ * the whole-lexicon failure, and core reports every entity `read-failed`.
1644
+ * Endpoint overrides apply as on the other reads, so an emulator answers.
1645
+ */
1646
+ observeHealth?(options: import("./health.js").HealthReadOptions): Promise<import("./health.js").HealthReadResult>;
1620
1647
  /**
1621
1648
  * This lexicon's noise rules for deep observation (#1014): which fields are
1622
1649
  * read-only / server-populated / controller-managed / provider-defaulted, and