@intentius/chant-lexicon-fly 0.124.67 → 0.124.69

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/components/fly-release.d.ts +22 -3
  3. package/dist/components/fly-release.d.ts.map +1 -1
  4. package/dist/components/fly-release.js +86 -38
  5. package/dist/describe-resources.d.ts.map +1 -1
  6. package/dist/describe-resources.js +8 -3
  7. package/dist/integrity.json +6 -6
  8. package/dist/lint/audit-catalog.js +1 -1
  9. package/dist/lint/post-synth/fly011-mount-references-declared-volume.d.ts +4 -0
  10. package/dist/lint/post-synth/fly011-mount-references-declared-volume.d.ts.map +1 -1
  11. package/dist/lint/post-synth/fly011-mount-references-declared-volume.js +10 -2
  12. package/dist/manifest.json +1 -1
  13. package/dist/okf/index.md +1 -1
  14. package/dist/okf/rules/FLY011.md +2 -2
  15. package/dist/okf/types/Machine.md +1 -1
  16. package/dist/okf/types/Volume.md +1 -1
  17. package/dist/op/activities/fly-apply.d.ts +91 -1
  18. package/dist/op/activities/fly-apply.d.ts.map +1 -1
  19. package/dist/op/activities/fly-apply.js +165 -7
  20. package/dist/op/activities/machine-release.d.ts +105 -4
  21. package/dist/op/activities/machine-release.d.ts.map +1 -1
  22. package/dist/op/activities/machine-release.js +147 -8
  23. package/dist/op/activities/machines-fake.d.ts +30 -0
  24. package/dist/op/activities/machines-fake.d.ts.map +1 -1
  25. package/dist/op/activities/machines-fake.js +60 -3
  26. package/dist/rules/fly011-mount-references-declared-volume.ts +9 -2
  27. package/dist/skills/chant-fly-ops.md +26 -2
  28. package/dist/skills/chant-fly-patterns.md +4 -2
  29. package/dist/skills/chant-fly.md +1 -1
  30. package/dist/volume-id.d.ts +9 -0
  31. package/dist/volume-id.d.ts.map +1 -0
  32. package/dist/volume-id.js +10 -0
  33. package/package.json +3 -3
  34. package/src/components/fly-release.test.ts +231 -0
  35. package/src/components/fly-release.ts +106 -39
  36. package/src/describe-resources.test.ts +27 -0
  37. package/src/describe-resources.ts +9 -2
  38. package/src/lint/audit-catalog.ts +1 -1
  39. package/src/lint/post-synth/fly011-mount-references-declared-volume.test.ts +31 -0
  40. package/src/lint/post-synth/fly011-mount-references-declared-volume.ts +9 -2
  41. package/src/op/activities/fly-apply.test.ts +170 -2
  42. package/src/op/activities/fly-apply.ts +229 -9
  43. package/src/op/activities/machine-release.test.ts +175 -1
  44. package/src/op/activities/machine-release.ts +222 -12
  45. package/src/op/activities/machines-fake.ts +89 -3
  46. package/src/op/activities/machines-local.test.ts +1 -0
  47. package/src/skills/chant-fly-ops.md +26 -2
  48. package/src/skills/chant-fly-patterns.md +4 -2
  49. package/src/skills/chant-fly.md +1 -1
  50. package/src/volume-id.ts +11 -0
package/README.md CHANGED
@@ -85,7 +85,7 @@ Lint rules run during `chant build`, before anything reaches the API:
85
85
 
86
86
  - `region` must be a real Fly region.
87
87
  - Guest sizing (`cpu_kind` / `cpus` / `memory_mb`) must be a valid combination.
88
- - Every machine mount must reference a `Volume` declared in the stack, checked across files.
88
+ - Every machine mount must reference a `Volume` declared in the stack, checked across files, or give an existing volume's id (`vol_...`).
89
89
  - Secret values may not be written inline; they belong in a `Secret` or a reference.
90
90
 
91
91
  ## The deploy Op
@@ -29,8 +29,19 @@
29
29
  * 3. verify: the Machine is started with this release, and its health
30
30
  * endpoint answers when a URL is given;
31
31
  * 4. on a failure after the Machine changed, restore: the config it replaced
32
- * goes back (or, on a first release, the Machine is stopped), and the step
33
- * fails.
32
+ * goes back (or, on a first release, the Machine is stopped), and so does
33
+ * the old config of each other Machine the release updated, and the step
34
+ * fails. A failure partway through the apply restores the Machines it had
35
+ * already updated the same way.
36
+ *
37
+ * Given `checks` (#4719), the apply rolls the plan's Machines one at a time on
38
+ * their Fly checks: each Machine it starts must pass its checks
39
+ * (`checks[].status` on the Machine, reported since its update) before the
40
+ * next is updated, within `checks.timeoutMs` (default five minutes). A Machine
41
+ * with no checks is done once started. Verify then also needs every Machine
42
+ * the release started to pass its checks, and a failure restores the Machines
43
+ * the release changed last changed first, each restore waiting for its
44
+ * Machine's checks. Without `checks`, only `started` is waited for.
34
45
  *
35
46
  * A scheduled Machine (`config.schedule`, #4696) is released without being
36
47
  * started, whether it is the one that serves or another Machine in the plan:
@@ -53,7 +64,7 @@
53
64
  */
54
65
  import type { Capability, DeployContext } from "@intentius/chant/components/capability";
55
66
  import type { ReceiptStore, EffectReceiptRef } from "@intentius/chant/op/receipt-store";
56
- import { type MachineFile, type MachineRelease } from "../op/activities/machine-release.js";
67
+ import { type MachineChecksOpts, type MachineFile, type MachineRelease } from "../op/activities/machine-release.js";
57
68
  import { type FlyHttp, type WaitOpts } from "../op/activities/fly-apply.js";
58
69
  import type { MachineConfigStore } from "../release-store.js";
59
70
  import { type ReleaseIdentity } from "@intentius/chant/telemetry-attribution";
@@ -126,6 +137,14 @@ export interface FlyReleaseInput {
126
137
  /** Migrations, in order. */
127
138
  migrations?: FlyMigration[];
128
139
  verify?: FlyVerify;
140
+ /**
141
+ * Roll the Machines one at a time on their Fly checks (#4719): each Machine
142
+ * the release starts must pass its checks before the next is updated, and
143
+ * verify reads every one's checks. `true` takes the defaults: five minutes
144
+ * per Machine, polled every second. A Machine with no checks configured is
145
+ * done once started. Without it, only `started` is waited for.
146
+ */
147
+ checks?: MachineChecksOpts | true;
129
148
  /** flaps endpoint override. Default: `FLY_FLAPS_BASE_URL`, else real Fly. */
130
149
  endpoint?: string;
131
150
  wait?: WaitOpts;
@@ -1 +1 @@
1
- {"version":3,"file":"fly-release.d.ts","sourceRoot":"","sources":["../../src/components/fly-release.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAWL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAA4E,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACnJ,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAEvG,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC3B,4HAA4H;IAC5H,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6FAA6F;IAC7F,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CAC1B;AAED,8GAA8G;AAC9G,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAkB9C,kGAAkG;AAClG,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAW5G;AAID,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kHAAkH;IAClH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,yEAAyE;IACzE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,qDAAqD;IACrD,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,+BAA+B;IAC/B,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;CACjC;AAED,oGAAoG;AACpG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gGAAgG;IAChG,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvF,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5E,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAmBD,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG;IAAE,GAAG,EAAE,gBAAgB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAMrH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CASzG;AASD,0CAA0C;AAC1C,wBAAgB,0BAA0B,CAAC,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAsJnH;AAED,2CAA2C;AAC3C,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CA4DtH;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWrH;AAED,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,+CAA+B,CAAC;AACjE,6CAA6C;AAC7C,eAAO,MAAM,qBAAqB,iDAAgC,CAAC"}
1
+ {"version":3,"file":"fly-release.d.ts","sourceRoot":"","sources":["../../src/components/fly-release.ts"],"names":[],"mappings":"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"}
@@ -29,8 +29,19 @@
29
29
  * 3. verify: the Machine is started with this release, and its health
30
30
  * endpoint answers when a URL is given;
31
31
  * 4. on a failure after the Machine changed, restore: the config it replaced
32
- * goes back (or, on a first release, the Machine is stopped), and the step
33
- * fails.
32
+ * goes back (or, on a first release, the Machine is stopped), and so does
33
+ * the old config of each other Machine the release updated, and the step
34
+ * fails. A failure partway through the apply restores the Machines it had
35
+ * already updated the same way.
36
+ *
37
+ * Given `checks` (#4719), the apply rolls the plan's Machines one at a time on
38
+ * their Fly checks: each Machine it starts must pass its checks
39
+ * (`checks[].status` on the Machine, reported since its update) before the
40
+ * next is updated, within `checks.timeoutMs` (default five minutes). A Machine
41
+ * with no checks is done once started. Verify then also needs every Machine
42
+ * the release started to pass its checks, and a failure restores the Machines
43
+ * the release changed last changed first, each restore waiting for its
44
+ * Machine's checks. Without `checks`, only `started` is waited for.
34
45
  *
35
46
  * A scheduled Machine (`config.schedule`, #4696) is released without being
36
47
  * started, whether it is the one that serves or another Machine in the plan:
@@ -52,7 +63,7 @@
52
63
  * call and refuses a recorded config that does not carry exactly that tree.
53
64
  */
54
65
  import { createHash } from "node:crypto";
55
- import { findMachine, flyMachineExec, flyMachineRelease, flyMachineRestart, flyMachineRestore, flyMachineStop, flyMachineVerify, readMachineRelease, releaseTarget, } from "../op/activities/machine-release.js";
66
+ import { findMachine, flyMachineChecks, flyMachineExec, flyMachineRelease, flyMachineRestart, flyMachineRestore, flyMachineStop, flyMachineVerify, readMachineRelease, releaseTarget, MachineReleaseError, } from "../op/activities/machine-release.js";
56
67
  import { defaultFlyHttp, machineSchedule, parsePlan, resolveEndpoint, skipsLaunch } from "../op/activities/fly-apply.js";
57
68
  import { readFileSync } from "node:fs";
58
69
  import { releaseAttributesSuffix } from "@intentius/chant/telemetry-attribution";
@@ -139,17 +150,36 @@ const uriOf = (app, machine) => `fly://${app}/${machine}`;
139
150
  export function createFlyReleaseCapability(deps = {}) {
140
151
  const log = deps.log ?? ((line) => console.error(line));
141
152
  const httpFor = () => deps.http ?? defaultFlyHttp();
142
- /** On a failed release, put the plan's other Machines it updated back on their old configs. */
143
- async function restoreOthers(released, input, http) {
144
- for (const other of released.others) {
145
- if (other.action !== "updated" || !other.previous)
153
+ /**
154
+ * On a failed release, put each Machine it changed back. The serving
155
+ * Machine gets its old config, or is stopped when it served nothing before;
156
+ * another Machine it updated gets its old config.
157
+ *
158
+ * Without `checks` this is the order and the calls fly-release made before
159
+ * #4719: the serving Machine first, then the others in plan order. With
160
+ * `checks`, the last Machine changed goes back first, and each restore
161
+ * waits for its Machine's checks before the next.
162
+ */
163
+ async function restoreApplied(applied, input, http, rolling, servingWas) {
164
+ const order = rolling ? [...applied].reverse() : [...applied.filter((m) => m.serving), ...applied.filter((m) => !m.serving)];
165
+ for (const m of order) {
166
+ if (m.serving ? rolling && m.action === "noop" : m.action !== "updated" || !m.previous)
146
167
  continue;
168
+ const where = { app: m.app, machine: m.name, endpoint: input.endpoint, wait: input.wait };
147
169
  try {
148
- await flyMachineRestore({ app: other.app, machine: other.name, config: other.previous, endpoint: input.endpoint, wait: input.wait }, undefined, http);
149
- log(`fly-release: restored ${other.app}/${other.name} to its previous config`);
170
+ if (m.previous) {
171
+ await flyMachineRestore({ ...where, config: m.previous, ...(rolling ? { checks: rolling } : {}) }, undefined, http);
172
+ const was = m.serving ? (servingWas === undefined ? readMachineRelease(m.previous.metadata)?.digest : (servingWas ?? undefined)) : undefined;
173
+ log(`fly-release: restored ${m.app}/${m.name} to ${was ?? "its previous config"}`);
174
+ }
175
+ else {
176
+ await flyMachineStop(where, undefined, http);
177
+ log(`fly-release: nothing had been released on ${m.app}/${m.name}; stopped it`);
178
+ }
150
179
  }
151
180
  catch (restoreErr) {
152
- log(`fly-release: restore of ${other.app}/${other.name} failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
181
+ const why = restoreErr instanceof Error ? restoreErr.message : String(restoreErr);
182
+ log(m.serving && !rolling ? `fly-release: restore failed: ${why}` : `fly-release: restore of ${m.app}/${m.name} failed: ${why}`);
153
183
  }
154
184
  }
155
185
  }
@@ -203,17 +233,29 @@ export function createFlyReleaseCapability(deps = {}) {
203
233
  ...(attributes !== undefined ? { OTEL_RESOURCE_ATTRIBUTES: attributes } : {}),
204
234
  ...(input.env ?? {}),
205
235
  };
206
- const released = await flyMachineRelease({
207
- plan,
208
- machine: input.machine,
209
- release: { digest: input.digest, gitSha, ...(input.release ? { release: input.release } : {}) },
210
- image: input.image,
211
- ...(input.images ? { images: input.images } : {}),
212
- env,
213
- ...(source ? { files: source.files, cmd: source.cmd } : {}),
214
- endpoint: input.endpoint,
215
- wait: input.wait,
216
- }, undefined, http);
236
+ const rolling = input.checks === true ? {} : input.checks;
237
+ let released;
238
+ try {
239
+ released = await flyMachineRelease({
240
+ plan,
241
+ machine: input.machine,
242
+ release: { digest: input.digest, gitSha, ...(input.release ? { release: input.release } : {}) },
243
+ image: input.image,
244
+ ...(input.images ? { images: input.images } : {}),
245
+ env,
246
+ ...(source ? { files: source.files, cmd: source.cmd } : {}),
247
+ endpoint: input.endpoint,
248
+ wait: input.wait,
249
+ ...(rolling ? { checks: rolling } : {}),
250
+ }, undefined, http);
251
+ }
252
+ catch (err) {
253
+ // A Machine failed to settle or to pass its checks partway through the
254
+ // roll: put back every Machine changed so far, then fail.
255
+ if (err instanceof MachineReleaseError)
256
+ await restoreApplied(err.applied, input, http, rolling);
257
+ throw err;
258
+ }
217
259
  const where = { app: released.app, machine: released.machine.name, endpoint: input.endpoint };
218
260
  log(`fly-release: ${released.app}/${released.machine.name} ${released.action}, serving ${input.digest}${released.unlaunched ? ", stopped until its schedule starts it" : ""}`);
219
261
  for (const other of released.others) {
@@ -250,25 +292,31 @@ export function createFlyReleaseCapability(deps = {}) {
250
292
  if (migrations.some((m) => m.fired))
251
293
  await flyMachineRestart({ ...where, wait: input.wait }, undefined, http);
252
294
  }
253
- await flyMachineVerify({ ...where, digest: input.digest, ...(input.verify ?? {}) }, undefined, http, deps.fetch);
295
+ const serving = released.applied.find((m) => m.serving);
296
+ await flyMachineVerify({
297
+ ...where,
298
+ digest: input.digest,
299
+ ...(input.verify ?? {}),
300
+ ...(rolling ? { checks: true, ...(serving?.updatedAt ? { checksSince: serving.updatedAt } : {}) } : {}),
301
+ }, undefined, http, deps.fetch);
302
+ // With checks, every other Machine the release started serves too: started, with its checks passing.
303
+ for (const m of rolling ? released.applied : []) {
304
+ if (m.serving || m.action === "noop" || m.unlaunched)
305
+ continue;
306
+ await flyMachineChecks({
307
+ app: m.app,
308
+ machine: m.name,
309
+ endpoint: input.endpoint,
310
+ timeoutMs: input.verify?.timeoutMs ?? 120000,
311
+ intervalMs: input.verify?.intervalMs,
312
+ ...(m.updatedAt ? { since: m.updatedAt } : {}),
313
+ }, undefined, http);
314
+ }
254
315
  }
255
316
  catch (err) {
256
- // Restore: the Machine changed, so put back what it served (or stop it
257
- // when it served nothing), then fail the step.
258
- try {
259
- if (released.previous) {
260
- await flyMachineRestore({ ...where, config: released.previous.config, wait: input.wait }, undefined, http);
261
- log(`fly-release: restored ${released.app}/${released.machine.name} to ${previousRelease?.digest ?? "its previous config"}`);
262
- }
263
- else {
264
- await flyMachineStop({ ...where, wait: input.wait }, undefined, http);
265
- log(`fly-release: nothing had been released on ${released.app}/${released.machine.name}; stopped it`);
266
- }
267
- }
268
- catch (restoreErr) {
269
- log(`fly-release: restore failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
270
- }
271
- await restoreOthers(released, input, http);
317
+ // Restore: the Machines changed, so put back what each served (or stop
318
+ // the serving one when it served nothing), then fail the step.
319
+ await restoreApplied(released.applied, input, http, rolling, previousRelease?.digest ?? null);
272
320
  throw err;
273
321
  }
274
322
  await configs.write({ app: released.app, machine: released.machine.name, digest: input.digest }, released.config);
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AAKtG,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAqBL,KAAK,OAAO,EAIb,MAAM,2BAA2B,CAAC;AAcnC,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA2BD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5E,MAAM,CA8BR;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,IAAI,GAAE,OAA0B,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CA2S5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,wBAAwB,GAAG;IAClC,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACjD,EACD,IAAI,GAAE,OAA0B,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCrD"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AAKtG,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAuBL,KAAK,OAAO,EAIb,MAAM,2BAA2B,CAAC;AAcnC,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA2BD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5E,MAAM,CA8BR;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,IAAI,GAAE,OAA0B,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CA2S5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,wBAAwB,GAAG;IAClC,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACjD,EACD,IAAI,GAAE,OAA0B,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCrD"}
@@ -31,7 +31,7 @@ import { readOwnership } from "@intentius/chant/ownership";
31
31
  import { FLY_METADATA_OWNERSHIP_KEYS } from "./ownership.js";
32
32
  import { readMachineRelease } from "./release-metadata.js";
33
33
  import { buildChangeSet, renderChangeSet, } from "@intentius/chant/lifecycle/change-set";
34
- import { resolveEndpoint, defaultFlyHttp, parsePlan, isAppRequest, isMachineRequest, isVolumeRequest, isIpRequest, isCertRequest, appNameFromRequest, machineAppSegment, resourceAppSegment, resolveApp, isChantOwned, configEqual, listMachines, listVolumes, listIps, listCerts, ipType, declaredIpType, } from "./op/activities/fly-apply.js";
34
+ import { resolveEndpoint, defaultFlyHttp, parsePlan, isAppRequest, isMachineRequest, isVolumeRequest, isIpRequest, isCertRequest, appNameFromRequest, machineAppSegment, resourceAppSegment, resolveApp, isChantOwned, configEqual, alignMountsWithLive, volumeAliases, listMachines, listVolumes, listIps, listCerts, ipType, declaredIpType, } from "./op/activities/fly-apply.js";
35
35
  /** The chant entity types the fly serializer emits, for the metadata `type` field. */
36
36
  const TYPE = {
37
37
  app: "Fly::Machines::App",
@@ -419,8 +419,11 @@ export async function flyPlan(options, http = defaultFlyHttp(), signal) {
419
419
  });
420
420
  // Declared config per entity, for the machine update/noop refinement.
421
421
  const declaredConfig = new Map();
422
+ let aliases = new Map();
422
423
  try {
423
- for (const [entityName, req] of Object.entries(parsePlan(options.buildOutput))) {
424
+ const plan = parsePlan(options.buildOutput);
425
+ aliases = volumeAliases(plan);
426
+ for (const [entityName, req] of Object.entries(plan)) {
424
427
  if (isMachineRequest(req))
425
428
  declaredConfig.set(entityName, req.body.config);
426
429
  }
@@ -435,7 +438,9 @@ export async function flyPlan(options, http = defaultFlyHttp(), signal) {
435
438
  if (declaredCfg === undefined)
436
439
  continue; // not a declared machine
437
440
  const liveCfg = observedNow[entry.name]?.attributes?.config;
438
- if (!configEqual(declaredCfg, liveCfg)) {
441
+ // A declared mount names its volume; the live one has the id flyApply
442
+ // looked up, and the name beside it (#4717).
443
+ if (!configEqual(alignMountsWithLive(declaredCfg, liveCfg, aliases), liveCfg)) {
439
444
  entry.action = "update";
440
445
  }
441
446
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "11ee92fa970ded6aee7906a391726e01f76176ddd440bc7e32452b3cffea86d8",
4
+ "manifest.json": "30ca5e8ced397902a71529dc62b4072fc6c30488b1353ee27901ebbf4dc3174d",
5
5
  "meta.json": "273f2a523b66f08ca91642dba2592c44765ab59ddf7329998884a0b08c6c3f24",
6
6
  "types/index.d.ts": "75c29d3ba9faa7c2792a0a25c51dc13df011906ffbd4c9e3ccc80023f5f32667",
7
7
  "rules/guest-sizing.ts": "8dd2db79a47ceab3d488f84f560aed1b1e4a66b4e36bf11f3baf6a8544d6e126",
@@ -9,12 +9,12 @@
9
9
  "rules/valid-region.ts": "238afb252ffb59ef131d070a705a49a1ac87b98f6a86c029a69dc82200e1aabf",
10
10
  "rules/fly-helpers.ts": "dd29d4b520727210a56ef604a89df380f91f8b35770bc28f5c51d0f99ca92dff",
11
11
  "rules/fly010-machine-requires-image.ts": "dd9b70ee604867a9f02334013476afcf44a78b49bbba3d66296b8ccc2b02c8a0",
12
- "rules/fly011-mount-references-declared-volume.ts": "4698f73b85954a4094a1445d42336698440bf99a4004c067529c5f9fa1047cc6",
12
+ "rules/fly011-mount-references-declared-volume.ts": "b54c4bb4d29cdd13ca3ff5e9e6e9b2870703ccbb23856c2be8f6983f6833b61e",
13
13
  "rules/fly012-machine-schedule.ts": "f94c75d64dd3888797c89da4e5dd11d95c47d45ff19a69a725cf098f705003b1",
14
- "skills/chant-fly.md": "7346fd12cffcb4ce39feb0655550f47e330bf0cafc679dee1b08e4db5140969a",
15
- "skills/chant-fly-patterns.md": "adc3c4432344c833e23d41ab470b7e12eb8d55e512cf856ac3ddda5b199e2a90",
16
- "skills/chant-fly-ops.md": "e2ec53f55d89ffaace9183c348d80a6c2f13da3ef3ac3987a0e4f17b62264b20",
14
+ "skills/chant-fly.md": "a81a2f60b593f311b6553a1b4aa2578ac843f1539f9496ba58a8309ed0357196",
15
+ "skills/chant-fly-patterns.md": "25156b507116c988a304f77900f3c6afabf24dce45b7a9d121a8d23950d7e6cc",
16
+ "skills/chant-fly-ops.md": "cf25cf3ef9fb68fdb320a35c411d893d57bbf0ad4216f571aa9ce42eaebacd24",
17
17
  "skills/chant-fly-sprites.md": "3d100680422151b58d082c3a7b30a286edfc52faa4887995c9ad5553873bab95"
18
18
  },
19
- "composite": "220b6c76a5ff5eee5413ed9f1c43412486cddb661ceb76842b9c5628f89a51f4"
19
+ "composite": "5ab19c38c1cbb14e7bae8d7f86eb9ee04eceb9b54b5ea2c16cbc09d1f00075d7"
20
20
  }
@@ -22,7 +22,7 @@ function rule(id, tier, category, title, remediation) {
22
22
  }
23
23
  export const flyAuditCatalog = {
24
24
  FLY010: rule("FLY010", "merge-worthy", "correctness", "Machine config has no image", "Set `config.image` on the Machine — a Machine cannot boot without one."),
25
- FLY011: rule("FLY011", "merge-worthy", "correctness", "Machine mount references an undeclared volume", "Declare the Volume in the same stack, or point the mount at one that is."),
25
+ FLY011: rule("FLY011", "merge-worthy", "correctness", "Machine mount references an undeclared volume", "Declare the Volume in the same stack, point the mount at one that is, or mount an existing volume by its id (vol_...)."),
26
26
  FLY012: rule("FLY012", "merge-worthy", "correctness", "Machine schedule is not one Fly runs", "Set `config.schedule` to `hourly`, `daily`, `weekly` or `monthly`; the Machines API takes no cron expression."),
27
27
  };
28
28
  // Prior art credits live beside the rules in ./audit-lineage.ts (see core audit/prior-art.ts).
@@ -6,6 +6,10 @@
6
6
  * apply time, so catch it at synth. This is a whole-stack check: it reads all
7
7
  * entities in the build (`ctx.entities`), so the mount and the Volume can be
8
8
  * declared in different files.
9
+ *
10
+ * A mount given a volume id (`vol_...`) names a volume that already exists on
11
+ * Fly, which the stack need not declare, so it passes (#4717). `flyApply`
12
+ * sends it as it is, and looks a name up to send its id.
9
13
  */
10
14
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
11
15
  export declare const fly011: PostSynthCheck;
@@ -1 +1 @@
1
- {"version":3,"file":"fly011-mount-references-declared-volume.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/fly011-mount-references-declared-volume.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAM9G,eAAO,MAAM,MAAM,EAAE,cA4CpB,CAAC"}
1
+ {"version":3,"file":"fly011-mount-references-declared-volume.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/fly011-mount-references-declared-volume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAO9G,eAAO,MAAM,MAAM,EAAE,cA8CpB,CAAC"}
@@ -6,13 +6,18 @@
6
6
  * apply time, so catch it at synth. This is a whole-stack check: it reads all
7
7
  * entities in the build (`ctx.entities`), so the mount and the Volume can be
8
8
  * declared in different files.
9
+ *
10
+ * A mount given a volume id (`vol_...`) names a volume that already exists on
11
+ * Fly, which the stack need not declare, so it passes (#4717). `flyApply`
12
+ * sends it as it is, and looks a name up to send its id.
9
13
  */
10
14
  import { readProps, entityTypeOf } from "./fly-helpers.js";
15
+ import { isVolumeId } from "../../volume-id.js";
11
16
  const MACHINE_ENTITY_TYPE = "Fly::Machines::Machine";
12
17
  const VOLUME_ENTITY_TYPE = "Fly::Machines::Volume";
13
18
  export const fly011 = {
14
19
  id: "FLY011",
15
- description: "A machine mount's volume must resolve to a Volume declared in the stack",
20
+ description: "A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)",
16
21
  check(ctx) {
17
22
  const diagnostics = [];
18
23
  // A mount can name a declared volume by the Volume's `name` or by its
@@ -42,11 +47,14 @@ export const fly011 = {
42
47
  // never dangles — skip it to avoid false positives.
43
48
  if (typeof volume !== "string" || volume.length === 0)
44
49
  continue;
50
+ // A volume id names a volume that exists outside the stack.
51
+ if (isVolumeId(volume))
52
+ continue;
45
53
  if (!declaredVolumes.has(volume)) {
46
54
  diagnostics.push({
47
55
  checkId: "FLY011",
48
56
  severity: "error",
49
- message: `Machine "${machineName}" mounts volume "${volume}", which is not declared as a Volume in the stack.`,
57
+ message: `Machine "${machineName}" mounts volume "${volume}", which is not declared as a Volume in the stack. Declare the Volume, or mount an existing volume by its id (vol_...).`,
50
58
  entity: machineName,
51
59
  lexicon: "fly",
52
60
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fly",
3
- "version": "0.124.67",
3
+ "version": "0.124.69",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Fly"
6
6
  }
package/dist/okf/index.md CHANGED
@@ -17,5 +17,5 @@ okf_version: '0.2'
17
17
  * [FLY002](/rules/FLY002.md) - Sane guest sizing
18
18
  * [FLY004](/rules/FLY004.md) - No secret literals in machine config
19
19
  * [FLY010](/rules/FLY010.md) - Machine config must specify an image — a Machine cannot boot without one
20
- * [FLY011](/rules/FLY011.md) - A machine mount's volume must resolve to a Volume declared in the stack
20
+ * [FLY011](/rules/FLY011.md) - A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)
21
21
  * [FLY012](/rules/FLY012.md) - Machine schedule must be hourly, daily, weekly or monthly
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  type: post-synth-check
3
3
  title: FLY011
4
- description: A machine mount's volume must resolve to a Volume declared in the stack
4
+ description: A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)
5
5
  id: FLY011
6
6
  severity: error
7
7
  category: post-synth
8
8
  lexicon: fly
9
9
  docs: https://intentius.io/chant/lexicons/fly/rules/
10
10
  ---
11
- A machine mount's volume must resolve to a Volume declared in the stack
11
+ A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)
12
12
 
13
13
  ## Applies to
14
14
 
@@ -22,5 +22,5 @@ resource_type: Fly::Machines::Machine
22
22
  ## Governed by
23
23
 
24
24
  - [FLY010](/rules/FLY010.md): Machine config must specify an image — a Machine cannot boot without one
25
- - [FLY011](/rules/FLY011.md): A machine mount's volume must resolve to a Volume declared in the stack
25
+ - [FLY011](/rules/FLY011.md): A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)
26
26
  - [FLY012](/rules/FLY012.md): Machine schedule must be hourly, daily, weekly or monthly
@@ -26,4 +26,4 @@ resource_type: Fly::Machines::Volume
26
26
 
27
27
  ## Governed by
28
28
 
29
- - [FLY011](/rules/FLY011.md): A machine mount's volume must resolve to a Volume declared in the stack
29
+ - [FLY011](/rules/FLY011.md): A machine mount's volume must resolve to a Volume declared in the stack, or be a volume id (vol_...)
@@ -21,6 +21,7 @@
21
21
  * `FLY_FLAPS_BASE_URL` (or an explicit `endpoint` arg), so the same code points
22
22
  * at real Fly or at mudflaps (`:4280`).
23
23
  */
24
+ export { isVolumeId } from "../../volume-id.js";
24
25
  import { type ApplyResult } from "@intentius/chant/apply";
25
26
  /** Default flaps host when neither an `endpoint` arg nor `FLY_FLAPS_BASE_URL` is set. */
26
27
  export declare const DEFAULT_FLAPS_BASE_URL = "https://api.machines.dev";
@@ -52,9 +53,34 @@ export interface FlapsMachine {
52
53
  name: string;
53
54
  state: string;
54
55
  instance_id: string;
56
+ region?: string;
55
57
  config?: {
56
58
  metadata?: Record<string, string>;
57
59
  } & Record<string, unknown>;
60
+ /** When flaps last changed the Machine, by its own clock (RFC 3339). */
61
+ updated_at?: string;
62
+ /** The status of each of the Machine's Fly checks, as the Machines API reports them (#4719). */
63
+ checks?: FlapsCheckStatus[];
64
+ }
65
+ /** One of a Machine's Fly checks as the Machines API reports it: `passing`, `warning` or `critical`. */
66
+ export interface FlapsCheckStatus {
67
+ name: string;
68
+ status: string;
69
+ output?: string;
70
+ updated_at?: string;
71
+ }
72
+ /** One Machine {@link flyApply} has just created, updated or left alone. */
73
+ export interface AppliedMachine {
74
+ app: string;
75
+ /** The plan entity that declares it. */
76
+ entity: string;
77
+ name: string;
78
+ id: string;
79
+ action: "created" | "updated" | "noop";
80
+ /** True when it was left unstarted (a scheduled Machine, #4696). */
81
+ unlaunched: boolean;
82
+ /** The Machine's `updated_at` as the create or update call answered it, when it changed and flaps gave one. */
83
+ updatedAt?: string;
58
84
  }
59
85
  /**
60
86
  * Resolve the flaps base URL (D3): an explicit `endpoint` arg wins, then the
@@ -214,11 +240,18 @@ export declare function listMachines(ctx: ApplyCtx, app: string, http: FlyHttp,
214
240
  *
215
241
  * A Machine that {@link skipsLaunch} (a scheduled one, #4696) is created and
216
242
  * updated with `skip_launch: true`, and the wait is for it to rest unstarted.
243
+ *
244
+ * A mount that names a volume is sent with the volume's id (#4717), looked up
245
+ * among the app's live volumes ({@link pickVolume}); the comparison with the
246
+ * live config is made after the lookup, so an unchanged Machine stays a no-op.
217
247
  */
218
- export declare function applyMachine(ctx: ApplyCtx, app: string, entityName: string, req: FlapsRequest, http: FlyHttp, signal?: AbortSignal, opts?: WaitOpts): Promise<{
248
+ export declare function applyMachine(ctx: ApplyCtx, app: string, entityName: string, req: FlapsRequest, http: FlyHttp, signal?: AbortSignal, opts?: WaitOpts & {
249
+ volumeAliases?: ReadonlyMap<string, string>;
250
+ }): Promise<{
219
251
  action: "created" | "updated" | "noop";
220
252
  id: string;
221
253
  name: string;
254
+ updatedAt?: string;
222
255
  }>;
223
256
  /** How {@link destroyMachine} treats a machine that is still running. */
224
257
  export interface DestroyOpts extends WaitOpts {
@@ -266,6 +299,9 @@ export interface FlapsVolume {
266
299
  id: string;
267
300
  name: string;
268
301
  state?: string;
302
+ region?: string;
303
+ /** The Machine the volume is attached to; empty or absent when it is free. */
304
+ attached_machine_id?: string | null;
269
305
  }
270
306
  /** A live IP assignment as flaps lists it. */
271
307
  export interface FlapsIp {
@@ -291,6 +327,53 @@ export declare function ipType(shared: boolean | undefined, address: string): st
291
327
  export declare function declaredIpType(type: unknown): string;
292
328
  /** List an app's live volumes (flaps returns a bare array). */
293
329
  export declare function listVolumes(ctx: ApplyCtx, app: string, http: FlyHttp, signal?: AbortSignal): Promise<FlapsVolume[]>;
330
+ /** The Machine a mount is resolved for: its id once it exists, and its region. */
331
+ export interface MountTarget {
332
+ machineId?: string;
333
+ region?: string;
334
+ }
335
+ /**
336
+ * Plan entity names that name a volume by its declaration, mapped to the
337
+ * volume's Fly name. A mount written as a `Volume` reference serializes to the
338
+ * declaration's name, which can differ from the volume's `name`. An entity
339
+ * name that is also some declared volume's `name` is left out, so a mount
340
+ * naming it means that volume. Pure.
341
+ */
342
+ export declare function volumeAliases(plan: FlyPlan): Map<string, string>;
343
+ /** True when a config has a mount that names its volume rather than giving its id. Pure. */
344
+ export declare function mountsNeedResolving(config: unknown): boolean;
345
+ /**
346
+ * The volume a mount naming `name` means, for the Machine `target`. Fly lets
347
+ * several volumes share a name (one per Machine, often across regions), so
348
+ * the pick is: the volume of that name already attached to this Machine, else
349
+ * the one in the Machine's region that is attached to no Machine. A volume
350
+ * whose region or attachment flaps doesn't report counts as matching. No
351
+ * volume of that name, none that fits, or more than one that fits, is refused
352
+ * naming the candidates. Pure.
353
+ */
354
+ export declare function pickVolume(app: string, name: string, volumes: FlapsVolume[], target: MountTarget): FlapsVolume;
355
+ /**
356
+ * A Machine config with each mount that names a volume given that volume's id
357
+ * in `volume` and its name in `name` (a `name` the mount already has is
358
+ * kept). A mount with an id goes through as it is. `aliases` maps a plan's
359
+ * volume declaration names to volume names ({@link volumeAliases}). Returns
360
+ * `config` itself when no mount names a volume. Pure.
361
+ */
362
+ export declare function resolveMountsWith<T>(app: string, config: T, volumes: FlapsVolume[], target: MountTarget, aliases?: ReadonlyMap<string, string>): T;
363
+ /**
364
+ * {@link resolveMountsWith} over the app's live volumes, listed only when a
365
+ * mount names a volume. Volumes are listed per call, so a volume an earlier
366
+ * Machine in the same apply took is seen as attached.
367
+ */
368
+ export declare function resolveMounts<T>(ctx: ApplyCtx, app: string, config: T, target: MountTarget, http: FlyHttp, signal?: AbortSignal, aliases?: ReadonlyMap<string, string>): Promise<T>;
369
+ /**
370
+ * A declared config with each mount that names a volume given the id the live
371
+ * config mounts at the same path, when the live mount carries that name. A
372
+ * read-only plan uses it to compare a declared config with a live one the way
373
+ * {@link applyMachine} will after it resolves the names, without listing
374
+ * volumes. Pure.
375
+ */
376
+ export declare function alignMountsWithLive<T>(declared: T, live: unknown, aliases?: ReadonlyMap<string, string>): T;
294
377
  /** List an app's live IP assignments (`{ ips: [...] }`). */
295
378
  export declare function listIps(ctx: ApplyCtx, app: string, http: FlyHttp, signal?: AbortSignal): Promise<FlapsIp[]>;
296
379
  /**
@@ -411,6 +494,13 @@ export interface FlyApplyArgs {
411
494
  force?: boolean;
412
495
  /** Wait-loop tuning (mainly for tests). */
413
496
  wait?: WaitOpts;
497
+ /**
498
+ * Called after each Machine is applied and settled, before the next one
499
+ * (#4719). `fly-release` waits here for the Machine's Fly checks, so a
500
+ * group of serving Machines rolls one at a time. A throw stops the apply
501
+ * there and fails it. For callers in code only; without it nothing changes.
502
+ */
503
+ afterMachine?: (machine: AppliedMachine) => Promise<void>;
414
504
  }
415
505
  export declare function toApplyResult(result: {
416
506
  apps: Array<{