@intentius/chant 0.124.51 → 0.124.52

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 (37) hide show
  1. package/dist/cli/handlers/pr.d.ts.map +1 -1
  2. package/dist/cli/handlers/pr.js +2 -1
  3. package/dist/components/capability.d.ts +9 -0
  4. package/dist/components/capability.d.ts.map +1 -1
  5. package/dist/components/covered-round.d.ts +173 -0
  6. package/dist/components/covered-round.d.ts.map +1 -0
  7. package/dist/components/covered-round.js +200 -0
  8. package/dist/components/driver.d.ts +8 -0
  9. package/dist/components/driver.d.ts.map +1 -1
  10. package/dist/components/driver.js +8 -0
  11. package/dist/components/index.d.ts +1 -0
  12. package/dist/components/index.d.ts.map +1 -1
  13. package/dist/components/index.js +1 -0
  14. package/dist/components/pr-run.d.ts +21 -3
  15. package/dist/components/pr-run.d.ts.map +1 -1
  16. package/dist/components/pr-run.js +147 -16
  17. package/dist/components/wave-plan.d.ts +2 -0
  18. package/dist/components/wave-plan.d.ts.map +1 -1
  19. package/dist/components/wave-plan.js +4 -1
  20. package/dist/pr-loop.d.ts +36 -2
  21. package/dist/pr-loop.d.ts.map +1 -1
  22. package/dist/pr-loop.js +11 -2
  23. package/dist/wave-runtime.mjs +3 -3
  24. package/dist/workspace/pr-report.schema.json +25 -1
  25. package/package.json +1 -1
  26. package/src/cli/handlers/pr.ts +2 -1
  27. package/src/components/capability.ts +9 -0
  28. package/src/components/covered-round.test.ts +166 -0
  29. package/src/components/covered-round.ts +293 -0
  30. package/src/components/driver.ts +12 -0
  31. package/src/components/index.ts +17 -0
  32. package/src/components/pr-run.test.ts +212 -1
  33. package/src/components/pr-run.ts +166 -16
  34. package/src/components/wave-plan.ts +5 -2
  35. package/src/pr-loop.test.ts +14 -1
  36. package/src/pr-loop.ts +43 -4
  37. package/src/workspace/pr-report.schema.json +25 -1
package/src/pr-loop.ts CHANGED
@@ -88,6 +88,9 @@ export const PR_REPORT_BEFORE_MERGE_FLOOR = "0.124.42";
88
88
  /** The first chant whose plan report can carry `migrations` (#4407). */
89
89
  export const PR_REPORT_MIGRATIONS_FLOOR = "0.124.49";
90
90
 
91
+ /** The first chant whose report can carry a member's `reads`, known at plan or not. */
92
+ export const PR_REPORT_READS_FLOOR = "0.124.52";
93
+
91
94
  /** The gate a pull request's apply waits on, unless the pipeline names another. */
92
95
  export const PR_APPLY_GATE = "pr-apply";
93
96
 
@@ -155,6 +158,16 @@ export type PrMemberStatus =
155
158
  */
156
159
  | "waiting";
157
160
 
161
+ /** One upstream a member reads outputs of (`PrMember.reads`). */
162
+ export interface PrMemberRead {
163
+ /** The component (or stack) whose outputs it reads. */
164
+ upstream: string;
165
+ /** The output names it reads, sorted. `publish.<field>` for a published artifact. */
166
+ outputs: string[];
167
+ /** The ones its plan had no value for, sorted: known only after the upstream applies. Absent when every one was known. */
168
+ unknown?: string[];
169
+ }
170
+
158
171
  export interface PrMember {
159
172
  /** The member's name in the change set: a root, for a terraform-family step. */
160
173
  member: string;
@@ -175,8 +188,10 @@ export interface PrMember {
175
188
  /**
176
189
  * The upstream outputs its plan read that the change's plans could not
177
190
  * say (#3999), as `<component>.<output>`, sorted: known only after that
178
- * upstream applies. The approval does not cover what this member does
179
- * with them; the apply plans it again once they are known.
191
+ * upstream applies. The apply plans the member again once they are known;
192
+ * the first approval covers that round when it does only what the preview
193
+ * showed with only these inputs filled in, and anything else waits for an
194
+ * approval of its own (`PrRound.covered`, `PrRound.notCovered`).
180
195
  */
181
196
  unknownInputs?: string[];
182
197
  /**
@@ -187,6 +202,15 @@ export interface PrMember {
187
202
  * applies without a later round.
188
203
  */
189
204
  buildInputs?: string[];
205
+ /**
206
+ * Every other component's output its steps read, one entry per upstream,
207
+ * by upstream: the output names, sorted, and under `unknown` the ones its
208
+ * plan had no value for. A read whose value the plan knew is here too, so
209
+ * a reader of the report sees each read between components from the plan
210
+ * on, not only the ones `unknownInputs` names. Added within contract 1 by
211
+ * chant {@link PR_REPORT_READS_FLOOR}.
212
+ */
213
+ reads?: PrMemberRead[];
190
214
  /** Apply stage (#3999): the round whose plan it applied or waits on, when not the first. */
191
215
  round?: number;
192
216
  /** Apply stage (#3999): why the first round's plan did not apply. */
@@ -210,6 +234,15 @@ export interface PrRound {
210
234
  status: "applied" | "failed" | "waiting" | "plan-failed";
211
235
  /** With `waiting`: why an approval that stands does not count (`--require-review`). */
212
236
  message?: string;
237
+ /**
238
+ * The first approval covered this round's plan (#4419): no gate of its own was
239
+ * decided. `digest` is the first approval's, which the coverage is bound
240
+ * to; `filledInputs` are the inputs it previewed as unknown and this plan
241
+ * had a value for, as `<component>.<output>`.
242
+ */
243
+ covered?: { digest: string; filledInputs: string[] };
244
+ /** Why the first approval did not cover this round, so it asked for its own (#4419). */
245
+ notCovered?: string;
213
246
  /** The change-set document the round planned. */
214
247
  changeSet: ChangeSetDocument;
215
248
  }
@@ -719,8 +752,8 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
719
752
  if (unknown.length > 0) {
720
753
  out.push(
721
754
  "",
722
- `Known only after apply: ${unknown.join("; ")}. This approval does not cover what those members do with them. ` +
723
- "The apply plans them again once their upstream applied and asks for an approval of that plan.",
755
+ `Known only after apply: ${unknown.join("; ")}. ` +
756
+ "The apply plans those members again once their upstream applied. When that plan does only what this one shows, with only those inputs filled in, this approval covers it; anything else asks for an approval of its own.",
724
757
  );
725
758
  }
726
759
  const fromBuild = [...new Set(report.members.flatMap((m) => (m.buildInputs ?? []).map((ref) => `${code(m.component)} reads ${code(ref)}`)))];
@@ -733,6 +766,12 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
733
766
  }
734
767
  for (const r of report.rounds ?? []) {
735
768
  const what = `Round ${r.round} planned ${r.components.map(code).join(", ")} again after their upstream applied, digest ${code(r.digest)}`;
769
+ if (r.covered) {
770
+ const filled = r.covered.filledInputs.length > 0 ? `, with ${r.covered.filledInputs.map(code).join(", ")} filled in` : "";
771
+ out.push("", `${what}: covered by the approval of ${code(r.covered.digest)}${filled}, so it ${r.status === "applied" ? "applied" : "did not apply"} without a new approval${r.status === "failed" ? " (the apply failed)" : ""}.`);
772
+ continue;
773
+ }
774
+ if (r.notCovered) out.push("", `Round ${r.round}'s plan is not covered by the approval of ${code(report.digest)}: ${r.notCovered}.`);
736
775
  if (r.status === "waiting") {
737
776
  if (r.message) out.push("", `> ${r.message}`);
738
777
  out.push("", `${what}. Not approved yet. Approve it, then run the apply again with the same \`--resume\` record:`, "", "```", prApproveCommand({ op: report.op, gate: r.gate, digest: r.digest }, options.approver ?? "<you>"), "```");
@@ -153,6 +153,16 @@
153
153
  },
154
154
  "status": { "enum": ["applied", "failed", "waiting", "plan-failed"] },
155
155
  "message": { "type": "string", "description": "With waiting: why an approval that stands does not count under --require-review." },
156
+ "covered": {
157
+ "type": "object",
158
+ "required": ["digest", "filledInputs"],
159
+ "description": "Added within contract version 1 by #4419. The first approval covered this round's plan, so no gate of its own was decided and approval is the first one. The plan touched only components and logical ids the first plan previewed, with the same actions, and only inputs the first plan marked unknown differed.",
160
+ "properties": {
161
+ "digest": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "The digest of the first approval, which the coverage is bound to: the report's digest." },
162
+ "filledInputs": { "type": "array", "items": { "type": "string" }, "description": "The inputs the first plan marked unknown that this plan had a value for, as <component>.<output>, sorted." }
163
+ }
164
+ },
165
+ "notCovered": { "type": "string", "description": "Added within contract version 1 by #4419. Why an approval that stands for the first plan did not cover this round, so the round asked for its own." },
156
166
  "changeSet": { "type": "object", "description": "The change-set document the round planned. Its digest equals digest." }
157
167
  }
158
168
  }
@@ -208,13 +218,27 @@
208
218
  "unknownInputs": {
209
219
  "type": "array",
210
220
  "items": { "type": "string" },
211
- "description": "#3999: the upstream outputs its plan read that the change's plans could not say, as <component>.<output>, sorted. Known only after that upstream applies; the approval does not cover what this member does with them."
221
+ "description": "#3999: the upstream outputs its plan read that the change's plans could not say, as <component>.<output>, sorted. Known only after that upstream applies. The first approval covers the round that plans the member again when that plan does only what the preview showed, with only these inputs filled in; anything else waits for an approval of its own."
212
222
  },
213
223
  "buildInputs": {
214
224
  "type": "array",
215
225
  "items": { "type": "string" },
216
226
  "description": "#3999: the published artifact identities its plan read from a build in the same change, as <component>.publish.<field>, sorted. Known only after the build runs and never approved again: the build's provenance binds them, so the member applies without a later round."
217
227
  },
228
+ "reads": {
229
+ "type": "array",
230
+ "description": "Added within contract version 1, written by chant 0.124.52 and newer. Every other component's output its steps read, one entry per upstream: the output names, sorted, and under unknown the ones its plan had no value for. A read whose value the plan knew is listed too. Absent when it reads none.",
231
+ "items": {
232
+ "type": "object",
233
+ "required": ["upstream", "outputs"],
234
+ "additionalProperties": false,
235
+ "properties": {
236
+ "upstream": { "type": "string", "description": "The component (or stack) whose outputs it reads." },
237
+ "outputs": { "type": "array", "items": { "type": "string" }, "description": "The output names it reads, sorted. publish.<field> for a published artifact." },
238
+ "unknown": { "type": "array", "items": { "type": "string" }, "description": "The ones its plan had no value for, sorted: known only after the upstream applies. Absent when every one was known." }
239
+ }
240
+ }
241
+ },
218
242
  "round": { "type": "integer", "minimum": 2, "description": "Apply stage (#3999): the round whose plan it applied or waits on, when not the first. planDigest and counts are that round's." },
219
243
  "held": { "type": "string", "description": "Apply stage (#3999): why the first round's plan did not apply." }
220
244
  }