agentwheel 0.20.16 → 0.20.18
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 +38 -0
- package/dist/index.js +710 -127
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +8 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -113,11 +113,11 @@ import {
|
|
|
113
113
|
} from "./chunk-GE7F76AC.js";
|
|
114
114
|
|
|
115
115
|
// src/cli/index.ts
|
|
116
|
-
import { createHash as
|
|
116
|
+
import { createHash as createHash16 } from "crypto";
|
|
117
117
|
import { existsSync } from "fs";
|
|
118
|
-
import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as
|
|
118
|
+
import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath3, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
119
119
|
import { homedir as homedir12, tmpdir as tmpdir5 } from "os";
|
|
120
|
-
import { basename as basename21, dirname as dirname23, isAbsolute as
|
|
120
|
+
import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute5, join as join46, relative as relative11, resolve as resolve27 } from "path";
|
|
121
121
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
122
122
|
import { Command } from "commander";
|
|
123
123
|
|
|
@@ -3113,14 +3113,14 @@ async function writeMermaidAsset(response) {
|
|
|
3113
3113
|
"content-type": "application/javascript; charset=utf-8",
|
|
3114
3114
|
"cache-control": "public, max-age=3600"
|
|
3115
3115
|
});
|
|
3116
|
-
await new Promise((
|
|
3116
|
+
await new Promise((resolve28) => {
|
|
3117
3117
|
const stream = createReadStream(assetPath);
|
|
3118
3118
|
stream.on("error", () => {
|
|
3119
3119
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
3120
3120
|
else response.destroy();
|
|
3121
|
-
|
|
3121
|
+
resolve28();
|
|
3122
3122
|
});
|
|
3123
|
-
stream.on("end",
|
|
3123
|
+
stream.on("end", resolve28);
|
|
3124
3124
|
stream.pipe(response);
|
|
3125
3125
|
});
|
|
3126
3126
|
}
|
|
@@ -3147,14 +3147,14 @@ function escapeHtml2(value) {
|
|
|
3147
3147
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3148
3148
|
}
|
|
3149
3149
|
function listen(server, port, bind) {
|
|
3150
|
-
return new Promise((
|
|
3150
|
+
return new Promise((resolve28, reject) => {
|
|
3151
3151
|
const onError = (error) => {
|
|
3152
3152
|
server.off("listening", onListening);
|
|
3153
3153
|
reject(error);
|
|
3154
3154
|
};
|
|
3155
3155
|
const onListening = () => {
|
|
3156
3156
|
server.off("error", onError);
|
|
3157
|
-
|
|
3157
|
+
resolve28();
|
|
3158
3158
|
};
|
|
3159
3159
|
server.once("error", onError);
|
|
3160
3160
|
server.once("listening", onListening);
|
|
@@ -3162,7 +3162,7 @@ function listen(server, port, bind) {
|
|
|
3162
3162
|
});
|
|
3163
3163
|
}
|
|
3164
3164
|
function waitForShutdown(server, beforeClose) {
|
|
3165
|
-
return new Promise((
|
|
3165
|
+
return new Promise((resolve28) => {
|
|
3166
3166
|
let closing = false;
|
|
3167
3167
|
const shutdown = () => {
|
|
3168
3168
|
if (closing) return;
|
|
@@ -3170,7 +3170,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3170
3170
|
process.off("SIGINT", shutdown);
|
|
3171
3171
|
process.off("SIGTERM", shutdown);
|
|
3172
3172
|
beforeClose();
|
|
3173
|
-
server.close(() =>
|
|
3173
|
+
server.close(() => resolve28());
|
|
3174
3174
|
};
|
|
3175
3175
|
process.once("SIGINT", shutdown);
|
|
3176
3176
|
process.once("SIGTERM", shutdown);
|
|
@@ -3180,7 +3180,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3180
3180
|
process.off("SIGINT", shutdown);
|
|
3181
3181
|
process.off("SIGTERM", shutdown);
|
|
3182
3182
|
beforeClose();
|
|
3183
|
-
|
|
3183
|
+
resolve28();
|
|
3184
3184
|
});
|
|
3185
3185
|
});
|
|
3186
3186
|
}
|
|
@@ -3733,7 +3733,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
3733
3733
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
3734
3734
|
}
|
|
3735
3735
|
async function delay(ms) {
|
|
3736
|
-
await new Promise((
|
|
3736
|
+
await new Promise((resolve28) => setTimeout(resolve28, ms));
|
|
3737
3737
|
}
|
|
3738
3738
|
function parseClawHubSource(source) {
|
|
3739
3739
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -3910,7 +3910,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
|
|
|
3910
3910
|
if (Date.now() - started > timeoutMs) {
|
|
3911
3911
|
throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
|
|
3912
3912
|
}
|
|
3913
|
-
await new Promise((
|
|
3913
|
+
await new Promise((resolve28) => setTimeout(resolve28, 50));
|
|
3914
3914
|
}
|
|
3915
3915
|
}
|
|
3916
3916
|
try {
|
|
@@ -6776,8 +6776,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
6776
6776
|
async function withCachePathLock(path, fn) {
|
|
6777
6777
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
6778
6778
|
let release = () => void 0;
|
|
6779
|
-
const current = previous.then(() => new Promise((
|
|
6780
|
-
release =
|
|
6779
|
+
const current = previous.then(() => new Promise((resolve28) => {
|
|
6780
|
+
release = resolve28;
|
|
6781
6781
|
}));
|
|
6782
6782
|
cacheLocks.set(path, current);
|
|
6783
6783
|
await previous;
|
|
@@ -8785,6 +8785,7 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8785
8785
|
}
|
|
8786
8786
|
const foreign = [...foreignByOwner.values()];
|
|
8787
8787
|
if (foreign.length === 0) return observations.sort((left, right) => left.path.localeCompare(right.path));
|
|
8788
|
+
const currentOwner = parseWorkspaceOwner(check.workspaceOwner);
|
|
8788
8789
|
throw new Error([
|
|
8789
8790
|
`Refusing to plan ${check.adapter} at ${check.installRoot}.`,
|
|
8790
8791
|
"This runtime root already carries Agentwheel state owned by another workspace, at paths this run would install:",
|
|
@@ -8795,7 +8796,10 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8795
8796
|
`Current workspace: ${check.workspaceRoot} (state key ${check.stateKey})`,
|
|
8796
8797
|
"Agentwheel keys install state by target fingerprint, so this run cannot read that manifest and",
|
|
8797
8798
|
"would report those paths as unmanaged conflicts or drift.",
|
|
8798
|
-
|
|
8799
|
+
...currentOwner?.fleetId ? [
|
|
8800
|
+
"Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet.",
|
|
8801
|
+
...currentOwner.root !== root ? ["For this nested workspace, adopt legacy fingerprint-keyed claims with agentwheel ownership adopt-legacy."] : []
|
|
8802
|
+
] : ["Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."]
|
|
8799
8803
|
].join("\n"));
|
|
8800
8804
|
}
|
|
8801
8805
|
async function exactForeignKeepOperation(check, collision, operations) {
|
|
@@ -10001,7 +10005,7 @@ async function observe(request, transport) {
|
|
|
10001
10005
|
const normalized = normalizeRequest(request);
|
|
10002
10006
|
const source = await requireManifest(normalized, normalized.sourceStateKey, transport, "source");
|
|
10003
10007
|
const destination = await requireManifest(normalized, normalized.destinationStateKey, transport, "destination");
|
|
10004
|
-
const sourceOwner = workspaceOwnerForRoot(normalized.fromWorkspaceRoot);
|
|
10008
|
+
const sourceOwner = workspaceOwnerForRoot(normalized.fromWorkspaceRoot, normalized.fromFleetId);
|
|
10005
10009
|
const destinationOwner = workspaceOwnerForRoot(normalized.toWorkspaceRoot, normalized.toFleetId);
|
|
10006
10010
|
const destinationByPath = /* @__PURE__ */ new Map();
|
|
10007
10011
|
for (const entry of destination.entries) {
|
|
@@ -10078,6 +10082,11 @@ function normalizeRequest(request) {
|
|
|
10078
10082
|
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
|
|
10079
10083
|
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
10080
10084
|
if (!request.toFleetId.trim()) throw new Error("Destination Fleet id is required.");
|
|
10085
|
+
const fromFleetId = request.fromFleetId?.trim();
|
|
10086
|
+
if (request.fromFleetId !== void 0 && !fromFleetId) throw new Error("Source Fleet id cannot be empty.");
|
|
10087
|
+
if (fromFleetId && (fromFleetId !== request.toFleetId.trim() || resolve21(request.fromWorkspaceRoot) !== resolve21(request.toWorkspaceRoot))) {
|
|
10088
|
+
throw new Error("Fleet-qualified stale ownership retirement requires the same source and destination Fleet and workspace root.");
|
|
10089
|
+
}
|
|
10081
10090
|
return {
|
|
10082
10091
|
...request,
|
|
10083
10092
|
targetRoot,
|
|
@@ -10085,6 +10094,7 @@ function normalizeRequest(request) {
|
|
|
10085
10094
|
sourceStateKey,
|
|
10086
10095
|
destinationStateKey,
|
|
10087
10096
|
fromWorkspaceRoot: resolve21(request.fromWorkspaceRoot),
|
|
10097
|
+
...fromFleetId ? { fromFleetId } : {},
|
|
10088
10098
|
toWorkspaceRoot: resolve21(request.toWorkspaceRoot),
|
|
10089
10099
|
toFleetId: request.toFleetId.trim()
|
|
10090
10100
|
};
|
|
@@ -10101,7 +10111,7 @@ async function requireManifest(request, stateKey, transport, label) {
|
|
|
10101
10111
|
stateKey
|
|
10102
10112
|
});
|
|
10103
10113
|
if (!manifest) throw new Error(`No ${label} install manifest for state key ${stateKey}.`);
|
|
10104
|
-
if (manifest.version !== 2) throw new Error(
|
|
10114
|
+
if (manifest.version !== 2) throw new Error(`The ${label} install manifest must be an Agentwheel v2 manifest.`);
|
|
10105
10115
|
const actualStateKey = stateKeyFor(manifest.adapter, {
|
|
10106
10116
|
installationType: manifest.installationType,
|
|
10107
10117
|
stateKey: manifest.stateKey
|
|
@@ -10121,6 +10131,7 @@ function assertRetirableSourceEntry(entry, abandonIncompleteMergeOwner) {
|
|
|
10121
10131
|
}
|
|
10122
10132
|
}
|
|
10123
10133
|
async function assertContributionCoverage(source, destination, targetRoot, transport, abandonIncompleteMergeOwner) {
|
|
10134
|
+
containedArtifactPath(targetRoot, source.path);
|
|
10124
10135
|
const sourceManagedBlock = source.mode === "managed-block";
|
|
10125
10136
|
const destinationManagedBlock = destination.mode === "managed-block";
|
|
10126
10137
|
const sourceMerge = source.mergeStrategy !== void 0;
|
|
@@ -10146,8 +10157,19 @@ async function assertContributionCoverage(source, destination, targetRoot, trans
|
|
|
10146
10157
|
if (abandonIncompleteMergeOwner) return "abandoned-incomplete-merge";
|
|
10147
10158
|
throw new Error(`Cannot retire incomplete merge ownership at ${source.path}.`);
|
|
10148
10159
|
}
|
|
10149
|
-
const
|
|
10160
|
+
const selectorsDiffer = contributionSelector(source) !== contributionSelector(destination);
|
|
10161
|
+
if (selectorsDiffer && !sameContributionSelector(source, destination)) {
|
|
10162
|
+
throw new Error(`Destination Fleet does not identify the same source merge artifact at ${source.path}.`);
|
|
10163
|
+
}
|
|
10164
|
+
if (source.mergeStrategy === "json-deep" && destination.mergeStrategy === "json-deep" && hasMergeRemovalContent(destination.mergeRemoval) && sameContributionSelector(source, destination)) {
|
|
10165
|
+
if (canonicalJson(combineMergeRemovals(destination.mergeRemoval, source.mergeRemoval)) === canonicalJson(destination.mergeRemoval)) return "exact";
|
|
10166
|
+
throw new Error(`Destination Fleet does not exactly cover stale source merge contribution at ${source.path}.`);
|
|
10167
|
+
}
|
|
10168
|
+
const exactlyCovered = destination.mergeStrategy === source.mergeStrategy && hasMergeRemovalContent(destination.mergeRemoval) && sameContributionSelector(source, destination) && canonicalJson(source.mergeRemoval) === canonicalJson(destination.mergeRemoval);
|
|
10150
10169
|
if (exactlyCovered) return "exact";
|
|
10170
|
+
if (selectorsDiffer) {
|
|
10171
|
+
throw new Error(`Destination Fleet does not exactly cover stale source merge contribution at ${source.path}.`);
|
|
10172
|
+
}
|
|
10151
10173
|
const current = await transport.readFile(containedArtifactPath(targetRoot, source.path));
|
|
10152
10174
|
if (!mergeContributionAbsent(source.mergeRemoval, source.mergeStrategy, current)) {
|
|
10153
10175
|
throw new Error(`Destination Fleet does not exactly cover or replace stale source merge contribution at ${source.path}.`);
|
|
@@ -10159,6 +10181,15 @@ async function assertContributionCoverage(source, destination, targetRoot, trans
|
|
|
10159
10181
|
function contributionSelector(entry) {
|
|
10160
10182
|
return entry.logicalSelector ?? `${entry.artifactType}/${entry.artifactName}`;
|
|
10161
10183
|
}
|
|
10184
|
+
function sameContributionSelector(source, destination) {
|
|
10185
|
+
if (contributionSelector(source) === contributionSelector(destination)) return true;
|
|
10186
|
+
if (!source.packageName || source.packageName !== destination.packageName || source.artifactType !== destination.artifactType || source.artifactName !== destination.artifactName || source.installName !== destination.installName || source.sourceHash !== destination.sourceHash || !source.graphNodeId || !destination.graphNodeId) return false;
|
|
10187
|
+
const selector = artifactSelectorKey({ type: source.artifactType, name: source.artifactName });
|
|
10188
|
+
if (source.logicalSelector !== `${source.graphNodeId}:${selector}` || destination.logicalSelector !== `${destination.graphNodeId}:${selector}`) return false;
|
|
10189
|
+
const sourceAt = source.graphNodeId.lastIndexOf("@");
|
|
10190
|
+
const destinationAt = destination.graphNodeId.lastIndexOf("@");
|
|
10191
|
+
return sourceAt > 0 && destinationAt > 0 && source.graphNodeId.slice(0, sourceAt) === source.packageName && destination.graphNodeId.slice(0, destinationAt) === destination.packageName && source.graphNodeId.indexOf("+", sourceAt) > sourceAt && destination.graphNodeId.indexOf("+", destinationAt) > destinationAt;
|
|
10192
|
+
}
|
|
10162
10193
|
function assertApplyPreconditions(request) {
|
|
10163
10194
|
for (const [label, value] of [
|
|
10164
10195
|
["plan digest", request.planDigest],
|
|
@@ -10187,9 +10218,14 @@ function canonicalJson(value) {
|
|
|
10187
10218
|
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
10188
10219
|
}
|
|
10189
10220
|
|
|
10221
|
+
// src/lifecycle/ownership-adopt-legacy.ts
|
|
10222
|
+
import { createHash as createHash11 } from "crypto";
|
|
10223
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
10224
|
+
import { isAbsolute as isAbsolute3, relative as relative9, resolve as resolve23 } from "path";
|
|
10225
|
+
|
|
10190
10226
|
// src/lifecycle/legacy-recovery-plan.ts
|
|
10191
10227
|
import { createHash as createHash10 } from "crypto";
|
|
10192
|
-
import { readdir as readdir10 } from "fs/promises";
|
|
10228
|
+
import { lstat as lstat3, readdir as readdir10 } from "fs/promises";
|
|
10193
10229
|
import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
|
|
10194
10230
|
async function inventoryHistoricalLocks(fleetRoot) {
|
|
10195
10231
|
const root = join39(fleetRoot, ".agentwheel", "locks");
|
|
@@ -10201,15 +10237,38 @@ async function inventoryHistoricalLocks(fleetRoot) {
|
|
|
10201
10237
|
})) {
|
|
10202
10238
|
const path = join39(dir, entry.name);
|
|
10203
10239
|
if (entry.isDirectory()) await visit(path);
|
|
10204
|
-
else if (entry.isFile() && entry.name.endsWith(".graph-lock.json"))
|
|
10205
|
-
const lock = await readGraphLock(path);
|
|
10206
|
-
found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
|
|
10207
|
-
}
|
|
10240
|
+
else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) found.push(await historicalLock(path));
|
|
10208
10241
|
}
|
|
10209
10242
|
}
|
|
10210
10243
|
await visit(root);
|
|
10211
10244
|
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
10212
10245
|
}
|
|
10246
|
+
async function findLegacyNamedLocks(workspaceRoot, adapter, fingerprint) {
|
|
10247
|
+
const root = join39(workspaceRoot, ".agentwheel", "locks");
|
|
10248
|
+
const found = [];
|
|
10249
|
+
for (const target of await readdir10(root, { withFileTypes: true }).catch((error) => {
|
|
10250
|
+
if (error.code === "ENOENT") return [];
|
|
10251
|
+
throw error;
|
|
10252
|
+
})) {
|
|
10253
|
+
if (!target.isDirectory()) continue;
|
|
10254
|
+
const path = join39(root, target.name, adapter, `${fingerprint}.graph-lock.json`);
|
|
10255
|
+
const stats = await lstat3(path).catch((error) => {
|
|
10256
|
+
if (error.code === "ENOENT") return void 0;
|
|
10257
|
+
throw error;
|
|
10258
|
+
});
|
|
10259
|
+
if (!stats?.isFile()) continue;
|
|
10260
|
+
const item = await historicalLock(path);
|
|
10261
|
+
if (item.lock.canonical.targetFingerprint === fingerprint) found.push(item);
|
|
10262
|
+
}
|
|
10263
|
+
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
10264
|
+
}
|
|
10265
|
+
function lockCoversEntry(lock, entry) {
|
|
10266
|
+
return lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === entry.graphNodeId && artifact.logicalSelector === entry.logicalSelector && artifact.hash === entry.sourceHash);
|
|
10267
|
+
}
|
|
10268
|
+
async function historicalLock(path) {
|
|
10269
|
+
const lock = await readGraphLock(path);
|
|
10270
|
+
return { path, digest: sha2564(canonicalGraphLockJson(lock)), lock };
|
|
10271
|
+
}
|
|
10213
10272
|
async function planLegacyRecovery(input) {
|
|
10214
10273
|
const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
|
|
10215
10274
|
assertCompleteGraphPlan(graphPlan);
|
|
@@ -10340,7 +10399,7 @@ function claimFor(item, entry, locks, binding) {
|
|
|
10340
10399
|
if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
|
|
10341
10400
|
if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
|
|
10342
10401
|
evidence.push("historical graph-lock selector/node does not cover claim");
|
|
10343
|
-
} else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock
|
|
10402
|
+
} else if (boundLocks.length && !boundLocks.some((candidate) => lockCoversEntry(candidate.lock, { graphNodeId: nodeId, logicalSelector: selector, sourceHash: entry.sourceHash }))) {
|
|
10344
10403
|
evidence.push("historical graph-lock artifact hash differs from manifest source hash");
|
|
10345
10404
|
}
|
|
10346
10405
|
const foreign = owner !== binding.expectedOwner || !sameRoot || item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType || item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter) || matches.length > 0 && inTargetDir.length === 0;
|
|
@@ -10439,10 +10498,394 @@ function sha2564(value) {
|
|
|
10439
10498
|
return createHash10("sha256").update(value).digest("hex");
|
|
10440
10499
|
}
|
|
10441
10500
|
|
|
10501
|
+
// src/lifecycle/ownership-adopt-legacy.ts
|
|
10502
|
+
var absentRevision = "absent";
|
|
10503
|
+
function desiredCoverageFromPlan(plan) {
|
|
10504
|
+
return plan.operations.filter((operation) => operation.desiredHash && operation.action !== "remove" && operation.action !== "keep").map((operation) => ({
|
|
10505
|
+
path: operation.relativeDestPath,
|
|
10506
|
+
artifactType: operation.artifactType,
|
|
10507
|
+
artifactName: operation.artifactName,
|
|
10508
|
+
kind: operation.kind,
|
|
10509
|
+
...operation.packageName ? { packageName: operation.packageName } : {}
|
|
10510
|
+
})).sort((left, right) => left.path.localeCompare(right.path));
|
|
10511
|
+
}
|
|
10512
|
+
async function planAdoptLegacyOwnership(request) {
|
|
10513
|
+
return observe2(request, request.transport ?? localTransport);
|
|
10514
|
+
}
|
|
10515
|
+
async function applyAdoptLegacyOwnership(request) {
|
|
10516
|
+
assertApplyPreconditions2(request);
|
|
10517
|
+
const transport = request.transport ?? localTransport;
|
|
10518
|
+
const targetRoot = resolve23(request.targetRoot);
|
|
10519
|
+
const installationType = request.installationType.trim();
|
|
10520
|
+
const lock = await acquireApplyLock(targetRoot, request.adapter, transport, {}, {
|
|
10521
|
+
installationType,
|
|
10522
|
+
stateKey: request.destinationStateKey
|
|
10523
|
+
});
|
|
10524
|
+
try {
|
|
10525
|
+
await assertNoPendingJournals(targetRoot, request.adapter, transport);
|
|
10526
|
+
const current = await observe2(request, transport);
|
|
10527
|
+
assertExpected(request.planDigest, current.planDigest, "plan digest");
|
|
10528
|
+
assertExpected(request.expectedSourceRevision, current.source.revision, "source manifest revision");
|
|
10529
|
+
assertExpected(request.expectedDestinationRevision, current.destination.revision ?? absentRevision, "destination manifest revision");
|
|
10530
|
+
assertExpected(request.expectedInventoryRevision, current.manifestInventoryRevision, "manifest inventory revision");
|
|
10531
|
+
const identity = { targetRoot, adapter: request.adapter, installationType };
|
|
10532
|
+
const source = await requireManifest(identity, current.source.stateKey, transport, "source");
|
|
10533
|
+
const destination = await readDestination(identity, current.destination.stateKey, transport);
|
|
10534
|
+
const selectedByDigest = new Map(current.selected.map((entry) => [entry.sourceEntryDigest, entry]));
|
|
10535
|
+
const adopted = [];
|
|
10536
|
+
const retained = [];
|
|
10537
|
+
for (const entry of source.entries) {
|
|
10538
|
+
const selected = selectedByDigest.get(entryDigest(entry));
|
|
10539
|
+
if (!selected) retained.push(entry);
|
|
10540
|
+
else if (selected.action === "adopt") adopted.push({ ...entry, workspaceOwner: current.destination.owner });
|
|
10541
|
+
}
|
|
10542
|
+
if (source.entries.length - retained.length !== current.selected.length) {
|
|
10543
|
+
throw new Error("Source manifest selection changed while locked; replan required.");
|
|
10544
|
+
}
|
|
10545
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10546
|
+
if (adopted.length > 0) {
|
|
10547
|
+
declareMutationPath(current.destination.manifestPath);
|
|
10548
|
+
const base = destination ?? {
|
|
10549
|
+
version: 2,
|
|
10550
|
+
adapter: request.adapter,
|
|
10551
|
+
installationType,
|
|
10552
|
+
stateKey: current.destination.stateKey,
|
|
10553
|
+
targetRoot,
|
|
10554
|
+
generatedAt: now,
|
|
10555
|
+
revision: "pending-apply-0000",
|
|
10556
|
+
legacy: false,
|
|
10557
|
+
entries: []
|
|
10558
|
+
};
|
|
10559
|
+
await commitMetadata({
|
|
10560
|
+
...base,
|
|
10561
|
+
entries: [...base.entries, ...adopted].sort((left, right) => left.path.localeCompare(right.path))
|
|
10562
|
+
}, destination?.revision ?? null, now, transport);
|
|
10563
|
+
}
|
|
10564
|
+
declareMutationPath(current.source.manifestPath);
|
|
10565
|
+
await commitMetadata({ ...source, entries: retained }, source.revision, now, transport);
|
|
10566
|
+
return {
|
|
10567
|
+
...current,
|
|
10568
|
+
applied: true,
|
|
10569
|
+
adoptedEntries: adopted.length,
|
|
10570
|
+
retiredCoveredEntries: current.selected.length - adopted.length,
|
|
10571
|
+
sourceManifestRemoved: retained.length === 0,
|
|
10572
|
+
retainedSourceEntries: retained.length
|
|
10573
|
+
};
|
|
10574
|
+
} finally {
|
|
10575
|
+
await lock.release();
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10578
|
+
async function commitMetadata(manifest, baseRevision, now, transport) {
|
|
10579
|
+
const mutation = mutationMetadataForApplyJournal();
|
|
10580
|
+
const journal = {
|
|
10581
|
+
version: mutation ? 2 : 1,
|
|
10582
|
+
...mutation ? { mutation } : {},
|
|
10583
|
+
mode: "uninstall",
|
|
10584
|
+
adapter: manifest.adapter,
|
|
10585
|
+
installationType: manifest.installationType,
|
|
10586
|
+
stateKey: manifest.stateKey,
|
|
10587
|
+
targetRoot: manifest.targetRoot,
|
|
10588
|
+
baseRevision,
|
|
10589
|
+
createdAt: now,
|
|
10590
|
+
updatedAt: now,
|
|
10591
|
+
operations: [],
|
|
10592
|
+
completed: [],
|
|
10593
|
+
manifest
|
|
10594
|
+
};
|
|
10595
|
+
await writeApplyJournal(journal, transport);
|
|
10596
|
+
await commitManifestMetadataJournal(journal, transport);
|
|
10597
|
+
}
|
|
10598
|
+
async function observe2(request, transport) {
|
|
10599
|
+
if (transport.kind === "ssh") {
|
|
10600
|
+
throw new Error("Legacy ownership adoption does not support SSH targets; legacy SSH state does not prove its endpoint.");
|
|
10601
|
+
}
|
|
10602
|
+
const input = await normalizeRequest2(request);
|
|
10603
|
+
const { targetRoot, adapter, installationType } = input;
|
|
10604
|
+
await assertNoPendingJournals(targetRoot, adapter, transport);
|
|
10605
|
+
const identity = { targetRoot, adapter, installationType };
|
|
10606
|
+
if (!await readInstallManifest(targetRoot, adapter, transport, { installationType, stateKey: input.sourceStateKey })) {
|
|
10607
|
+
throw new Error(`Nothing to adopt: no source install manifest for state key ${input.sourceStateKey}.`);
|
|
10608
|
+
}
|
|
10609
|
+
const source = await requireManifest(identity, input.sourceStateKey, transport, "source");
|
|
10610
|
+
const destination = await readDestination(identity, input.destinationStateKey, transport);
|
|
10611
|
+
if (!destination && await pathExists(request.destinationGraphLockPath)) {
|
|
10612
|
+
throw new Error(
|
|
10613
|
+
`Destination has a stable graph lock without a stable manifest at ${request.destinationGraphLockPath}; reconcile it before adopting legacy ownership.`
|
|
10614
|
+
);
|
|
10615
|
+
}
|
|
10616
|
+
const desiredByPath = /* @__PURE__ */ new Map();
|
|
10617
|
+
for (const item of request.desiredCoverage) desiredByPath.set(item.path, [...desiredByPath.get(item.path) ?? [], item]);
|
|
10618
|
+
const { entries: selectedEntries, retained, provenance } = await selectLegacySource(
|
|
10619
|
+
input,
|
|
10620
|
+
input.sourceStateKey,
|
|
10621
|
+
input.fingerprint,
|
|
10622
|
+
source,
|
|
10623
|
+
desiredByPath
|
|
10624
|
+
);
|
|
10625
|
+
const selectedPaths = new Set(selectedEntries.map((entry) => entry.path));
|
|
10626
|
+
const selected = [];
|
|
10627
|
+
for (const entry of selectedEntries) {
|
|
10628
|
+
const runtimePath = containedArtifactPath(targetRoot, entry.path);
|
|
10629
|
+
const runtimeHash = await transport.pathExists(runtimePath) ? await transport.hashPath(runtimePath) : null;
|
|
10630
|
+
const drift = runtimeHash !== entry.hash;
|
|
10631
|
+
if (drift && request.carryDrift !== true) {
|
|
10632
|
+
throw new Error(
|
|
10633
|
+
runtimeHash === null ? `Managed artifact is missing at ${entry.path}; pass --carry-drift to keep the recorded hash.` : `Managed artifact is drifted at ${entry.path}: manifest ${entry.hash}, current ${runtimeHash}; pass --carry-drift to keep the recorded hash.`
|
|
10634
|
+
);
|
|
10635
|
+
}
|
|
10636
|
+
const atPath = destination?.entries.filter((candidate) => candidate.path === entry.path) ?? [];
|
|
10637
|
+
let destinationEntryDigest;
|
|
10638
|
+
if (atPath.length > 0) {
|
|
10639
|
+
const covering = atPath.length === 1 ? atPath[0] : void 0;
|
|
10640
|
+
if (!covering || !coversSourceEntry(covering, entry, input.destinationOwner)) {
|
|
10641
|
+
throw new Error(`Destination state at ${entry.path} is not an exact copy of the source claim owned by this workspace.`);
|
|
10642
|
+
}
|
|
10643
|
+
if (!drift) await verifyManifestEntryRuntime(targetRoot, covering, transport);
|
|
10644
|
+
destinationEntryDigest = entryDigest(covering);
|
|
10645
|
+
}
|
|
10646
|
+
selected.push({
|
|
10647
|
+
path: entry.path,
|
|
10648
|
+
action: destinationEntryDigest ? "retire-covered" : "adopt",
|
|
10649
|
+
sourceEntryDigest: entryDigest(entry),
|
|
10650
|
+
...destinationEntryDigest ? { destinationEntryDigest } : {},
|
|
10651
|
+
recordedHash: entry.hash,
|
|
10652
|
+
runtimeHash,
|
|
10653
|
+
drift
|
|
10654
|
+
});
|
|
10655
|
+
}
|
|
10656
|
+
const remainingDuplicates = [];
|
|
10657
|
+
const selectedByPath = new Map(selectedEntries.map((entry) => [entry.path, entry]));
|
|
10658
|
+
for (const item of await listInstallManifests(targetRoot, adapter, transport)) {
|
|
10659
|
+
if (item.stateKey === input.destinationStateKey) continue;
|
|
10660
|
+
const duplicatePaths = [];
|
|
10661
|
+
for (const entry of item.manifest.entries) {
|
|
10662
|
+
if (!selectedPaths.has(entry.path)) continue;
|
|
10663
|
+
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
10664
|
+
if (owner !== input.sourceOwner) {
|
|
10665
|
+
throw new Error(`${entry.path} is also claimed by ${owner} in ${item.fileName}; legacy ownership cannot be adopted.`);
|
|
10666
|
+
}
|
|
10667
|
+
if (item.stateKey !== input.sourceStateKey) duplicatePaths.push(entry.path);
|
|
10668
|
+
}
|
|
10669
|
+
if (duplicatePaths.length === 0) continue;
|
|
10670
|
+
await assertRetirableDuplicate(input, item, duplicatePaths, selectedByPath, desiredByPath, transport);
|
|
10671
|
+
for (const path of duplicatePaths) remainingDuplicates.push({ stateKey: item.stateKey, path });
|
|
10672
|
+
}
|
|
10673
|
+
const withoutDigest = {
|
|
10674
|
+
adapter,
|
|
10675
|
+
installationType,
|
|
10676
|
+
targetRoot,
|
|
10677
|
+
source: {
|
|
10678
|
+
stateKey: input.sourceStateKey,
|
|
10679
|
+
manifestPath: installManifestPath(targetRoot, adapter, { installationType, stateKey: input.sourceStateKey }),
|
|
10680
|
+
revision: source.revision,
|
|
10681
|
+
owner: input.sourceOwner,
|
|
10682
|
+
class: input.sourceClass,
|
|
10683
|
+
provenanceLock: { path: provenance.path, digest: provenance.digest }
|
|
10684
|
+
},
|
|
10685
|
+
destination: {
|
|
10686
|
+
stateKey: input.destinationStateKey,
|
|
10687
|
+
manifestPath: installManifestPath(targetRoot, adapter, { installationType, stateKey: input.destinationStateKey }),
|
|
10688
|
+
revision: destination?.revision ?? null,
|
|
10689
|
+
owner: input.destinationOwner,
|
|
10690
|
+
workspaceRoot: input.workspaceRoot
|
|
10691
|
+
},
|
|
10692
|
+
manifestInventoryRevision: await computeInstallManifestInventoryRevision(targetRoot, adapter, transport),
|
|
10693
|
+
selected: selected.sort((left, right) => left.path.localeCompare(right.path)),
|
|
10694
|
+
retained: retained.sort((left, right) => left.path.localeCompare(right.path) || left.owner.localeCompare(right.owner)),
|
|
10695
|
+
remainingDuplicates: remainingDuplicates.sort((left, right) => left.stateKey.localeCompare(right.stateKey) || left.path.localeCompare(right.path)),
|
|
10696
|
+
// identity only, so a newer package revision doesn't invalidate a reviewed plan
|
|
10697
|
+
desiredCoverage: request.desiredCoverage.filter((item) => selectedPaths.has(item.path)).map(({ path, artifactType, artifactName, kind }) => ({ path, artifactType, artifactName, kind })).sort((left, right) => left.path.localeCompare(right.path) || left.artifactType.localeCompare(right.artifactType) || left.artifactName.localeCompare(right.artifactName))
|
|
10698
|
+
};
|
|
10699
|
+
return { ...withoutDigest, planDigest: createHash11("sha256").update(canonicalJson(withoutDigest)).digest("hex") };
|
|
10700
|
+
}
|
|
10701
|
+
async function normalizeRequest2(request) {
|
|
10702
|
+
if (request.destinationFleetId !== void 0) {
|
|
10703
|
+
throw new Error("Legacy ownership adoption targets a nested workspace; a Fleet target uses fleet normalize or ownership retire-stale.");
|
|
10704
|
+
}
|
|
10705
|
+
const targetRoot = resolve23(request.targetRoot);
|
|
10706
|
+
const installationType = request.installationType.trim();
|
|
10707
|
+
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, installationType, "source");
|
|
10708
|
+
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, installationType, "destination");
|
|
10709
|
+
const fingerprint = legacyFingerprint(request.adapter, installationType, sourceStateKey);
|
|
10710
|
+
if (!fingerprint) {
|
|
10711
|
+
throw new Error(`The source must be a legacy state key of the form ${request.adapter}.${installationType}.<64-hex target fingerprint>.`);
|
|
10712
|
+
}
|
|
10713
|
+
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
10714
|
+
const scope = await resolveWorkspaceOwnershipScope(request.workspaceRoot, { globalRoot: request.globalRoot });
|
|
10715
|
+
const destinationOwner = workspaceOwnerForRoot(scope.root, scope.fleetId);
|
|
10716
|
+
const workspaceRoot = await realpath2(request.workspaceRoot);
|
|
10717
|
+
const sourceRoot = resolve23(request.fromWorkspaceRoot);
|
|
10718
|
+
const sourceOwner = workspaceOwnerForRoot(sourceRoot);
|
|
10719
|
+
let sourceClass;
|
|
10720
|
+
if (sourceRoot === workspaceRoot || sourceRoot === resolve23(request.workspaceRoot)) {
|
|
10721
|
+
if (sourceOwner === destinationOwner) {
|
|
10722
|
+
throw new Error(`${sourceOwner} is already the destination owner; ordinary install migration handles its legacy state.`);
|
|
10723
|
+
}
|
|
10724
|
+
sourceClass = "own-legacy-owner";
|
|
10725
|
+
} else {
|
|
10726
|
+
const sourceForms = [sourceRoot, ...await realpath2(sourceRoot).then((path) => [path], () => [])];
|
|
10727
|
+
const workspaceForms = [workspaceRoot, resolve23(request.workspaceRoot), scope.root];
|
|
10728
|
+
if (sourceForms.some((source) => workspaceForms.some((workspace) => containsPath2(source, workspace) || containsPath2(workspace, source)))) {
|
|
10729
|
+
throw new Error(`Source root ${sourceRoot} contains or is contained by the destination workspace; it cannot be adopted as a foreign root.`);
|
|
10730
|
+
}
|
|
10731
|
+
for (const fleet of await listRegisteredFleets({ globalRoot: request.globalRoot })) {
|
|
10732
|
+
const fleetForms = [resolve23(fleet.root), ...await realpath2(fleet.root).then((path) => [path], () => [])];
|
|
10733
|
+
if (sourceForms.some((source) => fleetForms.some((root) => containsPath2(root, source)))) {
|
|
10734
|
+
throw new Error(`Source root ${sourceRoot} is inside registered Fleet '${fleet.id}'; use fleet normalize or ownership retire-stale.`);
|
|
10735
|
+
}
|
|
10736
|
+
}
|
|
10737
|
+
sourceClass = "foreign-root";
|
|
10738
|
+
}
|
|
10739
|
+
return {
|
|
10740
|
+
targetRoot,
|
|
10741
|
+
adapter: request.adapter,
|
|
10742
|
+
installationType,
|
|
10743
|
+
sourceStateKey,
|
|
10744
|
+
destinationStateKey,
|
|
10745
|
+
fingerprint,
|
|
10746
|
+
sourceRoot,
|
|
10747
|
+
sourceOwner,
|
|
10748
|
+
workspaceRoot,
|
|
10749
|
+
destinationOwner,
|
|
10750
|
+
sourceClass
|
|
10751
|
+
};
|
|
10752
|
+
}
|
|
10753
|
+
function legacyFingerprint(adapter, installationType, stateKey) {
|
|
10754
|
+
const prefix = `${adapter}.${installationType}.`;
|
|
10755
|
+
const fingerprint = stateKey.startsWith(prefix) ? stateKey.slice(prefix.length) : "";
|
|
10756
|
+
return /^[a-f0-9]{64}$/u.test(fingerprint) ? fingerprint : void 0;
|
|
10757
|
+
}
|
|
10758
|
+
async function selectLegacySource(input, stateKey, fingerprint, source, desiredByPath) {
|
|
10759
|
+
if (source.adapterCode) {
|
|
10760
|
+
throw new Error(`The install manifest for ${stateKey} records programmatic adapter code; legacy ownership adoption refuses it.`);
|
|
10761
|
+
}
|
|
10762
|
+
if (source.entries.some((entry) => entry.workspaceOwner === input.destinationOwner)) {
|
|
10763
|
+
throw new Error(
|
|
10764
|
+
`Source state ${stateKey} has entries already owned by the destination owner; run a normal install to migrate them first.`
|
|
10765
|
+
);
|
|
10766
|
+
}
|
|
10767
|
+
for (const entry of source.entries) {
|
|
10768
|
+
if (entry.workspaceOwner === input.sourceOwner) assertPlainEntry(entry, input.targetRoot);
|
|
10769
|
+
}
|
|
10770
|
+
const entries = [];
|
|
10771
|
+
const retained = [];
|
|
10772
|
+
for (const entry of source.entries) {
|
|
10773
|
+
if (entry.workspaceOwner !== input.sourceOwner) {
|
|
10774
|
+
retained.push({ path: entry.path, owner: entry.workspaceOwner, reason: "other-owner" });
|
|
10775
|
+
continue;
|
|
10776
|
+
}
|
|
10777
|
+
const desired = desiredByPath.get(entry.path) ?? [];
|
|
10778
|
+
if (desired.length === 0) {
|
|
10779
|
+
retained.push({ path: entry.path, owner: entry.workspaceOwner, reason: "not-desired" });
|
|
10780
|
+
continue;
|
|
10781
|
+
}
|
|
10782
|
+
if (!desired.some((item) => desiredCovers(item, entry))) {
|
|
10783
|
+
throw new Error(`The current graph plans a different artifact at ${entry.path}; legacy ownership cannot be adopted there.`);
|
|
10784
|
+
}
|
|
10785
|
+
entries.push(entry);
|
|
10786
|
+
}
|
|
10787
|
+
if (entries.length === 0) {
|
|
10788
|
+
throw new Error(`Nothing to adopt: no entry owned by ${input.sourceOwner} in ${stateKey} is desired by this workspace.`);
|
|
10789
|
+
}
|
|
10790
|
+
const paths = /* @__PURE__ */ new Set();
|
|
10791
|
+
for (const entry of entries) {
|
|
10792
|
+
if (paths.has(entry.path)) throw new Error(`Source state ${stateKey} has more than one entry at ${entry.path}.`);
|
|
10793
|
+
paths.add(entry.path);
|
|
10794
|
+
}
|
|
10795
|
+
return { entries, retained, provenance: await resolveProvenance(input, fingerprint, entries) };
|
|
10796
|
+
}
|
|
10797
|
+
async function assertRetirableDuplicate(input, item, paths, selectedByPath, desiredByPath, transport) {
|
|
10798
|
+
const refuse = (path, reason) => new Error(
|
|
10799
|
+
`${path} is also claimed by ${input.sourceOwner} in ${item.fileName}, which a follow-up adopt-legacy run could not retire: ${reason}`
|
|
10800
|
+
);
|
|
10801
|
+
const fingerprint = legacyFingerprint(input.adapter, input.installationType, item.stateKey);
|
|
10802
|
+
if (!fingerprint) throw refuse(paths[0], `${item.stateKey} is not a legacy fingerprint state key for ${input.adapter}/${input.installationType}.`);
|
|
10803
|
+
let duplicates;
|
|
10804
|
+
try {
|
|
10805
|
+
const identity = { targetRoot: input.targetRoot, adapter: input.adapter, installationType: input.installationType };
|
|
10806
|
+
const manifest = await requireManifest(identity, item.stateKey, transport, "duplicate source");
|
|
10807
|
+
duplicates = (await selectLegacySource(input, item.stateKey, fingerprint, manifest, desiredByPath)).entries;
|
|
10808
|
+
} catch (error) {
|
|
10809
|
+
throw refuse(paths[0], error instanceof Error ? error.message : String(error));
|
|
10810
|
+
}
|
|
10811
|
+
for (const path of paths) {
|
|
10812
|
+
const duplicate = duplicates.find((entry) => entry.path === path);
|
|
10813
|
+
if (!duplicate || !sameRecordedArtifact(duplicate, selectedByPath.get(path))) {
|
|
10814
|
+
throw refuse(path, "its artifact identity or recorded hashes differ from the adopted entry.");
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
}
|
|
10818
|
+
async function resolveProvenance(input, fingerprint, entries) {
|
|
10819
|
+
const locks = await findLegacyNamedLocks(input.sourceRoot, input.adapter, fingerprint);
|
|
10820
|
+
if (locks.length === 0) {
|
|
10821
|
+
throw new Error(
|
|
10822
|
+
`Source root ${input.sourceRoot} has no legacy-named graph lock for ${input.adapter}/${fingerprint}; provenance cannot be proven.`
|
|
10823
|
+
);
|
|
10824
|
+
}
|
|
10825
|
+
if (input.sourceClass === "own-legacy-owner") return locks[0];
|
|
10826
|
+
if (locks.length !== 1) {
|
|
10827
|
+
throw new Error(`Source root ${input.sourceRoot} must have exactly one legacy-named graph lock for ${fingerprint}, found ${locks.length}.`);
|
|
10828
|
+
}
|
|
10829
|
+
const lock = locks[0];
|
|
10830
|
+
for (const entry of entries) {
|
|
10831
|
+
if (entry.graphLockDigest !== lock.digest) {
|
|
10832
|
+
throw new Error(`Recorded graph-lock digest for ${entry.path} does not match ${lock.path}.`);
|
|
10833
|
+
}
|
|
10834
|
+
if (!lockCoversEntry(lock.lock, entry)) {
|
|
10835
|
+
throw new Error(`Legacy graph lock ${lock.path} does not cover ${entry.path} with its recorded node, selector, and source hash.`);
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10838
|
+
return lock;
|
|
10839
|
+
}
|
|
10840
|
+
async function readDestination(identity, stateKey, transport) {
|
|
10841
|
+
if (!await readInstallManifest(identity.targetRoot, identity.adapter, transport, { installationType: identity.installationType, stateKey })) {
|
|
10842
|
+
return void 0;
|
|
10843
|
+
}
|
|
10844
|
+
return requireManifest(identity, stateKey, transport, "destination");
|
|
10845
|
+
}
|
|
10846
|
+
function assertPlainEntry(entry, targetRoot) {
|
|
10847
|
+
if (entry.mode || entry.mergeStrategy || entry.semanticPlugin || entry.semanticCommand || entry.executed) {
|
|
10848
|
+
throw new Error(`Legacy ownership adoption supports only plain file or directory entries; ${entry.path} is not one.`);
|
|
10849
|
+
}
|
|
10850
|
+
containedArtifactPath(targetRoot, entry.path);
|
|
10851
|
+
}
|
|
10852
|
+
function desiredCovers(item, entry) {
|
|
10853
|
+
return item.artifactType === entry.artifactType && item.artifactName === entry.artifactName && item.kind === entry.kind && (!item.packageName || !entry.packageName || item.packageName === entry.packageName);
|
|
10854
|
+
}
|
|
10855
|
+
function coversSourceEntry(destination, source, owner) {
|
|
10856
|
+
return destination.workspaceOwner === owner && !destination.mode && !destination.mergeStrategy && !destination.semanticPlugin && sameRecordedArtifact(destination, source);
|
|
10857
|
+
}
|
|
10858
|
+
function sameRecordedArtifact(left, right) {
|
|
10859
|
+
return left.artifactType === right.artifactType && left.artifactName === right.artifactName && left.kind === right.kind && left.hash === right.hash && left.sourceHash === right.sourceHash;
|
|
10860
|
+
}
|
|
10861
|
+
async function assertNoPendingJournals(targetRoot, adapter, transport) {
|
|
10862
|
+
const pending = await listAllApplyJournals(targetRoot, adapter, transport);
|
|
10863
|
+
if (pending.length > 0) {
|
|
10864
|
+
throw new Error(`Cannot adopt legacy ownership while runtime apply journal(s) are pending: ${pending.map((item) => item.path).join(", ")}`);
|
|
10865
|
+
}
|
|
10866
|
+
}
|
|
10867
|
+
function assertApplyPreconditions2(request) {
|
|
10868
|
+
for (const [label, value, allowAbsent] of [
|
|
10869
|
+
["plan digest", request.planDigest, false],
|
|
10870
|
+
["source manifest revision", request.expectedSourceRevision, false],
|
|
10871
|
+
["destination manifest revision", request.expectedDestinationRevision, true],
|
|
10872
|
+
["manifest inventory revision", request.expectedInventoryRevision, false]
|
|
10873
|
+
]) {
|
|
10874
|
+
if (allowAbsent && value === absentRevision) continue;
|
|
10875
|
+
if (!value || !/^[a-f0-9]{64}$/.test(value)) {
|
|
10876
|
+
throw new Error(`Applying legacy ownership adoption requires a reviewed lowercase SHA-256 ${label}${allowAbsent ? " or 'absent'" : ""}.`);
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
function containsPath2(parent, candidate) {
|
|
10881
|
+
const path = relative9(parent, candidate);
|
|
10882
|
+
return path === "" || !path.startsWith("..") && !isAbsolute3(path);
|
|
10883
|
+
}
|
|
10884
|
+
|
|
10442
10885
|
// src/version/policy.ts
|
|
10443
10886
|
import { execFile as execFile3 } from "child_process";
|
|
10444
10887
|
import { readFile as readFile23 } from "fs/promises";
|
|
10445
|
-
import { join as join40, resolve as
|
|
10888
|
+
import { join as join40, resolve as resolve24 } from "path";
|
|
10446
10889
|
import { promisify as promisify3 } from "util";
|
|
10447
10890
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
10448
10891
|
import { z as z5 } from "zod";
|
|
@@ -10536,7 +10979,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
10536
10979
|
if (tagged.length > 0) return tagged;
|
|
10537
10980
|
}
|
|
10538
10981
|
if (driverName === "local") {
|
|
10539
|
-
const root =
|
|
10982
|
+
const root = resolve24(workspaceRoot, pkg.source);
|
|
10540
10983
|
const manifest2 = await readPackageManifest(root);
|
|
10541
10984
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
10542
10985
|
try {
|
|
@@ -10659,7 +11102,7 @@ async function readVersionCache(path) {
|
|
|
10659
11102
|
// src/profile/members.ts
|
|
10660
11103
|
import { execFile as execFile4 } from "child_process";
|
|
10661
11104
|
import { readFile as readFile24 } from "fs/promises";
|
|
10662
|
-
import { join as join41, resolve as
|
|
11105
|
+
import { join as join41, resolve as resolve25 } from "path";
|
|
10663
11106
|
import { promisify as promisify4 } from "util";
|
|
10664
11107
|
import { z as z7 } from "zod";
|
|
10665
11108
|
|
|
@@ -10834,7 +11277,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
10834
11277
|
let stderr = "";
|
|
10835
11278
|
try {
|
|
10836
11279
|
if (member.transport === "local") {
|
|
10837
|
-
const workspace =
|
|
11280
|
+
const workspace = resolve25(parentWorkspace, member.workspace);
|
|
10838
11281
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
10839
11282
|
cwd: workspace,
|
|
10840
11283
|
env: env2,
|
|
@@ -10880,7 +11323,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
10880
11323
|
process.execPath,
|
|
10881
11324
|
[process.argv[1], ...memberCommandArgs(member, args)],
|
|
10882
11325
|
{
|
|
10883
|
-
cwd:
|
|
11326
|
+
cwd: resolve25(parentWorkspace, member.workspace),
|
|
10884
11327
|
env: env2,
|
|
10885
11328
|
maxBuffer: 20 * 1024 * 1024
|
|
10886
11329
|
}
|
|
@@ -10986,7 +11429,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
10986
11429
|
}
|
|
10987
11430
|
}
|
|
10988
11431
|
function compositeKey(workspaceRoot, profileName) {
|
|
10989
|
-
return `${
|
|
11432
|
+
return `${resolve25(workspaceRoot)}#${profileName}`;
|
|
10990
11433
|
}
|
|
10991
11434
|
|
|
10992
11435
|
// src/status/repository.ts
|
|
@@ -11033,7 +11476,7 @@ function valueAfter(lines, prefix) {
|
|
|
11033
11476
|
}
|
|
11034
11477
|
|
|
11035
11478
|
// src/catalogue/client.ts
|
|
11036
|
-
import { createHash as
|
|
11479
|
+
import { createHash as createHash12 } from "crypto";
|
|
11037
11480
|
import { readFile as readFile25, rm as rm8 } from "fs/promises";
|
|
11038
11481
|
import { homedir as homedir9 } from "os";
|
|
11039
11482
|
import { join as join42 } from "path";
|
|
@@ -11308,7 +11751,7 @@ var CatalogueClient = class {
|
|
|
11308
11751
|
}
|
|
11309
11752
|
return {
|
|
11310
11753
|
value: schema.parse(value),
|
|
11311
|
-
digest:
|
|
11754
|
+
digest: createHash12("sha256").update(bytes).digest("hex")
|
|
11312
11755
|
};
|
|
11313
11756
|
}
|
|
11314
11757
|
};
|
|
@@ -11319,7 +11762,7 @@ function sameSources2(a, b) {
|
|
|
11319
11762
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
11320
11763
|
}
|
|
11321
11764
|
function catalogueContentHash(enriched, vercel) {
|
|
11322
|
-
return
|
|
11765
|
+
return createHash12("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
|
|
11323
11766
|
}
|
|
11324
11767
|
|
|
11325
11768
|
// src/search/index.ts
|
|
@@ -11681,7 +12124,7 @@ function includesToken(normalizedText, token) {
|
|
|
11681
12124
|
}
|
|
11682
12125
|
|
|
11683
12126
|
// src/semantic/index.ts
|
|
11684
|
-
import { createHash as
|
|
12127
|
+
import { createHash as createHash13 } from "crypto";
|
|
11685
12128
|
import { homedir as homedir10 } from "os";
|
|
11686
12129
|
import { join as join43 } from "path";
|
|
11687
12130
|
import { env, pipeline } from "@huggingface/transformers";
|
|
@@ -11771,7 +12214,7 @@ var SemanticSearchClient = class {
|
|
|
11771
12214
|
if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
|
|
11772
12215
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
11773
12216
|
if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
|
|
11774
|
-
const digest2 =
|
|
12217
|
+
const digest2 = createHash13("sha256").update(bytes).digest("hex");
|
|
11775
12218
|
if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
|
|
11776
12219
|
return bytes;
|
|
11777
12220
|
}
|
|
@@ -11861,7 +12304,7 @@ function ensureTrailingSlash(value) {
|
|
|
11861
12304
|
}
|
|
11862
12305
|
|
|
11863
12306
|
// src/trial/skill.ts
|
|
11864
|
-
import { createHash as
|
|
12307
|
+
import { createHash as createHash14 } from "crypto";
|
|
11865
12308
|
import { readFile as readFile26, stat as stat13 } from "fs/promises";
|
|
11866
12309
|
import { join as join44 } from "path";
|
|
11867
12310
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -11891,7 +12334,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
11891
12334
|
skill: {
|
|
11892
12335
|
name: artifact.name,
|
|
11893
12336
|
relativePath: artifact.relativePath,
|
|
11894
|
-
sha256:
|
|
12337
|
+
sha256: createHash14("sha256").update(content).digest("hex"),
|
|
11895
12338
|
frontmatter,
|
|
11896
12339
|
content
|
|
11897
12340
|
}
|
|
@@ -11910,12 +12353,12 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
11910
12353
|
}
|
|
11911
12354
|
|
|
11912
12355
|
// src/lifecycle/fleet-normalize.ts
|
|
11913
|
-
import { createHash as
|
|
12356
|
+
import { createHash as createHash15 } from "crypto";
|
|
11914
12357
|
import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
|
|
11915
12358
|
import { homedir as homedir11 } from "os";
|
|
11916
|
-
import { dirname as dirname22, isAbsolute as
|
|
12359
|
+
import { dirname as dirname22, isAbsolute as isAbsolute4, join as join45, relative as relative10, resolve as resolve26 } from "path";
|
|
11917
12360
|
async function planFleetNormalization(request) {
|
|
11918
|
-
const normalizedRequest =
|
|
12361
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
11919
12362
|
const destinationScope = await resolveWorkspaceScope({
|
|
11920
12363
|
fleet: normalizedRequest.destinationFleet,
|
|
11921
12364
|
globalRoot: normalizedRequest.globalRoot
|
|
@@ -11987,7 +12430,7 @@ async function applyFleetNormalization(request, hooks = {}) {
|
|
|
11987
12430
|
if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
|
|
11988
12431
|
if (!request.planDigest) throw new Error("Applying fleet normalization requires --plan-digest <digest> from a reviewed dry-run.");
|
|
11989
12432
|
assertDigest(request.planDigest);
|
|
11990
|
-
const normalizedRequest =
|
|
12433
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
11991
12434
|
const roots = await normalizationRoots(normalizedRequest);
|
|
11992
12435
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
11993
12436
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -12126,7 +12569,7 @@ async function assertNormalizationPostconditions(journal) {
|
|
|
12126
12569
|
}
|
|
12127
12570
|
}
|
|
12128
12571
|
async function recoverFleetNormalization(request) {
|
|
12129
|
-
const normalizedRequest =
|
|
12572
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
12130
12573
|
const roots = await normalizationRoots(normalizedRequest);
|
|
12131
12574
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
12132
12575
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -12169,7 +12612,7 @@ async function assertSourceFleetPostcondition(request, source, selectedPackages)
|
|
|
12169
12612
|
);
|
|
12170
12613
|
}
|
|
12171
12614
|
}
|
|
12172
|
-
function
|
|
12615
|
+
function normalizeRequest3(request) {
|
|
12173
12616
|
const packages = request.packages === void 0 ? void 0 : sortedUnique6(request.packages);
|
|
12174
12617
|
if (request.packages !== void 0 && packages?.length === 0) throw new Error("--package requires at least one package name.");
|
|
12175
12618
|
const artifacts = request.artifacts === void 0 ? void 0 : sortedUnique6(request.artifacts.map((artifact) => normalizeArtifactSelector(artifact)));
|
|
@@ -12191,17 +12634,17 @@ function normalizeRequest2(request) {
|
|
|
12191
12634
|
...profile ? { profile } : {},
|
|
12192
12635
|
...agent ? { agent } : {},
|
|
12193
12636
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
12194
|
-
...request.globalRoot ? { globalRoot:
|
|
12637
|
+
...request.globalRoot ? { globalRoot: resolve26(request.globalRoot) } : {}
|
|
12195
12638
|
};
|
|
12196
12639
|
}
|
|
12197
12640
|
function normalizeOrphanedOwnerRoot(value) {
|
|
12198
12641
|
const root = value.trim();
|
|
12199
|
-
if (!root || !
|
|
12200
|
-
return
|
|
12642
|
+
if (!root || !isAbsolute4(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
12643
|
+
return resolve26(root);
|
|
12201
12644
|
}
|
|
12202
12645
|
async function normalizationRoots(request) {
|
|
12203
12646
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
12204
|
-
const source = request.from === "user" ?
|
|
12647
|
+
const source = request.from === "user" ? resolve26(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
12205
12648
|
return { source, destination: destination.root };
|
|
12206
12649
|
}
|
|
12207
12650
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -12416,7 +12859,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
12416
12859
|
let normalizedGraphCount = 0;
|
|
12417
12860
|
for (const graph of graphCandidates) {
|
|
12418
12861
|
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages);
|
|
12419
|
-
if (
|
|
12862
|
+
if (resolve26(destinationState.graphLockPath) === resolve26(graph.path)) {
|
|
12420
12863
|
const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
|
|
12421
12864
|
if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
12422
12865
|
assertCorrelatedGraphManifest(
|
|
@@ -12431,7 +12874,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
12431
12874
|
}
|
|
12432
12875
|
}
|
|
12433
12876
|
const legacyState = await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages);
|
|
12434
|
-
if (
|
|
12877
|
+
if (resolve26(legacyState.graphLockPath) !== resolve26(graph.path)) {
|
|
12435
12878
|
throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12436
12879
|
}
|
|
12437
12880
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
@@ -12606,7 +13049,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
12606
13049
|
async function runtimeRoots(scope) {
|
|
12607
13050
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
12608
13051
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
12609
|
-
roots.add(
|
|
13052
|
+
roots.add(resolve26(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
12610
13053
|
}
|
|
12611
13054
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
12612
13055
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -12643,14 +13086,14 @@ async function collectManifests(roots) {
|
|
|
12643
13086
|
}
|
|
12644
13087
|
continue;
|
|
12645
13088
|
}
|
|
12646
|
-
if (
|
|
13089
|
+
if (resolve26(parsed.targetRoot) !== resolve26(root)) {
|
|
12647
13090
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
12648
13091
|
}
|
|
12649
13092
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
12650
13093
|
installationType: parsed.installationType,
|
|
12651
13094
|
stateKey: parsed.stateKey
|
|
12652
13095
|
});
|
|
12653
|
-
if (
|
|
13096
|
+
if (resolve26(expectedPath) !== resolve26(path)) {
|
|
12654
13097
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
12655
13098
|
}
|
|
12656
13099
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -12671,7 +13114,7 @@ async function collectManifestPaths(paths) {
|
|
|
12671
13114
|
installationType: parsed.installationType,
|
|
12672
13115
|
stateKey: parsed.stateKey
|
|
12673
13116
|
});
|
|
12674
|
-
if (
|
|
13117
|
+
if (resolve26(expectedPath) !== resolve26(path)) {
|
|
12675
13118
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
12676
13119
|
}
|
|
12677
13120
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -12697,7 +13140,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
12697
13140
|
if (!path.endsWith(".graph-lock.json")) continue;
|
|
12698
13141
|
const lock = await readGraphLock(path);
|
|
12699
13142
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
12700
|
-
const parts =
|
|
13143
|
+
const parts = relative10(root, path).split(/[\\/]/);
|
|
12701
13144
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
12702
13145
|
const [targetKey2, adapter] = parts;
|
|
12703
13146
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
@@ -12739,7 +13182,7 @@ async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
|
12739
13182
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
12740
13183
|
if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12741
13184
|
const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
|
|
12742
|
-
if (
|
|
13185
|
+
if (resolve26(stableState.graphLockPath) !== resolve26(graph.path)) return false;
|
|
12743
13186
|
const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
|
|
12744
13187
|
return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
|
|
12745
13188
|
}
|
|
@@ -12760,7 +13203,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
12760
13203
|
baseDir: scope.root
|
|
12761
13204
|
});
|
|
12762
13205
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
12763
|
-
const installRoot =
|
|
13206
|
+
const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
12764
13207
|
const stateKey = stateKeyFor(adapter.name, {
|
|
12765
13208
|
installationType: agent.installationType,
|
|
12766
13209
|
stateKey: agent.stateKey,
|
|
@@ -12828,7 +13271,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
12828
13271
|
ssh: void 0,
|
|
12829
13272
|
...agent?.stateKey ? { stateKey: agent.stateKey } : {}
|
|
12830
13273
|
};
|
|
12831
|
-
const installRoot =
|
|
13274
|
+
const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
12832
13275
|
const targetIdentity = resolveTargetStateIdentity({
|
|
12833
13276
|
targetFingerprintParts: fingerprintParts,
|
|
12834
13277
|
workspaceRoot: scope.root,
|
|
@@ -12860,16 +13303,16 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
12860
13303
|
async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
|
|
12861
13304
|
const stable = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12862
13305
|
let state;
|
|
12863
|
-
if (
|
|
13306
|
+
if (resolve26(graph.path) === resolve26(stable.graphLockPath)) {
|
|
12864
13307
|
state = stable;
|
|
12865
13308
|
} else {
|
|
12866
13309
|
const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
|
|
12867
|
-
if (
|
|
13310
|
+
if (resolve26(graph.path) !== resolve26(released.graphLockPath)) {
|
|
12868
13311
|
throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12869
13312
|
}
|
|
12870
13313
|
state = released;
|
|
12871
13314
|
}
|
|
12872
|
-
const manifest = manifests.find((candidate) =>
|
|
13315
|
+
const manifest = manifests.find((candidate) => resolve26(candidate.path) === resolve26(state.manifestPath));
|
|
12873
13316
|
if (!manifest) {
|
|
12874
13317
|
throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
|
|
12875
13318
|
}
|
|
@@ -12901,7 +13344,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
|
|
|
12901
13344
|
};
|
|
12902
13345
|
}
|
|
12903
13346
|
function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
|
|
12904
|
-
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey ||
|
|
13347
|
+
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve26(manifest.manifest.targetRoot) !== resolve26(state.installRoot)) {
|
|
12905
13348
|
throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
|
|
12906
13349
|
}
|
|
12907
13350
|
const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
|
|
@@ -12958,7 +13401,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
12958
13401
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
12959
13402
|
if (existing) {
|
|
12960
13403
|
const manifest2 = existing.manifest;
|
|
12961
|
-
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey ||
|
|
13404
|
+
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve26(manifest2.targetRoot) !== state.installRoot) {
|
|
12962
13405
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
12963
13406
|
}
|
|
12964
13407
|
}
|
|
@@ -13018,7 +13461,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
13018
13461
|
const content = await readFile27(path, "utf8");
|
|
13019
13462
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
13020
13463
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
13021
|
-
} else if (
|
|
13464
|
+
} else if (resolve26(sourcePath) === resolve26(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
13022
13465
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
13023
13466
|
} else {
|
|
13024
13467
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -13029,8 +13472,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
13029
13472
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
13030
13473
|
}
|
|
13031
13474
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
13032
|
-
const normalizedRoot =
|
|
13033
|
-
const candidate =
|
|
13475
|
+
const normalizedRoot = resolve26(root);
|
|
13476
|
+
const candidate = resolve26(normalizedRoot, entryPath);
|
|
13034
13477
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
13035
13478
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
13036
13479
|
}
|
|
@@ -13121,8 +13564,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
13121
13564
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
13122
13565
|
}
|
|
13123
13566
|
function renderedEntryPath(manifest, entry) {
|
|
13124
|
-
const root =
|
|
13125
|
-
const candidate =
|
|
13567
|
+
const root = resolve26(manifest.targetRoot);
|
|
13568
|
+
const candidate = resolve26(root, entry.path);
|
|
13126
13569
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
13127
13570
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
13128
13571
|
}
|
|
@@ -13476,7 +13919,7 @@ function sortValue(value) {
|
|
|
13476
13919
|
return value;
|
|
13477
13920
|
}
|
|
13478
13921
|
function sha2565(value) {
|
|
13479
|
-
return
|
|
13922
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
13480
13923
|
}
|
|
13481
13924
|
function assertDigest(value) {
|
|
13482
13925
|
if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
|
|
@@ -13516,6 +13959,7 @@ var commandGovernance = {
|
|
|
13516
13959
|
"ownership handoff": "governed-unless-dry-run",
|
|
13517
13960
|
"ownership recovery-plan": "incidental-cache",
|
|
13518
13961
|
"ownership retire-stale": "governed-apply",
|
|
13962
|
+
"ownership adopt-legacy": "governed-apply",
|
|
13519
13963
|
"mcp retire": "governed-apply",
|
|
13520
13964
|
eject: "governed-always",
|
|
13521
13965
|
uninstall: "governed-unless-dry-run",
|
|
@@ -13935,7 +14379,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13935
14379
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13936
14380
|
if (options.toFleet) {
|
|
13937
14381
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
13938
|
-
if (
|
|
14382
|
+
if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
|
|
13939
14383
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13940
14384
|
}
|
|
13941
14385
|
}
|
|
@@ -13969,49 +14413,15 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
|
|
|
13969
14413
|
const adapter = await resolveAdapterForTarget(target, {});
|
|
13970
14414
|
const transport = transportForTarget(target);
|
|
13971
14415
|
const requestedReport = normalizeCliPath(options.report);
|
|
13972
|
-
const reportPath = join46(await
|
|
13973
|
-
const reportInsideTarget =
|
|
13974
|
-
if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !
|
|
14416
|
+
const reportPath = join46(await realpath3(dirname23(requestedReport)), basename21(requestedReport));
|
|
14417
|
+
const reportInsideTarget = relative11(resolve27(target.targetRoot), reportPath);
|
|
14418
|
+
if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute5(reportInsideTarget)) {
|
|
13975
14419
|
throw new Error("Recovery report must be outside the runtime target root.");
|
|
13976
14420
|
}
|
|
13977
14421
|
const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
13978
14422
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
13979
14423
|
if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
|
|
13980
|
-
|
|
13981
|
-
const priorTmpdir = process.env.TMPDIR;
|
|
13982
|
-
try {
|
|
13983
|
-
process.env.TMPDIR = stage;
|
|
13984
|
-
const graphPlan = await createGraphSourcePlan({
|
|
13985
|
-
roots: config.packages.map((pkg) => ({
|
|
13986
|
-
rootId: pkg.name,
|
|
13987
|
-
source: pkg.source,
|
|
13988
|
-
mode: pkg.mode,
|
|
13989
|
-
version: pkg.version,
|
|
13990
|
-
ref: pkg.requestedRef,
|
|
13991
|
-
select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
13992
|
-
selection: pkg.selection,
|
|
13993
|
-
aliases: pkg.aliases,
|
|
13994
|
-
overrides: pkg.overrides,
|
|
13995
|
-
includeSuggestions: pkg.withSuggestions,
|
|
13996
|
-
suggestionAliases: pkg.suggestions,
|
|
13997
|
-
useLock: false
|
|
13998
|
-
})),
|
|
13999
|
-
targetRoot: target.targetRoot,
|
|
14000
|
-
workspaceRoot: target.workspaceRoot,
|
|
14001
|
-
fleetId: target.fleetId,
|
|
14002
|
-
adapter,
|
|
14003
|
-
transport,
|
|
14004
|
-
targetKey: targetKeyForTarget(target, adapter.name),
|
|
14005
|
-
targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
|
|
14006
|
-
installationType,
|
|
14007
|
-
stateKey: target.stateKey,
|
|
14008
|
-
readOnly: true,
|
|
14009
|
-
yes: true,
|
|
14010
|
-
freshGraphOnly: true,
|
|
14011
|
-
deferForeignStateCheck: true,
|
|
14012
|
-
cacheRoot: join46(stage, "source-cache"),
|
|
14013
|
-
registryCachePath: join46(stage, "registry.json")
|
|
14014
|
-
});
|
|
14424
|
+
await withFreshReadOnlyGraphPlan(target, adapter, adapterOptionsForTarget(target, {}), installationType, config.packages, async (graphPlan) => {
|
|
14015
14425
|
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
|
|
14016
14426
|
const report = await planLegacyRecovery({
|
|
14017
14427
|
graphPlan,
|
|
@@ -14029,17 +14439,13 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
|
|
|
14029
14439
|
`, { mode: 384, flag: "wx" });
|
|
14030
14440
|
console.log(`Recovery report: ${reportPath}`);
|
|
14031
14441
|
console.log(JSON.stringify(report.counts));
|
|
14032
|
-
}
|
|
14033
|
-
if (priorTmpdir === void 0) delete process.env.TMPDIR;
|
|
14034
|
-
else process.env.TMPDIR = priorTmpdir;
|
|
14035
|
-
await rm10(stage, { recursive: true, force: true });
|
|
14036
|
-
}
|
|
14442
|
+
});
|
|
14037
14443
|
});
|
|
14038
|
-
ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").option("--destination-state-key <key>", "exact Fleet-owned destination install-manifest state key (defaults to the stable target state)").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
14444
|
+
ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").option("--from-fleet-id <id>", "exact stale Fleet owner id for same-Fleet duplicate state").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").option("--destination-state-key <key>", "exact Fleet-owned destination install-manifest state key (defaults to the stable target state)").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
14039
14445
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
14040
14446
|
const fleet = await showRegisteredFleet(options.fleet);
|
|
14041
14447
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
14042
|
-
if (
|
|
14448
|
+
if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
|
|
14043
14449
|
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
14044
14450
|
}
|
|
14045
14451
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
@@ -14047,7 +14453,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14047
14453
|
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
14048
14454
|
}
|
|
14049
14455
|
const target = targets[0];
|
|
14050
|
-
if (target.fleetId !== options.fleet ||
|
|
14456
|
+
if (target.fleetId !== options.fleet || resolve27(target.workspaceRoot) !== resolve27(fleet.root)) {
|
|
14051
14457
|
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
14052
14458
|
}
|
|
14053
14459
|
const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
|
|
@@ -14061,6 +14467,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14061
14467
|
sourceStateKey: options.sourceStateKey,
|
|
14062
14468
|
destinationStateKey: options.destinationStateKey ?? stableState.stateKey,
|
|
14063
14469
|
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
14470
|
+
fromFleetId: options.fromFleetId,
|
|
14064
14471
|
toWorkspaceRoot,
|
|
14065
14472
|
toFleetId: fleet.id,
|
|
14066
14473
|
planDigest: options.planDigest,
|
|
@@ -14078,6 +14485,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14078
14485
|
"retire-stale",
|
|
14079
14486
|
"--from-workspace-root",
|
|
14080
14487
|
request.fromWorkspaceRoot,
|
|
14488
|
+
...request.fromFleetId ? ["--from-fleet-id", request.fromFleetId] : [],
|
|
14081
14489
|
"--to-workspace-root",
|
|
14082
14490
|
request.toWorkspaceRoot,
|
|
14083
14491
|
"--source-state-key",
|
|
@@ -14117,6 +14525,138 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14117
14525
|
console.log(`Plan digest: ${result.planDigest}`);
|
|
14118
14526
|
if (!options.apply) console.log(`Apply: ${applyCommand}`);
|
|
14119
14527
|
});
|
|
14528
|
+
ownershipCommand.command("adopt-legacy").description("move proven legacy-keyed ownership into this workspace's stable target state without rewriting runtime artifacts").requiredOption("--source-state-key <key>", "exact legacy install-manifest state key").requiredOption("--from-workspace-root <path>", "exact plain workspace owner root of the entries to move").option("--agent <name>", "named agent of this workspace").option("--profile <name>", "workspace profile resolving to one target").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--carry-drift", "also move entries whose runtime differs from the recorded hash; the recorded hash is kept", false).option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256|absent>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--apply", "apply the exact reviewed metadata-only adoption", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
14529
|
+
if (Boolean(options.agent) === Boolean(options.profile)) {
|
|
14530
|
+
throw new Error("Legacy ownership adoption requires exactly one of --agent or --profile.");
|
|
14531
|
+
}
|
|
14532
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
14533
|
+
const requestedInstallationType = normalizedOptions.installationType;
|
|
14534
|
+
const plainInstallOptions = {
|
|
14535
|
+
...normalizedOptions,
|
|
14536
|
+
installationType: void 0,
|
|
14537
|
+
adapterConfig: void 0,
|
|
14538
|
+
adapterModule: void 0
|
|
14539
|
+
};
|
|
14540
|
+
const targets = await resolveCliTargets(plainInstallOptions);
|
|
14541
|
+
if (targets.length !== 1) {
|
|
14542
|
+
throw new Error(`Legacy ownership adoption requires exactly one runtime target, found ${targets.length}.`);
|
|
14543
|
+
}
|
|
14544
|
+
const target = targets[0];
|
|
14545
|
+
if (target.fleetId) {
|
|
14546
|
+
throw new Error("Legacy ownership adoption targets a nested workspace; a Fleet target uses fleet normalize or ownership retire-stale.");
|
|
14547
|
+
}
|
|
14548
|
+
if (target.transport === "ssh") throw new Error("Legacy ownership adoption does not support SSH targets.");
|
|
14549
|
+
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14550
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14551
|
+
for (const pkg of config.packages) graphGroupForPackage(groups, target, pkg, plainInstallOptions).packages.push(pkg);
|
|
14552
|
+
if (groups.size === 0) throw new Error(`No configured source packages in ${target.workspaceRoot}.`);
|
|
14553
|
+
const installationTypes = [...new Set([...groups.values()].map((candidate) => candidate.installationType))];
|
|
14554
|
+
if (installationTypes.length > 1) {
|
|
14555
|
+
throw new Error(
|
|
14556
|
+
`Configured packages use installation types ${installationTypes.join(", ")}, each with its own install state; legacy ownership adoption needs exactly one.`
|
|
14557
|
+
);
|
|
14558
|
+
}
|
|
14559
|
+
if (requestedInstallationType !== void 0 && requestedInstallationType !== installationTypes[0]) {
|
|
14560
|
+
throw new Error(
|
|
14561
|
+
`--installation-type ${requestedInstallationType} does not match installation type ${installationTypes[0]}, which install uses for the configured packages.`
|
|
14562
|
+
);
|
|
14563
|
+
}
|
|
14564
|
+
if (groups.size > 1) {
|
|
14565
|
+
throw new Error(
|
|
14566
|
+
`Configured packages resolve to ${groups.size} adapter configurations for ${installationTypes[0]}, each with its own install state; legacy ownership adoption needs exactly one.`
|
|
14567
|
+
);
|
|
14568
|
+
}
|
|
14569
|
+
const group = [...groups.values()][0];
|
|
14570
|
+
const { installationType, adapterOptions } = group;
|
|
14571
|
+
const requestedAdapterSettings = [
|
|
14572
|
+
{ flag: "--adapter-config", requested: normalizedOptions.adapterConfig, resolved: adapterOptions.adapterConfig, none: "no adapter config" },
|
|
14573
|
+
{ flag: "--adapter-module", requested: normalizedOptions.adapterModule, resolved: adapterOptions.adapterModule, none: "no adapter module" }
|
|
14574
|
+
];
|
|
14575
|
+
for (const { flag, requested, resolved, none } of requestedAdapterSettings) {
|
|
14576
|
+
if (requested === void 0) continue;
|
|
14577
|
+
if (resolved !== void 0 && resolve27(target.workspaceRoot, requested) === resolve27(target.workspaceRoot, resolved)) continue;
|
|
14578
|
+
throw new Error(`${flag} ${requested} does not match what install resolves for the configured packages: ${resolved ?? none}.`);
|
|
14579
|
+
}
|
|
14580
|
+
const adapter = await resolveAdapterForTarget(group.target, adapterOptions);
|
|
14581
|
+
const stable = installStateForTarget(group.target, adapter, adapterOptions, installationType);
|
|
14582
|
+
const stableGraphLockPath = graphLockPathForTarget(
|
|
14583
|
+
group.target.workspaceRoot,
|
|
14584
|
+
targetKeyForTarget(group.target, adapter.name),
|
|
14585
|
+
adapter.name,
|
|
14586
|
+
targetFingerprintParts(group.target, adapter, adapterOptions, installationType),
|
|
14587
|
+
targetIdentityContext(group.target, adapter, installationType)
|
|
14588
|
+
);
|
|
14589
|
+
const desiredCoverage = await withFreshReadOnlyGraphPlan(group.target, adapter, adapterOptions, installationType, group.packages, async (graphPlan) => {
|
|
14590
|
+
if (graphPlan.plan.stateKey !== stable.stateKey || graphPlan.graphLockPath !== stableGraphLockPath) {
|
|
14591
|
+
throw new Error("Current source graph resolved a different target state identity; legacy ownership adoption cannot proceed.");
|
|
14592
|
+
}
|
|
14593
|
+
return desiredCoverageFromPlan(graphPlan.plan);
|
|
14594
|
+
});
|
|
14595
|
+
const request = {
|
|
14596
|
+
targetRoot: stable.installRoot,
|
|
14597
|
+
adapter: adapter.name,
|
|
14598
|
+
installationType,
|
|
14599
|
+
sourceStateKey: options.sourceStateKey,
|
|
14600
|
+
destinationStateKey: stable.stateKey,
|
|
14601
|
+
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
14602
|
+
workspaceRoot: target.workspaceRoot,
|
|
14603
|
+
destinationFleetId: target.fleetId,
|
|
14604
|
+
destinationGraphLockPath: stableGraphLockPath,
|
|
14605
|
+
desiredCoverage,
|
|
14606
|
+
carryDrift: options.carryDrift === true,
|
|
14607
|
+
planDigest: options.planDigest,
|
|
14608
|
+
expectedSourceRevision: options.expectedSourceRevision,
|
|
14609
|
+
expectedDestinationRevision: options.expectedDestinationRevision,
|
|
14610
|
+
expectedInventoryRevision: options.expectedInventoryRevision,
|
|
14611
|
+
transport: transportForTarget(target)
|
|
14612
|
+
};
|
|
14613
|
+
const result = options.apply ? await applyAdoptLegacyOwnership(request) : await planAdoptLegacyOwnership(request);
|
|
14614
|
+
const destinationRevision = result.destination.revision ?? "absent";
|
|
14615
|
+
const applyArgs = [
|
|
14616
|
+
"agentwheel",
|
|
14617
|
+
"ownership",
|
|
14618
|
+
"adopt-legacy",
|
|
14619
|
+
"--source-state-key",
|
|
14620
|
+
result.source.stateKey,
|
|
14621
|
+
"--from-workspace-root",
|
|
14622
|
+
request.fromWorkspaceRoot,
|
|
14623
|
+
...options.profile ? ["--profile", options.profile] : ["--agent", options.agent],
|
|
14624
|
+
"--installation-type",
|
|
14625
|
+
installationType,
|
|
14626
|
+
...options.adapterConfig ? ["--adapter-config", options.adapterConfig] : [],
|
|
14627
|
+
...options.adapterModule ? ["--adapter-module", options.adapterModule] : [],
|
|
14628
|
+
...options.allowAdapterCode ? ["--allow-adapter-code"] : [],
|
|
14629
|
+
...request.carryDrift ? ["--carry-drift"] : [],
|
|
14630
|
+
"--plan-digest",
|
|
14631
|
+
result.planDigest,
|
|
14632
|
+
"--expected-source-revision",
|
|
14633
|
+
result.source.revision,
|
|
14634
|
+
"--expected-destination-revision",
|
|
14635
|
+
destinationRevision,
|
|
14636
|
+
"--expected-inventory-revision",
|
|
14637
|
+
result.manifestInventoryRevision,
|
|
14638
|
+
"--apply"
|
|
14639
|
+
];
|
|
14640
|
+
const applyCommand = applyArgs.map(shellQuoteArgument).join(" ");
|
|
14641
|
+
if (options.json) {
|
|
14642
|
+
console.log(JSON.stringify({ ...result, applyCommand }, null, 2));
|
|
14643
|
+
return;
|
|
14644
|
+
}
|
|
14645
|
+
console.log(`${options.apply ? "Adopted legacy ownership" : "Legacy ownership adoption plan"}: ${result.selected.length} entr${result.selected.length === 1 ? "y" : "ies"}`);
|
|
14646
|
+
console.log(`Target: ${result.adapter}/${result.installationType} at ${result.targetRoot}`);
|
|
14647
|
+
console.log(`Source: ${result.source.stateKey} revision ${result.source.revision} (${result.source.class}, ${result.source.owner})`);
|
|
14648
|
+
console.log(`Provenance: ${result.source.provenanceLock.path} (${result.source.provenanceLock.digest})`);
|
|
14649
|
+
console.log(`Destination: ${result.destination.stateKey} revision ${destinationRevision} (${result.destination.owner})`);
|
|
14650
|
+
console.log(`Manifest inventory revision: ${result.manifestInventoryRevision}`);
|
|
14651
|
+
for (const entry of result.selected) {
|
|
14652
|
+
const drift = entry.drift ? ` drift: recorded ${entry.recordedHash}, runtime ${entry.runtimeHash ?? "missing"}` : "";
|
|
14653
|
+
console.log(`- ${entry.path}: ${entry.action}${drift}`);
|
|
14654
|
+
}
|
|
14655
|
+
for (const entry of result.retained) console.log(` retained ${entry.path} (${entry.reason}, ${entry.owner})`);
|
|
14656
|
+
for (const entry of result.remainingDuplicates) console.log(` duplicate claim remains in ${entry.stateKey}: ${entry.path}`);
|
|
14657
|
+
console.log(`Plan digest: ${result.planDigest}`);
|
|
14658
|
+
if (!options.apply) console.log(`Apply from ${target.workspaceRoot}: ${applyCommand}`);
|
|
14659
|
+
});
|
|
14120
14660
|
var mcpCommand = program.command("mcp").description("operate on MCP runtime configuration");
|
|
14121
14661
|
mcpCommand.command("retire").description("remove one exact legacy MCP contribution with explicit state ownership").argument("<package>", "configured package containing exactly one legacy MCP artifact").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit runtime/project root").option("--agent <name>", "one named agent from merged config").option("--profile <name>", "workspace runtime profile resolving to one target").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--from-workspace-root <path>", "required previous owner when managed legacy state exists").option("--dry-run", "show the retirement plan without writing (default)", false).option("--apply", "apply the exact reviewed retirement plan", false).option("--json", "print the install plan as JSON", false).action(async (packageName, options) => {
|
|
14122
14662
|
if (options.apply && options.dryRun) throw new Error("--apply cannot be combined with --dry-run.");
|
|
@@ -15132,7 +15672,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
15132
15672
|
selectedPreviousNodeIds,
|
|
15133
15673
|
selectedRootIds
|
|
15134
15674
|
);
|
|
15135
|
-
const graphLockDigest =
|
|
15675
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15136
15676
|
return {
|
|
15137
15677
|
...result,
|
|
15138
15678
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -15258,7 +15798,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
|
|
|
15258
15798
|
function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
15259
15799
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
|
|
15260
15800
|
const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
|
|
15261
|
-
const graphLockDigest =
|
|
15801
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15262
15802
|
return {
|
|
15263
15803
|
...scoped,
|
|
15264
15804
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15269,7 +15809,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
|
15269
15809
|
function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
|
|
15270
15810
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
|
|
15271
15811
|
const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
|
|
15272
|
-
const graphLockDigest =
|
|
15812
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15273
15813
|
return {
|
|
15274
15814
|
...scoped,
|
|
15275
15815
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15564,7 +16104,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
|
15564
16104
|
selectedPreviousNodeIds,
|
|
15565
16105
|
/* @__PURE__ */ new Set([rootId])
|
|
15566
16106
|
);
|
|
15567
|
-
const graphLockDigest =
|
|
16107
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15568
16108
|
return {
|
|
15569
16109
|
...scoped,
|
|
15570
16110
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15826,6 +16366,49 @@ function targetFingerprintParts(target, adapter, options, installationType) {
|
|
|
15826
16366
|
stateKey: target.stateKey
|
|
15827
16367
|
};
|
|
15828
16368
|
}
|
|
16369
|
+
async function withFreshReadOnlyGraphPlan(target, adapter, adapterOptions, installationType, packages, use) {
|
|
16370
|
+
const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
|
|
16371
|
+
const priorTmpdir = process.env.TMPDIR;
|
|
16372
|
+
try {
|
|
16373
|
+
process.env.TMPDIR = stage;
|
|
16374
|
+
const graphPlan = await createGraphSourcePlan({
|
|
16375
|
+
roots: packages.map((pkg) => ({
|
|
16376
|
+
rootId: pkg.name,
|
|
16377
|
+
source: pkg.source,
|
|
16378
|
+
mode: pkg.mode,
|
|
16379
|
+
version: pkg.version,
|
|
16380
|
+
ref: pkg.requestedRef,
|
|
16381
|
+
select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
16382
|
+
selection: pkg.selection,
|
|
16383
|
+
aliases: pkg.aliases,
|
|
16384
|
+
overrides: pkg.overrides,
|
|
16385
|
+
includeSuggestions: pkg.withSuggestions,
|
|
16386
|
+
suggestionAliases: pkg.suggestions,
|
|
16387
|
+
useLock: false
|
|
16388
|
+
})),
|
|
16389
|
+
targetRoot: target.targetRoot,
|
|
16390
|
+
workspaceRoot: target.workspaceRoot,
|
|
16391
|
+
fleetId: target.fleetId,
|
|
16392
|
+
adapter,
|
|
16393
|
+
transport: transportForTarget(target),
|
|
16394
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
16395
|
+
targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
16396
|
+
installationType,
|
|
16397
|
+
stateKey: target.stateKey,
|
|
16398
|
+
readOnly: true,
|
|
16399
|
+
yes: true,
|
|
16400
|
+
freshGraphOnly: true,
|
|
16401
|
+
deferForeignStateCheck: true,
|
|
16402
|
+
cacheRoot: join46(stage, "source-cache"),
|
|
16403
|
+
registryCachePath: join46(stage, "registry.json")
|
|
16404
|
+
});
|
|
16405
|
+
return await use(graphPlan);
|
|
16406
|
+
} finally {
|
|
16407
|
+
if (priorTmpdir === void 0) delete process.env.TMPDIR;
|
|
16408
|
+
else process.env.TMPDIR = priorTmpdir;
|
|
16409
|
+
await rm10(stage, { recursive: true, force: true });
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
15829
16412
|
function installStateForTarget(target, adapter, options, installationType) {
|
|
15830
16413
|
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
|
|
15831
16414
|
const targetIdentity = resolveTargetStateIdentity({
|
|
@@ -15904,7 +16487,7 @@ async function resolveCliTargetState(target, options) {
|
|
|
15904
16487
|
migration: prior.migration,
|
|
15905
16488
|
targetStateFilePreconditions: {
|
|
15906
16489
|
graphLockPath: stableGraphLockPath,
|
|
15907
|
-
graphLockRevision: stableLock ?
|
|
16490
|
+
graphLockRevision: stableLock ? createHash16("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
|
|
15908
16491
|
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
15909
16492
|
}
|
|
15910
16493
|
};
|
|
@@ -15927,7 +16510,7 @@ async function collectTargetStatus(target, options) {
|
|
|
15927
16510
|
const pending = await collectPendingInstallWork(target, options);
|
|
15928
16511
|
const foreignObservations = pending.foreignStateObservations;
|
|
15929
16512
|
const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
|
|
15930
|
-
const observedManifestRevision = foreignObservations.length > 0 ?
|
|
16513
|
+
const observedManifestRevision = foreignObservations.length > 0 ? createHash16("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
|
|
15931
16514
|
observation.fileName,
|
|
15932
16515
|
observation.manifestRevision
|
|
15933
16516
|
]).sort())).digest("hex") : null;
|
|
@@ -16174,7 +16757,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
16174
16757
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
16175
16758
|
);
|
|
16176
16759
|
}
|
|
16177
|
-
const memberRoot =
|
|
16760
|
+
const memberRoot = resolve27(workspaceRoot, member.workspace);
|
|
16178
16761
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
16179
16762
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
16180
16763
|
throw new Error(
|
|
@@ -16659,14 +17242,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
16659
17242
|
}
|
|
16660
17243
|
function normalizeCliPath(value) {
|
|
16661
17244
|
if (value === "~") return homedir12();
|
|
16662
|
-
if (value.startsWith("~/")) return
|
|
16663
|
-
return
|
|
17245
|
+
if (value.startsWith("~/")) return resolve27(homedir12(), value.slice(2));
|
|
17246
|
+
return resolve27(value);
|
|
16664
17247
|
}
|
|
16665
17248
|
function shellQuoteArgument(value) {
|
|
16666
17249
|
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
16667
17250
|
}
|
|
16668
17251
|
function isHomePath(path) {
|
|
16669
|
-
return
|
|
17252
|
+
return resolve27(path) === resolve27(homedir12());
|
|
16670
17253
|
}
|
|
16671
17254
|
function adapterListFromOption(adapter) {
|
|
16672
17255
|
if (!adapter) return [];
|
|
@@ -16787,7 +17370,7 @@ function withFleetExample(config) {
|
|
|
16787
17370
|
};
|
|
16788
17371
|
}
|
|
16789
17372
|
async function findAgentwheelPackageRoot(start) {
|
|
16790
|
-
let current =
|
|
17373
|
+
let current = resolve27(start);
|
|
16791
17374
|
while (true) {
|
|
16792
17375
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
16793
17376
|
const parent = dirname23(current);
|