agentwheel 0.20.17 → 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/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 createHash15 } from "crypto";
116
+ import { createHash as createHash16 } from "crypto";
117
117
  import { existsSync } from "fs";
118
- import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm10, writeFile as writeFile13 } from "fs/promises";
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 isAbsolute4, join as join46, relative as relative10, resolve as resolve26 } from "path";
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((resolve27) => {
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
- resolve27();
3121
+ resolve28();
3122
3122
  });
3123
- stream.on("end", resolve27);
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3148
3148
  }
3149
3149
  function listen(server, port, bind) {
3150
- return new Promise((resolve27, reject) => {
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
- resolve27();
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((resolve27) => {
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(() => resolve27());
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
- resolve27();
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((resolve27) => setTimeout(resolve27, ms));
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((resolve27) => setTimeout(resolve27, 50));
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((resolve27) => {
6780
- release = resolve27;
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
- parseWorkspaceOwner(check.workspaceOwner)?.fleetId ? "Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet." : "Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."
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) {
@@ -10107,7 +10111,7 @@ async function requireManifest(request, stateKey, transport, label) {
10107
10111
  stateKey
10108
10112
  });
10109
10113
  if (!manifest) throw new Error(`No ${label} install manifest for state key ${stateKey}.`);
10110
- if (manifest.version !== 2) throw new Error(`${label} ownership retirement requires an Agentwheel v2 install manifest.`);
10114
+ if (manifest.version !== 2) throw new Error(`The ${label} install manifest must be an Agentwheel v2 manifest.`);
10111
10115
  const actualStateKey = stateKeyFor(manifest.adapter, {
10112
10116
  installationType: manifest.installationType,
10113
10117
  stateKey: manifest.stateKey
@@ -10214,9 +10218,14 @@ function canonicalJson(value) {
10214
10218
  return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
10215
10219
  }
10216
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
+
10217
10226
  // src/lifecycle/legacy-recovery-plan.ts
10218
10227
  import { createHash as createHash10 } from "crypto";
10219
- import { readdir as readdir10 } from "fs/promises";
10228
+ import { lstat as lstat3, readdir as readdir10 } from "fs/promises";
10220
10229
  import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
10221
10230
  async function inventoryHistoricalLocks(fleetRoot) {
10222
10231
  const root = join39(fleetRoot, ".agentwheel", "locks");
@@ -10228,15 +10237,38 @@ async function inventoryHistoricalLocks(fleetRoot) {
10228
10237
  })) {
10229
10238
  const path = join39(dir, entry.name);
10230
10239
  if (entry.isDirectory()) await visit(path);
10231
- else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) {
10232
- const lock = await readGraphLock(path);
10233
- found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
10234
- }
10240
+ else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) found.push(await historicalLock(path));
10235
10241
  }
10236
10242
  }
10237
10243
  await visit(root);
10238
10244
  return found.sort((a, b) => a.path.localeCompare(b.path));
10239
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
+ }
10240
10272
  async function planLegacyRecovery(input) {
10241
10273
  const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
10242
10274
  assertCompleteGraphPlan(graphPlan);
@@ -10367,7 +10399,7 @@ function claimFor(item, entry, locks, binding) {
10367
10399
  if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
10368
10400
  if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
10369
10401
  evidence.push("historical graph-lock selector/node does not cover claim");
10370
- } else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector && artifact.hash === entry.sourceHash))) {
10402
+ } else if (boundLocks.length && !boundLocks.some((candidate) => lockCoversEntry(candidate.lock, { graphNodeId: nodeId, logicalSelector: selector, sourceHash: entry.sourceHash }))) {
10371
10403
  evidence.push("historical graph-lock artifact hash differs from manifest source hash");
10372
10404
  }
10373
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;
@@ -10466,10 +10498,394 @@ function sha2564(value) {
10466
10498
  return createHash10("sha256").update(value).digest("hex");
10467
10499
  }
10468
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
+
10469
10885
  // src/version/policy.ts
10470
10886
  import { execFile as execFile3 } from "child_process";
10471
10887
  import { readFile as readFile23 } from "fs/promises";
10472
- import { join as join40, resolve as resolve23 } from "path";
10888
+ import { join as join40, resolve as resolve24 } from "path";
10473
10889
  import { promisify as promisify3 } from "util";
10474
10890
  import { parse as parseJsonc } from "jsonc-parser";
10475
10891
  import { z as z5 } from "zod";
@@ -10563,7 +10979,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
10563
10979
  if (tagged.length > 0) return tagged;
10564
10980
  }
10565
10981
  if (driverName === "local") {
10566
- const root = resolve23(workspaceRoot, pkg.source);
10982
+ const root = resolve24(workspaceRoot, pkg.source);
10567
10983
  const manifest2 = await readPackageManifest(root);
10568
10984
  const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
10569
10985
  try {
@@ -10686,7 +11102,7 @@ async function readVersionCache(path) {
10686
11102
  // src/profile/members.ts
10687
11103
  import { execFile as execFile4 } from "child_process";
10688
11104
  import { readFile as readFile24 } from "fs/promises";
10689
- import { join as join41, resolve as resolve24 } from "path";
11105
+ import { join as join41, resolve as resolve25 } from "path";
10690
11106
  import { promisify as promisify4 } from "util";
10691
11107
  import { z as z7 } from "zod";
10692
11108
 
@@ -10861,7 +11277,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
10861
11277
  let stderr = "";
10862
11278
  try {
10863
11279
  if (member.transport === "local") {
10864
- const workspace = resolve24(parentWorkspace, member.workspace);
11280
+ const workspace = resolve25(parentWorkspace, member.workspace);
10865
11281
  const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
10866
11282
  cwd: workspace,
10867
11283
  env: env2,
@@ -10907,7 +11323,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
10907
11323
  process.execPath,
10908
11324
  [process.argv[1], ...memberCommandArgs(member, args)],
10909
11325
  {
10910
- cwd: resolve24(parentWorkspace, member.workspace),
11326
+ cwd: resolve25(parentWorkspace, member.workspace),
10911
11327
  env: env2,
10912
11328
  maxBuffer: 20 * 1024 * 1024
10913
11329
  }
@@ -11013,7 +11429,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
11013
11429
  }
11014
11430
  }
11015
11431
  function compositeKey(workspaceRoot, profileName) {
11016
- return `${resolve24(workspaceRoot)}#${profileName}`;
11432
+ return `${resolve25(workspaceRoot)}#${profileName}`;
11017
11433
  }
11018
11434
 
11019
11435
  // src/status/repository.ts
@@ -11060,7 +11476,7 @@ function valueAfter(lines, prefix) {
11060
11476
  }
11061
11477
 
11062
11478
  // src/catalogue/client.ts
11063
- import { createHash as createHash11 } from "crypto";
11479
+ import { createHash as createHash12 } from "crypto";
11064
11480
  import { readFile as readFile25, rm as rm8 } from "fs/promises";
11065
11481
  import { homedir as homedir9 } from "os";
11066
11482
  import { join as join42 } from "path";
@@ -11335,7 +11751,7 @@ var CatalogueClient = class {
11335
11751
  }
11336
11752
  return {
11337
11753
  value: schema.parse(value),
11338
- digest: createHash11("sha256").update(bytes).digest("hex")
11754
+ digest: createHash12("sha256").update(bytes).digest("hex")
11339
11755
  };
11340
11756
  }
11341
11757
  };
@@ -11346,7 +11762,7 @@ function sameSources2(a, b) {
11346
11762
  return a.length === b.length && a.every((source, index) => source === b[index]);
11347
11763
  }
11348
11764
  function catalogueContentHash(enriched, vercel) {
11349
- return createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11765
+ return createHash12("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11350
11766
  }
11351
11767
 
11352
11768
  // src/search/index.ts
@@ -11708,7 +12124,7 @@ function includesToken(normalizedText, token) {
11708
12124
  }
11709
12125
 
11710
12126
  // src/semantic/index.ts
11711
- import { createHash as createHash12 } from "crypto";
12127
+ import { createHash as createHash13 } from "crypto";
11712
12128
  import { homedir as homedir10 } from "os";
11713
12129
  import { join as join43 } from "path";
11714
12130
  import { env, pipeline } from "@huggingface/transformers";
@@ -11798,7 +12214,7 @@ var SemanticSearchClient = class {
11798
12214
  if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
11799
12215
  const bytes = new Uint8Array(await response.arrayBuffer());
11800
12216
  if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
11801
- const digest2 = createHash12("sha256").update(bytes).digest("hex");
12217
+ const digest2 = createHash13("sha256").update(bytes).digest("hex");
11802
12218
  if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
11803
12219
  return bytes;
11804
12220
  }
@@ -11888,7 +12304,7 @@ function ensureTrailingSlash(value) {
11888
12304
  }
11889
12305
 
11890
12306
  // src/trial/skill.ts
11891
- import { createHash as createHash13 } from "crypto";
12307
+ import { createHash as createHash14 } from "crypto";
11892
12308
  import { readFile as readFile26, stat as stat13 } from "fs/promises";
11893
12309
  import { join as join44 } from "path";
11894
12310
  import { parse as parseYaml2 } from "yaml";
@@ -11918,7 +12334,7 @@ async function createSkillTrial(driver, resolved, selectors) {
11918
12334
  skill: {
11919
12335
  name: artifact.name,
11920
12336
  relativePath: artifact.relativePath,
11921
- sha256: createHash13("sha256").update(content).digest("hex"),
12337
+ sha256: createHash14("sha256").update(content).digest("hex"),
11922
12338
  frontmatter,
11923
12339
  content
11924
12340
  }
@@ -11937,12 +12353,12 @@ function readSkillFrontmatter(content, artifact) {
11937
12353
  }
11938
12354
 
11939
12355
  // src/lifecycle/fleet-normalize.ts
11940
- import { createHash as createHash14 } from "crypto";
12356
+ import { createHash as createHash15 } from "crypto";
11941
12357
  import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
11942
12358
  import { homedir as homedir11 } from "os";
11943
- import { dirname as dirname22, isAbsolute as isAbsolute3, join as join45, relative as relative9, resolve as resolve25 } from "path";
12359
+ import { dirname as dirname22, isAbsolute as isAbsolute4, join as join45, relative as relative10, resolve as resolve26 } from "path";
11944
12360
  async function planFleetNormalization(request) {
11945
- const normalizedRequest = normalizeRequest2(request);
12361
+ const normalizedRequest = normalizeRequest3(request);
11946
12362
  const destinationScope = await resolveWorkspaceScope({
11947
12363
  fleet: normalizedRequest.destinationFleet,
11948
12364
  globalRoot: normalizedRequest.globalRoot
@@ -12014,7 +12430,7 @@ async function applyFleetNormalization(request, hooks = {}) {
12014
12430
  if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
12015
12431
  if (!request.planDigest) throw new Error("Applying fleet normalization requires --plan-digest <digest> from a reviewed dry-run.");
12016
12432
  assertDigest(request.planDigest);
12017
- const normalizedRequest = normalizeRequest2(request);
12433
+ const normalizedRequest = normalizeRequest3(request);
12018
12434
  const roots = await normalizationRoots(normalizedRequest);
12019
12435
  const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
12020
12436
  return withOrderedLocks(ordered, async () => {
@@ -12153,7 +12569,7 @@ async function assertNormalizationPostconditions(journal) {
12153
12569
  }
12154
12570
  }
12155
12571
  async function recoverFleetNormalization(request) {
12156
- const normalizedRequest = normalizeRequest2(request);
12572
+ const normalizedRequest = normalizeRequest3(request);
12157
12573
  const roots = await normalizationRoots(normalizedRequest);
12158
12574
  const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
12159
12575
  return withOrderedLocks(ordered, async () => {
@@ -12196,7 +12612,7 @@ async function assertSourceFleetPostcondition(request, source, selectedPackages)
12196
12612
  );
12197
12613
  }
12198
12614
  }
12199
- function normalizeRequest2(request) {
12615
+ function normalizeRequest3(request) {
12200
12616
  const packages = request.packages === void 0 ? void 0 : sortedUnique6(request.packages);
12201
12617
  if (request.packages !== void 0 && packages?.length === 0) throw new Error("--package requires at least one package name.");
12202
12618
  const artifacts = request.artifacts === void 0 ? void 0 : sortedUnique6(request.artifacts.map((artifact) => normalizeArtifactSelector(artifact)));
@@ -12218,17 +12634,17 @@ function normalizeRequest2(request) {
12218
12634
  ...profile ? { profile } : {},
12219
12635
  ...agent ? { agent } : {},
12220
12636
  ...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
12221
- ...request.globalRoot ? { globalRoot: resolve25(request.globalRoot) } : {}
12637
+ ...request.globalRoot ? { globalRoot: resolve26(request.globalRoot) } : {}
12222
12638
  };
12223
12639
  }
12224
12640
  function normalizeOrphanedOwnerRoot(value) {
12225
12641
  const root = value.trim();
12226
- if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
12227
- return resolve25(root);
12642
+ if (!root || !isAbsolute4(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
12643
+ return resolve26(root);
12228
12644
  }
12229
12645
  async function normalizationRoots(request) {
12230
12646
  const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
12231
- const source = request.from === "user" ? resolve25(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
12647
+ const source = request.from === "user" ? resolve26(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
12232
12648
  return { source, destination: destination.root };
12233
12649
  }
12234
12650
  async function withOrderedLocks(roots, fn) {
@@ -12443,7 +12859,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12443
12859
  let normalizedGraphCount = 0;
12444
12860
  for (const graph of graphCandidates) {
12445
12861
  const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages);
12446
- if (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
12862
+ if (resolve26(destinationState.graphLockPath) === resolve26(graph.path)) {
12447
12863
  const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
12448
12864
  if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
12449
12865
  assertCorrelatedGraphManifest(
@@ -12458,7 +12874,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12458
12874
  }
12459
12875
  }
12460
12876
  const legacyState = await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages);
12461
- if (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
12877
+ if (resolve26(legacyState.graphLockPath) !== resolve26(graph.path)) {
12462
12878
  throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
12463
12879
  }
12464
12880
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
@@ -12633,7 +13049,7 @@ function targetKeysForAgent(scope, name) {
12633
13049
  async function runtimeRoots(scope) {
12634
13050
  const roots = /* @__PURE__ */ new Set([scope.root]);
12635
13051
  if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
12636
- roots.add(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
13052
+ roots.add(resolve26(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
12637
13053
  }
12638
13054
  for (const [name, agent] of Object.entries(scope.config.agents)) {
12639
13055
  if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
@@ -12670,14 +13086,14 @@ async function collectManifests(roots) {
12670
13086
  }
12671
13087
  continue;
12672
13088
  }
12673
- if (resolve25(parsed.targetRoot) !== resolve25(root)) {
13089
+ if (resolve26(parsed.targetRoot) !== resolve26(root)) {
12674
13090
  throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
12675
13091
  }
12676
13092
  const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
12677
13093
  installationType: parsed.installationType,
12678
13094
  stateKey: parsed.stateKey
12679
13095
  });
12680
- if (resolve25(expectedPath) !== resolve25(path)) {
13096
+ if (resolve26(expectedPath) !== resolve26(path)) {
12681
13097
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12682
13098
  }
12683
13099
  found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12698,7 +13114,7 @@ async function collectManifestPaths(paths) {
12698
13114
  installationType: parsed.installationType,
12699
13115
  stateKey: parsed.stateKey
12700
13116
  });
12701
- if (resolve25(expectedPath) !== resolve25(path)) {
13117
+ if (resolve26(expectedPath) !== resolve26(path)) {
12702
13118
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12703
13119
  }
12704
13120
  found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12724,7 +13140,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
12724
13140
  if (!path.endsWith(".graph-lock.json")) continue;
12725
13141
  const lock = await readGraphLock(path);
12726
13142
  if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
12727
- const parts = relative9(root, path).split(/[\\/]/);
13143
+ const parts = relative10(root, path).split(/[\\/]/);
12728
13144
  if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
12729
13145
  const [targetKey2, adapter] = parts;
12730
13146
  if (targetKeys && !targetKeys.has(targetKey2)) continue;
@@ -12766,7 +13182,7 @@ async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
12766
13182
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
12767
13183
  if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
12768
13184
  const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
12769
- if (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
13185
+ if (resolve26(stableState.graphLockPath) !== resolve26(graph.path)) return false;
12770
13186
  const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
12771
13187
  return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
12772
13188
  }
@@ -12787,7 +13203,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
12787
13203
  baseDir: scope.root
12788
13204
  });
12789
13205
  const targetRoot = resolveConfigPath(agent.root, scope.root);
12790
- const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
13206
+ const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
12791
13207
  const stateKey = stateKeyFor(adapter.name, {
12792
13208
  installationType: agent.installationType,
12793
13209
  stateKey: agent.stateKey,
@@ -12855,7 +13271,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12855
13271
  ssh: void 0,
12856
13272
  ...agent?.stateKey ? { stateKey: agent.stateKey } : {}
12857
13273
  };
12858
- const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
13274
+ const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
12859
13275
  const targetIdentity = resolveTargetStateIdentity({
12860
13276
  targetFingerprintParts: fingerprintParts,
12861
13277
  workspaceRoot: scope.root,
@@ -12887,16 +13303,16 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12887
13303
  async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
12888
13304
  const stable = await targetStateForGraph(scope, graph, identityFleetId);
12889
13305
  let state;
12890
- if (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
13306
+ if (resolve26(graph.path) === resolve26(stable.graphLockPath)) {
12891
13307
  state = stable;
12892
13308
  } else {
12893
13309
  const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
12894
- if (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
13310
+ if (resolve26(graph.path) !== resolve26(released.graphLockPath)) {
12895
13311
  throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
12896
13312
  }
12897
13313
  state = released;
12898
13314
  }
12899
- const manifest = manifests.find((candidate) => resolve25(candidate.path) === resolve25(state.manifestPath));
13315
+ const manifest = manifests.find((candidate) => resolve26(candidate.path) === resolve26(state.manifestPath));
12900
13316
  if (!manifest) {
12901
13317
  throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
12902
13318
  }
@@ -12928,7 +13344,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
12928
13344
  };
12929
13345
  }
12930
13346
  function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
12931
- if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve25(manifest.manifest.targetRoot) !== resolve25(state.installRoot)) {
13347
+ if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve26(manifest.manifest.targetRoot) !== resolve26(state.installRoot)) {
12932
13348
  throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
12933
13349
  }
12934
13350
  const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
@@ -12985,7 +13401,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
12985
13401
  function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
12986
13402
  if (existing) {
12987
13403
  const manifest2 = existing.manifest;
12988
- if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve25(manifest2.targetRoot) !== state.installRoot) {
13404
+ if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve26(manifest2.targetRoot) !== state.installRoot) {
12989
13405
  throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
12990
13406
  }
12991
13407
  }
@@ -13045,7 +13461,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
13045
13461
  const content = await readFile27(path, "utf8");
13046
13462
  if (hasMergeRemovalContent(entry.mergeRemoval)) {
13047
13463
  assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
13048
- } else if (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
13464
+ } else if (resolve26(sourcePath) === resolve26(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
13049
13465
  assertExactMergeContribution({}, entry.mergeStrategy, content);
13050
13466
  } else {
13051
13467
  throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
@@ -13056,8 +13472,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
13056
13472
  await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
13057
13473
  }
13058
13474
  function renderedEntryPathForRoot(root, entryPath) {
13059
- const normalizedRoot = resolve25(root);
13060
- const candidate = resolve25(normalizedRoot, entryPath);
13475
+ const normalizedRoot = resolve26(root);
13476
+ const candidate = resolve26(normalizedRoot, entryPath);
13061
13477
  if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
13062
13478
  throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
13063
13479
  }
@@ -13148,8 +13564,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
13148
13564
  return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
13149
13565
  }
13150
13566
  function renderedEntryPath(manifest, entry) {
13151
- const root = resolve25(manifest.targetRoot);
13152
- const candidate = resolve25(root, entry.path);
13567
+ const root = resolve26(manifest.targetRoot);
13568
+ const candidate = resolve26(root, entry.path);
13153
13569
  if (candidate !== root && !candidate.startsWith(`${root}/`)) {
13154
13570
  throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
13155
13571
  }
@@ -13503,7 +13919,7 @@ function sortValue(value) {
13503
13919
  return value;
13504
13920
  }
13505
13921
  function sha2565(value) {
13506
- return createHash14("sha256").update(value).digest("hex");
13922
+ return createHash15("sha256").update(value).digest("hex");
13507
13923
  }
13508
13924
  function assertDigest(value) {
13509
13925
  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
@@ -13543,6 +13959,7 @@ var commandGovernance = {
13543
13959
  "ownership handoff": "governed-unless-dry-run",
13544
13960
  "ownership recovery-plan": "incidental-cache",
13545
13961
  "ownership retire-stale": "governed-apply",
13962
+ "ownership adopt-legacy": "governed-apply",
13546
13963
  "mcp retire": "governed-apply",
13547
13964
  eject: "governed-always",
13548
13965
  uninstall: "governed-unless-dry-run",
@@ -13962,7 +14379,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
13962
14379
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
13963
14380
  if (options.toFleet) {
13964
14381
  const fleet = await showRegisteredFleet(options.toFleet);
13965
- if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
14382
+ if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
13966
14383
  throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
13967
14384
  }
13968
14385
  }
@@ -13996,49 +14413,15 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
13996
14413
  const adapter = await resolveAdapterForTarget(target, {});
13997
14414
  const transport = transportForTarget(target);
13998
14415
  const requestedReport = normalizeCliPath(options.report);
13999
- const reportPath = join46(await realpath2(dirname23(requestedReport)), basename21(requestedReport));
14000
- const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
14001
- if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute4(reportInsideTarget)) {
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)) {
14002
14419
  throw new Error("Recovery report must be outside the runtime target root.");
14003
14420
  }
14004
14421
  const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
14005
14422
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
14006
14423
  if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
14007
- const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
14008
- const priorTmpdir = process.env.TMPDIR;
14009
- try {
14010
- process.env.TMPDIR = stage;
14011
- const graphPlan = await createGraphSourcePlan({
14012
- roots: config.packages.map((pkg) => ({
14013
- rootId: pkg.name,
14014
- source: pkg.source,
14015
- mode: pkg.mode,
14016
- version: pkg.version,
14017
- ref: pkg.requestedRef,
14018
- select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
14019
- selection: pkg.selection,
14020
- aliases: pkg.aliases,
14021
- overrides: pkg.overrides,
14022
- includeSuggestions: pkg.withSuggestions,
14023
- suggestionAliases: pkg.suggestions,
14024
- useLock: false
14025
- })),
14026
- targetRoot: target.targetRoot,
14027
- workspaceRoot: target.workspaceRoot,
14028
- fleetId: target.fleetId,
14029
- adapter,
14030
- transport,
14031
- targetKey: targetKeyForTarget(target, adapter.name),
14032
- targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
14033
- installationType,
14034
- stateKey: target.stateKey,
14035
- readOnly: true,
14036
- yes: true,
14037
- freshGraphOnly: true,
14038
- deferForeignStateCheck: true,
14039
- cacheRoot: join46(stage, "source-cache"),
14040
- registryCachePath: join46(stage, "registry.json")
14041
- });
14424
+ await withFreshReadOnlyGraphPlan(target, adapter, adapterOptionsForTarget(target, {}), installationType, config.packages, async (graphPlan) => {
14042
14425
  const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
14043
14426
  const report = await planLegacyRecovery({
14044
14427
  graphPlan,
@@ -14056,17 +14439,13 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
14056
14439
  `, { mode: 384, flag: "wx" });
14057
14440
  console.log(`Recovery report: ${reportPath}`);
14058
14441
  console.log(JSON.stringify(report.counts));
14059
- } finally {
14060
- if (priorTmpdir === void 0) delete process.env.TMPDIR;
14061
- else process.env.TMPDIR = priorTmpdir;
14062
- await rm10(stage, { recursive: true, force: true });
14063
- }
14442
+ });
14064
14443
  });
14065
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) => {
14066
14445
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
14067
14446
  const fleet = await showRegisteredFleet(options.fleet);
14068
14447
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
14069
- if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
14448
+ if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
14070
14449
  throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
14071
14450
  }
14072
14451
  const targets = await resolveCliTargets(normalizedOptions);
@@ -14074,7 +14453,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
14074
14453
  throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
14075
14454
  }
14076
14455
  const target = targets[0];
14077
- if (target.fleetId !== options.fleet || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
14456
+ if (target.fleetId !== options.fleet || resolve27(target.workspaceRoot) !== resolve27(fleet.root)) {
14078
14457
  throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
14079
14458
  }
14080
14459
  const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
@@ -14146,6 +14525,138 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
14146
14525
  console.log(`Plan digest: ${result.planDigest}`);
14147
14526
  if (!options.apply) console.log(`Apply: ${applyCommand}`);
14148
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
+ });
14149
14660
  var mcpCommand = program.command("mcp").description("operate on MCP runtime configuration");
14150
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) => {
14151
14662
  if (options.apply && options.dryRun) throw new Error("--apply cannot be combined with --dry-run.");
@@ -15161,7 +15672,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
15161
15672
  selectedPreviousNodeIds,
15162
15673
  selectedRootIds
15163
15674
  );
15164
- const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15675
+ const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15165
15676
  return {
15166
15677
  ...result,
15167
15678
  bundle: { ...result.bundle, graphLock },
@@ -15287,7 +15798,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
15287
15798
  function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
15288
15799
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
15289
15800
  const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
15290
- const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15801
+ const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15291
15802
  return {
15292
15803
  ...scoped,
15293
15804
  bundle: { ...scoped.bundle, graphLock },
@@ -15298,7 +15809,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
15298
15809
  function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
15299
15810
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
15300
15811
  const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
15301
- const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15812
+ const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15302
15813
  return {
15303
15814
  ...scoped,
15304
15815
  bundle: { ...scoped.bundle, graphLock },
@@ -15593,7 +16104,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
15593
16104
  selectedPreviousNodeIds,
15594
16105
  /* @__PURE__ */ new Set([rootId])
15595
16106
  );
15596
- const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
16107
+ const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15597
16108
  return {
15598
16109
  ...scoped,
15599
16110
  bundle: { ...scoped.bundle, graphLock },
@@ -15855,6 +16366,49 @@ function targetFingerprintParts(target, adapter, options, installationType) {
15855
16366
  stateKey: target.stateKey
15856
16367
  };
15857
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
+ }
15858
16412
  function installStateForTarget(target, adapter, options, installationType) {
15859
16413
  const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
15860
16414
  const targetIdentity = resolveTargetStateIdentity({
@@ -15933,7 +16487,7 @@ async function resolveCliTargetState(target, options) {
15933
16487
  migration: prior.migration,
15934
16488
  targetStateFilePreconditions: {
15935
16489
  graphLockPath: stableGraphLockPath,
15936
- graphLockRevision: stableLock ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
16490
+ graphLockRevision: stableLock ? createHash16("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
15937
16491
  sourceLockRevision: computeSourceLockRevision(stableSourceLock)
15938
16492
  }
15939
16493
  };
@@ -15956,7 +16510,7 @@ async function collectTargetStatus(target, options) {
15956
16510
  const pending = await collectPendingInstallWork(target, options);
15957
16511
  const foreignObservations = pending.foreignStateObservations;
15958
16512
  const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
15959
- const observedManifestRevision = foreignObservations.length > 0 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
16513
+ const observedManifestRevision = foreignObservations.length > 0 ? createHash16("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
15960
16514
  observation.fileName,
15961
16515
  observation.manifestRevision
15962
16516
  ]).sort())).digest("hex") : null;
@@ -16203,7 +16757,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
16203
16757
  `Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
16204
16758
  );
16205
16759
  }
16206
- const memberRoot = resolve26(workspaceRoot, member.workspace);
16760
+ const memberRoot = resolve27(workspaceRoot, member.workspace);
16207
16761
  const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
16208
16762
  if (memberPolicy?.revisioning.mode === "commit-after-verify") {
16209
16763
  throw new Error(
@@ -16688,14 +17242,14 @@ function looksLikeSourceSpecifier(value) {
16688
17242
  }
16689
17243
  function normalizeCliPath(value) {
16690
17244
  if (value === "~") return homedir12();
16691
- if (value.startsWith("~/")) return resolve26(homedir12(), value.slice(2));
16692
- return resolve26(value);
17245
+ if (value.startsWith("~/")) return resolve27(homedir12(), value.slice(2));
17246
+ return resolve27(value);
16693
17247
  }
16694
17248
  function shellQuoteArgument(value) {
16695
17249
  return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
16696
17250
  }
16697
17251
  function isHomePath(path) {
16698
- return resolve26(path) === resolve26(homedir12());
17252
+ return resolve27(path) === resolve27(homedir12());
16699
17253
  }
16700
17254
  function adapterListFromOption(adapter) {
16701
17255
  if (!adapter) return [];
@@ -16816,7 +17370,7 @@ function withFleetExample(config) {
16816
17370
  };
16817
17371
  }
16818
17372
  async function findAgentwheelPackageRoot(start) {
16819
- let current = resolve26(start);
17373
+ let current = resolve27(start);
16820
17374
  while (true) {
16821
17375
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
16822
17376
  const parent = dirname23(current);