@moinulmoin/eden 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE CHANGED
@@ -9,3 +9,11 @@ Eden's generated Worker host bundle includes software from
9
9
  `@cloudflare/containers`, distributed under MIT OR Apache-2.0. Eden
10
10
  redistributes that bundled software under the Apache-2.0 option included in
11
11
  LICENSE.
12
+
13
+ Workflow SDK
14
+ @workflow/world-postgres, version 5.0.0-beta.47
15
+ https://github.com/vercel/workflow
16
+
17
+ Eden's Cloudflare World storage implementation
18
+ (`@moinulmoin/eden-world-cloudflare`) is adapted from the Workflow SDK
19
+ PostgreSQL World, distributed under the Apache License, Version 2.0.
package/README.md CHANGED
@@ -12,15 +12,15 @@ If you already have an Eve project, start with `eden deploy`.
12
12
  Use any one of these package managers:
13
13
 
14
14
  ```sh
15
- npm install --global @moinulmoin/eden@0.3.0
15
+ npm install --global @moinulmoin/eden@0.5.0
16
16
  ```
17
17
 
18
18
  ```sh
19
- pnpm add --global @moinulmoin/eden@0.3.0
19
+ pnpm add --global @moinulmoin/eden@0.5.0
20
20
  ```
21
21
 
22
22
  ```sh
23
- bun add --global @moinulmoin/eden@0.3.0
23
+ bun add --global @moinulmoin/eden@0.5.0
24
24
  ```
25
25
 
26
26
  Then confirm the CLI is available:
@@ -21,6 +21,12 @@ export interface EvePreflightCandidate {
21
21
  export type EveDeploymentStatus = "deployed" | "failed" | "indeterminate";
22
22
  export interface EveDeploymentMetadata {
23
23
  readonly status: EveDeploymentStatus;
24
+ /**
25
+ * `create` is a first deploy onto an absent target; `update` is an
26
+ * in-place redeploy of a provably Eden-owned target that keeps the Worker,
27
+ * Durable Object classes, and durable state.
28
+ */
29
+ readonly operation: "create" | "update";
24
30
  readonly targetKey: string;
25
31
  readonly deploymentId: string;
26
32
  readonly workerName: string;
@@ -28,6 +34,9 @@ export interface EveDeploymentMetadata {
28
34
  readonly stableContainerInstanceName: string;
29
35
  readonly stableWorkersDevOrigin: string;
30
36
  readonly imageReference: string;
37
+ /** Present on updates: the superseded generation and its registry image. */
38
+ readonly supersededGenerationId?: string;
39
+ readonly supersededImageReference?: string;
31
40
  readonly runtimeVariableNames: readonly string[];
32
41
  readonly evidenceRetained: true;
33
42
  }
@@ -50,6 +59,7 @@ export type EveCloudflareTargetState = "absent" | "owned" | "unowned" | "mismatc
50
59
  export interface EveCloudflareReadRequest {
51
60
  readonly projectId: string;
52
61
  readonly sourceDigest: string;
62
+ readonly projectRoot: string;
53
63
  readonly environment: EveCliEnvironment;
54
64
  readonly name: string;
55
65
  }
@@ -65,6 +75,29 @@ export interface EveCloudflareReadResult {
65
75
  readonly message?: string;
66
76
  readonly remediation?: string;
67
77
  readonly identity?: EveDeploymentIdentityProof;
78
+ /**
79
+ * Which exact resources the read observed. `undefined` means the
80
+ * inventory could not be proven; `worker`/`container` booleans mean the
81
+ * exact names were present or absent without any ownership claim.
82
+ */
83
+ readonly observed?: {
84
+ readonly worker: boolean;
85
+ readonly container: boolean;
86
+ };
87
+ /**
88
+ * Provider-backed facts about the live target, read through the
89
+ * Cloudflare API (not self-asserted response headers, which any Worker
90
+ * can forge): the Container application's configured image and the
91
+ * `EDEN_EVE_DEPLOYMENT_ID` plain-text var Eden stamps into every Worker
92
+ * it publishes. `settingsReadable` tells the ownership resolver whether
93
+ * a missing var is evidence of a foreign Worker or just an unreadable
94
+ * settings call.
95
+ */
96
+ readonly providerEvidence?: {
97
+ readonly containerImage?: string;
98
+ readonly workerDeploymentId?: string;
99
+ readonly settingsReadable?: boolean;
100
+ };
68
101
  };
69
102
  }
70
103
  export type EveCloudflareReadRunner = (request: EveCloudflareReadRequest) => EveCloudflareReadResult | Promise<EveCloudflareReadResult>;
@@ -104,7 +137,10 @@ export interface EveDeploymentIdentity {
104
137
  readonly runtimeVariableNames: readonly string[];
105
138
  readonly runtimeRevisionHandle?: string;
106
139
  }
107
- export type EveDeploymentIdentityProof = Partial<EveDeploymentIdentity>;
140
+ export type EveDeploymentIdentityProof = Partial<EveDeploymentIdentity> & {
141
+ /** The deployment the serving Container verifiably started under; set only by a deployed Eden host that proves its running image before stamping. */
142
+ readonly startedDeploymentId?: string;
143
+ };
108
144
  export interface EveDeploymentPublicationRequest {
109
145
  readonly identity: EveDeploymentIdentity;
110
146
  readonly hostConfig: EveHostConfig;
@@ -252,6 +288,7 @@ export interface EveDestroyCloudflareReadRequest {
252
288
  export declare function exactTargetContainerEntries(value: unknown, name: string): readonly {
253
289
  readonly id: string;
254
290
  readonly name: string;
291
+ readonly image?: string;
255
292
  }[] | undefined;
256
293
  export declare function runEveControlPlane(request: EveCliExecutionRequest, options?: EvePreflightOptions): Promise<void>;
257
294
  export interface EveDestroyOutcome {
@@ -142,12 +142,13 @@ async function readInstalledManifest(snapshotRoot, packageName) {
142
142
  }
143
143
  }
144
144
  /**
145
- * Fails deploy/preflight when the selected `@workflow/world-postgres` would
146
- * trip Eve's runtime World-compatibility rejection. Unreadable manifests
145
+ * Fails deploy/preflight when a known durable World would trip Eve's runtime
146
+ * World-compatibility rejection. Unreadable manifests
147
147
  * mirror Eve's own behavior (skip); Eve is the runtime authority.
148
148
  */
149
149
  async function checkEveWorldPairing(candidate, world) {
150
- if (world !== "@workflow/world-postgres")
150
+ if (world !== "@workflow/world-postgres" &&
151
+ world !== "@moinulmoin/eden-world-cloudflare")
151
152
  return undefined;
152
153
  const eveManifest = await readInstalledManifest(candidate.snapshotRoot, "eve");
153
154
  const expectedRange = eveManifest === undefined
@@ -183,9 +184,9 @@ async function checkEveWorldPairing(candidate, world) {
183
184
  expectedLine.prereleaseTag !== undefined &&
184
185
  declaredLine.prereleaseTag !== expectedLine.prereleaseTag);
185
186
  if (mismatch) {
186
- return check("EVE_WORLD_PAIRING", "failed", `The installed @workflow/world-postgres targets ${declared.packageName} ${declared.range}, but the project's Eve bundles @workflow/core ${expectedRange}; Eve rejects this World at runtime.`, `Install the @workflow/world-postgres release whose ${declared.packageName} dependency matches the Eve-bundled @workflow/core ${expectedRange} line (e.g. \`pnpm add @workflow/world-postgres@<matching-version>\`); see docs/deploy.md#durable-state-postgres-world.`);
187
+ return check("EVE_WORLD_PAIRING", "failed", `The installed ${world} targets ${declared.packageName} ${declared.range}, but the project's Eve bundles @workflow/core ${expectedRange}; Eve rejects this World at runtime.`, `Install the ${world} release whose ${declared.packageName} dependency matches the Eve-bundled @workflow/core ${expectedRange} line; see docs/deploy.md.`);
187
188
  }
188
- return check("EVE_WORLD_PAIRING", "passed", `The installed @workflow/world-postgres ${declared.packageName} dependency ${declared.range} pairs with the Eve-bundled @workflow/core ${expectedRange}.`);
189
+ return check("EVE_WORLD_PAIRING", "passed", `The installed ${world} ${declared.packageName} dependency ${declared.range} pairs with the Eve-bundled @workflow/core ${expectedRange}.`);
189
190
  }
190
191
  /**
191
192
  * Returns a generic reason when the URL targets a transaction-mode pooler.
@@ -289,7 +290,7 @@ function humanEveResultLines(result, startedAt, eveVersion) {
289
290
  if (result.ok) {
290
291
  if (result.command === "eve deploy") {
291
292
  const url = result.deployment?.stableWorkersDevOrigin;
292
- lines.push("", `✓ deployed ${result.name} (${result.environment})` +
293
+ lines.push("", `✓ ${result.deployment?.operation === "update" ? "updated" : "deployed"} ${result.name} (${result.environment})` +
293
294
  (eveVersion === undefined ? "" : ` · eve ${eveVersion}`));
294
295
  if (url !== undefined)
295
296
  lines.push(url);
@@ -305,7 +306,7 @@ function humanEveResultLines(result, startedAt, eveVersion) {
305
306
  lines.push("", `✗ ${commandLabel} ${result.deployment.status}`, ` → ${result.deployment.status === "indeterminate"
306
307
  ? "The outcome is indeterminate; inspect the retained ownership evidence before retrying the same exact target."
307
308
  : result.deployment.status === "deployed"
308
- ? "The deployment is live; only local runtime-image cleanup failed, so do not retry the deployment."
309
+ ? "The deployment is live; only image cleanup failed (local runtime image or the superseded registry tag), so do not retry the deployment."
309
310
  : "The deployment failed before promotion; the exact target was not claimed."}`);
310
311
  }
311
312
  else if (firstFailure !== undefined) {
@@ -441,8 +442,21 @@ function exactContainerEntries(value, name) {
441
442
  }
442
443
  const id = entry.id;
443
444
  const entryName = entry.name;
444
- if (entryName === name)
445
- entries.push({ id, name: entryName });
445
+ if (entryName === name) {
446
+ const configuration = entry.configuration;
447
+ const image = typeof entry.image === "string"
448
+ ? entry.image
449
+ : typeof configuration === "object" &&
450
+ configuration !== null &&
451
+ typeof configuration.image === "string"
452
+ ? configuration.image
453
+ : undefined;
454
+ entries.push({
455
+ id,
456
+ name: entryName,
457
+ ...(image === undefined ? {} : { image }),
458
+ });
459
+ }
446
460
  }
447
461
  if (entries.length === 0 && collection.length >= 100) {
448
462
  return undefined;
@@ -594,6 +608,9 @@ async function defaultCloudflareRead(request) {
594
608
  target: { state: "absent" },
595
609
  };
596
610
  }
611
+ const workerPresent = deploymentEntries.length > 0;
612
+ const containerPresent = (containerEntries?.length ?? 0) > 0;
613
+ const providerEvidence = await readProviderEvidence(request.name, accountId, workerPresent, containerPresent ? containerEntries?.[0]?.image : undefined);
597
614
  return {
598
615
  accountAccess,
599
616
  containerAccess,
@@ -604,16 +621,141 @@ async function defaultCloudflareRead(request) {
604
621
  ? {}
605
622
  : { workersDevSubdomain }),
606
623
  target: {
607
- state: "ambiguous",
608
- message: "The exact target could not be proven absent or owned from the read-only Cloudflare inventory.",
609
- remediation: "Resolve the exact Worker and Container ownership state before deployment; no target was claimed or changed.",
624
+ // The read-only inventory proves the exact target exists but cannot
625
+ // prove who created it; Eden's immutable deployment record resolves
626
+ // ownership next.
627
+ state: "unowned",
628
+ observed: { worker: workerPresent, container: containerPresent },
629
+ ...(providerEvidence === undefined ? {} : { providerEvidence }),
630
+ message: "The exact Worker or Container target already exists, and the read-only inventory cannot prove Eden owns it.",
631
+ remediation: "Deploy under a different --name or remove the conflicting Worker/Container yourself; Eden never adopts or replaces resources it cannot prove it created.",
632
+ },
633
+ };
634
+ }
635
+ /**
636
+ * Reads provider-backed facts about the live target: the Container
637
+ * application's configured image (from `wrangler containers list`) and the
638
+ * `EDEN_EVE_DEPLOYMENT_ID` plain-text var Eden stamps into every Worker it
639
+ * publishes (from the script settings API). Both are provider state — a
640
+ * foreign Worker can forge `x-eden-*` response headers but cannot set a var
641
+ * Eden recorded at publish time.
642
+ */
643
+ async function readProviderEvidence(workerName, accountId, workerPresent, containerImage) {
644
+ const evidence = {
645
+ ...(containerImage === undefined ? {} : { containerImage }),
646
+ };
647
+ if (workerPresent && accountId !== undefined) {
648
+ const token = process.env.CLOUDFLARE_API_TOKEN ??
649
+ (await readWranglerOAuthToken());
650
+ if (token !== undefined) {
651
+ try {
652
+ const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts/${encodeURIComponent(workerName)}/settings`, {
653
+ headers: { authorization: `Bearer ${token}` },
654
+ signal: AbortSignal.timeout(20_000),
655
+ });
656
+ if (response.ok) {
657
+ evidence.settingsReadable = true;
658
+ const parsed = (await response.json());
659
+ const binding = parsed.result?.bindings?.find((value) => value.type === "plain_text" &&
660
+ value.name === "EDEN_EVE_DEPLOYMENT_ID");
661
+ if (typeof binding?.text === "string") {
662
+ evidence.workerDeploymentId = binding.text;
663
+ }
664
+ }
665
+ else if (response.status !== 404) {
666
+ evidence.settingsReadable = false;
667
+ }
668
+ }
669
+ catch {
670
+ evidence.settingsReadable = false;
671
+ }
672
+ }
673
+ }
674
+ return evidence;
675
+ }
676
+ /**
677
+ * Resolves `unowned` reads against Eden's immutable deployment records AND
678
+ * the live remote target. The local record proves Eden once created the
679
+ * target; the remote Worker's stamped identity proves it is still the one
680
+ * Eden deployed — a same-name Worker recreated by something else after the
681
+ * target was destroyed (or a forged local record) must never be upgraded to
682
+ * `owned`.
683
+ */
684
+ async function resolveEveTargetOwnership(request, result) {
685
+ if (result.target.state !== "unowned")
686
+ return result;
687
+ const record = await readEveDeploymentRecord(request.projectRoot, request.environment, request.name, result.accountId, request.projectId === "unknown-project" ? undefined : request.projectId);
688
+ if (record === undefined ||
689
+ record.identity.workerName !== request.name ||
690
+ record.identity.accountId !== result.accountId ||
691
+ (request.projectId !== "unknown-project" &&
692
+ record.identity.projectId !== undefined &&
693
+ record.identity.projectId !== request.projectId) ||
694
+ (result.target.observed?.container === true &&
695
+ record.identity.containerApplicationName !==
696
+ boundedResourceName(request.name, "container"))) {
697
+ return result;
698
+ }
699
+ // Remote proof must come from provider state. A foreign Worker can echo
700
+ // `x-eden-*` response headers, but it cannot plant the plain-text var Eden
701
+ // publishes into script settings, nor point the Container application at an
702
+ // image digest Eden recorded. The Worker is proven only by its own settings:
703
+ // a matching Container image proves the Container, never the Worker, so
704
+ // unreadable settings fail closed.
705
+ const evidence = result.target.providerEvidence;
706
+ const recordedDeploymentIds = new Set(record.deploymentIds);
707
+ const workerProven = result.target.observed?.worker !== true ||
708
+ (evidence?.settingsReadable === true &&
709
+ evidence.workerDeploymentId !== undefined &&
710
+ recordedDeploymentIds.has(evidence.workerDeploymentId));
711
+ const containerProven = result.target.observed?.container !== true ||
712
+ (evidence?.containerImage !== undefined &&
713
+ record.images.includes(evidence.containerImage));
714
+ const remoteProven = evidence !== undefined && workerProven && containerProven;
715
+ if (!remoteProven) {
716
+ return {
717
+ ...result,
718
+ target: {
719
+ ...result.target,
720
+ message: "The exact Worker or Container target exists, and although a local Eden record matches, provider state cannot prove it is the one Eden deployed — the target may have been recreated outside Eden.",
721
+ remediation: "Inspect the Worker at its workers.dev origin and the recorded deployment history before redeploying; if it is not Eden's, deploy under a different --name or delete it yourself.",
722
+ },
723
+ };
724
+ }
725
+ const priorIdentity = {
726
+ workerName: record.identity.workerName,
727
+ containerApplicationName: record.identity.containerApplicationName,
728
+ stableContainerInstanceName: record.identity.stableContainerInstanceName,
729
+ accountId: record.identity.accountId,
730
+ environment: request.environment,
731
+ name: request.name,
732
+ ...(record.identity.projectId === undefined
733
+ ? {}
734
+ : { projectId: record.identity.projectId }),
735
+ ...(record.identity.generationId === undefined
736
+ ? {}
737
+ : { generationId: record.identity.generationId }),
738
+ ...(record.identity.containerImage === undefined
739
+ ? {}
740
+ : { containerImage: record.identity.containerImage }),
741
+ };
742
+ return {
743
+ ...result,
744
+ target: {
745
+ state: "owned",
746
+ identity: priorIdentity,
747
+ ...(result.target.observed === undefined
748
+ ? {}
749
+ : { observed: result.target.observed }),
750
+ message: "The immutable deployment record proves Eden owns the exact target; deploy proceeds as an in-place update.",
610
751
  },
611
752
  };
612
753
  }
613
754
  async function readEveCloudflareTarget(request, options) {
614
- return options.cloudflareRead === undefined
615
- ? defaultCloudflareRead(request)
616
- : options.cloudflareRead(request);
755
+ const result = options.cloudflareRead === undefined
756
+ ? await defaultCloudflareRead(request)
757
+ : await options.cloudflareRead(request);
758
+ return resolveEveTargetOwnership(request, result);
617
759
  }
618
760
  async function resolvedCloudflareOrigin(result, workerName) {
619
761
  if (result.accountId === undefined ||
@@ -712,12 +854,12 @@ function cloudflareChecks(result) {
712
854
  ? undefined
713
855
  : "Use an authenticated read-only adapter with account and Container access; preflight never uploads, publishes, or changes remote state.";
714
856
  const target = result.target;
715
- const targetPasses = target.state === "absent" ||
716
- (target.state === "owned" && target.matchesCandidate === true);
857
+ const targetPasses = target.state === "absent" || target.state === "owned";
717
858
  const targetMessage = targetPasses
718
859
  ? target.state === "absent"
719
860
  ? "The exact target read is absent and is eligible without claiming ownership."
720
- : "The exact target is already owned by this candidate identity; no ownership claim was attempted."
861
+ : target.message ??
862
+ "The exact target is provably Eden-owned; deploy proceeds as an in-place update."
721
863
  : target.message ??
722
864
  "The exact target read is unowned, mismatched, cross-environment, or ambiguous.";
723
865
  const targetRemediation = targetPasses
@@ -1061,6 +1203,10 @@ function responseIdentity(response, body) {
1061
1203
  read("workerName", "x-eden-eve-worker-name");
1062
1204
  read("containerApplicationName", "x-eden-eve-container-application");
1063
1205
  read("stableContainerInstanceName", "x-eden-eve-container-instance");
1206
+ const started = response.headers.get("x-eden-eve-started-deployment");
1207
+ if (started !== null) {
1208
+ identity["startedDeploymentId"] = started;
1209
+ }
1064
1210
  read("containerImage", "x-eden-eve-container-image");
1065
1211
  return identity;
1066
1212
  }
@@ -1104,9 +1250,20 @@ function defaultHealthRunner() {
1104
1250
  body.state === "ready" ||
1105
1251
  body.ready === true);
1106
1252
  if (ready) {
1253
+ const observedIdentity = responseIdentity(response, body);
1254
+ // A Worker activates before its image rollout completes; promotion
1255
+ // must wait until the *served container* verifiably runs this
1256
+ // generation's image (the host stamps the header only after
1257
+ // inspecting the running instance).
1258
+ if (observedIdentity.startedDeploymentId !== request.identity.deploymentId) {
1259
+ lastReason =
1260
+ "The served Container has not verifiably started this deployment's image yet; waiting for the rollout to land.";
1261
+ await wait();
1262
+ continue;
1263
+ }
1107
1264
  return {
1108
1265
  status: "ready",
1109
- identity: responseIdentity(response, body),
1266
+ identity: observedIdentity,
1110
1267
  };
1111
1268
  }
1112
1269
  lastReason =
@@ -1160,8 +1317,13 @@ async function releaseEveTargetLock(lock) {
1160
1317
  await rm(lock.path, { force: true });
1161
1318
  return (await lstat(lock.path).catch(() => undefined)) === undefined;
1162
1319
  }
1163
- async function promoteEveTargetPointer(deploymentRoot, targetKey, generationRoot) {
1164
- const targetRoot = join(deploymentRoot, "targets", targetKey);
1320
+ /**
1321
+ * Atomically re-points the target's CURRENT symlink at a promoted immutable
1322
+ * generation. The pointer key is `<environment>-<name>` — the same path
1323
+ * `destroy` clears and the update ownership resolver reads.
1324
+ */
1325
+ async function promoteEveTargetPointer(deploymentRoot, pointerKey, generationRoot) {
1326
+ const targetRoot = join(deploymentRoot, "targets", pointerKey);
1165
1327
  const artifactBoundary = await realpath(dirname(deploymentRoot)).catch(() => undefined);
1166
1328
  const generationCanonical = await realpath(generationRoot).catch(() => undefined);
1167
1329
  if (artifactBoundary === undefined ||
@@ -1190,7 +1352,7 @@ async function promoteEveTargetPointer(deploymentRoot, targetKey, generationRoot
1190
1352
  throw new EveCliError({
1191
1353
  code: "EVE_DEPLOY_PROMOTION_FAILED",
1192
1354
  message: "The Eve target pointer directory could not be verified safely.",
1193
- source: targetKey,
1355
+ source: pointerKey,
1194
1356
  });
1195
1357
  }
1196
1358
  const currentPath = join(targetRoot, "CURRENT");
@@ -1207,7 +1369,7 @@ async function promoteEveTargetPointer(deploymentRoot, targetKey, generationRoot
1207
1369
  throw new EveCliError({
1208
1370
  code: "EVE_DEPLOY_PROMOTION_FAILED",
1209
1371
  message: "The Eve target pointer did not resolve to the promoted generation.",
1210
- source: targetKey,
1372
+ source: pointerKey,
1211
1373
  });
1212
1374
  }
1213
1375
  return currentPath;
@@ -1241,9 +1403,10 @@ async function writeEveIndeterminateEvidence(candidate, evidence, extra = {}) {
1241
1403
  flag: "wx",
1242
1404
  });
1243
1405
  }
1244
- function deploymentMetadata(identity, targetKey, status) {
1406
+ function deploymentMetadata(identity, targetKey, status, operation, superseded) {
1245
1407
  return {
1246
1408
  status,
1409
+ operation,
1247
1410
  targetKey,
1248
1411
  deploymentId: identity.deploymentId,
1249
1412
  workerName: identity.workerName,
@@ -1251,6 +1414,12 @@ function deploymentMetadata(identity, targetKey, status) {
1251
1414
  stableContainerInstanceName: identity.stableContainerInstanceName,
1252
1415
  stableWorkersDevOrigin: identity.stableWorkersDevOrigin,
1253
1416
  imageReference: identity.containerImage,
1417
+ ...(superseded?.generationId === undefined
1418
+ ? {}
1419
+ : { supersededGenerationId: superseded.generationId }),
1420
+ ...(superseded?.containerImage === undefined
1421
+ ? {}
1422
+ : { supersededImageReference: superseded.containerImage }),
1254
1423
  runtimeVariableNames: identity.runtimeVariableNames,
1255
1424
  evidenceRetained: true,
1256
1425
  };
@@ -1272,6 +1441,46 @@ async function runEveDeployment(request, options, collected) {
1272
1441
  collected.projectId === undefined) {
1273
1442
  throw deploymentFailure("EVE_DEPLOY_CHECKS_FAILED", "The inline Eve deploy checks did not return a complete immutable candidate.");
1274
1443
  }
1444
+ const isUpdate = cloudflare.target.state === "owned";
1445
+ const operation = isUpdate ? "update" : "create";
1446
+ const prior = collected.priorDeployment;
1447
+ if (isUpdate && prior === undefined) {
1448
+ throw deploymentFailure("EVE_UPDATE_RECORD_UNAVAILABLE", "The exact target is provably Eden-owned, but its immutable deployment record is unreadable; refusing to update without the recorded Durable Object history.");
1449
+ }
1450
+ if (isUpdate && prior?.durableObjects === undefined) {
1451
+ throw deploymentFailure("EVE_UPDATE_RECORD_UNAVAILABLE", "The prior deployment record does not carry a readable Durable Object history; refusing to update because replacing it could emit a divergent migration list.");
1452
+ }
1453
+ const priorIdentity = prior?.identity;
1454
+ const priorDurableObjects = prior?.durableObjects;
1455
+ const superseded = isUpdate
1456
+ ? {
1457
+ generationId: priorIdentity?.generationId,
1458
+ containerImage: priorIdentity?.containerImage,
1459
+ }
1460
+ : undefined;
1461
+ const resultWarnings = collected.result.warnings;
1462
+ if (isUpdate) {
1463
+ const world = collected.workflowWorld;
1464
+ const durableWorld = world !== undefined &&
1465
+ world !== "local" &&
1466
+ world !== "@workflow/world-local";
1467
+ if (durableWorld) {
1468
+ resultWarnings.push({
1469
+ id: "EVE_UPDATE_IN_FLIGHT",
1470
+ message: "This update replaces the running Container; durable World runs in flight resume on the new code — Cloudflare has no deployment pinning, so keep step names and payload shapes backward compatible.",
1471
+ remediation: "Let pending approvals and long runs settle before updating, or keep step names, queue names, and serialized payloads compatible across the change.",
1472
+ });
1473
+ }
1474
+ if (prior?.worldCloudflare !==
1475
+ (world === "@moinulmoin/eden-world-cloudflare")) {
1476
+ resultWarnings.push({
1477
+ id: "EVE_UPDATE_WORLD_SWITCH",
1478
+ message: "The compiled Workflow World changed since the last deploy; existing sessions and pending approvals stay in the previous World and do not migrate to the new one.",
1479
+ remediation: "Existing Durable Object data is preserved (classes and bindings are never removed), but runs started before this update remain where they were created.",
1480
+ });
1481
+ }
1482
+ eveProgress(options, "update: Durable Object data is preserved; the running Container restarts on its next request to serve the new image");
1483
+ }
1275
1484
  const accountId = cloudflare.accountId ??
1276
1485
  cloudflare.target.identity?.accountId;
1277
1486
  const workersDevSubdomain = cloudflare.workersDevSubdomain ??
@@ -1417,6 +1626,16 @@ async function runEveDeployment(request, options, collected) {
1417
1626
  ? {}
1418
1627
  : { runtimeRevisionHandle: identity.runtimeRevisionHandle }),
1419
1628
  schedules: collected.schedules ?? [],
1629
+ ...(collected.workflowWorld === undefined
1630
+ ? {}
1631
+ : { workflowWorld: collected.workflowWorld }),
1632
+ // In-place updates carry the prior Durable Object bindings and
1633
+ // migration history forward verbatim: classes are append-only, never
1634
+ // re-declared, and never deleted (a deleted_classes migration destroys
1635
+ // the data).
1636
+ ...(isUpdate && priorDurableObjects !== undefined
1637
+ ? { durableObjects: priorDurableObjects }
1638
+ : {}),
1420
1639
  ...(process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER === undefined
1421
1640
  ? {}
1422
1641
  : { containerSleepAfter: process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER }),
@@ -1444,7 +1663,7 @@ async function runEveDeployment(request, options, collected) {
1444
1663
  }
1445
1664
  catch {
1446
1665
  await writeEveDeploymentRecord(candidate, identity, hostConfig, "indeterminate", { evidence: "publication-threw-before-terminal-result" });
1447
- const deployment = deploymentMetadata(identity, targetKey, "indeterminate");
1666
+ const deployment = deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded);
1448
1667
  return {
1449
1668
  ...collected.result,
1450
1669
  ok: false,
@@ -1456,7 +1675,7 @@ async function runEveDeployment(request, options, collected) {
1456
1675
  return {
1457
1676
  ...collected.result,
1458
1677
  ok: false,
1459
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1678
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1460
1679
  };
1461
1680
  }
1462
1681
  if (publication.status === "failed" ||
@@ -1468,7 +1687,7 @@ async function runEveDeployment(request, options, collected) {
1468
1687
  return {
1469
1688
  ...collected.result,
1470
1689
  ok: false,
1471
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1690
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1472
1691
  };
1473
1692
  }
1474
1693
  if (publication.status === "failed" &&
@@ -1479,7 +1698,7 @@ async function runEveDeployment(request, options, collected) {
1479
1698
  return {
1480
1699
  ...collected.result,
1481
1700
  ok: false,
1482
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1701
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1483
1702
  };
1484
1703
  }
1485
1704
  if (publication.status === "failed" &&
@@ -1497,7 +1716,7 @@ async function runEveDeployment(request, options, collected) {
1497
1716
  return {
1498
1717
  ...collected.result,
1499
1718
  ok: false,
1500
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1719
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1501
1720
  };
1502
1721
  }
1503
1722
  }
@@ -1509,7 +1728,7 @@ async function runEveDeployment(request, options, collected) {
1509
1728
  return {
1510
1729
  ...collected.result,
1511
1730
  ok: false,
1512
- deployment: deploymentMetadata(identity, targetKey, "failed"),
1731
+ deployment: deploymentMetadata(identity, targetKey, "failed", operation, superseded),
1513
1732
  };
1514
1733
  }
1515
1734
  let health;
@@ -1525,7 +1744,7 @@ async function runEveDeployment(request, options, collected) {
1525
1744
  return {
1526
1745
  ...collected.result,
1527
1746
  ok: false,
1528
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1747
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1529
1748
  };
1530
1749
  }
1531
1750
  if (health.status === "indeterminate") {
@@ -1533,7 +1752,7 @@ async function runEveDeployment(request, options, collected) {
1533
1752
  return {
1534
1753
  ...collected.result,
1535
1754
  ok: false,
1536
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1755
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1537
1756
  };
1538
1757
  }
1539
1758
  const observedHealthIdentity = health.status === "ready"
@@ -1550,7 +1769,7 @@ async function runEveDeployment(request, options, collected) {
1550
1769
  return {
1551
1770
  ...collected.result,
1552
1771
  ok: false,
1553
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1772
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1554
1773
  };
1555
1774
  }
1556
1775
  if (publication.createdByAttempt === true &&
@@ -1560,7 +1779,7 @@ async function runEveDeployment(request, options, collected) {
1560
1779
  return {
1561
1780
  ...collected.result,
1562
1781
  ok: false,
1563
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1782
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1564
1783
  };
1565
1784
  }
1566
1785
  if (publication.createdByAttempt === true &&
@@ -1574,7 +1793,7 @@ async function runEveDeployment(request, options, collected) {
1574
1793
  return {
1575
1794
  ...collected.result,
1576
1795
  ok: false,
1577
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1796
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1578
1797
  };
1579
1798
  }
1580
1799
  }
@@ -1586,7 +1805,7 @@ async function runEveDeployment(request, options, collected) {
1586
1805
  return {
1587
1806
  ...collected.result,
1588
1807
  ok: false,
1589
- deployment: deploymentMetadata(identity, targetKey, "failed"),
1808
+ deployment: deploymentMetadata(identity, targetKey, "failed", operation, superseded),
1590
1809
  };
1591
1810
  }
1592
1811
  try {
@@ -1605,7 +1824,7 @@ async function runEveDeployment(request, options, collected) {
1605
1824
  return {
1606
1825
  ...collected.result,
1607
1826
  ok: false,
1608
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1827
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1609
1828
  };
1610
1829
  }
1611
1830
  await writeEveDeploymentRecord(candidate, identity, hostConfig, "failed", {
@@ -1616,7 +1835,7 @@ async function runEveDeployment(request, options, collected) {
1616
1835
  return {
1617
1836
  ...collected.result,
1618
1837
  ok: false,
1619
- deployment: deploymentMetadata(identity, targetKey, "failed"),
1838
+ deployment: deploymentMetadata(identity, targetKey, "failed", operation, superseded),
1620
1839
  };
1621
1840
  }
1622
1841
  await writeEveDeploymentRecord(candidate, identity, hostConfig, "indeterminate", {
@@ -1627,7 +1846,7 @@ async function runEveDeployment(request, options, collected) {
1627
1846
  return {
1628
1847
  ...collected.result,
1629
1848
  ok: false,
1630
- deployment: deploymentMetadata(identity, targetKey, "indeterminate"),
1849
+ deployment: deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded),
1631
1850
  };
1632
1851
  }
1633
1852
  await writeEveDeploymentRecord(candidate, identity, hostConfig, "deployed", {
@@ -1637,8 +1856,33 @@ async function runEveDeployment(request, options, collected) {
1637
1856
  identity: "exact",
1638
1857
  },
1639
1858
  });
1640
- await promoteEveTargetPointer(join(request.projectRoot, ".eden", "eve-deploy"), targetKey, candidate.generationRoot);
1859
+ await promoteEveTargetPointer(join(request.projectRoot, ".eden", "eve-deploy"), `${request.environment}-${request.name}`, candidate.generationRoot);
1641
1860
  await options.afterPromotion?.();
1861
+ let supersededImageCleanup;
1862
+ if (isUpdate) {
1863
+ // The superseded generation's registry repository is generation-scoped
1864
+ // and no longer referenced once CURRENT promotes this generation; only
1865
+ // then is it safe to delete.
1866
+ const previousImage = priorIdentity === undefined
1867
+ ? undefined
1868
+ : ownedRegistryImageRef(priorIdentity, request.environment, request.name, accountId);
1869
+ if (previousImage !== undefined) {
1870
+ eveProgress(options, `deleting superseded registry image ${previousImage}`);
1871
+ let deleted = "indeterminate";
1872
+ try {
1873
+ deleted = await (options.deleteRegistryImage ??
1874
+ defaultRegistryImageDelete)({ image: previousImage });
1875
+ }
1876
+ catch {
1877
+ deleted = "indeterminate";
1878
+ }
1879
+ supersededImageCleanup = deleted === "indeterminate"
1880
+ ? check("VAL-LIFE-007", "failed", `The update is healthy and promoted, but the superseded registry image ${previousImage} could not be proven deleted.`, `Remove only the recorded image ref ${previousImage}; do not retry or run broad registry cleanup.`)
1881
+ : check("VAL-LIFE-007", "passed", deleted === "deleted"
1882
+ ? "The superseded generation's registry image was deleted after promotion."
1883
+ : "The superseded generation's registry image was already absent.");
1884
+ }
1885
+ }
1642
1886
  let runtimeImageCleanup;
1643
1887
  if (runtimeEvidence.cleanup.imageRetained) {
1644
1888
  if (options.retainRuntimeImage === true) {
@@ -1662,13 +1906,18 @@ async function runEveDeployment(request, options, collected) {
1662
1906
  : check("VAL-CROSS-004", "failed", "The remote deployment is healthy, but exact local runtime-image cleanup could not be verified.", `Remove only the recorded image ${imageDigest}; do not retry or run broad Docker cleanup.`);
1663
1907
  }
1664
1908
  }
1909
+ const postPromotionChecks = [
1910
+ ...(supersededImageCleanup === undefined ? [] : [supersededImageCleanup]),
1911
+ ...(runtimeImageCleanup === undefined ? [] : [runtimeImageCleanup]),
1912
+ ];
1665
1913
  return {
1666
1914
  ...collected.result,
1667
- ok: runtimeImageCleanup?.status !== "failed",
1668
- ...(runtimeImageCleanup === undefined
1915
+ ok: runtimeImageCleanup?.status !== "failed" &&
1916
+ supersededImageCleanup?.status !== "failed",
1917
+ ...(postPromotionChecks.length === 0
1669
1918
  ? {}
1670
- : { checks: [...collected.result.checks, runtimeImageCleanup] }),
1671
- deployment: deploymentMetadata(identity, targetKey, "deployed"),
1919
+ : { checks: [...collected.result.checks, ...postPromotionChecks] }),
1920
+ deployment: deploymentMetadata(identity, targetKey, "deployed", operation, superseded),
1672
1921
  };
1673
1922
  }
1674
1923
  async function collectEvePreflight(request, options) {
@@ -1804,6 +2053,9 @@ async function collectEvePreflight(request, options) {
1804
2053
  }
1805
2054
  const candidate = packaging.candidate;
1806
2055
  const workflowWorld = await readCompiledWorldSpecifier(candidate);
2056
+ if (workflowWorld === "@moinulmoin/eden-world-cloudflare") {
2057
+ checks.push(check("EVE-WORLD", "passed", "world: Cloudflare Durable Object"));
2058
+ }
1807
2059
  warnings.push(...(await collectEveWorldWarnings(candidate, undefined)));
1808
2060
  const schedules = await readCompiledSchedules(candidate);
1809
2061
  // The runtime package is imported lazily so `eden` commands that never
@@ -1841,12 +2093,14 @@ async function collectEvePreflight(request, options) {
1841
2093
  }
1842
2094
  let cloudflare;
1843
2095
  let publicOrigin;
2096
+ let priorDeployment;
1844
2097
  if (request.command === "deploy") {
1845
2098
  eveProgress(options, "checking Cloudflare access and exact target");
1846
2099
  try {
1847
2100
  cloudflare = await readEveCloudflareTarget({
1848
2101
  projectId: packaging.project?.projectId ?? "unknown-project",
1849
2102
  sourceDigest: candidate.sourceDigest,
2103
+ projectRoot: request.projectRoot,
1850
2104
  environment: request.environment,
1851
2105
  name: request.name,
1852
2106
  }, options);
@@ -1868,6 +2122,17 @@ async function collectEvePreflight(request, options) {
1868
2122
  };
1869
2123
  }
1870
2124
  checks.push(...cloudflareChecks(cloudflare));
2125
+ // An owned target also requires its immutable record so the update can
2126
+ // carry the published Durable Object history forward append-only.
2127
+ if (cloudflare.target.state === "owned") {
2128
+ const record = await readEveDeploymentRecord(request.projectRoot, request.environment, request.name, cloudflare.accountId, packaging.project?.projectId === "unknown-project"
2129
+ ? undefined
2130
+ : packaging.project?.projectId);
2131
+ priorDeployment = record ?? undefined;
2132
+ if (record === undefined || record.durableObjects === undefined) {
2133
+ checks.push(check("VAL-CLI-007-TARGET-CONFLICT", "failed", "The exact target is Eden-owned, but its immutable deployment record does not carry a readable Durable Object history.", "Re-run the last successful deploy generation or destroy the exact target; updating without the recorded migration history could diverge Durable Object declarations."));
2134
+ }
2135
+ }
1871
2136
  publicOrigin = await resolvedCloudflareOrigin(cloudflare, request.name);
1872
2137
  if (publicOrigin === undefined) {
1873
2138
  checks.push(check("VAL-HOST-006", "failed", "The authenticated Cloudflare read did not prove the exact workers.dev origin before Eve initialization.", "Return one authenticated account ID, one workers.dev subdomain, and the exact Worker-origin binding."));
@@ -1999,6 +2264,7 @@ async function collectEvePreflight(request, options) {
1999
2264
  cloudflare = await readEveCloudflareTarget({
2000
2265
  projectId: packaging.project?.projectId ?? "unknown-project",
2001
2266
  sourceDigest: candidate.sourceDigest,
2267
+ projectRoot: request.projectRoot,
2002
2268
  environment: request.environment,
2003
2269
  name: request.name,
2004
2270
  }, options);
@@ -2042,6 +2308,8 @@ async function collectEvePreflight(request, options) {
2042
2308
  // reported as EVE_SCHEDULE_UNSUPPORTED and fire only while awake.
2043
2309
  schedules: expressible,
2044
2310
  cloudflare,
2311
+ ...(priorDeployment === undefined ? {} : { priorDeployment }),
2312
+ ...(workflowWorld === undefined ? {} : { workflowWorld }),
2045
2313
  };
2046
2314
  }
2047
2315
  export async function runEveControlPlane(request, options = {}) {
@@ -2087,7 +2355,7 @@ export async function runEveControlPlane(request, options = {}) {
2087
2355
  message: status === "indeterminate"
2088
2356
  ? "The Eve deployment outcome is indeterminate; inspect the retained exact ownership evidence before retrying."
2089
2357
  : status === "deployed"
2090
- ? "The Eve deployment is healthy and promoted, but exact local runtime-image cleanup failed; do not retry the deployment."
2358
+ ? "The Eve deployment is healthy and promoted, but exact image cleanup failed; do not retry the deployment."
2091
2359
  : "The Eve deployment failed before exact target promotion.",
2092
2360
  });
2093
2361
  }
@@ -2215,6 +2483,172 @@ async function parseEveDestroyRecord(recordPath, environment, name) {
2215
2483
  identity: verified,
2216
2484
  };
2217
2485
  }
2486
+ /**
2487
+ * Reads the immutable records for this target. The live identity comes from
2488
+ * CURRENT first, then the newest `deployed` record, then the newest record
2489
+ * of any status; the Durable Object history comes from the newest record
2490
+ * that carries one, healthy or not, so a failed update's published tags are
2491
+ * never dropped.
2492
+ */
2493
+ async function readEveDeploymentRecord(projectRoot, environment, name, accountId, projectId) {
2494
+ const targetsRoot = join(projectRoot, ".eden", "eve-deploy", "targets", `${environment}-${name}`);
2495
+ const generationsRoot = join(projectRoot, ".eden", "eve-deploy", "generations");
2496
+ const entries = await readdir(generationsRoot).catch(() => []);
2497
+ const scanned = [];
2498
+ const deploymentIds = new Set();
2499
+ const images = new Set();
2500
+ for (const entry of entries) {
2501
+ const generationRoot = join(generationsRoot, entry);
2502
+ const recordPath = join(generationRoot, "deployment.json");
2503
+ const details = await lstat(recordPath).catch(() => undefined);
2504
+ if (details === undefined || !details.isFile())
2505
+ continue;
2506
+ const parsed = await parseEveDeploymentRecord(recordPath, environment, name);
2507
+ if (parsed !== undefined) {
2508
+ // Only records for this account and (when known) this project may
2509
+ // arbitrate ownership or supply migration history.
2510
+ if ((accountId !== undefined && parsed.identity.accountId !== accountId) ||
2511
+ (projectId !== undefined && parsed.identity.projectId !== projectId)) {
2512
+ continue;
2513
+ }
2514
+ scanned.push({
2515
+ record: parsed,
2516
+ generationRoot,
2517
+ modifiedAt: details.mtimeMs,
2518
+ });
2519
+ const deploymentId = parsed.deploymentId;
2520
+ if (deploymentId !== undefined)
2521
+ deploymentIds.add(deploymentId);
2522
+ if (typeof parsed.identity.containerImage === "string") {
2523
+ images.add(parsed.identity.containerImage);
2524
+ }
2525
+ }
2526
+ }
2527
+ scanned.sort((left, right) => right.modifiedAt - left.modifiedAt);
2528
+ const current = await lstat(join(targetsRoot, "CURRENT")).catch(() => undefined);
2529
+ let live = scanned.find((entry) => entry.record.status === "deployed") ?? scanned[0];
2530
+ if (current !== undefined && current.isSymbolicLink()) {
2531
+ const generationRoot = await realpath(join(targetsRoot, "CURRENT")).catch(() => undefined);
2532
+ if (generationRoot !== undefined) {
2533
+ const pinned = scanned.find((entry) => entry.generationRoot === generationRoot);
2534
+ if (pinned !== undefined) {
2535
+ live = pinned;
2536
+ }
2537
+ else {
2538
+ const parsed = await parseEveDeploymentRecord(join(generationRoot, "deployment.json"), environment, name);
2539
+ if (parsed !== undefined &&
2540
+ !((accountId !== undefined &&
2541
+ parsed.identity.accountId !== accountId) ||
2542
+ (projectId !== undefined &&
2543
+ parsed.identity.projectId !== projectId))) {
2544
+ live = { record: parsed, generationRoot, modifiedAt: 0 };
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ if (live === undefined)
2550
+ return undefined;
2551
+ // Newest record carrying a usable DO history wins, regardless of its
2552
+ // deployment outcome: a tag it published must never be replayed.
2553
+ const historySource = scanned.find((entry) => entry.record.durableObjects !== undefined);
2554
+ return {
2555
+ recordPath: live.record.recordPath,
2556
+ generationRoot: live.generationRoot,
2557
+ identity: live.record.identity,
2558
+ status: live.record.status,
2559
+ deploymentIds: [...deploymentIds],
2560
+ images: [...images],
2561
+ worldCloudflare: live.record.worldCloudflare,
2562
+ durableObjects: historySource?.record.durableObjects,
2563
+ };
2564
+ }
2565
+ async function parseEveDeploymentRecord(recordPath, environment, name) {
2566
+ const base = await parseEveDestroyRecord(recordPath, environment, name);
2567
+ if (base === undefined)
2568
+ return undefined;
2569
+ let parsed;
2570
+ try {
2571
+ parsed = JSON.parse(await readFile(recordPath, "utf8"));
2572
+ }
2573
+ catch {
2574
+ return undefined;
2575
+ }
2576
+ if (typeof parsed !== "object" || parsed === null)
2577
+ return undefined;
2578
+ const record = parsed;
2579
+ const identityRaw = parsed.identity;
2580
+ const deploymentId = typeof identityRaw === "object" &&
2581
+ identityRaw !== null &&
2582
+ typeof identityRaw
2583
+ .deploymentId === "string"
2584
+ ? identityRaw.deploymentId
2585
+ : undefined;
2586
+ const status = typeof record.status === "string" &&
2587
+ ["deployed", "failed", "indeterminate"].includes(record.status)
2588
+ ? record.status
2589
+ : undefined;
2590
+ const worker = typeof record.worker === "object" && record.worker !== null
2591
+ ? record.worker
2592
+ : undefined;
2593
+ const rawBindings = worker !== undefined && "durable_objects" in worker &&
2594
+ typeof worker.durable_objects === "object" &&
2595
+ worker.durable_objects !== null &&
2596
+ "bindings" in worker.durable_objects
2597
+ ? worker.durable_objects.bindings
2598
+ : undefined;
2599
+ const rawMigrations = worker !== undefined && "migrations" in worker
2600
+ ? worker.migrations
2601
+ : undefined;
2602
+ const rawVars = worker !== undefined && "vars" in worker &&
2603
+ typeof worker.vars === "object" && worker.vars !== null
2604
+ ? worker.vars
2605
+ : undefined;
2606
+ const bindings = [];
2607
+ const migrations = [];
2608
+ const durableObjectsValid = Array.isArray(rawBindings) &&
2609
+ Array.isArray(rawMigrations);
2610
+ if (durableObjectsValid) {
2611
+ for (const binding of rawBindings) {
2612
+ if (typeof binding !== "object" || binding === null ||
2613
+ typeof binding.name !== "string" ||
2614
+ typeof binding.class_name !== "string") {
2615
+ return {
2616
+ recordPath,
2617
+ identity: base.identity,
2618
+ status,
2619
+ deploymentId,
2620
+ worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
2621
+ durableObjects: undefined,
2622
+ };
2623
+ }
2624
+ bindings.push(binding);
2625
+ }
2626
+ for (const migration of rawMigrations) {
2627
+ if (typeof migration !== "object" || migration === null ||
2628
+ typeof migration.tag !== "string" ||
2629
+ !Array.isArray(migration.new_sqlite_classes) ||
2630
+ migration.new_sqlite_classes.some((className) => typeof className !== "string")) {
2631
+ return {
2632
+ recordPath,
2633
+ identity: base.identity,
2634
+ status,
2635
+ deploymentId,
2636
+ worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
2637
+ durableObjects: undefined,
2638
+ };
2639
+ }
2640
+ migrations.push(migration);
2641
+ }
2642
+ }
2643
+ return {
2644
+ recordPath,
2645
+ identity: base.identity,
2646
+ status,
2647
+ deploymentId,
2648
+ worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
2649
+ durableObjects: durableObjectsValid ? { bindings, migrations } : undefined,
2650
+ };
2651
+ }
2218
2652
  async function defaultDestroyTargetRead(request) {
2219
2653
  const account = await readWranglerJson(["whoami", "--json"]);
2220
2654
  const deployments = await readWranglerJson([
@@ -1,4 +1,4 @@
1
- export declare const EVE_RESERVED_HOST_VARIABLES: readonly ["HOST", "NITRO_HOST", "PORT", "NITRO_PORT", "NODE_ENV", "NODE_EXTRA_CA_CERTS", "WORKFLOW_LOCAL_BASE_URL", "EDEN_EVE_DEPLOYMENT_ID", "EDEN_EVE_GENERATION_ID"];
1
+ export declare const EVE_RESERVED_HOST_VARIABLES: readonly ["HOST", "NITRO_HOST", "PORT", "NITRO_PORT", "NODE_ENV", "NODE_EXTRA_CA_CERTS", "WORKFLOW_LOCAL_BASE_URL", "EDEN_WORLD_URL", "EDEN_EVE_DEPLOYMENT_ID", "EDEN_EVE_GENERATION_ID"];
2
2
  export type EveReservedHostVariable = (typeof EVE_RESERVED_HOST_VARIABLES)[number];
3
3
  export declare const EVE_START_COMMAND: readonly ["./node_modules/.bin/eve", "start", "--host", "0.0.0.0", "--port", "8080"];
4
4
  type EveRuntimeValueMap = Readonly<Record<string, string>>;
@@ -9,6 +9,7 @@ export const EVE_RESERVED_HOST_VARIABLES = Object.freeze([
9
9
  "NODE_ENV",
10
10
  "NODE_EXTRA_CA_CERTS",
11
11
  "WORKFLOW_LOCAL_BASE_URL",
12
+ "EDEN_WORLD_URL",
12
13
  "EDEN_EVE_DEPLOYMENT_ID",
13
14
  "EDEN_EVE_GENERATION_ID",
14
15
  ]);
@@ -275,7 +276,8 @@ export class EveRuntimeConfig {
275
276
  createStartEnvironment(hostEnvironment) {
276
277
  this.assertUsable();
277
278
  for (const name of Object.keys(hostEnvironment)) {
278
- if (!EVE_RESERVED_HOST_VARIABLES.includes(name)) {
279
+ if (!EVE_RESERVED_HOST_VARIABLES.includes(name) &&
280
+ name !== "CBOR_NATIVE_ACCELERATION_DISABLED") {
279
281
  throw new EveRuntimeConfigError({
280
282
  code: "EVE_RUNTIME_INJECTION_INVALID",
281
283
  message: `The Eve start environment contains a non-host variable ${name}.`,
@@ -564,6 +564,12 @@ function runtimeContainerEnvironment(request) {
564
564
  ...(request.publicOrigin === undefined
565
565
  ? {}
566
566
  : { WORKFLOW_LOCAL_BASE_URL: request.publicOrigin }),
567
+ ...(request.workflowWorld === "@moinulmoin/eden-world-cloudflare"
568
+ ? {
569
+ EDEN_WORLD_URL: `${request.publicOrigin ?? "http://localhost:8080"}/__eden/world/rpc`,
570
+ CBOR_NATIVE_ACCELERATION_DISABLED: "true",
571
+ }
572
+ : {}),
567
573
  };
568
574
  }
569
575
  function validateHealthPort(port) {
package/dist/eve.js CHANGED
@@ -49,7 +49,7 @@ Options:
49
49
  `,
50
50
  deploy: `Usage: eden deploy [--project <path>] [--env <preview|production>] [--name <name>] [--env-file <path>] [--json]
51
51
 
52
- Deploy the selected Eve project to one exact target after host checks pass.
52
+ Deploy the selected Eve project to one exact target after host checks pass. When the exact target already exists and Eden's immutable record proves it owns it, deploy updates the Worker in place and keeps Durable Object data; a target Eden cannot prove it owns still fails the conflict check.
53
53
 
54
54
  Options:
55
55
  --project <path> Eve project root (default: current directory)
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export type { EveCliCommand, EveCliEnvironment, EveCliExecutionRequest, EveCliHe
10
10
  export type { EveNodeImage, EvePackagingCheck, EvePackagingCode, EveProjectBuilder, EveProjectBuilderRequest, EveProjectBuilderResult, EveProjectBuildCandidate, EveProjectFile, EveProjectImage, EveProjectInputManifest, EveProjectOutput, EveProjectPackagingResult, EveProjectRuntime, EveProjectSnapshot, EveProjectSnapshotOptions, EveProjectToolchain, EveRuntimeConfigExclusion, } from "./eve-packaging.js";
11
11
  export type { EveHostRequirements, EveRuntimeClosure, EveRuntimeClosureFile, EveRuntimeCleanup, EveRuntimeImage, EveRuntimeImageDiscardRequest, EveRuntimeImageMetadata, EveRuntimeImageRequest, EveRuntimeImageResult, EveRuntimeNativeModule, EveRuntimeStatus, } from "./eve-runtime-image.js";
12
12
  export { DEFAULT_EVE_HOST_REQUIREMENTS, DEFAULT_EVE_NODE_IMAGE, runEveControlPlane, runEveDestroy, } from "./eve-control-plane.js";
13
- export type { EveCloudflareReadRequest, EveCloudflareReadResult, EveCloudflareReadRunner, EveCloudflareTargetState, EveContainerDeleteRunner, EveDeploymentCompensationRequest, EveDeploymentCompensationRunner, EveDeploymentHealthRequest, EveDeploymentHealthResult, EveDeploymentHealthRunner, EveDeploymentIdentity, EveDeploymentIdentityProof, EveDeploymentMetadata, EveDeploymentPublicationRequest, EveDeploymentPublicationResult, EveDeploymentPublicationRunner, EveDeploymentStatus, EveDestroyCloudflareReadRequest, EveDestroyCloudflareReadRunner, EveDestroyOutcome, EveDestroyTargetRead, EveImagePublicationRequest, EveImagePublicationResult, EveImagePublicationRunner, EvePreflightCandidate, EvePreflightResult, EvePreflightRuntimeEvidence, EvePreflightWarning, EvePreflightRuntimeRunner, EvePreflightRuntimeRunnerRequest, EveRuntimeConfigLoader, EveRuntimeImageDiscardRunner, EveWorkerDeleteRunner, } from "./eve-control-plane.js";
13
+ export type { EveCloudflareReadRequest, EveCloudflareReadResult, EveCloudflareReadRunner, EveCloudflareTargetState, EveContainerDeleteRunner, EveDeploymentCompensationRequest, EveDeploymentCompensationRunner, EveDeploymentHealthRequest, EveDeploymentHealthResult, EveDeploymentHealthRunner, EveDeploymentIdentity, EveDeploymentIdentityProof, EveDeploymentMetadata, EveDeploymentPublicationRequest, EveDeploymentPublicationResult, EveDeploymentPublicationRunner, EveDeploymentStatus, EveDestroyCloudflareReadRequest, EveDestroyCloudflareReadRunner, EveDestroyOutcome, EveDestroyTargetRead, EveImagePublicationRequest, EveImagePublicationResult, EveImagePublicationRunner, EvePreflightCandidate, EvePreflightResult, EvePreflightRuntimeEvidence, EvePreflightWarning, EvePreflightRuntimeRunner, EvePreflightRuntimeRunnerRequest, EveRegistryImageDeleteRunner, EveRuntimeConfigLoader, EveRuntimeImageDiscardRunner, EveWorkerDeleteRunner, } from "./eve-control-plane.js";
14
14
  export declare const EDEN_CLI_COMMANDS: readonly ["preflight", "deploy", "destroy"];
15
15
  export type EdenCliCommand = (typeof EDEN_CLI_COMMANDS)[number];
16
16
  export interface EdenCliRunOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moinulmoin/eden",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Deploy existing Eve projects to your own Cloudflare account.",
5
5
  "keywords": [
6
6
  "ai",
@@ -31,7 +31,7 @@
31
31
  "NOTICE"
32
32
  ],
33
33
  "dependencies": {
34
- "@moinulmoin/eden-runtime-cloudflare": "0.3.0",
34
+ "@moinulmoin/eden-runtime-cloudflare": "0.5.0",
35
35
  "wrangler": "4.120.0"
36
36
  },
37
37
  "devDependencies": {