@nowcrew/daemon 0.6.77 → 0.6.79-beta.1
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 +27 -0
- package/dist/automatic-daemon-installer.js +44 -24
- package/dist/automatic-install-target.js +34 -9
- package/dist/computer-cli.js +43 -14
- package/dist/computer-profile.js +15 -2
- package/dist/computer-service.js +26 -5
- package/dist/config.js +28 -8
- package/dist/control-plane-url.js +3 -9
- package/dist/daemon-installation-lease.js +7 -4
- package/dist/daemon-local-home.js +108 -0
- package/dist/daemon-migration-controller.js +15 -4
- package/dist/daemon-registry.js +20 -0
- package/dist/daemon-update-controller.js +1 -0
- package/dist/daemon-update-eligibility.js +17 -7
- package/dist/daemon-updater.js +26 -26
- package/dist/execution-runner.js +7 -7
- package/dist/host-execution-coordinator.js +4 -3
- package/dist/local-executor.js +23 -17
- package/dist/local-memory/runtime.js +5 -3
- package/dist/machine-info.js +3 -0
- package/dist/main.js +13 -2
- package/dist/managed-service-diagnostics.js +1 -0
- package/dist/managed-service-registry.js +12 -7
- package/dist/managed-service-startup.js +4 -1
- package/dist/npm-command.js +9 -0
- package/dist/project-skills/controller.js +6 -71
- package/dist/project-skills/execution-adapter.js +7 -0
- package/dist/project-skills/execution-projection.js +163 -0
- package/dist/project-skills/initialized-reconciler.js +10 -8
- package/dist/project-skills/legacy-cleanup.js +224 -0
- package/dist/project-skills/machine-hello-publisher.js +20 -0
- package/dist/project-skills/reconciler.js +114 -500
- package/dist/project-skills/resolution-records.js +63 -0
- package/dist/project-skills/runtime-launch.js +0 -42
- package/dist/project-skills/runtime-warning.js +1 -3
- package/dist/project-skills/scanner.js +1 -17
- package/dist/project-skills/startup-cleanup.js +24 -0
- package/dist/runner.js +2 -1
- package/dist/serve.js +49 -49
- package/dist/slog.js +14 -1
- package/dist/storage-control.js +12 -1
- package/dist/storage-identity.js +15 -1
- package/dist/storage-migration-controller.js +25 -4
- package/dist/storage-ownership.js +55 -0
- package/dist/storage-runtime-controller.js +29 -7
- package/dist/storage-runtime-readiness.js +26 -4
- package/dist/storage-runtime.js +18 -2
- package/dist/storage-status-delivery.js +4 -0
- package/dist/storage-status-replay.js +16 -1
- package/dist/storage-worker-executor.js +1 -0
- package/dist/storage-worker-operation.js +4 -0
- package/package.json +1 -1
- package/dist/project-skills/capability.js +0 -109
- package/dist/project-skills/controller-convergence.js +0 -57
- package/dist/project-skills/projection-set-switch.js +0 -419
- package/dist/project-skills/projection-state-domain.js +0 -170
- package/dist/project-skills/projection-state-store.js +0 -842
- package/dist/project-skills/projection-state-transaction.js +0 -322
- package/dist/project-skills/projection-state.js +0 -3
- package/dist/project-skills/runtime-root-bootstrap.js +0 -47
- package/dist/project-skills/runtime-root-domain.js +0 -268
- package/dist/project-skills/runtime-root-gc.js +0 -293
- package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
- package/dist/project-skills/runtime-root-leases.js +0 -487
- package/dist/project-skills/runtime-root-source-identity.js +0 -60
- package/dist/project-skills/runtime-root-startup.js +0 -49
- package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
- package/dist/project-skills/runtime-root-state-index.js +0 -356
- package/dist/project-skills/runtime-root-store.js +0 -722
- package/dist/project-skills/serve-capability.js +0 -28
- package/dist/project-skills/serve-startup.js +0 -37
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const FingerprintSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
|
|
6
|
+
const StorageOwnershipClaimSchema = z.object({
|
|
7
|
+
version: z.literal(1),
|
|
8
|
+
machineId: z.string().uuid(),
|
|
9
|
+
workspaceId: z.string().uuid(),
|
|
10
|
+
environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
|
|
11
|
+
volumeFingerprint: FingerprintSchema,
|
|
12
|
+
workspaceRootFingerprint: FingerprintSchema,
|
|
13
|
+
ownerEpoch: z.number().int().positive().safe(),
|
|
14
|
+
claimFingerprint: FingerprintSchema,
|
|
15
|
+
}).strict();
|
|
16
|
+
function claimFingerprint(identity) {
|
|
17
|
+
const digest = createHash("sha256").update([
|
|
18
|
+
"nowwork-storage-ownership-v1",
|
|
19
|
+
identity.machineId,
|
|
20
|
+
identity.workspaceId,
|
|
21
|
+
identity.environment,
|
|
22
|
+
identity.volumeFingerprint,
|
|
23
|
+
identity.workspaceRootFingerprint,
|
|
24
|
+
String(identity.ownerEpoch),
|
|
25
|
+
].join("\0")).digest("hex");
|
|
26
|
+
return `sha256:${digest}`;
|
|
27
|
+
}
|
|
28
|
+
export function createStorageOwnershipClaim(input) {
|
|
29
|
+
const parsed = StorageOwnershipClaimSchema.omit({ version: true, claimFingerprint: true }).parse(input);
|
|
30
|
+
const identity = Object.freeze({
|
|
31
|
+
...parsed,
|
|
32
|
+
machineId: parsed.machineId.toLowerCase(),
|
|
33
|
+
workspaceId: parsed.workspaceId.toLowerCase(),
|
|
34
|
+
environment: parsed.environment.toLowerCase(),
|
|
35
|
+
});
|
|
36
|
+
return Object.freeze({ ...identity, claimFingerprint: claimFingerprint(identity) });
|
|
37
|
+
}
|
|
38
|
+
export function storageOwnershipClaimPath(nowworkHome) {
|
|
39
|
+
return join(nowworkHome, "storage-layout.json");
|
|
40
|
+
}
|
|
41
|
+
export async function readStorageOwnershipClaim(nowworkHome) {
|
|
42
|
+
const path = storageOwnershipClaimPath(nowworkHome);
|
|
43
|
+
const metadata = await lstat(path);
|
|
44
|
+
if (!metadata.isFile())
|
|
45
|
+
throw new Error("storage ownership claim must be a file");
|
|
46
|
+
if (metadata.size > 16 * 1024)
|
|
47
|
+
throw new Error("storage ownership claim file is too large");
|
|
48
|
+
const parsed = StorageOwnershipClaimSchema.parse(JSON.parse(await readFile(path, "utf8")));
|
|
49
|
+
const { version: _version, claimFingerprint: reportedFingerprint, ...identity } = parsed;
|
|
50
|
+
const expected = createStorageOwnershipClaim(identity);
|
|
51
|
+
if (reportedFingerprint !== expected.claimFingerprint) {
|
|
52
|
+
throw new Error("storage ownership claim fingerprint is invalid");
|
|
53
|
+
}
|
|
54
|
+
return expected;
|
|
55
|
+
}
|
|
@@ -23,9 +23,10 @@ function deterministicUuid(value) {
|
|
|
23
23
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
24
24
|
}
|
|
25
25
|
function storageId(input, workspaceId) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (input.ownership.workspaceId !== workspaceId || input.ownership.environment !== input.environment) {
|
|
27
|
+
throw new Error("Storage ownership claim does not match the migration identity");
|
|
28
|
+
}
|
|
29
|
+
return `storage_${input.ownership.claimFingerprint.slice("sha256:".length)}`;
|
|
29
30
|
}
|
|
30
31
|
function installationBase(input, workspaceId, migrationId, createdAt) {
|
|
31
32
|
const layout = storageLayout({
|
|
@@ -102,6 +103,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
|
|
|
102
103
|
...injected,
|
|
103
104
|
};
|
|
104
105
|
let workspaceId = null;
|
|
106
|
+
let machineId = null;
|
|
105
107
|
let identity;
|
|
106
108
|
let gate = null;
|
|
107
109
|
let sendStatus = null;
|
|
@@ -194,6 +196,10 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
|
|
|
194
196
|
targetWorkspaceRoot: target.workspaceRoot,
|
|
195
197
|
backupsRoot: input.local.backupsRoot,
|
|
196
198
|
backupArtifacts: input.backupArtifacts.map((artifact) => ({ ...artifact })),
|
|
199
|
+
ownership: {
|
|
200
|
+
ownerEpoch: input.ownership.ownerEpoch,
|
|
201
|
+
ownershipClaimFingerprint: input.ownership.claimFingerprint,
|
|
202
|
+
},
|
|
197
203
|
acceptedWarnings: [...input.policy.acceptedWarnings],
|
|
198
204
|
},
|
|
199
205
|
service: {
|
|
@@ -229,6 +235,10 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
|
|
|
229
235
|
profile: input.profile,
|
|
230
236
|
sourceStorageFingerprint,
|
|
231
237
|
targetStorageFingerprint: identity.storageIdFingerprint,
|
|
238
|
+
ownership: {
|
|
239
|
+
ownerEpoch: input.ownership.ownerEpoch,
|
|
240
|
+
ownershipClaimFingerprint: input.ownership.claimFingerprint,
|
|
241
|
+
},
|
|
232
242
|
runDoctor: async (migrationId) => dependencies.runDoctor(await doctorInput(migrationId)),
|
|
233
243
|
runMigration: async (doctor) => {
|
|
234
244
|
await gate.closeAndFlush();
|
|
@@ -264,6 +274,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
|
|
|
264
274
|
environment: operation.environment,
|
|
265
275
|
workspaceId: operation.workspaceId,
|
|
266
276
|
profile: operation.profile,
|
|
277
|
+
...(operation.migration.ownership === undefined ? {} : operation.migration.ownership),
|
|
267
278
|
}, sendStatus);
|
|
268
279
|
if (outcome.reopenAdmission)
|
|
269
280
|
gate.reopen();
|
|
@@ -285,20 +296,31 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
|
|
|
285
296
|
};
|
|
286
297
|
const bindServerIdentity = (server) => {
|
|
287
298
|
const nextWorkspaceId = UUID.parse(server.workspaceId).toLowerCase();
|
|
299
|
+
const nextMachineId = UUID.parse(server.machineId).toLowerCase();
|
|
288
300
|
if (workspaceId !== null && workspaceId !== nextWorkspaceId) {
|
|
289
301
|
throw new Error("Storage migration server workspace identity changed");
|
|
290
302
|
}
|
|
291
|
-
|
|
303
|
+
if (machineId !== null && machineId !== nextMachineId) {
|
|
304
|
+
throw new Error("Storage migration server machine identity changed");
|
|
305
|
+
}
|
|
306
|
+
if (input.ownership.workspaceId !== nextWorkspaceId
|
|
307
|
+
|| input.ownership.machineId !== nextMachineId
|
|
308
|
+
|| input.ownership.environment !== input.environment) {
|
|
309
|
+
throw new Error("Storage ownership claim does not match authenticated server identity");
|
|
310
|
+
}
|
|
292
311
|
const createdAt = dependencies.now().toISOString();
|
|
293
|
-
const installation = installationBase(input,
|
|
312
|
+
const installation = installationBase(input, nextWorkspaceId, undefined, createdAt);
|
|
294
313
|
const layout = storageLayout({
|
|
295
314
|
nowworkHome: input.nowworkHome,
|
|
296
315
|
environment: input.environment,
|
|
297
|
-
workspaceId,
|
|
316
|
+
workspaceId: nextWorkspaceId,
|
|
298
317
|
runtimeBase: input.local.runtimeBase,
|
|
299
318
|
platform: input.platform,
|
|
300
319
|
});
|
|
301
|
-
|
|
320
|
+
const nextIdentity = storageHelloIdentity({ installation, layout, ownership: input.ownership });
|
|
321
|
+
workspaceId = nextWorkspaceId;
|
|
322
|
+
machineId = nextMachineId;
|
|
323
|
+
identity = nextIdentity;
|
|
302
324
|
ensureControl();
|
|
303
325
|
drainPendingControlMessages();
|
|
304
326
|
};
|
|
@@ -5,10 +5,12 @@ import { buildServiceSpec } from "./computer-service.js";
|
|
|
5
5
|
import { profilePath } from "./computer-profile.js";
|
|
6
6
|
import { assertManagedServiceIdentity } from "./managed-service-lifecycle.js";
|
|
7
7
|
import { managedServiceRegistryPath, readManagedServiceRegistry, } from "./managed-service-registry.js";
|
|
8
|
-
import { resolveLocalStorageRoots } from "./storage-config.js";
|
|
8
|
+
import { installationsPath, resolveLocalStorageRoots } from "./storage-config.js";
|
|
9
9
|
import { readStorageMigrationPolicy } from "./storage-migration-policy.js";
|
|
10
10
|
import { createStorageMigrationRuntimeController, } from "./storage-runtime-controller.js";
|
|
11
11
|
import { resolveEnvTag, resolveNowworkHome } from "./storage-layout.js";
|
|
12
|
+
import { readStorageOwnershipClaim } from "./storage-ownership.js";
|
|
13
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
12
14
|
const notReady = (reason) => Object.freeze({ ready: false, reason });
|
|
13
15
|
function canonicalPath(value) {
|
|
14
16
|
const absolute = resolve(value);
|
|
@@ -73,6 +75,12 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
73
75
|
serverUrl: z.string().url().parse(rawInput.serverUrl),
|
|
74
76
|
platform: rawInput.platform,
|
|
75
77
|
};
|
|
78
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
79
|
+
userHome: rawInput.userHome,
|
|
80
|
+
platform: rawInput.platform,
|
|
81
|
+
explicit: rawInput.environment.NOWWORK_LOCAL_HOME,
|
|
82
|
+
mode: rawInput.environment.NOWWORK_LOCAL_HOME_MODE ?? rawInput.environment.NOWWORK_STORAGE_MODE,
|
|
83
|
+
});
|
|
76
84
|
let nowworkHome;
|
|
77
85
|
let environment;
|
|
78
86
|
try {
|
|
@@ -106,11 +114,12 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
106
114
|
assertManagedServiceIdentity,
|
|
107
115
|
readPolicy: readStorageMigrationPolicy,
|
|
108
116
|
createController: createStorageMigrationRuntimeController,
|
|
117
|
+
readOwnership: readStorageOwnershipClaim,
|
|
109
118
|
...injected,
|
|
110
119
|
};
|
|
111
120
|
let sourceRecord;
|
|
112
121
|
try {
|
|
113
|
-
const records = await dependencies.readManagedServices({ userHome: input.userHome });
|
|
122
|
+
const records = await dependencies.readManagedServices({ userHome: input.userHome, localHome });
|
|
114
123
|
const found = findSourceRecord(records, input);
|
|
115
124
|
if (found === null)
|
|
116
125
|
return notReady("source_service_record_missing");
|
|
@@ -132,7 +141,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
132
141
|
const assertSourceIdentity = () => dependencies.assertManagedServiceIdentity({
|
|
133
142
|
spec: sourceSpec,
|
|
134
143
|
record: sourceRecord,
|
|
135
|
-
registry: { userHome: input.userHome },
|
|
144
|
+
registry: { userHome: input.userHome, localHome },
|
|
136
145
|
});
|
|
137
146
|
try {
|
|
138
147
|
await assertSourceIdentity();
|
|
@@ -153,6 +162,17 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
153
162
|
catch {
|
|
154
163
|
return notReady("migration_policy_invalid");
|
|
155
164
|
}
|
|
165
|
+
let ownership;
|
|
166
|
+
try {
|
|
167
|
+
ownership = await dependencies.readOwnership(nowworkHome);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return notReady(error.code === "ENOENT"
|
|
171
|
+
? "storage_ownership_claim_missing"
|
|
172
|
+
: "storage_ownership_claim_invalid");
|
|
173
|
+
}
|
|
174
|
+
if (ownership.environment !== environment)
|
|
175
|
+
return notReady("storage_ownership_claim_invalid");
|
|
156
176
|
const local = resolveLocalStorageRoots({
|
|
157
177
|
platform: input.platform,
|
|
158
178
|
userHome: input.userHome,
|
|
@@ -169,6 +189,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
169
189
|
sourceRecord,
|
|
170
190
|
local,
|
|
171
191
|
policy,
|
|
192
|
+
ownership,
|
|
172
193
|
serviceEnvironment: {
|
|
173
194
|
NOWWORK_HOME: nowworkHome,
|
|
174
195
|
NOWWORK_ENV_TAG: environment,
|
|
@@ -177,7 +198,8 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
177
198
|
backupArtifacts: [
|
|
178
199
|
{ name: "profile.json", path: profilePath(input.profile, input.currentDaemonHome) },
|
|
179
200
|
{ name: "service-descriptor", path: sourceRecord.descriptorPath },
|
|
180
|
-
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome) },
|
|
201
|
+
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome, localHome) },
|
|
202
|
+
{ name: "installations.json", path: installationsPath(local.configRoot) },
|
|
181
203
|
],
|
|
182
204
|
}, { assertSourceIdentity });
|
|
183
205
|
return Object.freeze({ ready: true, runtime });
|
package/dist/storage-runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findInstallationForProfile, readInstallations, resolveLocalStorageRoots, } from "./storage-config.js";
|
|
2
2
|
import { storageHelloIdentity } from "./storage-identity.js";
|
|
3
3
|
import { storageLayout } from "./storage-layout.js";
|
|
4
|
+
import { readStorageOwnershipClaim } from "./storage-ownership.js";
|
|
4
5
|
const canonicalServerUrl = (value) => value.replace(/\/+$/u, "");
|
|
5
6
|
export function resolveStorageRuntime(input) {
|
|
6
7
|
const installation = findInstallationForProfile(input.records, input.profile);
|
|
@@ -24,7 +25,11 @@ export function resolveStorageRuntime(input) {
|
|
|
24
25
|
return Object.freeze({
|
|
25
26
|
installation,
|
|
26
27
|
layout,
|
|
27
|
-
identity: storageHelloIdentity({
|
|
28
|
+
identity: storageHelloIdentity({
|
|
29
|
+
installation,
|
|
30
|
+
layout,
|
|
31
|
+
...(input.ownership === undefined ? {} : { ownership: input.ownership }),
|
|
32
|
+
}),
|
|
28
33
|
local,
|
|
29
34
|
});
|
|
30
35
|
}
|
|
@@ -35,5 +40,16 @@ export async function loadStorageRuntime(input) {
|
|
|
35
40
|
environment: input.environment,
|
|
36
41
|
});
|
|
37
42
|
const records = await readInstallations({ configRoot: local.configRoot });
|
|
38
|
-
|
|
43
|
+
const baseline = resolveStorageRuntime({ ...input, records });
|
|
44
|
+
if (baseline === null)
|
|
45
|
+
return null;
|
|
46
|
+
let ownership;
|
|
47
|
+
try {
|
|
48
|
+
ownership = await readStorageOwnershipClaim(baseline.layout.persistentRoot);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code !== "ENOENT")
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
return ownership === undefined ? baseline : resolveStorageRuntime({ ...input, records, ownership });
|
|
39
55
|
}
|
|
@@ -18,6 +18,8 @@ const DeliveredFrameSchema = z.object({
|
|
|
18
18
|
environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
|
|
19
19
|
workspaceId: z.string().uuid(),
|
|
20
20
|
profile: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/u),
|
|
21
|
+
ownerEpoch: z.number().int().positive().safe().optional(),
|
|
22
|
+
ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u).optional(),
|
|
21
23
|
sourceStorageFingerprint: z.string().optional(),
|
|
22
24
|
targetStorageFingerprint: z.string().optional(),
|
|
23
25
|
manifestDigest: z.string().optional(),
|
|
@@ -45,6 +47,8 @@ export function storageStatusEventId(frame) {
|
|
|
45
47
|
frame.environment,
|
|
46
48
|
frame.workspaceId,
|
|
47
49
|
frame.profile,
|
|
50
|
+
frame.ownerEpoch ?? null,
|
|
51
|
+
frame.ownershipClaimFingerprint ?? null,
|
|
48
52
|
frame.sourceStorageFingerprint ?? null,
|
|
49
53
|
frame.targetStorageFingerprint ?? null,
|
|
50
54
|
frame.manifestDigest ?? null,
|
|
@@ -35,7 +35,10 @@ export function storageMigrationStatusReplay(receipt, expected) {
|
|
|
35
35
|
if (expected !== undefined && (receipt.migrationId !== expected.migrationId
|
|
36
36
|
|| receipt.environment !== expected.environment
|
|
37
37
|
|| receipt.workspaceId !== expected.workspaceId
|
|
38
|
-
|| receipt.profile !== expected.profile
|
|
38
|
+
|| receipt.profile !== expected.profile
|
|
39
|
+
|| (expected.ownerEpoch !== undefined && receipt.ownerEpoch !== expected.ownerEpoch)
|
|
40
|
+
|| (expected.ownershipClaimFingerprint !== undefined
|
|
41
|
+
&& receipt.ownershipClaimFingerprint !== expected.ownershipClaimFingerprint))) {
|
|
39
42
|
throw new Error("storage migration receipt identity mismatch");
|
|
40
43
|
}
|
|
41
44
|
return Object.freeze(storageMigrationReplayStatuses(receipt).map((status) => Object.freeze({
|
|
@@ -45,6 +48,10 @@ export function storageMigrationStatusReplay(receipt, expected) {
|
|
|
45
48
|
environment: receipt.environment,
|
|
46
49
|
workspaceId: receipt.workspaceId,
|
|
47
50
|
profile: receipt.profile,
|
|
51
|
+
...(receipt.ownerEpoch === undefined ? {} : { ownerEpoch: receipt.ownerEpoch }),
|
|
52
|
+
...(receipt.ownershipClaimFingerprint === undefined ? {} : {
|
|
53
|
+
ownershipClaimFingerprint: receipt.ownershipClaimFingerprint,
|
|
54
|
+
}),
|
|
48
55
|
...(status === "verified" ? { finalManifestDigest: receipt.manifestDigest } : {}),
|
|
49
56
|
...(status === "failed"
|
|
50
57
|
? {
|
|
@@ -98,6 +105,10 @@ export async function monitorStorageMigrationStatus(input, send, injected = {})
|
|
|
98
105
|
environment: input.environment,
|
|
99
106
|
workspaceId: input.workspaceId,
|
|
100
107
|
profile: input.profile,
|
|
108
|
+
...(input.ownerEpoch === undefined ? {} : { ownerEpoch: input.ownerEpoch }),
|
|
109
|
+
...(input.ownershipClaimFingerprint === undefined ? {} : {
|
|
110
|
+
ownershipClaimFingerprint: input.ownershipClaimFingerprint,
|
|
111
|
+
}),
|
|
101
112
|
errorCode: workerFailure.errorCode,
|
|
102
113
|
errorMessage: workerFailure.errorMessage,
|
|
103
114
|
...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
|
|
@@ -114,6 +125,10 @@ export async function monitorStorageMigrationStatus(input, send, injected = {})
|
|
|
114
125
|
environment: input.environment,
|
|
115
126
|
workspaceId: input.workspaceId,
|
|
116
127
|
profile: input.profile,
|
|
128
|
+
...(input.ownerEpoch === undefined ? {} : { ownerEpoch: input.ownerEpoch }),
|
|
129
|
+
...(input.ownershipClaimFingerprint === undefined ? {} : {
|
|
130
|
+
ownershipClaimFingerprint: input.ownershipClaimFingerprint,
|
|
131
|
+
}),
|
|
117
132
|
errorCode: "worker_heartbeat_stale_recovery_required",
|
|
118
133
|
errorMessage: "storage migration worker heartbeat expired; explicit recovery is required",
|
|
119
134
|
...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
|
|
@@ -367,6 +367,7 @@ export async function executeStorageMigrationWorker(handoffPath, injected = {})
|
|
|
367
367
|
targetWorkspaceRoot: operation.migration.targetWorkspaceRoot,
|
|
368
368
|
backupsRoot: operation.migration.backupsRoot,
|
|
369
369
|
backupArtifacts: operation.migration.backupArtifacts,
|
|
370
|
+
...(operation.migration.ownership === undefined ? {} : { ownership: operation.migration.ownership }),
|
|
370
371
|
acceptedWarnings: operation.migration.acceptedWarnings,
|
|
371
372
|
onStatus: () => assertHeartbeatHealthy(),
|
|
372
373
|
assertHealthy: assertHeartbeatHealthy,
|
|
@@ -118,6 +118,10 @@ const OperationSchema = z.object({
|
|
|
118
118
|
name: z.string().regex(/^[a-z0-9][a-z0-9_.-]{0,127}$/u),
|
|
119
119
|
path: AbsolutePathSchema,
|
|
120
120
|
}).strict()).max(32),
|
|
121
|
+
ownership: z.object({
|
|
122
|
+
ownerEpoch: z.number().int().positive().safe(),
|
|
123
|
+
ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u),
|
|
124
|
+
}).strict().optional(),
|
|
121
125
|
acceptedWarnings: z.array(StorageMigrationWarningCodeSchema)
|
|
122
126
|
.max(StorageMigrationWarningCodeSchema.options.length),
|
|
123
127
|
}).strict(),
|
package/package.json
CHANGED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { PROJECT_SKILLS_CAPABILITY, PROJECT_SKILL_PROJECTION_V2_CAPABILITY, } from "./types.js";
|
|
2
|
-
export function projectSkillV2RolloutReady(platform, readiness) {
|
|
3
|
-
const platformReady = platform === "darwin"
|
|
4
|
-
|| platform === "linux"
|
|
5
|
-
|| (platform === "win32" && readiness.nativeWindowsSmokePassed);
|
|
6
|
-
return platformReady
|
|
7
|
-
&& readiness.runtimeRootStorePrimitiveReady
|
|
8
|
-
&& readiness.runtimeRootStoreRecoveryReady
|
|
9
|
-
&& readiness.leaseRecoveryReady
|
|
10
|
-
&& readiness.controllerReady
|
|
11
|
-
&& readiness.executionHandlerReady
|
|
12
|
-
&& readiness.codexAdapterReady
|
|
13
|
-
&& readiness.claudeAdapterReady;
|
|
14
|
-
}
|
|
15
|
-
export function effectiveProjectSkillCapabilities(capabilities, projectSkillsReady, projectSkillV2Ready) {
|
|
16
|
-
const projectSkillCapabilitiesReady = projectSkillsReady && projectSkillV2Ready;
|
|
17
|
-
if (!projectSkillCapabilitiesReady
|
|
18
|
-
&& !capabilities.includes(PROJECT_SKILLS_CAPABILITY)
|
|
19
|
-
&& !capabilities.includes(PROJECT_SKILL_PROJECTION_V2_CAPABILITY)
|
|
20
|
-
&& Object.isFrozen(capabilities))
|
|
21
|
-
return capabilities;
|
|
22
|
-
const filtered = capabilities.filter((capability) => capability !== PROJECT_SKILL_PROJECTION_V2_CAPABILITY
|
|
23
|
-
&& (projectSkillCapabilitiesReady || capability !== PROJECT_SKILLS_CAPABILITY));
|
|
24
|
-
if (!projectSkillCapabilitiesReady || !filtered.includes(PROJECT_SKILLS_CAPABILITY)) {
|
|
25
|
-
return Object.freeze(filtered);
|
|
26
|
-
}
|
|
27
|
-
const v1Index = filtered.indexOf(PROJECT_SKILLS_CAPABILITY);
|
|
28
|
-
return Object.freeze([
|
|
29
|
-
...filtered.slice(0, v1Index + 1),
|
|
30
|
-
PROJECT_SKILL_PROJECTION_V2_CAPABILITY,
|
|
31
|
-
...filtered.slice(v1Index + 1),
|
|
32
|
-
]);
|
|
33
|
-
}
|
|
34
|
-
export function withProjectSkillCapabilityStatus(value, status) {
|
|
35
|
-
return {
|
|
36
|
-
...value,
|
|
37
|
-
projectSkillsStatus: status,
|
|
38
|
-
capabilities: status === "ready"
|
|
39
|
-
? value.capabilities
|
|
40
|
-
: effectiveProjectSkillCapabilities(value.capabilities, false, false),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
const settleReadinessGate = async (operation) => {
|
|
44
|
-
try {
|
|
45
|
-
return await operation() === true;
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
export async function createProjectSkillConnectionCapabilitySnapshot(input) {
|
|
52
|
-
const runtimeRootStorePrimitiveReady = await settleReadinessGate(input.probeRuntimeRootStorePrimitive);
|
|
53
|
-
const controllerReady = await settleReadinessGate(input.initializeController);
|
|
54
|
-
const recoveryReady = await settleReadinessGate(input.recoverRuntimeRootsAndLeases);
|
|
55
|
-
const readiness = Object.freeze({
|
|
56
|
-
runtimeRootStorePrimitiveReady,
|
|
57
|
-
runtimeRootStoreRecoveryReady: recoveryReady,
|
|
58
|
-
leaseRecoveryReady: recoveryReady,
|
|
59
|
-
controllerReady,
|
|
60
|
-
executionHandlerReady: true,
|
|
61
|
-
codexAdapterReady: true,
|
|
62
|
-
claudeAdapterReady: true,
|
|
63
|
-
nativeWindowsSmokePassed: false,
|
|
64
|
-
});
|
|
65
|
-
const projectSkillsReady = projectSkillV2RolloutReady(input.platform, readiness);
|
|
66
|
-
return Object.freeze({
|
|
67
|
-
capabilities: effectiveProjectSkillCapabilities(input.baseCapabilities, projectSkillsReady, projectSkillsReady),
|
|
68
|
-
projectSkillsReady,
|
|
69
|
-
readiness,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
export async function prepareProjectSkillConnectionCapabilities(input) {
|
|
73
|
-
const snapshot = await createProjectSkillConnectionCapabilitySnapshot({
|
|
74
|
-
...input,
|
|
75
|
-
baseCapabilities: [...input.baseCapabilities, ...await input.additionalCapabilities()],
|
|
76
|
-
});
|
|
77
|
-
input.applyReadiness(snapshot.projectSkillsReady);
|
|
78
|
-
return snapshot.capabilities;
|
|
79
|
-
}
|
|
80
|
-
export function createProjectSkillControllerReadiness(input) {
|
|
81
|
-
let status = "initializing";
|
|
82
|
-
let initialization = null;
|
|
83
|
-
const ensure = () => {
|
|
84
|
-
if (status === "ready")
|
|
85
|
-
return Promise.resolve(true);
|
|
86
|
-
if (initialization !== null)
|
|
87
|
-
return initialization;
|
|
88
|
-
status = "initializing";
|
|
89
|
-
const attempt = input.initialize().then(() => {
|
|
90
|
-
status = "ready";
|
|
91
|
-
return true;
|
|
92
|
-
}, (error) => {
|
|
93
|
-
status = "unavailable";
|
|
94
|
-
input.onUnavailable(error);
|
|
95
|
-
return false;
|
|
96
|
-
}).finally(() => {
|
|
97
|
-
if (initialization === attempt)
|
|
98
|
-
initialization = null;
|
|
99
|
-
input.onSettled();
|
|
100
|
-
});
|
|
101
|
-
initialization = attempt;
|
|
102
|
-
return attempt;
|
|
103
|
-
};
|
|
104
|
-
return Object.freeze({
|
|
105
|
-
status: () => status,
|
|
106
|
-
ensure,
|
|
107
|
-
applyPreflight: (ready) => { status = ready ? "ready" : "unavailable"; },
|
|
108
|
-
});
|
|
109
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { ProjectProjectionError, } from "./reconciler.js";
|
|
2
|
-
export const MAX_REMEMBERED_PROJECT_SKILL_AGENTS = 64;
|
|
3
|
-
const compareBinary = (left, right) => {
|
|
4
|
-
if (left < right)
|
|
5
|
-
return -1;
|
|
6
|
-
if (left > right)
|
|
7
|
-
return 1;
|
|
8
|
-
return 0;
|
|
9
|
-
};
|
|
10
|
-
const copySnapshot = (snapshot) => Object.freeze({
|
|
11
|
-
bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
|
|
12
|
-
generation: snapshot.generation,
|
|
13
|
-
});
|
|
14
|
-
export function createProjectSkillSnapshotEpoch(maxEntries = MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
|
|
15
|
-
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
16
|
-
throw new ProjectProjectionError("skill_projection_failed");
|
|
17
|
-
}
|
|
18
|
-
const snapshots = new Map();
|
|
19
|
-
return Object.freeze({
|
|
20
|
-
begin() {
|
|
21
|
-
snapshots.clear();
|
|
22
|
-
},
|
|
23
|
-
remember(handle, snapshot) {
|
|
24
|
-
const previous = snapshots.get(handle);
|
|
25
|
-
if (previous !== undefined && snapshot.generation < previous.generation)
|
|
26
|
-
return;
|
|
27
|
-
if (previous === undefined && snapshots.size >= maxEntries) {
|
|
28
|
-
throw new ProjectProjectionError("skill_projection_failed");
|
|
29
|
-
}
|
|
30
|
-
snapshots.set(handle, copySnapshot(snapshot));
|
|
31
|
-
},
|
|
32
|
-
forget(handle) {
|
|
33
|
-
snapshots.delete(handle);
|
|
34
|
-
},
|
|
35
|
-
entries() {
|
|
36
|
-
return Object.freeze([...snapshots.entries()]
|
|
37
|
-
.sort(([left], [right]) => compareBinary(left, right))
|
|
38
|
-
.map(([handle, snapshot]) => Object.freeze([handle, copySnapshot(snapshot)])));
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const stableErrorCode = (reason) => reason?.code === "skill_projection_snapshot_corrupt"
|
|
43
|
-
? "skill_projection_snapshot_corrupt"
|
|
44
|
-
: "skill_projection_failed";
|
|
45
|
-
export function toPathPrivateConvergenceResults(entries, results) {
|
|
46
|
-
return Object.freeze(entries.map(([handle], index) => {
|
|
47
|
-
const result = results[index];
|
|
48
|
-
if (result?.status === "fulfilled") {
|
|
49
|
-
return Object.freeze({ handle, status: "fulfilled" });
|
|
50
|
-
}
|
|
51
|
-
return Object.freeze({
|
|
52
|
-
handle,
|
|
53
|
-
status: "rejected",
|
|
54
|
-
errorCode: stableErrorCode(result?.reason),
|
|
55
|
-
});
|
|
56
|
-
}));
|
|
57
|
-
}
|