@molecule/api-code-sandbox-flyio 1.3.6 → 1.3.9

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.
package/dist/api.d.ts CHANGED
@@ -43,13 +43,15 @@ export declare class FlyApiError extends Error {
43
43
  });
44
44
  }
45
45
  /** Options for a single Fly API request. */
46
- export interface FlyRequestOptions {
46
+ export interface FlyRequestOptions<T = unknown> {
47
47
  /** HTTP method. Defaults to `GET`. */
48
48
  method?: string;
49
49
  /** JSON request body. Omitted entirely when undefined. */
50
50
  body?: unknown;
51
51
  /** Per-request timeout in ms. Defaults to the client's configured timeout. */
52
52
  timeoutMs?: number;
53
+ /** Cancels in-flight provisioning when the owning lifecycle lock is lost. */
54
+ signal?: AbortSignal;
53
55
  /** Max attempts for a retryable failure. Defaults to 4. Pass 1 to disable retries. */
54
56
  attempts?: number;
55
57
  /**
@@ -64,6 +66,19 @@ export interface FlyRequestOptions {
64
66
  * under exec-burst load, not a real absence, so it is worth repeating.
65
67
  */
66
68
  retryStatuses?: number[];
69
+ /**
70
+ * Restricts retries opted into by `retryStatuses` using the response error.
71
+ * Return false to surface that response immediately. The client's standard
72
+ * transport, 429 and 5xx retries are unaffected.
73
+ */
74
+ retryWhen?: (error: FlyApiError) => boolean;
75
+ /**
76
+ * Reconciles a retryable failure before the client backs off or throws.
77
+ * Returning a value completes the request; `undefined` keeps the normal
78
+ * retry/exhaustion path. This lets an ambiguous write look for the resource
79
+ * it may have created without adding a second POST retry loop.
80
+ */
81
+ reconcile?: (error: FlyApiError) => Promise<T | undefined>;
67
82
  }
68
83
  /**
69
84
  * Decides whether a failed attempt is worth repeating.
@@ -147,6 +162,6 @@ export declare class FlyApiClient {
147
162
  * @throws {FlyApiError} On a non-2xx status that is not listed in `nullOn`,
148
163
  * or when every retryable attempt is exhausted.
149
164
  */
150
- request<T>(path: string, options?: FlyRequestOptions): Promise<T | null>;
165
+ request<T>(path: string, options?: FlyRequestOptions<T>): Promise<T | null>;
151
166
  }
152
167
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgBH;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;;;;;OAOG;gBAED,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAgBhC;AAED,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAQhF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAoBjE;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,KAAK,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAC/B,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAgDD,kFAAkF;AAClF,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7C;;;OAGG;gBACS,OAAO,EAAE,mBAAmB;IASxC;;;;;;;;;;OAUG;IACG,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CAiFnF"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -216,9 +216,15 @@ export class FlyApiClient {
216
216
  }
217
217
  let lastError;
218
218
  for (let attempt = 1; attempt <= attempts; attempt++) {
219
+ if (options.signal?.aborted)
220
+ throw options.signal.reason;
219
221
  // Pace EVERY network call (including retries) through the process-wide
220
222
  // limiter so a boot's burst of exec calls never trips Fly's account 429.
221
223
  await paceFlyRequest(this.sleep);
224
+ // Pacing is cancellable too: ownership protection starts only when this
225
+ // request really enters fetch, never while it is still waiting for a slot.
226
+ if (options.signal?.aborted)
227
+ throw options.signal.reason;
222
228
  const controller = new AbortController();
223
229
  const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? this.timeoutMs);
224
230
  // Initialized to 0 ("no answer received"), which is the value that
@@ -226,18 +232,23 @@ export class FlyApiClient {
226
232
  let status = 0;
227
233
  let retryAfter = null;
228
234
  try {
229
- const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
235
+ const pendingResponse = this.fetchImpl(`${this.baseUrl}${path}`, {
230
236
  method,
231
237
  headers: {
232
238
  Authorization: `Bearer ${token}`,
233
239
  ...(options.body === undefined ? {} : { 'Content-Type': 'application/json' }),
234
240
  },
235
241
  ...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
236
- signal: controller.signal,
242
+ signal: options.signal
243
+ ? AbortSignal.any([controller.signal, options.signal])
244
+ : controller.signal,
237
245
  });
246
+ const response = await pendingResponse;
238
247
  status = response.status;
239
248
  retryAfter = response.headers?.get?.('retry-after') ?? null;
240
249
  const text = await response.text();
250
+ if (options.signal?.aborted)
251
+ throw options.signal.reason;
241
252
  if (status >= 200 && status < 300) {
242
253
  if (!text.trim())
243
254
  return null;
@@ -256,6 +267,8 @@ export class FlyApiClient {
256
267
  lastError = new FlyApiError(method, path, status, text.slice(0, 500));
257
268
  }
258
269
  catch (error) {
270
+ if (options.signal?.aborted)
271
+ throw options.signal.reason;
259
272
  if (error instanceof FlyApiError)
260
273
  throw error;
261
274
  // Transport-level failure (abort/timeout, DNS, connection reset) — no
@@ -265,7 +278,15 @@ export class FlyApiClient {
265
278
  finally {
266
279
  clearTimeout(timer);
267
280
  }
268
- const retryable = isRetryableStatus(status) || (options.retryStatuses?.includes(status) ?? false);
281
+ const extraRetry = Boolean(options.retryStatuses?.includes(status) &&
282
+ lastError &&
283
+ (options.retryWhen?.(lastError) ?? true));
284
+ const retryable = isRetryableStatus(status) || extraRetry;
285
+ if (retryable && lastError && options.reconcile) {
286
+ const recovered = await options.reconcile(lastError);
287
+ if (recovered !== undefined)
288
+ return recovered;
289
+ }
269
290
  if (!retryable || attempt === attempts)
270
291
  break;
271
292
  const delay = retryDelayMs(attempt, retryAfter);
package/dist/ids.d.ts CHANGED
@@ -31,18 +31,17 @@ export declare function parseSandboxId(id: string): {
31
31
  app: string;
32
32
  machineId: string;
33
33
  };
34
+ /** The one namespace mapping shared by app naming and fleet discovery. */
35
+ export declare function appNameNamespace(prefix: string): {
36
+ prefix: string;
37
+ hashedPrefix: string;
38
+ searchPrefixes: string[];
39
+ };
34
40
  /**
35
- * Derives a Fly app name for a project.
36
- *
37
- * Fly app names are globally unique DNS labels: lowercase alphanumerics and
38
- * hyphens, no leading/trailing hyphen, at most 63 characters (they become
39
- * `<app>.fly.dev`). Project ids are uuids, so `<prefix>-<uuid>` fits with room
40
- * to spare; longer ids are truncated rather than rejected, and the trailing
41
- * hyphen that truncation can leave is stripped.
41
+ * Derive the collision-free app name for a complete prefix/project identity.
42
42
  * @param prefix - Configured app-name prefix.
43
- * @param projectId - The project id from `SandboxConfig`.
44
- * @returns A syntactically valid Fly app name.
45
- * @throws {Error} When `projectId` has no characters usable in a DNS label.
43
+ * @param projectId - Complete project or deploy identity.
44
+ * @returns A DNS-safe app name no longer than 63 characters.
46
45
  */
47
46
  export declare function appNameForProject(prefix: string, projectId: string): string;
48
47
  /**
package/dist/ids.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAKjD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAgB7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAqB3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,GACrB,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAiBjD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAE7D"}
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAKjD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAgB7E;AAWD,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAUA;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAmB3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,GACrB,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAiBjD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAE7D"}
package/dist/ids.js CHANGED
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * @module
13
13
  */
14
+ import { createHash } from 'node:crypto';
14
15
  import { t } from '@molecule/api-i18n';
15
16
  /** Separator between the app name and the Machine id in a composite sandbox id. */
16
17
  const ID_SEPARATOR = ':';
@@ -41,33 +42,46 @@ export function parseSandboxId(id) {
41
42
  }
42
43
  return { app: id.slice(0, index), machineId: id.slice(index + 1) };
43
44
  }
44
- /**
45
- * Derives a Fly app name for a project.
46
- *
47
- * Fly app names are globally unique DNS labels: lowercase alphanumerics and
48
- * hyphens, no leading/trailing hyphen, at most 63 characters (they become
49
- * `<app>.fly.dev`). Project ids are uuids, so `<prefix>-<uuid>` fits with room
50
- * to spare; longer ids are truncated rather than rejected, and the trailing
51
- * hyphen that truncation can leave is stripped.
52
- * @param prefix - Configured app-name prefix.
53
- * @param projectId - The project id from `SandboxConfig`.
54
- * @returns A syntactically valid Fly app name.
55
- * @throws {Error} When `projectId` has no characters usable in a DNS label.
56
- */
57
- export function appNameForProject(prefix, projectId) {
58
- const sanitize = (value) => value
45
+ /** Normalize one prefix/project component for a Fly DNS label. */
46
+ function sanitizeAppNamePart(value) {
47
+ return value
59
48
  .toLowerCase()
60
49
  .replace(/[^a-z0-9-]+/g, '-')
61
50
  .replace(/-+/g, '-')
62
51
  .replace(/^-|-$/g, '');
63
- const safePrefix = sanitize(prefix) || 'mol-sandbox';
64
- const safeProject = sanitize(projectId);
52
+ }
53
+ /** The one namespace mapping shared by app naming and fleet discovery. */
54
+ export function appNameNamespace(prefix) {
55
+ const safePrefix = sanitizeAppNamePart(prefix) || 'mol-sandbox';
56
+ // appNameForProject uses a 20-character digest, leaving 42 characters plus
57
+ // the separating hyphen inside Fly's 63-character DNS-label limit.
58
+ const hashedPrefix = safePrefix.slice(0, 42).replace(/-+$/, '');
59
+ return {
60
+ prefix: safePrefix,
61
+ hashedPrefix,
62
+ searchPrefixes: [...new Set([`${safePrefix}-`, `${hashedPrefix}-`])],
63
+ };
64
+ }
65
+ /**
66
+ * Derive the collision-free app name for a complete prefix/project identity.
67
+ * @param prefix - Configured app-name prefix.
68
+ * @param projectId - Complete project or deploy identity.
69
+ * @returns A DNS-safe app name no longer than 63 characters.
70
+ */
71
+ export function appNameForProject(prefix, projectId) {
72
+ const namespace = appNameNamespace(prefix);
73
+ const safePrefix = namespace.prefix;
74
+ const safeProject = sanitizeAppNamePart(projectId);
65
75
  if (!safeProject) {
66
76
  throw new Error(t('codeSandbox.flyio.error.badProjectId', { projectId }, {
67
77
  defaultValue: `Cannot derive a Fly app name from projectId "${projectId}" — no DNS-safe characters.`,
68
78
  }));
69
79
  }
70
- return `${safePrefix}-${safeProject}`.slice(0, 63).replace(/-+$/, '');
80
+ const readable = `${safePrefix}-${safeProject}`;
81
+ if (readable.length <= 63)
82
+ return readable;
83
+ const digest = createHash('sha256').update(`${prefix}\0${projectId}`).digest('hex').slice(0, 20);
84
+ return `${namespace.hashedPrefix}-${digest}`;
71
85
  }
72
86
  /**
73
87
  * Maps a Fly Machine state onto the core `Sandbox['status']` union.
package/dist/index.d.ts CHANGED
@@ -7,6 +7,10 @@
7
7
  * memory to disk, and a suspended Machine bills for storage only. Waking is a
8
8
  * resume from that snapshot rather than a cold boot.
9
9
  *
10
+ * `FLY_REGION` is the API Machine's own location and is IGNORED for tenant
11
+ * placement. Set the provider's `region` default, or pass one create's
12
+ * `SandboxConfig.region` for a project whose placement was recorded at creation.
13
+ *
10
14
  * @example
11
15
  * ```typescript
12
16
  * import { bond } from '@molecule/api-bond'
@@ -86,6 +90,21 @@
86
90
  * setuid/setgid dropped by `--no-same-permissions` — were verified by running
87
91
  * GNU tar 1.35 against crafted archives rather than inferred from its manual.
88
92
  *
93
+ * **Lifecycle ownership.** Create NEVER deletes a Machine, volume, app or
94
+ * address. Configure `createAttemptJournal` to record every intended resource
95
+ * before its Fly write. Workspace ownership is durable immediately: an
96
+ * ambiguous create retains it for a later create or an explicit sweep, never
97
+ * infers deletion from a missing listing or an uncertain dispatch. The
98
+ * reconciler settles only retired attempt generations and skips serving
99
+ * Machines, attached volumes and bound workspaces. The one automatic exception
100
+ * is a durably recorded, definitive Machine-create rejection: under the same
101
+ * lifecycle lock, a fresh listing must show no Machine reference (and no
102
+ * Machines or untracked volumes in a per-project app) before the fenced sweep
103
+ * deletes the failed attempt's volume. The empty app remains KEEP until
104
+ * deliberate retirement through `destroyApp()`. Ambiguous outcomes and
105
+ * referenced resources stay KEPT and are reported. Deployed callers must
106
+ * serialize lifecycle operations per app through their lifecycle-lock adapter.
107
+ *
89
108
  * **Absence vs failure: `get()` only answers `null` for a REAL absence.**
90
109
  * `get()` (and each read shaped like it — `list()`, `find()`, `getTemplate()`)
91
110
  * reserves `null`/`[]` for a positive not-found answer — Fly answered 404 for
@@ -229,10 +248,19 @@
229
248
  * needs an app, a region and a size, and a volume created in the wrong app can
230
249
  * never be mounted. So passing `SandboxConfig.volumeName` provisions the volume
231
250
  * inside the project's app, mounted at `/workspace`; `resources.diskMB` is
232
- * rounded UP to whole GB. Fly documents that "a volume can be attached to only
233
- * one Machine", which matches one sandbox per project. Fly does not document the
234
- * legal character set for volume names, so names are conservatively reduced to
235
- * `[A-Za-z0-9_]` — an assumption, flagged as such.
251
+ * rounded UP to whole GB. Creation sends the Machine's exact guest as Fly's
252
+ * `compute` placement hint, so Fly can choose a volume host that can run that
253
+ * Machine. If Machine creation still answers `412 volume_placement_capacity`,
254
+ * the provider preserves the volume and propagates the error for the caller's
255
+ * bounded retry, which adopts it by its durable Fly ID. Create itself never
256
+ * deletes its app or volume; only explicit retirement or the reference-checked
257
+ * definitive-failure sweep above can do so. Volume creates retain transport and
258
+ * 429 backoff. After an ambiguous failure, bounded lookups by name adopt a
259
+ * volume whose response was lost before any POST replay, preventing duplicates.
260
+ * Fly documents that "a volume can be attached to only one Machine", which
261
+ * matches one sandbox per project. Fly does
262
+ * not document the legal character set for volume names, so names are
263
+ * conservatively reduced to `[A-Za-z0-9_]` — an assumption, flagged as such.
236
264
  *
237
265
  * **Warm start: templates are tar archives in object storage.** Fly cannot
238
266
  * `commit` a running Machine — it PULLS images from a registry — and the
@@ -360,6 +388,7 @@ export * from './egress.js';
360
388
  export * from './exec.js';
361
389
  export * from './flycast.js';
362
390
  export * from './ids.js';
391
+ export * from './lifecycle.js';
363
392
  export * from './provider.js';
364
393
  export * from './storage.js';
365
394
  export * from './templates.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmWG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAE9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAMjE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,eAUrB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+XG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAE9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAMjE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,eAUrB,CAAA"}
package/dist/index.js CHANGED
@@ -7,6 +7,10 @@
7
7
  * memory to disk, and a suspended Machine bills for storage only. Waking is a
8
8
  * resume from that snapshot rather than a cold boot.
9
9
  *
10
+ * `FLY_REGION` is the API Machine's own location and is IGNORED for tenant
11
+ * placement. Set the provider's `region` default, or pass one create's
12
+ * `SandboxConfig.region` for a project whose placement was recorded at creation.
13
+ *
10
14
  * @example
11
15
  * ```typescript
12
16
  * import { bond } from '@molecule/api-bond'
@@ -86,6 +90,21 @@
86
90
  * setuid/setgid dropped by `--no-same-permissions` — were verified by running
87
91
  * GNU tar 1.35 against crafted archives rather than inferred from its manual.
88
92
  *
93
+ * **Lifecycle ownership.** Create NEVER deletes a Machine, volume, app or
94
+ * address. Configure `createAttemptJournal` to record every intended resource
95
+ * before its Fly write. Workspace ownership is durable immediately: an
96
+ * ambiguous create retains it for a later create or an explicit sweep, never
97
+ * infers deletion from a missing listing or an uncertain dispatch. The
98
+ * reconciler settles only retired attempt generations and skips serving
99
+ * Machines, attached volumes and bound workspaces. The one automatic exception
100
+ * is a durably recorded, definitive Machine-create rejection: under the same
101
+ * lifecycle lock, a fresh listing must show no Machine reference (and no
102
+ * Machines or untracked volumes in a per-project app) before the fenced sweep
103
+ * deletes the failed attempt's volume. The empty app remains KEEP until
104
+ * deliberate retirement through `destroyApp()`. Ambiguous outcomes and
105
+ * referenced resources stay KEPT and are reported. Deployed callers must
106
+ * serialize lifecycle operations per app through their lifecycle-lock adapter.
107
+ *
89
108
  * **Absence vs failure: `get()` only answers `null` for a REAL absence.**
90
109
  * `get()` (and each read shaped like it — `list()`, `find()`, `getTemplate()`)
91
110
  * reserves `null`/`[]` for a positive not-found answer — Fly answered 404 for
@@ -229,10 +248,19 @@
229
248
  * needs an app, a region and a size, and a volume created in the wrong app can
230
249
  * never be mounted. So passing `SandboxConfig.volumeName` provisions the volume
231
250
  * inside the project's app, mounted at `/workspace`; `resources.diskMB` is
232
- * rounded UP to whole GB. Fly documents that "a volume can be attached to only
233
- * one Machine", which matches one sandbox per project. Fly does not document the
234
- * legal character set for volume names, so names are conservatively reduced to
235
- * `[A-Za-z0-9_]` — an assumption, flagged as such.
251
+ * rounded UP to whole GB. Creation sends the Machine's exact guest as Fly's
252
+ * `compute` placement hint, so Fly can choose a volume host that can run that
253
+ * Machine. If Machine creation still answers `412 volume_placement_capacity`,
254
+ * the provider preserves the volume and propagates the error for the caller's
255
+ * bounded retry, which adopts it by its durable Fly ID. Create itself never
256
+ * deletes its app or volume; only explicit retirement or the reference-checked
257
+ * definitive-failure sweep above can do so. Volume creates retain transport and
258
+ * 429 backoff. After an ambiguous failure, bounded lookups by name adopt a
259
+ * volume whose response was lost before any POST replay, preventing duplicates.
260
+ * Fly documents that "a volume can be attached to only one Machine", which
261
+ * matches one sandbox per project. Fly does
262
+ * not document the legal character set for volume names, so names are
263
+ * conservatively reduced to `[A-Za-z0-9_]` — an assumption, flagged as such.
236
264
  *
237
265
  * **Warm start: templates are tar archives in object storage.** Fly cannot
238
266
  * `commit` a running Machine — it PULLS images from a registry — and the
@@ -360,6 +388,7 @@ export * from './egress.js';
360
388
  export * from './exec.js';
361
389
  export * from './flycast.js';
362
390
  export * from './ids.js';
391
+ export * from './lifecycle.js';
363
392
  export * from './provider.js';
364
393
  export * from './storage.js';
365
394
  export * from './templates.js';
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Durable ownership records for Fly create attempts.
3
+ *
4
+ * The bond owns provisioning and reconciliation; the application owns durable
5
+ * storage. Every write is recorded before Fly is called, so a lost response is
6
+ * still represented by an attempt that a later reconciler can settle.
7
+ *
8
+ * Rollout rule: a journal-aware writer that ignores provenance must never run
9
+ * beside this code. Pre-journal providers that delete listed Machines are also
10
+ * unsafe beside it; durable journals fence new creates until those writers drain.
11
+ *
12
+ * @module
13
+ */
14
+ /** Durable lifecycle state; only failed/abandoned attempts are automatically reclaimed. */
15
+ export type FlyCreateAttemptState = 'creating' | 'serving' | 'failed' | 'abandoned';
16
+ /** A volume name generated for one attempt, or an existing durable workspace. */
17
+ export interface FlyCreateVolumeIntent {
18
+ /** Fly's resource ID. Once known, this—not the name—is the ownership key. */
19
+ id?: string;
20
+ /** The current Fly name, unique for a newly-created attempt volume. */
21
+ name: string;
22
+ /** Caller-level workspace identity, stable across create attempts. */
23
+ logicalName?: string;
24
+ /** Once bound, only explicit destruction or a reference-checked failed-create sweep may delete it. */
25
+ workspace: boolean;
26
+ /** False until an ambiguous create is adopted or definitely rejected. */
27
+ resolved?: boolean;
28
+ /** Recorded before this attempt's create POST; automatic deletion also requires a resolved Fly ID. */
29
+ createdByAttemptId?: string;
30
+ /** Recorded when this attempt adopted an existing/listed volume; never automatic-delete authority. */
31
+ adoptedByAttemptId?: string;
32
+ }
33
+ /** An address on another Fly app which must be released before forgetting intent. */
34
+ export interface FlyCreateAddressIntent {
35
+ targetApp: string;
36
+ network: string;
37
+ ip?: string;
38
+ /** True only after Fly returned this allocation to this attempt. */
39
+ owned: boolean;
40
+ /** False while a lost response leaves allocation ownership unknown. */
41
+ resolved: boolean;
42
+ }
43
+ /** The complete durable responsibility for one create attempt. */
44
+ export interface FlyCreateAttempt {
45
+ attemptId: string;
46
+ projectId: string;
47
+ appName: string;
48
+ state: FlyCreateAttemptState;
49
+ /** Monotonic fencing token, incremented whenever lifecycle ownership is reacquired. */
50
+ generation?: number;
51
+ sandboxId?: string;
52
+ /** Fly definitively rejected the Machine POST; an unknown outcome is never marked. */
53
+ machineCreateFailed?: boolean;
54
+ /**
55
+ * Durable Machine POST lifecycle. New attempts start at not-started; pending is
56
+ * written before dispatch. NULL is a legacy unknown, never proof of no POST.
57
+ */
58
+ machineCreateOutcome?: 'not-started' | 'pending' | 'rejected' | 'unknown' | 'accepted';
59
+ /** The exact Machine ID returned by this attempt's accepted POST response, or an observation without delete provenance. */
60
+ machineId?: string;
61
+ /** Accepted-POST provenance; name, metadata, sandboxId and adoption never supply it. */
62
+ machineCreatedByAttemptId?: string;
63
+ /** This attempt's app-create intent, recorded before the POST. */
64
+ appCreatedByAttemptId?: string;
65
+ /** True only when the app POST itself was accepted; a pending/unknown create cannot be deleted. */
66
+ appCreateResolved?: boolean;
67
+ /** True when explicit destruction identity-validated and adopted a pre-journal Machine. */
68
+ machineAdopted?: boolean;
69
+ volumes: FlyCreateVolumeIntent[];
70
+ addresses: FlyCreateAddressIntent[];
71
+ }
72
+ /**
73
+ * Application-supplied durable journal used by create and its reconciler.
74
+ * A journal-aware writer that ignores provenance must never run beside this
75
+ * contract; {@link assertCreateAllowed} is the durable rollout boundary.
76
+ */
77
+ export interface FlyCreateAttemptJournal {
78
+ begin(attempt: Pick<FlyCreateAttempt, 'attemptId' | 'projectId' | 'appName'> & {
79
+ ownerProjectId?: string;
80
+ /** Legacy adoption persists its observed serving state and identity in the INSERT. */
81
+ state?: FlyCreateAttemptState;
82
+ sandboxId?: string;
83
+ machineAdopted?: boolean;
84
+ }): Promise<void>;
85
+ /** Refuse provisioning until pre-journal and provenance-ignoring writers have drained. */
86
+ assertCreateAllowed(): Promise<void>;
87
+ /** Re-read one attempt, including after a lifecycle lock has been acquired. */
88
+ get(attemptId: string): Promise<FlyCreateAttempt | null>;
89
+ /** Re-read every durable attempt for an app, including creating/serving attempts. */
90
+ forApp(appName: string): Promise<FlyCreateAttempt[]>;
91
+ /** Find the durable workspace binding for a logical caller workspace. */
92
+ findWorkspace(projectId: string, logicalName: string): Promise<FlyCreateVolumeIntent | null>;
93
+ /** Record app-create intent/acceptance, or clear it when the app was adopted. */
94
+ recordAppProvenance(attemptId: string, generation: number, outcome: 'pending' | 'created' | 'adopted'): Promise<void>;
95
+ /** Record an intent or transfer a Fly-ID binding to this attempt. */
96
+ recordVolume(attemptId: string, volume: FlyCreateVolumeIntent): Promise<void>;
97
+ markVolumeWorkspace(attemptId: string, name: string, workspace?: boolean): Promise<void>;
98
+ recordAddress(attemptId: string, address: FlyCreateAddressIntent): Promise<void>;
99
+ markState(attemptId: string, state: FlyCreateAttemptState, sandboxId?: string): Promise<void>;
100
+ /** Record that explicit destruction adopted this pre-journal Machine. */
101
+ markMachineAdopted(attemptId: string): Promise<void>;
102
+ /** Record POST intent before dispatch and an ambiguous outcome before recovery reads. */
103
+ recordMachineCreateOutcome(attemptId: string, generation: number, outcome: 'pending' | 'unknown'): Promise<void>;
104
+ /** Persist a recovered exact ID as observation only, never accepted-POST authority. */
105
+ recordMachineObservation(attemptId: string, generation: number, machineId: string): Promise<void>;
106
+ /** Record only the exact ID from this generation's accepted Machine POST response. */
107
+ recordMachineProvenance(attemptId: string, generation: number, machineId: string): Promise<void>;
108
+ /** Record a definitive rejection only while this create generation still owns the attempt. */
109
+ markMachineCreateFailed(attemptId: string, generation: number): Promise<void>;
110
+ /** Throw unless this generation is still the attempt's durable fencing token. */
111
+ assertGeneration(attemptId: string, generation: number): Promise<void>;
112
+ /** Atomically retire this exact generation while its lifecycle lock session is live. */
113
+ retire(attemptId: string, generation: number): Promise<void>;
114
+ /** Re-authorize one destructive request against the retired row on the lock session. */
115
+ authorizeDestruction(attemptId: string, generation: number): Promise<void>;
116
+ /** Failed/abandoned attempts, plus creating attempts whose updatedAt is at/before cutoff. */
117
+ reclaimable(cutoff: Date): Promise<FlyCreateAttempt[]>;
118
+ /** Acquire the same lifecycle lock the application uses for create/destroy. */
119
+ withLock?<T>(attempt: FlyCreateAttempt, body: (signal: AbortSignal, generation: number) => Promise<T>): Promise<T>;
120
+ forget(attemptId: string, generation?: number): Promise<void>;
121
+ }
122
+ /** Attempts forgotten after completed cleanup, and attempts retained after errors. */
123
+ export interface FlyCreateReconcileSummary {
124
+ deleted: number;
125
+ failed: number;
126
+ }
127
+ /** Fly-specific lifecycle capabilities exposed by the provider. */
128
+ export interface FlyCreateAttemptProvider {
129
+ /** Explicit retirement is fenced by the caller's live lifecycle lock. */
130
+ destroy(id: string, signal: AbortSignal): Promise<void>;
131
+ /** Deliberately retire exact recorded resources for one shared-app attempt. */
132
+ destroyAttempt(attemptId: string, signal: AbortSignal): Promise<void>;
133
+ /** Per-project app retirement is fenced by the caller's live lifecycle lock. */
134
+ destroyApp(projectId: string, expectedAppName: string | undefined, signal: AbortSignal): Promise<void>;
135
+ reconcileCreateAttempts(options?: {
136
+ now?: number;
137
+ createTimeoutMs?: number;
138
+ /** Restrict a pass to one durable attempt. Every attempt is still locked. */
139
+ attemptId?: string;
140
+ }): Promise<FlyCreateReconcileSummary>;
141
+ }
142
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Durable ownership records for Fly create attempts.
3
+ *
4
+ * The bond owns provisioning and reconciliation; the application owns durable
5
+ * storage. Every write is recorded before Fly is called, so a lost response is
6
+ * still represented by an attempt that a later reconciler can settle.
7
+ *
8
+ * Rollout rule: a journal-aware writer that ignores provenance must never run
9
+ * beside this code. Pre-journal providers that delete listed Machines are also
10
+ * unsafe beside it; durable journals fence new creates until those writers drain.
11
+ *
12
+ * @module
13
+ */
14
+ export {};
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { SandboxProvider } from '@molecule/api-code-sandbox';
10
10
  import { FlyApiClient } from './api.js';
11
+ import type { FlyCreateAttemptProvider } from './lifecycle.js';
11
12
  import { type ObjectStore } from './storage.js';
12
13
  import type { FlyioConfig } from './types.js';
13
14
  /**
@@ -64,5 +65,5 @@ export declare function isMachineWarmingUp(state: string | null | undefined): bo
64
65
  * template settings (or their env fallbacks) on first use when omitted.
65
66
  * @returns A `SandboxProvider` backed by Fly Machines.
66
67
  */
67
- export declare function createProvider(config?: FlyioConfig, client?: FlyApiClient, store?: ObjectStore | null): SandboxProvider;
68
+ export declare function createProvider(config?: FlyioConfig, client?: FlyApiClient, store?: ObjectStore | null): SandboxProvider & FlyCreateAttemptProvider;
68
69
  //# sourceMappingURL=provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAaV,eAAe,EAGhB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,YAAY,EAAgC,MAAM,UAAU,CAAA;AAwBrE,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAEhG,OAAO,KAAK,EAIV,WAAW,EAUZ,MAAM,YAAY,CAAA;AAkBnB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIjG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE5E;AAwzFD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,YAAY,EACrB,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,GACzB,eAAe,CAEjB"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAaV,eAAe,EAGhB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EACL,YAAY,EAKb,MAAM,UAAU,CAAA;AA8BjB,OAAO,KAAK,EAEV,wBAAwB,EAEzB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAEhG,OAAO,KAAK,EAIV,WAAW,EAUZ,MAAM,YAAY,CAAA;AAyBnB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIjG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE5E;AAgyID;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,YAAY,EACrB,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,GACzB,eAAe,GAAG,wBAAwB,CAE5C"}