@moinulmoin/eden 0.11.1 → 0.11.2

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/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.11.1
15
+ npm install --global @moinulmoin/eden@0.11.2
16
16
  ```
17
17
 
18
18
  ```sh
19
- pnpm add --global @moinulmoin/eden@0.11.1
19
+ pnpm add --global @moinulmoin/eden@0.11.2
20
20
  ```
21
21
 
22
22
  ```sh
23
- bun add --global @moinulmoin/eden@0.11.1
23
+ bun add --global @moinulmoin/eden@0.11.2
24
24
  ```
25
25
 
26
26
  Then confirm the CLI is available:
package/dist/SKILL.md CHANGED
@@ -70,7 +70,10 @@ save a build.
70
70
  `agent/channels/eve.ts` (`vercelOidc()`, `localDev()`, `placeholderAuth()`)
71
71
  rejects every Cloudflare caller with 401. Replace it with a real
72
72
  authenticator, for example a bearer token read from the env file. Keep
73
- `localDev()` so `eve dev` still works locally; it is ignored in production:
73
+ `localDev()` so `eve dev` still works locally. Never set `EVE_DEV`,
74
+ `VERCEL`, `VERCEL_ENV`, or `VERCEL_TARGET_ENV` in the deployment env file:
75
+ they switch Eve into development mode and can let unauthenticated callers
76
+ in. Eden rejects these names with `EVE_ENV_RESERVED_VARIABLE`:
74
77
 
75
78
  ```ts
76
79
  import { localDev, type AuthFn } from "eve/channels/auth";
@@ -88,16 +88,17 @@ export interface EveCloudflareReadResult {
88
88
  * Provider-backed facts about the live target, read through the
89
89
  * Cloudflare API (not self-asserted response headers, which any Worker
90
90
  * can forge): the Container application's Durable Object namespace and
91
- * the `EDEN_EVE_DEPLOYMENT_ID` plain-text var Eden stamps into every
92
- * Worker it publishes. `settingsReadable` tells the ownership resolver
93
- * whether a missing var is evidence of a foreign Worker or just an
94
- * unreadable settings call.
91
+ * the `EDEN_EVE_DEPLOYMENT_ID` and `EDEN_EVE_RELEASE_ID` plain-text vars
92
+ * Eden stamps into every Worker it publishes. `settingsReadable` tells
93
+ * the ownership resolver whether a missing var is evidence of a foreign
94
+ * Worker or just an unreadable settings call.
95
95
  */
96
96
  readonly providerEvidence?: {
97
97
  readonly containerApplicationId?: string;
98
98
  readonly containerSchedulingPolicy?: string;
99
99
  readonly containerNamespaceId?: string;
100
100
  readonly workerDeploymentId?: string;
101
+ readonly workerReleaseId?: string;
101
102
  readonly workerContainerNamespaceId?: string;
102
103
  readonly settingsReadable?: boolean;
103
104
  };
@@ -284,6 +285,8 @@ export interface EvePreflightOptions {
284
285
  }
285
286
  export interface EveDestroyTargetRead {
286
287
  readonly workerExists: boolean;
288
+ readonly inventoryComplete?: boolean;
289
+ readonly providerEvidence?: EveCloudflareReadResult["target"]["providerEvidence"];
287
290
  readonly containerApplicationId?: string;
288
291
  readonly accountId?: string;
289
292
  /**
@@ -2,8 +2,8 @@ import { setTimeout as waitForActivation } from "node:timers/promises";
2
2
  import { createRequire, } from "node:module";
3
3
  import { createHash, randomUUID, } from "node:crypto";
4
4
  import { join, dirname, relative, } from "node:path";
5
- import { lstat, mkdir, readFile, readdir, realpath, rename, rm, symlink, writeFile, } from "node:fs/promises";
6
- import { homedir, } from "node:os";
5
+ import { lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, symlink, writeFile, } from "node:fs/promises";
6
+ import { homedir, tmpdir, } from "node:os";
7
7
  import { createServer, } from "node:net";
8
8
  import { execFile, spawn, } from "node:child_process";
9
9
  import { fileURLToPath, } from "node:url";
@@ -26,7 +26,6 @@ export const DEFAULT_EVE_HOST_REQUIREMENTS = Object.freeze({
26
26
  network: "supported",
27
27
  durableLocalFilesystem: false,
28
28
  });
29
- const wranglerCwd = dirname(wranglerEntrypoint);
30
29
  export const DEFAULT_EVE_NODE_IMAGE = Object.freeze({
31
30
  reference: "node:24.17.0-bookworm-slim",
32
31
  digest: "sha256:862263c612aa437e3037674b85419622a9d93bff80aa1eee5398dfe686375532",
@@ -223,11 +222,15 @@ async function checkEveWorldPairing(candidate, world) {
223
222
  return undefined;
224
223
  const eveManifest = await readInstalledManifest(candidate.snapshotRoot, "eve");
225
224
  const worldManifest = await readInstalledManifest(candidate.snapshotRoot, world);
225
+ if (world === "@moinulmoin/eden-world-cloudflare" &&
226
+ worldManifest?.eden?.capabilities?.worldRpcAuth !== true) {
227
+ return check("EVE_WORLD_PAIRING", "failed", "The installed Cloudflare World lets the agent's sandbox read and change every session's workflow state.", "Install @moinulmoin/eden-world-cloudflare@0.11.2 or later; see docs/troubleshooting.md.");
228
+ }
226
229
  if (world === "@moinulmoin/eden-world-cloudflare" &&
227
230
  worldManifest !== undefined &&
228
231
  eveRequiresAtomicStartHook(eveManifest?.version) &&
229
232
  worldManifest.eden?.capabilities?.atomicStartHook !== true) {
230
- return check("EVE_WORLD_PAIRING", "failed", `Eve ${eveManifest?.version} needs a World with atomic start hooks, and the installed ${world} ${worldManifest.version ?? "(unknown version)"} does not have them; every new session would fail with HTTP 500.`, `Install ${world}@0.11.1 or later; see docs/troubleshooting.md.`);
233
+ return check("EVE_WORLD_PAIRING", "failed", `Eve ${eveManifest?.version} needs a World with atomic start hooks, and the installed ${world} ${worldManifest.version ?? "(unknown version)"} does not have them; every new session would fail with HTTP 500.`, `Install ${world}@0.11.2 or later; see docs/troubleshooting.md.`);
231
234
  }
232
235
  const expectedRange = eveManifest === undefined
233
236
  ? undefined
@@ -588,6 +591,7 @@ function safeWranglerEnvironment() {
588
591
  LANG: process.env.LANG,
589
592
  LC_ALL: process.env.LC_ALL,
590
593
  CLOUDFLARE_API_TOKEN: process.env.CLOUDFLARE_API_TOKEN,
594
+ CLOUDFLARE_API_BASE_URL: "https://api.cloudflare.com/client/v4",
591
595
  NODE_OPTIONS: undefined,
592
596
  };
593
597
  }
@@ -602,14 +606,23 @@ function safeDockerEnvironment() {
602
606
  DOCKER_CONTEXT: process.env.DOCKER_CONTEXT,
603
607
  };
604
608
  }
609
+ async function withWranglerDirectory(operation) {
610
+ const cwd = await mkdtemp(join(tmpdir(), "eden-wrangler-"));
611
+ try {
612
+ return await operation(cwd);
613
+ }
614
+ finally {
615
+ await rm(cwd, { recursive: true, force: true });
616
+ }
617
+ }
605
618
  async function readWranglerJson(args, timeoutMs = 30_000) {
606
619
  try {
607
- const result = await execFileAsync(process.execPath, [wranglerEntrypoint, ...args], {
620
+ const result = await withWranglerDirectory((cwd) => execFileAsync(process.execPath, [wranglerEntrypoint, ...args], {
608
621
  env: safeWranglerEnvironment(),
609
- cwd: wranglerCwd,
622
+ cwd,
610
623
  maxBuffer: 2 * 1024 * 1024,
611
624
  timeout: timeoutMs,
612
- });
625
+ }));
613
626
  const parsed = JSON.parse(result.stdout.trim());
614
627
  return { value: parsed, failed: false };
615
628
  }
@@ -744,6 +757,8 @@ export function exactTargetContainerEntries(value, name) {
744
757
  return exactContainerEntries(value, boundedResourceName(name, "container"));
745
758
  }
746
759
  async function readExactTargetInventory(workerName, containerApp) {
760
+ // Wrangler's JSON output does not expose a pagination cursor. A full page
761
+ // without the exact name remains indeterminate in exactContainerEntries.
747
762
  const containers = await readWranglerJson([
748
763
  "containers", "list", "--json", "--per-page", "100",
749
764
  ]);
@@ -753,7 +768,7 @@ async function readExactTargetInventory(workerName, containerApp) {
753
768
  return {
754
769
  containers,
755
770
  deployments,
756
- containerEntries: exactContainerEntries(containers.value, containerApp),
771
+ containerEntries: containers.failed ? undefined : exactContainerEntries(containers.value, containerApp),
757
772
  deploymentEntries: exactDeploymentEntries(deployments.value),
758
773
  };
759
774
  }
@@ -883,6 +898,11 @@ async function readProviderEvidence(workerName, accountId, workerPresent, contai
883
898
  if (typeof deploymentBinding?.text === "string") {
884
899
  evidence.workerDeploymentId = deploymentBinding.text;
885
900
  }
901
+ const releaseBinding = bindings.find((value) => value.type === "plain_text" &&
902
+ value.name === "EDEN_EVE_RELEASE_ID");
903
+ if (typeof releaseBinding?.text === "string") {
904
+ evidence.workerReleaseId = releaseBinding.text;
905
+ }
886
906
  const containerBinding = bindings.find((value) => value.type === "durable_object_namespace" &&
887
907
  value.name === "EVE_CONTAINER");
888
908
  if (typeof containerBinding?.namespace_id === "string") {
@@ -901,12 +921,9 @@ async function readProviderEvidence(workerName, accountId, workerPresent, contai
901
921
  return evidence;
902
922
  }
903
923
  /**
904
- * Resolves `unowned` reads against Eden's immutable deployment records AND
905
- * the live remote target. The local record proves Eden once created the
906
- * target; the remote Worker's stamped identity proves it is still the one
907
- * Eden deployed — a same-name Worker recreated by something else after the
908
- * target was destroyed (or a forged local record) must never be upgraded to
909
- * `owned`.
924
+ * Resolves local deployment history against authenticated provider state.
925
+ * This detects name reuse and stale records, but deployment IDs are public:
926
+ * a malicious forged local record is not an independent ownership proof.
910
927
  */
911
928
  async function resolveEveTargetOwnership(request, result) {
912
929
  if (result.target.state !== "unowned")
@@ -930,10 +947,18 @@ async function resolveEveTargetOwnership(request, result) {
930
947
  // its own settings: unreadable settings fail closed.
931
948
  const evidence = result.target.providerEvidence;
932
949
  const recordedDeploymentIds = new Set(record.deploymentIds);
950
+ // Every Eden project on the same Eve version publishes the same affinity
951
+ // deployment ID, so the per-publish release ID is what tells a same-name
952
+ // Worker from another project apart. Workers from before release IDs
953
+ // existed used the release ID as their deployment ID.
954
+ const recordedReleaseIds = new Set(record.releaseIds);
955
+ const workerReleaseId = evidence?.workerReleaseId ?? evidence?.workerDeploymentId;
933
956
  const workerProven = result.target.observed?.worker !== true ||
934
957
  (evidence?.settingsReadable === true &&
935
958
  evidence.workerDeploymentId !== undefined &&
936
- recordedDeploymentIds.has(evidence.workerDeploymentId));
959
+ recordedDeploymentIds.has(evidence.workerDeploymentId) &&
960
+ workerReleaseId !== undefined &&
961
+ recordedReleaseIds.has(workerReleaseId));
937
962
  // The Container proof is the application running the durable_object
938
963
  // scheduling policy with a namespace that matches the Worker binding's
939
964
  // namespace; anything else fails closed.
@@ -954,6 +979,8 @@ async function resolveEveTargetOwnership(request, result) {
954
979
  failures.push("Worker deployment stamp absent");
955
980
  else if (!recordedDeploymentIds.has(evidence.workerDeploymentId))
956
981
  failures.push("Worker deployment stamp differs from recorded identities");
982
+ else if (workerReleaseId === undefined || !recordedReleaseIds.has(workerReleaseId))
983
+ failures.push("Worker release stamp differs from recorded releases");
957
984
  }
958
985
  if (result.target.observed?.container === true) {
959
986
  if (evidence?.containerSchedulingPolicy !== "durable_object")
@@ -1252,8 +1279,9 @@ async function writeEveDeploymentArtifacts(candidate, hostConfig, workerSource)
1252
1279
  const WRANGLER_OPERATION_TIMEOUT_MS = 120_000;
1253
1280
  /** Deadline for `wrangler deploy`, whose Container image preparation alone can approach two minutes. */
1254
1281
  const WRANGLER_PUBLICATION_TIMEOUT_MS = 600_000;
1255
- function runWranglerWithInput(args, cwd, input, timeoutMs = WRANGLER_OPERATION_TIMEOUT_MS) {
1256
- return new Promise((resolveResult) => {
1282
+ async function runWranglerWithInput(args, input, timeoutMs = WRANGLER_OPERATION_TIMEOUT_MS) {
1283
+ // The project's TypeScript lib predates Promise.withResolvers.
1284
+ return withWranglerDirectory((cwd) => new Promise((resolveResult) => {
1257
1285
  const child = spawn(process.execPath, [wranglerEntrypoint, ...args], {
1258
1286
  cwd,
1259
1287
  env: safeWranglerEnvironment(),
@@ -1298,7 +1326,7 @@ function runWranglerWithInput(args, cwd, input, timeoutMs = WRANGLER_OPERATION_T
1298
1326
  child.stdin.end();
1299
1327
  else
1300
1328
  child.stdin.end(input);
1301
- });
1329
+ }));
1302
1330
  }
1303
1331
  function defaultProtectedStore(workerName) {
1304
1332
  return {
@@ -1310,7 +1338,7 @@ function defaultProtectedStore(workerName) {
1310
1338
  name,
1311
1339
  "--name",
1312
1340
  workerName,
1313
- ], process.cwd(), request.values[name] === undefined ? "" : `${request.values[name]}\n`);
1341
+ ], request.values[name] === undefined ? "" : `${request.values[name]}\n`);
1314
1342
  if (result.exitCode !== 0) {
1315
1343
  throw new EveRuntimeConfigError({
1316
1344
  code: "EVE_RUNTIME_PROTECTED_UPLOAD_FAILED",
@@ -1332,7 +1360,7 @@ async function defaultImagePublicationRunner(request) {
1332
1360
  cwd: request.generationRoot,
1333
1361
  maxBuffer: 512 * 1024,
1334
1362
  });
1335
- const pushed = await runWranglerWithInput(["containers", "push", request.localImageTag], request.generationRoot);
1363
+ const pushed = await runWranglerWithInput(["containers", "push", request.localImageTag]);
1336
1364
  if (pushed.exitCode !== 0) {
1337
1365
  return {
1338
1366
  status: "indeterminate",
@@ -1398,7 +1426,7 @@ function defaultPublicationRunner() {
1398
1426
  "--env",
1399
1427
  request.identity.environment,
1400
1428
  "--strict",
1401
- ], dirname(request.wranglerConfigPath), undefined, WRANGLER_PUBLICATION_TIMEOUT_MS);
1429
+ ], undefined, WRANGLER_PUBLICATION_TIMEOUT_MS);
1402
1430
  if (result.exitCode !== 0) {
1403
1431
  return {
1404
1432
  status: "indeterminate",
@@ -1795,15 +1823,32 @@ async function runEveDeployment(request, options, collected) {
1795
1823
  ...(priorIdentity.runtimeRevisionHandle === undefined ? {} : { runtimeRevisionHandle: priorIdentity.runtimeRevisionHandle }),
1796
1824
  };
1797
1825
  }
1798
- const matchingDeployment = Object.entries(retainedDeployments).find(([, entry]) => entry.eveVersion === candidate.eveVersion && entry.affinityContract === EDEN_AFFINITY_CONTRACT)?.[0];
1826
+ const matchingDeployment = Object.entries(retainedDeployments).find(([, entry]) => entry.worldRpcAuth === true && entry.eveVersion === candidate.eveVersion && entry.affinityContract === EDEN_AFFINITY_CONTRACT)?.[0];
1827
+ const affinityDeploymentId = `eden-affinity-${createHash("sha256").update(`${candidate.eveVersion}:${EDEN_AFFINITY_CONTRACT}`).digest("hex").slice(0, 24)}`;
1799
1828
  const deploymentId = !affinityEnabled ? releaseId : matchingDeployment ??
1800
- `eden-affinity-${createHash("sha256").update(`${candidate.eveVersion}:${EDEN_AFFINITY_CONTRACT}`).digest("hex").slice(0, 24)}`;
1829
+ (affinityDeploymentId in retainedDeployments ? `${affinityDeploymentId}-${randomUUID().slice(0, 8)}` : affinityDeploymentId);
1801
1830
  const retiringDeployments = { ...priorIdentity?.pendingRetirements };
1802
1831
  delete retiringDeployments[deploymentId];
1803
1832
  if (priorIdentity?.deploymentId !== undefined)
1804
1833
  delete retiringDeployments[priorIdentity.deploymentId];
1805
1834
  for (const id of prior?.protectedDeploymentIds ?? [])
1806
1835
  delete retiringDeployments[id];
1836
+ // Old containers can access every session's World without authentication.
1837
+ // Drop their routes even when live sessions or transfers would prevent drain.
1838
+ const insecureRetirements = affinityEnabled
1839
+ ? Object.entries(retainedDeployments).filter(([id, entry]) => id !== deploymentId && entry.worldRpcAuth !== true)
1840
+ : [];
1841
+ for (const [id, entry] of insecureRetirements) {
1842
+ retiringDeployments[id] = entry;
1843
+ delete retainedDeployments[id];
1844
+ }
1845
+ if (insecureRetirements.length > 0) {
1846
+ resultWarnings.push({
1847
+ id: "EVE_UNAUTHENTICATED_VERSIONS_RETIRED",
1848
+ message: `${insecureRetirements.length} kept version${insecureRetirements.length === 1 ? " was" : "s were"} retired because World RPC was unauthenticated; their sessions ended.`,
1849
+ remediation: 'Old sessions now fail; Eve may report "Failed to send the session message." Start new sessions on the current deployment.',
1850
+ });
1851
+ }
1807
1852
  const hasRetainedNoncurrentDeployments = priorIdentity?.retainedDeployments !== undefined &&
1808
1853
  Object.keys(retainedDeployments).some((id) => id !== priorIdentity.deploymentId);
1809
1854
  const controlToken = `${randomUUID()}${randomUUID()}`;
@@ -2256,10 +2301,11 @@ async function runEveDeployment(request, options, collected) {
2256
2301
  }
2257
2302
  completedRetirements.push(id);
2258
2303
  }
2259
- supersededImageCleanup = check("VAL-LIFE-007", "passed", "Drained deployment instances and their owned registry repositories were retired.");
2304
+ supersededImageCleanup = check("VAL-LIFE-007", "passed", "Retired deployment instances and their owned registry repositories were removed.");
2260
2305
  }
2261
- catch {
2262
- supersededImageCleanup = check("VAL-LIFE-007", "failed", "The deployment is healthy, but drained resource retirement was not proven; exact pending retirement records were retained for retry.", "Retry deploy to finish only recorded drained resource cleanup.");
2306
+ catch (error) {
2307
+ const reason = error instanceof Error ? safeText(error.message) : "Unknown retirement failure.";
2308
+ supersededImageCleanup = check("VAL-LIFE-007", "failed", `The deployment is healthy, but resource retirement was not proven: ${reason} Exact pending retirement records were retained for retry.`, "Retry deploy to finish only recorded retired resource cleanup.");
2263
2309
  }
2264
2310
  if (completedRetirements.length > 0) {
2265
2311
  // Keep the immutable deployment record as evidence, but exclude proven
@@ -2959,6 +3005,7 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
2959
3005
  const entries = await readdir(generationsRoot).catch(() => []);
2960
3006
  const scanned = [];
2961
3007
  const deploymentIds = new Set();
3008
+ const releaseIds = new Set();
2962
3009
  for (const entry of entries) {
2963
3010
  const generationRoot = join(generationsRoot, entry);
2964
3011
  const recordPath = await deploymentRecordPath(generationRoot);
@@ -2981,6 +3028,9 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
2981
3028
  const deploymentId = parsed.deploymentId;
2982
3029
  if (deploymentId !== undefined)
2983
3030
  deploymentIds.add(deploymentId);
3031
+ const releaseId = parsed.identity.releaseId ?? deploymentId;
3032
+ if (releaseId !== undefined)
3033
+ releaseIds.add(releaseId);
2984
3034
  }
2985
3035
  }
2986
3036
  scanned.sort((left, right) => right.modifiedAt - left.modifiedAt);
@@ -3037,6 +3087,7 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
3037
3087
  protectedDeploymentIds: [...protectedDeploymentIds],
3038
3088
  status: live.record.status,
3039
3089
  deploymentIds: [...deploymentIds],
3090
+ releaseIds: [...releaseIds],
3040
3091
  worldCloudflare: live.record.worldCloudflare,
3041
3092
  durableObjects: historySource?.record.durableObjects,
3042
3093
  };
@@ -3137,10 +3188,12 @@ async function parseEveDeploymentRecord(recordPath, environment, name) {
3137
3188
  }
3138
3189
  async function defaultDestroyTargetRead(request) {
3139
3190
  const account = await readWranglerJson(["whoami", "--json"]);
3140
- const { deployments, containerEntries: entries, deploymentEntries } = await readExactTargetInventory(request.workerName, request.containerApplicationName);
3191
+ const { containers, deployments, containerEntries: entries, deploymentEntries: listedDeployments } = await readExactTargetInventory(request.workerName, request.containerApplicationName);
3141
3192
  const accountId = authenticatedAccountId(account.value);
3142
- const workerExists = !deployments.failed &&
3143
- (deploymentEntries?.length ?? 0) > 0;
3193
+ const deploymentEntries = deployments.failed &&
3194
+ /(?:does not exist|not found|10007)/iu.test(deployments.stderr ?? "")
3195
+ ? [] : deployments.failed ? undefined : listedDeployments;
3196
+ const workerExists = (deploymentEntries?.length ?? 0) > 0;
3144
3197
  const firstContainer = entries === undefined ? undefined : entries[0];
3145
3198
  const ownedRepositories = request.registryRepositories;
3146
3199
  let registryTagsPresent;
@@ -3165,6 +3218,10 @@ async function defaultDestroyTargetRead(request) {
3165
3218
  }
3166
3219
  return {
3167
3220
  workerExists,
3221
+ inventoryComplete: !account.failed && accountId !== undefined &&
3222
+ !containers.failed && entries !== undefined && entries.length <= 1 &&
3223
+ deploymentEntries !== undefined,
3224
+ providerEvidence: await readProviderEvidence(request.workerName, accountId, workerExists, firstContainer),
3168
3225
  ...(firstContainer === undefined
3169
3226
  ? {}
3170
3227
  : { containerApplicationId: firstContainer.id }),
@@ -3204,7 +3261,7 @@ function registryTagsByRepository(value) {
3204
3261
  return tagsByRepository;
3205
3262
  }
3206
3263
  async function defaultWorkerDelete(request) {
3207
- const result = await runWranglerWithInput(["delete", "--name", request.name, "--force"], process.cwd(), "y\n");
3264
+ const result = await runWranglerWithInput(["delete", "--name", request.name, "--force"], "y\n");
3208
3265
  if (result.exitCode === 0)
3209
3266
  return "deleted";
3210
3267
  if (/(?:does not exist|not found|10007)/iu.test(result.stderr)) {
@@ -3213,7 +3270,7 @@ async function defaultWorkerDelete(request) {
3213
3270
  return "indeterminate";
3214
3271
  }
3215
3272
  async function defaultContainerDelete(request) {
3216
- const result = await runWranglerWithInput(["containers", "delete", request.applicationId], process.cwd());
3273
+ const result = await runWranglerWithInput(["containers", "delete", request.applicationId]);
3217
3274
  if (result.exitCode === 0)
3218
3275
  return "deleted";
3219
3276
  if (/(?:does not exist|not found|not supported|10007)/iu.test(result.stderr)) {
@@ -3256,7 +3313,7 @@ async function defaultRegistryRepositoryDelete(request) {
3256
3313
  "delete",
3257
3314
  `${request.repository}:${tag}`,
3258
3315
  "--skip-confirmation",
3259
- ], process.cwd());
3316
+ ]);
3260
3317
  if (result.exitCode === 0) {
3261
3318
  deletedAny = true;
3262
3319
  continue;
@@ -3421,6 +3478,7 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
3421
3478
  const expectedWorker = record.identity.workerName;
3422
3479
  const expectedContainer = record.identity.containerApplicationName;
3423
3480
  if (expectedWorker !== request.name ||
3481
+ expectedContainer !== boundedResourceName(request.name, "container") ||
3424
3482
  typeof record.identity.accountId !== "string") {
3425
3483
  fail("EVE_DESTROY_RECORD_MISMATCH", "The immutable deployment record does not match the selected target.");
3426
3484
  }
@@ -3435,6 +3493,9 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
3435
3493
  const reader = options.destroyCloudflareRead ?? defaultDestroyTargetRead;
3436
3494
  eveProgress(options, "reading the exact remote inventory");
3437
3495
  const before = await readTarget();
3496
+ if (before.inventoryComplete === false) {
3497
+ fail("EVE_DESTROY_INDETERMINATE", "The exact remote inventory could not be read completely; nothing was deleted. Retry the read. If Wrangler returned a full Container page without the exact target, inspect and delete only the named Container application in the Cloudflare dashboard, then retry destroy.");
3498
+ }
3438
3499
  if (before.accountId !== record.identity.accountId) {
3439
3500
  fail("EVE_DESTROY_OWNERSHIP_UNPROVEN", "The authenticated account does not own the recorded deployment account.");
3440
3501
  }
@@ -3455,6 +3516,27 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
3455
3516
  });
3456
3517
  return;
3457
3518
  }
3519
+ if (before.workerExists) {
3520
+ const ownership = await resolveEveTargetOwnership({
3521
+ projectRoot: request.projectRoot,
3522
+ environment: request.environment,
3523
+ name: request.name,
3524
+ projectId: record.identity.projectId ?? "unknown-project",
3525
+ sourceDigest: "unknown",
3526
+ }, {
3527
+ accountAccess: "available",
3528
+ containerAccess: "available",
3529
+ accountId: before.accountId,
3530
+ target: {
3531
+ state: "unowned",
3532
+ observed: { worker: true, container: before.containerApplicationId !== undefined },
3533
+ ...(before.providerEvidence === undefined ? {} : { providerEvidence: before.providerEvidence }),
3534
+ },
3535
+ });
3536
+ if (ownership.target.state !== "owned") {
3537
+ fail("EVE_DESTROY_OWNERSHIP_UNPROVEN", "Live provider settings do not match the recorded Worker and Container identity; nothing was deleted.");
3538
+ }
3539
+ }
3458
3540
  const deleteWorker = options.deleteWorker ?? defaultWorkerDelete;
3459
3541
  if (before.workerExists) {
3460
3542
  eveProgress(options, `deleting Worker ${expectedWorker}`);
@@ -3519,6 +3601,9 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
3519
3601
  }
3520
3602
  eveProgress(options, "verifying the exact target is absent");
3521
3603
  const after = await readTarget();
3604
+ if (after.inventoryComplete === false) {
3605
+ fail("EVE_DESTROY_INDETERMINATE", "The exact remote inventory could not be read completely after deletion; absence is unproven.");
3606
+ }
3522
3607
  if (after.workerExists || after.containerApplicationId !== undefined) {
3523
3608
  emit({
3524
3609
  ok: false,
@@ -365,6 +365,14 @@ const EVE_EXCLUDED_FILE_NAMES = new Set([
365
365
  ".bunfig.toml",
366
366
  ".yarnrc",
367
367
  ".yarnrc.yml",
368
+ ".dev.vars",
369
+ ".netrc",
370
+ ".git-credentials",
371
+ "id_rsa",
372
+ "id_ecdsa",
373
+ "id_ed25519",
374
+ "id_dsa",
375
+ "secrets.json",
368
376
  "credentials.json",
369
377
  "service-account.json",
370
378
  ]);
@@ -419,6 +427,16 @@ function isAllowedSystemAlias(path) {
419
427
  const normalized = resolve(path);
420
428
  return normalized === "/var" || normalized === "/tmp";
421
429
  }
430
+ function isCredentialPath(relativePath) {
431
+ const parts = relativePath.split("/");
432
+ const name = parts[parts.length - 1];
433
+ return name !== undefined && (EVE_EXCLUDED_FILE_NAMES.has(name) ||
434
+ name.startsWith(".dev.vars.") ||
435
+ name.endsWith(".pem") || name.endsWith(".key") ||
436
+ name.endsWith(".p12") || name.endsWith(".pfx") ||
437
+ name.endsWith(".tfvars") ||
438
+ (name === "credentials" && parts[parts.length - 2] === ".aws"));
439
+ }
422
440
  function isPathExcluded(relativePath, runtimeEnvRelativePath) {
423
441
  const parts = relativePath.split("/");
424
442
  const name = parts[parts.length - 1];
@@ -440,8 +458,7 @@ function isPathExcluded(relativePath, runtimeEnvRelativePath) {
440
458
  if (name !== undefined && name.startsWith(".env")) {
441
459
  return { excluded: true, category: "runtime-env" };
442
460
  }
443
- if ((name !== undefined && EVE_EXCLUDED_FILE_NAMES.has(name)) ||
444
- (name !== undefined && (name.endsWith(".pem") || name.endsWith(".key")))) {
461
+ if (isCredentialPath(relativePath)) {
445
462
  return { excluded: true, category: "credentials" };
446
463
  }
447
464
  if (name !== undefined &&
@@ -1737,9 +1754,7 @@ async function assertGeneratedTreesExcludeSecrets(snapshotRoot) {
1737
1754
  : `${relativeDirectory}/${entry.name}`;
1738
1755
  const candidate = join(directory, entry.name);
1739
1756
  if (entry.name.startsWith(".env") ||
1740
- EVE_EXCLUDED_FILE_NAMES.has(entry.name) ||
1741
- entry.name.endsWith(".pem") ||
1742
- entry.name.endsWith(".key")) {
1757
+ isCredentialPath(relativePath)) {
1743
1758
  throw new EvePackagingError({
1744
1759
  code: "SECRET_EXCLUSION_FAILED",
1745
1760
  subject: relativePath,
@@ -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_WORLD_URL", "EDEN_WORLD_TOKEN", "EDEN_EVE_WORLD_RPC_AUTH", "EDEN_EVE_DEPLOYMENT_ID", "EDEN_EVE_GENERATION_ID", "EDEN_EVE_RELEASE_ID", "EDEN_EVE_DEPLOYMENTS", "EDEN_AFFINITY_CONTRACT", "EVE_SCHEDULES_DISABLED", "EDEN_EVE_CONTROL_TOKEN"];
1
+ export declare const EVE_RESERVED_HOST_VARIABLES: readonly ["HOST", "NITRO_HOST", "PORT", "NITRO_PORT", "NODE_ENV", "EVE_DEV", "VERCEL", "VERCEL_ENV", "VERCEL_TARGET_ENV", "NODE_EXTRA_CA_CERTS", "WORKFLOW_LOCAL_BASE_URL", "EDEN_WORLD_URL", "EDEN_WORLD_TOKEN", "EDEN_EVE_WORLD_RPC_AUTH", "EDEN_EVE_DEPLOYMENT_ID", "EDEN_EVE_GENERATION_ID", "EDEN_EVE_RELEASE_ID", "EDEN_EVE_DEPLOYMENTS", "EDEN_AFFINITY_CONTRACT", "EVE_SCHEDULES_DISABLED", "EDEN_EVE_CONTROL_TOKEN"];
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
  export declare const EVE_HOST_ENVIRONMENT: Readonly<{
@@ -7,6 +7,10 @@ export const EVE_RESERVED_HOST_VARIABLES = Object.freeze([
7
7
  "PORT",
8
8
  "NITRO_PORT",
9
9
  "NODE_ENV",
10
+ "EVE_DEV",
11
+ "VERCEL",
12
+ "VERCEL_ENV",
13
+ "VERCEL_TARGET_ENV",
10
14
  "NODE_EXTRA_CA_CERTS",
11
15
  "WORKFLOW_LOCAL_BASE_URL",
12
16
  "EDEN_WORLD_URL",
@@ -194,7 +198,10 @@ function parseEnvironmentText(text) {
194
198
  if (EVE_RESERVED_HOST_VARIABLES.includes(name)) {
195
199
  throw new EveRuntimeConfigError({
196
200
  code: "EVE_ENV_RESERVED_VARIABLE",
197
- message: `The explicit Eve environment file attempts to override reserved host variable ${name}.`,
201
+ message: `The explicit Eve environment file attempts to override reserved host variable ${name}.` +
202
+ (["EVE_DEV", "VERCEL", "VERCEL_ENV", "VERCEL_TARGET_ENV"].includes(name)
203
+ ? " Remove it: Eve development-mode variables can let unauthenticated callers in."
204
+ : ""),
198
205
  source: EVE_ENV_FILE_SOURCE,
199
206
  variableName: name,
200
207
  });
@@ -1000,7 +1000,7 @@ export async function buildEveRuntimeImage(request) {
1000
1000
  "--label",
1001
1001
  state.imageLabel,
1002
1002
  "--publish",
1003
- `${healthPort}:${INTERNAL_PORT}`,
1003
+ `127.0.0.1:${healthPort}:${INTERNAL_PORT}`,
1004
1004
  ...(request.runtimeInjection === undefined
1005
1005
  ? Object.entries(runtimeEnvironment).flatMap(([name, value]) => [
1006
1006
  "--env",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moinulmoin/eden",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Deploy existing Eve projects to your own Cloudflare account.",
5
5
  "keywords": [
6
6
  "ai",
@@ -32,7 +32,7 @@
32
32
  "NOTICE"
33
33
  ],
34
34
  "dependencies": {
35
- "@moinulmoin/eden-runtime-cloudflare": "0.11.1",
35
+ "@moinulmoin/eden-runtime-cloudflare": "0.11.2",
36
36
  "wrangler": "4.147.0"
37
37
  },
38
38
  "devDependencies": {