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/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) {
@@ -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(`${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.`);
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 exactlyCovered = destination.mergeStrategy === source.mergeStrategy && hasMergeRemovalContent(destination.mergeRemoval) && contributionSelector(source) === contributionSelector(destination) && canonicalJson(source.mergeRemoval) === canonicalJson(destination.mergeRemoval);
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.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 }))) {
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 resolve23 } from "path";
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 = resolve23(workspaceRoot, pkg.source);
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 resolve24 } from "path";
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 = resolve24(parentWorkspace, member.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: resolve24(parentWorkspace, member.workspace),
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 `${resolve24(workspaceRoot)}#${profileName}`;
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 createHash11 } from "crypto";
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: createHash11("sha256").update(bytes).digest("hex")
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 createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
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 createHash12 } from "crypto";
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 = createHash12("sha256").update(bytes).digest("hex");
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 createHash13 } from "crypto";
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: createHash13("sha256").update(content).digest("hex"),
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 createHash14 } from "crypto";
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 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";
11917
12360
  async function planFleetNormalization(request) {
11918
- const normalizedRequest = normalizeRequest2(request);
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 = normalizeRequest2(request);
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 = normalizeRequest2(request);
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 normalizeRequest2(request) {
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: resolve25(request.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 || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
12200
- 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);
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" ? 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;
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 (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
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 (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
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(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
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 (resolve25(parsed.targetRoot) !== resolve25(root)) {
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 (resolve25(expectedPath) !== resolve25(path)) {
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 (resolve25(expectedPath) !== resolve25(path)) {
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 = relative9(root, path).split(/[\\/]/);
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 (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
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 = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
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 = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
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 (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
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 (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
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) => resolve25(candidate.path) === resolve25(state.manifestPath));
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 || 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)) {
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 || resolve25(manifest2.targetRoot) !== state.installRoot) {
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 (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
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 = resolve25(root);
13033
- const candidate = resolve25(normalizedRoot, entryPath);
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 = resolve25(manifest.targetRoot);
13125
- const candidate = resolve25(root, entry.path);
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 createHash14("sha256").update(value).digest("hex");
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 (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
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 realpath2(dirname23(requestedReport)), basename21(requestedReport));
13973
- const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
13974
- 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)) {
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
- const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
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
- } finally {
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 (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
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 || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
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 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
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 = resolve26(workspaceRoot, member.workspace);
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 resolve26(homedir12(), value.slice(2));
16663
- return resolve26(value);
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 resolve26(path) === resolve26(homedir12());
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 = resolve26(start);
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);