@moinulmoin/eden 0.11.0 → 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 +3 -3
- package/dist/SKILL.md +4 -1
- package/dist/eve-control-plane.d.ts +7 -4
- package/dist/eve-control-plane.js +141 -32
- package/dist/eve-packaging.js +20 -5
- package/dist/eve-runtime-config.d.ts +1 -1
- package/dist/eve-runtime-config.js +8 -1
- package/dist/eve-runtime-image.js +1 -1
- package/package.json +2 -2
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.
|
|
15
|
+
npm install --global @moinulmoin/eden@0.11.2
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
pnpm add --global @moinulmoin/eden@0.11.
|
|
19
|
+
pnpm add --global @moinulmoin/eden@0.11.2
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
bun add --global @moinulmoin/eden@0.11.
|
|
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
|
|
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
|
|
92
|
-
* Worker it publishes. `settingsReadable` tells
|
|
93
|
-
* whether a missing var is evidence of a foreign
|
|
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",
|
|
@@ -194,6 +193,24 @@ async function readInstalledManifest(snapshotRoot, packageName) {
|
|
|
194
193
|
return undefined;
|
|
195
194
|
}
|
|
196
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Eve `1.0.0-rc.6` and later refuse to start a session unless the World
|
|
198
|
+
* admits start Hooks atomically; earlier prereleases of 1.0.0 do not ask.
|
|
199
|
+
*/
|
|
200
|
+
function eveRequiresAtomicStartHook(version) {
|
|
201
|
+
const match = typeof version === "string"
|
|
202
|
+
? /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/u.exec(version)
|
|
203
|
+
: null;
|
|
204
|
+
if (match === null)
|
|
205
|
+
return false;
|
|
206
|
+
const [major, minor, patch] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
207
|
+
if (major !== 1 || minor !== 0 || patch !== 0)
|
|
208
|
+
return major >= 1;
|
|
209
|
+
if (match[4] === undefined)
|
|
210
|
+
return true;
|
|
211
|
+
const rc = /^rc\.(\d+)$/u.exec(match[4]);
|
|
212
|
+
return rc !== null && Number(rc[1]) >= 6;
|
|
213
|
+
}
|
|
197
214
|
/**
|
|
198
215
|
* Fails deploy/preflight when a known durable World would trip Eve's runtime
|
|
199
216
|
* World-compatibility rejection. Unreadable manifests
|
|
@@ -204,6 +221,17 @@ async function checkEveWorldPairing(candidate, world) {
|
|
|
204
221
|
world !== "@moinulmoin/eden-world-cloudflare")
|
|
205
222
|
return undefined;
|
|
206
223
|
const eveManifest = await readInstalledManifest(candidate.snapshotRoot, "eve");
|
|
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
|
+
}
|
|
229
|
+
if (world === "@moinulmoin/eden-world-cloudflare" &&
|
|
230
|
+
worldManifest !== undefined &&
|
|
231
|
+
eveRequiresAtomicStartHook(eveManifest?.version) &&
|
|
232
|
+
worldManifest.eden?.capabilities?.atomicStartHook !== true) {
|
|
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.`);
|
|
234
|
+
}
|
|
207
235
|
const expectedRange = eveManifest === undefined
|
|
208
236
|
? undefined
|
|
209
237
|
: [
|
|
@@ -215,7 +243,6 @@ async function checkEveWorldPairing(candidate, world) {
|
|
|
215
243
|
.find((value) => typeof value === "string" && value.trim().length > 0);
|
|
216
244
|
if (expectedRange === undefined)
|
|
217
245
|
return undefined;
|
|
218
|
-
const worldManifest = await readInstalledManifest(candidate.snapshotRoot, world);
|
|
219
246
|
const declared = worldManifest === undefined
|
|
220
247
|
? undefined
|
|
221
248
|
: ["@workflow/core", "@workflow/world"]
|
|
@@ -564,6 +591,7 @@ function safeWranglerEnvironment() {
|
|
|
564
591
|
LANG: process.env.LANG,
|
|
565
592
|
LC_ALL: process.env.LC_ALL,
|
|
566
593
|
CLOUDFLARE_API_TOKEN: process.env.CLOUDFLARE_API_TOKEN,
|
|
594
|
+
CLOUDFLARE_API_BASE_URL: "https://api.cloudflare.com/client/v4",
|
|
567
595
|
NODE_OPTIONS: undefined,
|
|
568
596
|
};
|
|
569
597
|
}
|
|
@@ -578,14 +606,23 @@ function safeDockerEnvironment() {
|
|
|
578
606
|
DOCKER_CONTEXT: process.env.DOCKER_CONTEXT,
|
|
579
607
|
};
|
|
580
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
|
+
}
|
|
581
618
|
async function readWranglerJson(args, timeoutMs = 30_000) {
|
|
582
619
|
try {
|
|
583
|
-
const result = await execFileAsync(process.execPath, [wranglerEntrypoint, ...args], {
|
|
620
|
+
const result = await withWranglerDirectory((cwd) => execFileAsync(process.execPath, [wranglerEntrypoint, ...args], {
|
|
584
621
|
env: safeWranglerEnvironment(),
|
|
585
|
-
cwd
|
|
622
|
+
cwd,
|
|
586
623
|
maxBuffer: 2 * 1024 * 1024,
|
|
587
624
|
timeout: timeoutMs,
|
|
588
|
-
});
|
|
625
|
+
}));
|
|
589
626
|
const parsed = JSON.parse(result.stdout.trim());
|
|
590
627
|
return { value: parsed, failed: false };
|
|
591
628
|
}
|
|
@@ -720,6 +757,8 @@ export function exactTargetContainerEntries(value, name) {
|
|
|
720
757
|
return exactContainerEntries(value, boundedResourceName(name, "container"));
|
|
721
758
|
}
|
|
722
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.
|
|
723
762
|
const containers = await readWranglerJson([
|
|
724
763
|
"containers", "list", "--json", "--per-page", "100",
|
|
725
764
|
]);
|
|
@@ -729,7 +768,7 @@ async function readExactTargetInventory(workerName, containerApp) {
|
|
|
729
768
|
return {
|
|
730
769
|
containers,
|
|
731
770
|
deployments,
|
|
732
|
-
containerEntries: exactContainerEntries(containers.value, containerApp),
|
|
771
|
+
containerEntries: containers.failed ? undefined : exactContainerEntries(containers.value, containerApp),
|
|
733
772
|
deploymentEntries: exactDeploymentEntries(deployments.value),
|
|
734
773
|
};
|
|
735
774
|
}
|
|
@@ -859,6 +898,11 @@ async function readProviderEvidence(workerName, accountId, workerPresent, contai
|
|
|
859
898
|
if (typeof deploymentBinding?.text === "string") {
|
|
860
899
|
evidence.workerDeploymentId = deploymentBinding.text;
|
|
861
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
|
+
}
|
|
862
906
|
const containerBinding = bindings.find((value) => value.type === "durable_object_namespace" &&
|
|
863
907
|
value.name === "EVE_CONTAINER");
|
|
864
908
|
if (typeof containerBinding?.namespace_id === "string") {
|
|
@@ -877,12 +921,9 @@ async function readProviderEvidence(workerName, accountId, workerPresent, contai
|
|
|
877
921
|
return evidence;
|
|
878
922
|
}
|
|
879
923
|
/**
|
|
880
|
-
* Resolves
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
* Eden deployed — a same-name Worker recreated by something else after the
|
|
884
|
-
* target was destroyed (or a forged local record) must never be upgraded to
|
|
885
|
-
* `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.
|
|
886
927
|
*/
|
|
887
928
|
async function resolveEveTargetOwnership(request, result) {
|
|
888
929
|
if (result.target.state !== "unowned")
|
|
@@ -906,10 +947,18 @@ async function resolveEveTargetOwnership(request, result) {
|
|
|
906
947
|
// its own settings: unreadable settings fail closed.
|
|
907
948
|
const evidence = result.target.providerEvidence;
|
|
908
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;
|
|
909
956
|
const workerProven = result.target.observed?.worker !== true ||
|
|
910
957
|
(evidence?.settingsReadable === true &&
|
|
911
958
|
evidence.workerDeploymentId !== undefined &&
|
|
912
|
-
recordedDeploymentIds.has(evidence.workerDeploymentId)
|
|
959
|
+
recordedDeploymentIds.has(evidence.workerDeploymentId) &&
|
|
960
|
+
workerReleaseId !== undefined &&
|
|
961
|
+
recordedReleaseIds.has(workerReleaseId));
|
|
913
962
|
// The Container proof is the application running the durable_object
|
|
914
963
|
// scheduling policy with a namespace that matches the Worker binding's
|
|
915
964
|
// namespace; anything else fails closed.
|
|
@@ -930,6 +979,8 @@ async function resolveEveTargetOwnership(request, result) {
|
|
|
930
979
|
failures.push("Worker deployment stamp absent");
|
|
931
980
|
else if (!recordedDeploymentIds.has(evidence.workerDeploymentId))
|
|
932
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");
|
|
933
984
|
}
|
|
934
985
|
if (result.target.observed?.container === true) {
|
|
935
986
|
if (evidence?.containerSchedulingPolicy !== "durable_object")
|
|
@@ -1228,8 +1279,9 @@ async function writeEveDeploymentArtifacts(candidate, hostConfig, workerSource)
|
|
|
1228
1279
|
const WRANGLER_OPERATION_TIMEOUT_MS = 120_000;
|
|
1229
1280
|
/** Deadline for `wrangler deploy`, whose Container image preparation alone can approach two minutes. */
|
|
1230
1281
|
const WRANGLER_PUBLICATION_TIMEOUT_MS = 600_000;
|
|
1231
|
-
function runWranglerWithInput(args,
|
|
1232
|
-
|
|
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) => {
|
|
1233
1285
|
const child = spawn(process.execPath, [wranglerEntrypoint, ...args], {
|
|
1234
1286
|
cwd,
|
|
1235
1287
|
env: safeWranglerEnvironment(),
|
|
@@ -1274,7 +1326,7 @@ function runWranglerWithInput(args, cwd, input, timeoutMs = WRANGLER_OPERATION_T
|
|
|
1274
1326
|
child.stdin.end();
|
|
1275
1327
|
else
|
|
1276
1328
|
child.stdin.end(input);
|
|
1277
|
-
});
|
|
1329
|
+
}));
|
|
1278
1330
|
}
|
|
1279
1331
|
function defaultProtectedStore(workerName) {
|
|
1280
1332
|
return {
|
|
@@ -1286,7 +1338,7 @@ function defaultProtectedStore(workerName) {
|
|
|
1286
1338
|
name,
|
|
1287
1339
|
"--name",
|
|
1288
1340
|
workerName,
|
|
1289
|
-
],
|
|
1341
|
+
], request.values[name] === undefined ? "" : `${request.values[name]}\n`);
|
|
1290
1342
|
if (result.exitCode !== 0) {
|
|
1291
1343
|
throw new EveRuntimeConfigError({
|
|
1292
1344
|
code: "EVE_RUNTIME_PROTECTED_UPLOAD_FAILED",
|
|
@@ -1308,7 +1360,7 @@ async function defaultImagePublicationRunner(request) {
|
|
|
1308
1360
|
cwd: request.generationRoot,
|
|
1309
1361
|
maxBuffer: 512 * 1024,
|
|
1310
1362
|
});
|
|
1311
|
-
const pushed = await runWranglerWithInput(["containers", "push", request.localImageTag]
|
|
1363
|
+
const pushed = await runWranglerWithInput(["containers", "push", request.localImageTag]);
|
|
1312
1364
|
if (pushed.exitCode !== 0) {
|
|
1313
1365
|
return {
|
|
1314
1366
|
status: "indeterminate",
|
|
@@ -1374,7 +1426,7 @@ function defaultPublicationRunner() {
|
|
|
1374
1426
|
"--env",
|
|
1375
1427
|
request.identity.environment,
|
|
1376
1428
|
"--strict",
|
|
1377
|
-
],
|
|
1429
|
+
], undefined, WRANGLER_PUBLICATION_TIMEOUT_MS);
|
|
1378
1430
|
if (result.exitCode !== 0) {
|
|
1379
1431
|
return {
|
|
1380
1432
|
status: "indeterminate",
|
|
@@ -1771,15 +1823,32 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1771
1823
|
...(priorIdentity.runtimeRevisionHandle === undefined ? {} : { runtimeRevisionHandle: priorIdentity.runtimeRevisionHandle }),
|
|
1772
1824
|
};
|
|
1773
1825
|
}
|
|
1774
|
-
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)}`;
|
|
1775
1828
|
const deploymentId = !affinityEnabled ? releaseId : matchingDeployment ??
|
|
1776
|
-
|
|
1829
|
+
(affinityDeploymentId in retainedDeployments ? `${affinityDeploymentId}-${randomUUID().slice(0, 8)}` : affinityDeploymentId);
|
|
1777
1830
|
const retiringDeployments = { ...priorIdentity?.pendingRetirements };
|
|
1778
1831
|
delete retiringDeployments[deploymentId];
|
|
1779
1832
|
if (priorIdentity?.deploymentId !== undefined)
|
|
1780
1833
|
delete retiringDeployments[priorIdentity.deploymentId];
|
|
1781
1834
|
for (const id of prior?.protectedDeploymentIds ?? [])
|
|
1782
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
|
+
}
|
|
1783
1852
|
const hasRetainedNoncurrentDeployments = priorIdentity?.retainedDeployments !== undefined &&
|
|
1784
1853
|
Object.keys(retainedDeployments).some((id) => id !== priorIdentity.deploymentId);
|
|
1785
1854
|
const controlToken = `${randomUUID()}${randomUUID()}`;
|
|
@@ -2232,10 +2301,11 @@ async function runEveDeployment(request, options, collected) {
|
|
|
2232
2301
|
}
|
|
2233
2302
|
completedRetirements.push(id);
|
|
2234
2303
|
}
|
|
2235
|
-
supersededImageCleanup = check("VAL-LIFE-007", "passed", "
|
|
2304
|
+
supersededImageCleanup = check("VAL-LIFE-007", "passed", "Retired deployment instances and their owned registry repositories were removed.");
|
|
2236
2305
|
}
|
|
2237
|
-
catch {
|
|
2238
|
-
|
|
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.");
|
|
2239
2309
|
}
|
|
2240
2310
|
if (completedRetirements.length > 0) {
|
|
2241
2311
|
// Keep the immutable deployment record as evidence, but exclude proven
|
|
@@ -2935,6 +3005,7 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
2935
3005
|
const entries = await readdir(generationsRoot).catch(() => []);
|
|
2936
3006
|
const scanned = [];
|
|
2937
3007
|
const deploymentIds = new Set();
|
|
3008
|
+
const releaseIds = new Set();
|
|
2938
3009
|
for (const entry of entries) {
|
|
2939
3010
|
const generationRoot = join(generationsRoot, entry);
|
|
2940
3011
|
const recordPath = await deploymentRecordPath(generationRoot);
|
|
@@ -2957,6 +3028,9 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
2957
3028
|
const deploymentId = parsed.deploymentId;
|
|
2958
3029
|
if (deploymentId !== undefined)
|
|
2959
3030
|
deploymentIds.add(deploymentId);
|
|
3031
|
+
const releaseId = parsed.identity.releaseId ?? deploymentId;
|
|
3032
|
+
if (releaseId !== undefined)
|
|
3033
|
+
releaseIds.add(releaseId);
|
|
2960
3034
|
}
|
|
2961
3035
|
}
|
|
2962
3036
|
scanned.sort((left, right) => right.modifiedAt - left.modifiedAt);
|
|
@@ -3013,6 +3087,7 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
3013
3087
|
protectedDeploymentIds: [...protectedDeploymentIds],
|
|
3014
3088
|
status: live.record.status,
|
|
3015
3089
|
deploymentIds: [...deploymentIds],
|
|
3090
|
+
releaseIds: [...releaseIds],
|
|
3016
3091
|
worldCloudflare: live.record.worldCloudflare,
|
|
3017
3092
|
durableObjects: historySource?.record.durableObjects,
|
|
3018
3093
|
};
|
|
@@ -3113,10 +3188,12 @@ async function parseEveDeploymentRecord(recordPath, environment, name) {
|
|
|
3113
3188
|
}
|
|
3114
3189
|
async function defaultDestroyTargetRead(request) {
|
|
3115
3190
|
const account = await readWranglerJson(["whoami", "--json"]);
|
|
3116
|
-
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);
|
|
3117
3192
|
const accountId = authenticatedAccountId(account.value);
|
|
3118
|
-
const
|
|
3119
|
-
(
|
|
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;
|
|
3120
3197
|
const firstContainer = entries === undefined ? undefined : entries[0];
|
|
3121
3198
|
const ownedRepositories = request.registryRepositories;
|
|
3122
3199
|
let registryTagsPresent;
|
|
@@ -3141,6 +3218,10 @@ async function defaultDestroyTargetRead(request) {
|
|
|
3141
3218
|
}
|
|
3142
3219
|
return {
|
|
3143
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),
|
|
3144
3225
|
...(firstContainer === undefined
|
|
3145
3226
|
? {}
|
|
3146
3227
|
: { containerApplicationId: firstContainer.id }),
|
|
@@ -3180,7 +3261,7 @@ function registryTagsByRepository(value) {
|
|
|
3180
3261
|
return tagsByRepository;
|
|
3181
3262
|
}
|
|
3182
3263
|
async function defaultWorkerDelete(request) {
|
|
3183
|
-
const result = await runWranglerWithInput(["delete", "--name", request.name, "--force"],
|
|
3264
|
+
const result = await runWranglerWithInput(["delete", "--name", request.name, "--force"], "y\n");
|
|
3184
3265
|
if (result.exitCode === 0)
|
|
3185
3266
|
return "deleted";
|
|
3186
3267
|
if (/(?:does not exist|not found|10007)/iu.test(result.stderr)) {
|
|
@@ -3189,7 +3270,7 @@ async function defaultWorkerDelete(request) {
|
|
|
3189
3270
|
return "indeterminate";
|
|
3190
3271
|
}
|
|
3191
3272
|
async function defaultContainerDelete(request) {
|
|
3192
|
-
const result = await runWranglerWithInput(["containers", "delete", request.applicationId]
|
|
3273
|
+
const result = await runWranglerWithInput(["containers", "delete", request.applicationId]);
|
|
3193
3274
|
if (result.exitCode === 0)
|
|
3194
3275
|
return "deleted";
|
|
3195
3276
|
if (/(?:does not exist|not found|not supported|10007)/iu.test(result.stderr)) {
|
|
@@ -3232,7 +3313,7 @@ async function defaultRegistryRepositoryDelete(request) {
|
|
|
3232
3313
|
"delete",
|
|
3233
3314
|
`${request.repository}:${tag}`,
|
|
3234
3315
|
"--skip-confirmation",
|
|
3235
|
-
]
|
|
3316
|
+
]);
|
|
3236
3317
|
if (result.exitCode === 0) {
|
|
3237
3318
|
deletedAny = true;
|
|
3238
3319
|
continue;
|
|
@@ -3397,6 +3478,7 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
3397
3478
|
const expectedWorker = record.identity.workerName;
|
|
3398
3479
|
const expectedContainer = record.identity.containerApplicationName;
|
|
3399
3480
|
if (expectedWorker !== request.name ||
|
|
3481
|
+
expectedContainer !== boundedResourceName(request.name, "container") ||
|
|
3400
3482
|
typeof record.identity.accountId !== "string") {
|
|
3401
3483
|
fail("EVE_DESTROY_RECORD_MISMATCH", "The immutable deployment record does not match the selected target.");
|
|
3402
3484
|
}
|
|
@@ -3411,6 +3493,9 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
3411
3493
|
const reader = options.destroyCloudflareRead ?? defaultDestroyTargetRead;
|
|
3412
3494
|
eveProgress(options, "reading the exact remote inventory");
|
|
3413
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
|
+
}
|
|
3414
3499
|
if (before.accountId !== record.identity.accountId) {
|
|
3415
3500
|
fail("EVE_DESTROY_OWNERSHIP_UNPROVEN", "The authenticated account does not own the recorded deployment account.");
|
|
3416
3501
|
}
|
|
@@ -3431,6 +3516,27 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
3431
3516
|
});
|
|
3432
3517
|
return;
|
|
3433
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
|
+
}
|
|
3434
3540
|
const deleteWorker = options.deleteWorker ?? defaultWorkerDelete;
|
|
3435
3541
|
if (before.workerExists) {
|
|
3436
3542
|
eveProgress(options, `deleting Worker ${expectedWorker}`);
|
|
@@ -3495,6 +3601,9 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
3495
3601
|
}
|
|
3496
3602
|
eveProgress(options, "verifying the exact target is absent");
|
|
3497
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
|
+
}
|
|
3498
3607
|
if (after.workerExists || after.containerApplicationId !== undefined) {
|
|
3499
3608
|
emit({
|
|
3500
3609
|
ok: false,
|
package/dist/eve-packaging.js
CHANGED
|
@@ -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 ((
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
35
|
+
"@moinulmoin/eden-runtime-cloudflare": "0.11.2",
|
|
36
36
|
"wrangler": "4.147.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|