agentwheel 0.20.1 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,16 @@
2
2
  import {
3
3
  applyCombinedInstallPlan,
4
4
  applyInstallPlan,
5
+ commitManifestMetadataJournal,
5
6
  recoverPendingApply,
6
7
  uninstall
7
- } from "./chunk-AFVGRRZW.js";
8
- import "./chunk-BJEZXIXX.js";
8
+ } from "./chunk-BAJEWUI6.js";
9
+ import "./chunk-Z4N7TB6Q.js";
9
10
  import "./chunk-7VPI5J5Y.js";
10
11
  export {
11
12
  applyCombinedInstallPlan,
12
13
  applyInstallPlan,
14
+ commitManifestMetadataJournal,
13
15
  recoverPendingApply,
14
16
  uninstall
15
17
  };
@@ -24,7 +24,7 @@ import {
24
24
  rollbackCompletedOperations,
25
25
  sourceLockPath,
26
26
  writeApplyJournal
27
- } from "./chunk-BJEZXIXX.js";
27
+ } from "./chunk-Z4N7TB6Q.js";
28
28
  import {
29
29
  pathExists,
30
30
  writeJsonAtomic
@@ -1607,6 +1607,12 @@ function shouldPreserveKeptOperationWhenKeepingFiles(operation) {
1607
1607
  function isForceRemovableKeep(operation) {
1608
1608
  return operation.action === "keep" && operation.preserveInManifest !== true;
1609
1609
  }
1610
+ async function commitManifestMetadataJournal(journal, transport = localTransport) {
1611
+ if (journal.mode !== "uninstall" || journal.operations.length !== 0) {
1612
+ throw new Error("Manifest metadata journal must contain no runtime operations.");
1613
+ }
1614
+ return commitJournalState(journal, transport, void 0, journal.createdAt);
1615
+ }
1610
1616
  async function commitJournalState(journal, transport, entries, now) {
1611
1617
  const manifest = withManifestRevision({
1612
1618
  ...journal.manifest,
@@ -2291,6 +2297,7 @@ export {
2291
2297
  combineMergeRemovals,
2292
2298
  hasMergeRemovalContent,
2293
2299
  mergeRemovalForInstall,
2300
+ mergeContributionAbsent,
2294
2301
  normalizeOwners,
2295
2302
  managedInstructionBlockMode,
2296
2303
  desiredManagedInstructionBlockHash,
@@ -2301,5 +2308,6 @@ export {
2301
2308
  applyInstallPlan,
2302
2309
  applyCombinedInstallPlan,
2303
2310
  recoverPendingApply,
2304
- uninstall
2311
+ uninstall,
2312
+ commitManifestMetadataJournal
2305
2313
  };
@@ -2624,8 +2624,8 @@ async function recoverMutationRuntime(operationId, options = {}) {
2624
2624
  }
2625
2625
  mutation = GovernedMutation.activateExisting(receipt, baseline, lock);
2626
2626
  const [{ recoverPendingApply }, { readLinkedLocalApplyJournal: readLinkedLocalApplyJournal2, removeApplyJournal: removeApplyJournal2, localPathExists: localPathExists2 }] = await Promise.all([
2627
- import("./apply-R5VHCIUN.js"),
2628
- import("./transaction-ITXC7FFV.js")
2627
+ import("./apply-OGC6E2Y4.js"),
2628
+ import("./transaction-7CNV5MAQ.js")
2629
2629
  ]);
2630
2630
  let missingReservation = false;
2631
2631
  for (const entry of pending) {
@@ -2870,6 +2870,17 @@ async function listApplyJournals(targetRoot, adapter, transport = localTransport
2870
2870
  }
2871
2871
  return found.sort((a, b) => a.path.localeCompare(b.path));
2872
2872
  }
2873
+ async function listAllApplyJournals(targetRoot, adapter, transport = localTransport) {
2874
+ const suffix = ".apply-journal.json";
2875
+ const found = [];
2876
+ for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
2877
+ if (!fileName.endsWith(suffix)) continue;
2878
+ const path = join6(metadataDir(targetRoot), fileName);
2879
+ const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
2880
+ if (journal.adapter === adapter) found.push({ path, journal });
2881
+ }
2882
+ return found.sort((a, b) => a.path.localeCompare(b.path));
2883
+ }
2873
2884
  async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, expectedTransportDescription) {
2874
2885
  const absolutePath = resolve9(path);
2875
2886
  const journal = parseApplyJournal(JSON.parse(await readFile7(absolutePath, "utf8")));
@@ -3150,6 +3161,7 @@ export {
3150
3161
  writeApplyJournal,
3151
3162
  readApplyJournal,
3152
3163
  listApplyJournals,
3164
+ listAllApplyJournals,
3153
3165
  readLinkedLocalApplyJournal,
3154
3166
  removeApplyJournal,
3155
3167
  abortApplyJournal,
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  canonicalizeGraphLock,
10
10
  claudeInstructionBridgesAgents,
11
11
  combineMergeRemovals,
12
+ commitManifestMetadataJournal,
12
13
  computeInstallManifestInventoryRevision,
13
14
  computeManifestRevision,
14
15
  computeTargetFingerprint,
@@ -20,6 +21,7 @@ import {
20
21
  managedInstructionBlockMode,
21
22
  managedInstructionPhysicalKey,
22
23
  managedInstructionSelector,
24
+ mergeContributionAbsent,
23
25
  mergeRemovalForInstall,
24
26
  normalizeImmutableCacheIdentity,
25
27
  normalizeOwners,
@@ -31,7 +33,7 @@ import {
31
33
  uninstall,
32
34
  withManifestRevision,
33
35
  writeInstallManifest
34
- } from "./chunk-AFVGRRZW.js";
36
+ } from "./chunk-BAJEWUI6.js";
35
37
  import {
36
38
  CURRENT_WORKSPACE_SCHEMA_VERSION,
37
39
  GovernedMutation,
@@ -55,10 +57,12 @@ import {
55
57
  installRootForAdapterInstallationType,
56
58
  installRootForArtifacts,
57
59
  isCompositeWorkspaceProfile,
60
+ listAllApplyJournals,
58
61
  listApplyJournals,
59
62
  listMutationReceipts,
60
63
  loadAdapterConfig,
61
64
  localTransport,
65
+ mutationMetadataForApplyJournal,
62
66
  mutationPolicyForWorkspace,
63
67
  packageAssetSchema,
64
68
  packageComposeEntrySchema,
@@ -88,8 +92,9 @@ import {
88
92
  workspaceConfigSchema,
89
93
  workspaceExportsSchema,
90
94
  workspaceSelectionImportSchema,
95
+ writeApplyJournal,
91
96
  writeWorkspaceConfig
92
- } from "./chunk-BJEZXIXX.js";
97
+ } from "./chunk-Z4N7TB6Q.js";
93
98
  import {
94
99
  inferSourceDriverName
95
100
  } from "./chunk-EMDIG24I.js";
@@ -104,11 +109,11 @@ import {
104
109
  } from "./chunk-7VPI5J5Y.js";
105
110
 
106
111
  // src/cli/index.ts
107
- import { createHash as createHash13 } from "crypto";
112
+ import { createHash as createHash14 } from "crypto";
108
113
  import { existsSync } from "fs";
109
114
  import { mkdir as mkdir16, rm as rm10, writeFile as writeFile13 } from "fs/promises";
110
115
  import { homedir as homedir12 } from "os";
111
- import { dirname as dirname22, join as join45, resolve as resolve23 } from "path";
116
+ import { dirname as dirname22, join as join45, resolve as resolve24 } from "path";
112
117
  import { fileURLToPath as fileURLToPath3 } from "url";
113
118
  import { Command } from "commander";
114
119
 
@@ -3102,14 +3107,14 @@ async function writeMermaidAsset(response) {
3102
3107
  "content-type": "application/javascript; charset=utf-8",
3103
3108
  "cache-control": "public, max-age=3600"
3104
3109
  });
3105
- await new Promise((resolve24) => {
3110
+ await new Promise((resolve25) => {
3106
3111
  const stream = createReadStream(assetPath);
3107
3112
  stream.on("error", () => {
3108
3113
  if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
3109
3114
  else response.destroy();
3110
- resolve24();
3115
+ resolve25();
3111
3116
  });
3112
- stream.on("end", resolve24);
3117
+ stream.on("end", resolve25);
3113
3118
  stream.pipe(response);
3114
3119
  });
3115
3120
  }
@@ -3136,14 +3141,14 @@ function escapeHtml2(value) {
3136
3141
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3137
3142
  }
3138
3143
  function listen(server, port, bind) {
3139
- return new Promise((resolve24, reject) => {
3144
+ return new Promise((resolve25, reject) => {
3140
3145
  const onError = (error) => {
3141
3146
  server.off("listening", onListening);
3142
3147
  reject(error);
3143
3148
  };
3144
3149
  const onListening = () => {
3145
3150
  server.off("error", onError);
3146
- resolve24();
3151
+ resolve25();
3147
3152
  };
3148
3153
  server.once("error", onError);
3149
3154
  server.once("listening", onListening);
@@ -3151,7 +3156,7 @@ function listen(server, port, bind) {
3151
3156
  });
3152
3157
  }
3153
3158
  function waitForShutdown(server, beforeClose) {
3154
- return new Promise((resolve24) => {
3159
+ return new Promise((resolve25) => {
3155
3160
  let closing = false;
3156
3161
  const shutdown = () => {
3157
3162
  if (closing) return;
@@ -3159,7 +3164,7 @@ function waitForShutdown(server, beforeClose) {
3159
3164
  process.off("SIGINT", shutdown);
3160
3165
  process.off("SIGTERM", shutdown);
3161
3166
  beforeClose();
3162
- server.close(() => resolve24());
3167
+ server.close(() => resolve25());
3163
3168
  };
3164
3169
  process.once("SIGINT", shutdown);
3165
3170
  process.once("SIGTERM", shutdown);
@@ -3169,7 +3174,7 @@ function waitForShutdown(server, beforeClose) {
3169
3174
  process.off("SIGINT", shutdown);
3170
3175
  process.off("SIGTERM", shutdown);
3171
3176
  beforeClose();
3172
- resolve24();
3177
+ resolve25();
3173
3178
  });
3174
3179
  });
3175
3180
  }
@@ -3722,7 +3727,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
3722
3727
  throw new Error(`ClawHub lookup failed for ${packageName}`);
3723
3728
  }
3724
3729
  async function delay(ms) {
3725
- await new Promise((resolve24) => setTimeout(resolve24, ms));
3730
+ await new Promise((resolve25) => setTimeout(resolve25, ms));
3726
3731
  }
3727
3732
  function parseClawHubSource(source) {
3728
3733
  if (!source.startsWith(sourcePrefix)) {
@@ -3893,7 +3898,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
3893
3898
  if (Date.now() - started > timeoutMs) {
3894
3899
  throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
3895
3900
  }
3896
- await new Promise((resolve24) => setTimeout(resolve24, 50));
3901
+ await new Promise((resolve25) => setTimeout(resolve25, 50));
3897
3902
  }
3898
3903
  }
3899
3904
  try {
@@ -6684,8 +6689,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
6684
6689
  async function withCachePathLock(path, fn) {
6685
6690
  const previous = cacheLocks.get(path) ?? Promise.resolve();
6686
6691
  let release = () => void 0;
6687
- const current = previous.then(() => new Promise((resolve24) => {
6688
- release = resolve24;
6692
+ const current = previous.then(() => new Promise((resolve25) => {
6693
+ release = resolve25;
6689
6694
  }));
6690
6695
  cacheLocks.set(path, current);
6691
6696
  await previous;
@@ -6761,8 +6766,8 @@ function detectDirectCollisions(nodes) {
6761
6766
  }
6762
6767
  }
6763
6768
  function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
6764
- const digest = createHash6("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
6765
- return `${name}@${version}+${digest}`;
6769
+ const digest2 = createHash6("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
6770
+ return `${name}@${version}+${digest2}`;
6766
6771
  }
6767
6772
  function sortedUnique2(values) {
6768
6773
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
@@ -9246,6 +9251,11 @@ async function verifiedEntryHash(entry, destPath, transport) {
9246
9251
  }
9247
9252
  return currentHash;
9248
9253
  }
9254
+ async function verifyManifestEntryRuntime(targetRoot, entry, transport = localTransport) {
9255
+ const path = containedArtifactPath(targetRoot, entry.path);
9256
+ if (!await transport.pathExists(path)) throw new Error(`Managed artifact is missing: ${entry.path}`);
9257
+ return verifiedEntryHash(entry, path, transport);
9258
+ }
9249
9259
  function containedArtifactPath(targetRoot, relativePath) {
9250
9260
  if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
9251
9261
  throw new Error(`Unsafe managed artifact path: ${relativePath}`);
@@ -9282,10 +9292,246 @@ function assertManifestIdentity(manifest, request) {
9282
9292
  }
9283
9293
  }
9284
9294
 
9295
+ // src/lifecycle/ownership-retire-stale.ts
9296
+ import { createHash as createHash9 } from "crypto";
9297
+ import { resolve as resolve20 } from "path";
9298
+ async function planRetireStaleOwnership(request) {
9299
+ return observe(request, request.transport ?? localTransport);
9300
+ }
9301
+ async function applyRetireStaleOwnership(request) {
9302
+ assertApplyPreconditions(request);
9303
+ const transport = request.transport ?? localTransport;
9304
+ const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, {
9305
+ installationType: request.installationType,
9306
+ stateKey: request.destinationStateKey
9307
+ });
9308
+ try {
9309
+ const pending = await listAllApplyJournals(request.targetRoot, request.adapter, transport);
9310
+ if (pending.length > 0) {
9311
+ throw new Error(`Cannot retire stale ownership while runtime apply journal(s) are pending: ${pending.map((item) => item.path).join(", ")}`);
9312
+ }
9313
+ const current = await observe(request, transport);
9314
+ assertExpected(request.planDigest, current.planDigest, "plan digest");
9315
+ assertExpected(request.expectedSourceRevision, current.source.revision, "source manifest revision");
9316
+ assertExpected(request.expectedDestinationRevision, current.destination.revision, "destination manifest revision");
9317
+ assertExpected(request.expectedInventoryRevision, current.manifestInventoryRevision, "manifest inventory revision");
9318
+ const source = await requireManifest(request, request.sourceStateKey, transport, "source");
9319
+ const selectedDigests = new Set(current.selected.map((entry) => entry.sourceEntryDigest));
9320
+ const retained = source.entries.filter((entry) => !selectedDigests.has(entryDigest(entry)));
9321
+ if (source.entries.length - retained.length !== current.selected.length) {
9322
+ throw new Error("Source manifest selection changed while locked; replan required.");
9323
+ }
9324
+ declareMutationPath(current.source.manifestPath);
9325
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9326
+ const mutation = mutationMetadataForApplyJournal();
9327
+ const journal = {
9328
+ version: mutation ? 2 : 1,
9329
+ ...mutation ? { mutation } : {},
9330
+ mode: "uninstall",
9331
+ adapter: request.adapter,
9332
+ installationType: request.installationType,
9333
+ stateKey: request.sourceStateKey,
9334
+ targetRoot: request.targetRoot,
9335
+ baseRevision: source.revision,
9336
+ createdAt: now,
9337
+ updatedAt: now,
9338
+ operations: [],
9339
+ completed: [],
9340
+ manifest: { ...source, entries: retained }
9341
+ };
9342
+ await writeApplyJournal(journal, transport);
9343
+ await commitManifestMetadataJournal(journal, transport);
9344
+ return {
9345
+ ...current,
9346
+ applied: true,
9347
+ sourceManifestRemoved: retained.length === 0,
9348
+ retainedSourceEntries: retained.length
9349
+ };
9350
+ } finally {
9351
+ await lock.release();
9352
+ }
9353
+ }
9354
+ async function observe(request, transport) {
9355
+ const normalized = normalizeRequest(request);
9356
+ const source = await requireManifest(normalized, normalized.sourceStateKey, transport, "source");
9357
+ const destination = await requireManifest(normalized, normalized.destinationStateKey, transport, "destination");
9358
+ const sourceOwner = workspaceOwnerForRoot(normalized.fromWorkspaceRoot);
9359
+ const destinationOwner = workspaceOwnerForRoot(normalized.toWorkspaceRoot, normalized.toFleetId);
9360
+ const destinationByPath = /* @__PURE__ */ new Map();
9361
+ for (const entry of destination.entries) {
9362
+ if (entry.workspaceOwner !== destinationOwner) continue;
9363
+ const entries = destinationByPath.get(entry.path) ?? [];
9364
+ entries.push(entry);
9365
+ destinationByPath.set(entry.path, entries);
9366
+ }
9367
+ const selected = [];
9368
+ for (const sourceEntry of source.entries) {
9369
+ if (sourceEntry.workspaceOwner !== sourceOwner) continue;
9370
+ const candidates = destinationByPath.get(sourceEntry.path) ?? [];
9371
+ if (candidates.length === 0) continue;
9372
+ if (candidates.length !== 1) {
9373
+ throw new Error(`Destination Fleet manifest has ambiguous ownership for ${sourceEntry.path}.`);
9374
+ }
9375
+ assertRetirableSourceEntry(sourceEntry);
9376
+ const destinationEntry = candidates[0];
9377
+ await assertContributionCoverage(sourceEntry, destinationEntry, normalized.targetRoot, transport);
9378
+ const runtimeHash = await verifyManifestEntryRuntime(normalized.targetRoot, destinationEntry, transport);
9379
+ selected.push({
9380
+ path: sourceEntry.path,
9381
+ sourceEntryDigest: entryDigest(sourceEntry),
9382
+ destinationEntryDigest: entryDigest(destinationEntry),
9383
+ runtimeHash
9384
+ });
9385
+ }
9386
+ selected.sort((a, b) => a.path.localeCompare(b.path) || a.sourceEntryDigest.localeCompare(b.sourceEntryDigest) || a.destinationEntryDigest.localeCompare(b.destinationEntryDigest));
9387
+ if (selected.length === 0) {
9388
+ throw new Error("No stale source ownership entries match an exact path in the destination Fleet manifest.");
9389
+ }
9390
+ const withoutDigest = {
9391
+ adapter: normalized.adapter,
9392
+ installationType: normalized.installationType,
9393
+ targetRoot: normalized.targetRoot,
9394
+ source: {
9395
+ stateKey: normalized.sourceStateKey,
9396
+ manifestPath: installManifestPath(normalized.targetRoot, normalized.adapter, {
9397
+ installationType: normalized.installationType,
9398
+ stateKey: normalized.sourceStateKey
9399
+ }),
9400
+ revision: source.revision,
9401
+ owner: sourceOwner
9402
+ },
9403
+ destination: {
9404
+ stateKey: normalized.destinationStateKey,
9405
+ manifestPath: installManifestPath(normalized.targetRoot, normalized.adapter, {
9406
+ installationType: normalized.installationType,
9407
+ stateKey: normalized.destinationStateKey
9408
+ }),
9409
+ revision: destination.revision,
9410
+ owner: destinationOwner,
9411
+ fleetId: normalized.toFleetId
9412
+ },
9413
+ manifestInventoryRevision: await computeInstallManifestInventoryRevision(
9414
+ normalized.targetRoot,
9415
+ normalized.adapter,
9416
+ transport
9417
+ ),
9418
+ selected
9419
+ };
9420
+ return { ...withoutDigest, planDigest: digest(withoutDigest) };
9421
+ }
9422
+ function normalizeRequest(request) {
9423
+ const targetRoot = resolve20(request.targetRoot);
9424
+ const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, request.installationType, "source");
9425
+ const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
9426
+ if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
9427
+ if (!request.toFleetId.trim()) throw new Error("Destination Fleet id is required.");
9428
+ return {
9429
+ ...request,
9430
+ targetRoot,
9431
+ installationType: request.installationType.trim(),
9432
+ sourceStateKey,
9433
+ destinationStateKey,
9434
+ fromWorkspaceRoot: resolve20(request.fromWorkspaceRoot),
9435
+ toWorkspaceRoot: resolve20(request.toWorkspaceRoot),
9436
+ toFleetId: request.toFleetId.trim()
9437
+ };
9438
+ }
9439
+ function exactStateKey(adapter, value, installationType, label) {
9440
+ if (!value || stateKeyFor(adapter, { installationType, stateKey: value }) !== value) {
9441
+ throw new Error(`The ${label} state key must be an exact canonical state key.`);
9442
+ }
9443
+ return value;
9444
+ }
9445
+ async function requireManifest(request, stateKey, transport, label) {
9446
+ const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, {
9447
+ installationType: request.installationType,
9448
+ stateKey
9449
+ });
9450
+ if (!manifest) throw new Error(`No ${label} install manifest for state key ${stateKey}.`);
9451
+ if (manifest.version !== 2) throw new Error(`${label} ownership retirement requires an Agentwheel v2 install manifest.`);
9452
+ const actualStateKey = stateKeyFor(manifest.adapter, {
9453
+ installationType: manifest.installationType,
9454
+ stateKey: manifest.stateKey
9455
+ });
9456
+ if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType || resolve20(manifest.targetRoot) !== request.targetRoot || actualStateKey !== stateKey) {
9457
+ throw new Error(`${label} install manifest identity does not match the requested adapter, target, installation type, and state key.`);
9458
+ }
9459
+ return manifest;
9460
+ }
9461
+ function assertRetirableSourceEntry(entry) {
9462
+ if (entry.semanticPlugin || entry.semanticCommand || entry.executed) {
9463
+ throw new Error(`Cannot retire semantic source ownership at ${entry.path}.`);
9464
+ }
9465
+ if (entry.mergeStrategy && !entry.mergeRemoval) {
9466
+ throw new Error(`Cannot retire incomplete merge ownership at ${entry.path}.`);
9467
+ }
9468
+ }
9469
+ async function assertContributionCoverage(source, destination, targetRoot, transport) {
9470
+ const sourceManagedBlock = source.mode === "managed-block";
9471
+ const destinationManagedBlock = destination.mode === "managed-block";
9472
+ const sourceMerge = source.mergeStrategy !== void 0;
9473
+ const destinationMerge = destination.mergeStrategy !== void 0;
9474
+ const sourceCategory = sourceManagedBlock ? "managed-block" : sourceMerge ? "merge" : "plain";
9475
+ const destinationCategory = destinationManagedBlock ? "managed-block" : destinationMerge ? "merge" : "plain";
9476
+ if (sourceCategory !== destinationCategory) {
9477
+ throw new Error(
9478
+ `Destination Fleet ownership category ${destinationCategory} cannot replace stale source category ${sourceCategory} at ${source.path}.`
9479
+ );
9480
+ }
9481
+ if (sourceCategory === "plain") return;
9482
+ if (sourceManagedBlock) {
9483
+ const sourceSelector = managedInstructionSelector(source.logicalSelector, source.artifactType, source.artifactName);
9484
+ const destinationSelector = managedInstructionSelector(destination.logicalSelector, destination.artifactType, destination.artifactName);
9485
+ if (!sourceManagedBlock || !destinationManagedBlock || sourceSelector !== destinationSelector) {
9486
+ throw new Error(`Destination Fleet does not exactly cover stale source managed block contribution at ${source.path}.`);
9487
+ }
9488
+ return;
9489
+ }
9490
+ if (source.mergeStrategy && destination.mergeStrategy) {
9491
+ if (!source.mergeRemoval) throw new Error(`Cannot retire incomplete merge ownership at ${source.path}.`);
9492
+ const exactlyCovered = destination.mergeStrategy === source.mergeStrategy && destination.mergeRemoval && contributionSelector(source) === contributionSelector(destination) && canonicalJson(source.mergeRemoval) === canonicalJson(destination.mergeRemoval);
9493
+ if (exactlyCovered) return;
9494
+ const current = await transport.readFile(containedArtifactPath(targetRoot, source.path));
9495
+ if (!mergeContributionAbsent(source.mergeRemoval, source.mergeStrategy, current)) {
9496
+ throw new Error(`Destination Fleet does not exactly cover or replace stale source merge contribution at ${source.path}.`);
9497
+ }
9498
+ }
9499
+ }
9500
+ function contributionSelector(entry) {
9501
+ return entry.logicalSelector ?? `${entry.artifactType}/${entry.artifactName}`;
9502
+ }
9503
+ function assertApplyPreconditions(request) {
9504
+ for (const [label, value] of [
9505
+ ["plan digest", request.planDigest],
9506
+ ["source manifest revision", request.expectedSourceRevision],
9507
+ ["destination manifest revision", request.expectedDestinationRevision],
9508
+ ["manifest inventory revision", request.expectedInventoryRevision]
9509
+ ]) {
9510
+ if (!value || !/^[a-f0-9]{64}$/.test(value)) {
9511
+ throw new Error(`Applying stale ownership retirement requires a reviewed lowercase SHA-256 ${label}.`);
9512
+ }
9513
+ }
9514
+ }
9515
+ function assertExpected(expected, current, label) {
9516
+ if (expected !== current) throw new Error(`Stale ${label}: expected ${expected}, found ${current}; replan required.`);
9517
+ }
9518
+ function entryDigest(entry) {
9519
+ return digest(entry);
9520
+ }
9521
+ function digest(value) {
9522
+ return createHash9("sha256").update(canonicalJson(value)).digest("hex");
9523
+ }
9524
+ function canonicalJson(value) {
9525
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
9526
+ if (!value || typeof value !== "object") return JSON.stringify(value);
9527
+ const record = value;
9528
+ return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
9529
+ }
9530
+
9285
9531
  // src/version/policy.ts
9286
9532
  import { execFile as execFile3 } from "child_process";
9287
9533
  import { readFile as readFile23 } from "fs/promises";
9288
- import { join as join39, resolve as resolve20 } from "path";
9534
+ import { join as join39, resolve as resolve21 } from "path";
9289
9535
  import { promisify as promisify3 } from "util";
9290
9536
  import { parse as parseJsonc } from "jsonc-parser";
9291
9537
  import { z as z5 } from "zod";
@@ -9379,7 +9625,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
9379
9625
  if (tagged.length > 0) return tagged;
9380
9626
  }
9381
9627
  if (driverName === "local") {
9382
- const root = resolve20(workspaceRoot, pkg.source);
9628
+ const root = resolve21(workspaceRoot, pkg.source);
9383
9629
  const manifest2 = await readPackageManifest(root);
9384
9630
  const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
9385
9631
  try {
@@ -9502,7 +9748,7 @@ async function readVersionCache(path) {
9502
9748
  // src/profile/members.ts
9503
9749
  import { execFile as execFile4 } from "child_process";
9504
9750
  import { readFile as readFile24 } from "fs/promises";
9505
- import { join as join40, resolve as resolve21 } from "path";
9751
+ import { join as join40, resolve as resolve22 } from "path";
9506
9752
  import { promisify as promisify4 } from "util";
9507
9753
  import { z as z7 } from "zod";
9508
9754
 
@@ -9677,7 +9923,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
9677
9923
  let stderr = "";
9678
9924
  try {
9679
9925
  if (member.transport === "local") {
9680
- const workspace = resolve21(parentWorkspace, member.workspace);
9926
+ const workspace = resolve22(parentWorkspace, member.workspace);
9681
9927
  const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
9682
9928
  cwd: workspace,
9683
9929
  env: env2,
@@ -9723,7 +9969,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
9723
9969
  process.execPath,
9724
9970
  [process.argv[1], "--no-update-check", ...args],
9725
9971
  {
9726
- cwd: resolve21(parentWorkspace, member.workspace),
9972
+ cwd: resolve22(parentWorkspace, member.workspace),
9727
9973
  env: env2,
9728
9974
  maxBuffer: 20 * 1024 * 1024
9729
9975
  }
@@ -9824,7 +10070,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
9824
10070
  }
9825
10071
  }
9826
10072
  function compositeKey(workspaceRoot, profileName) {
9827
- return `${resolve21(workspaceRoot)}#${profileName}`;
10073
+ return `${resolve22(workspaceRoot)}#${profileName}`;
9828
10074
  }
9829
10075
 
9830
10076
  // src/status/repository.ts
@@ -9871,7 +10117,7 @@ function valueAfter(lines, prefix) {
9871
10117
  }
9872
10118
 
9873
10119
  // src/catalogue/client.ts
9874
- import { createHash as createHash9 } from "crypto";
10120
+ import { createHash as createHash10 } from "crypto";
9875
10121
  import { readFile as readFile25, rm as rm8 } from "fs/promises";
9876
10122
  import { homedir as homedir9 } from "os";
9877
10123
  import { join as join41 } from "path";
@@ -10146,7 +10392,7 @@ var CatalogueClient = class {
10146
10392
  }
10147
10393
  return {
10148
10394
  value: schema.parse(value),
10149
- digest: createHash9("sha256").update(bytes).digest("hex")
10395
+ digest: createHash10("sha256").update(bytes).digest("hex")
10150
10396
  };
10151
10397
  }
10152
10398
  };
@@ -10157,7 +10403,7 @@ function sameSources2(a, b) {
10157
10403
  return a.length === b.length && a.every((source, index) => source === b[index]);
10158
10404
  }
10159
10405
  function catalogueContentHash(enriched, vercel) {
10160
- return createHash9("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
10406
+ return createHash10("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
10161
10407
  }
10162
10408
 
10163
10409
  // src/search/index.ts
@@ -10519,7 +10765,7 @@ function includesToken(normalizedText, token) {
10519
10765
  }
10520
10766
 
10521
10767
  // src/semantic/index.ts
10522
- import { createHash as createHash10 } from "crypto";
10768
+ import { createHash as createHash11 } from "crypto";
10523
10769
  import { homedir as homedir10 } from "os";
10524
10770
  import { join as join42 } from "path";
10525
10771
  import { env, pipeline } from "@huggingface/transformers";
@@ -10609,8 +10855,8 @@ var SemanticSearchClient = class {
10609
10855
  if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
10610
10856
  const bytes = new Uint8Array(await response.arrayBuffer());
10611
10857
  if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
10612
- const digest = createHash10("sha256").update(bytes).digest("hex");
10613
- if (digest !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
10858
+ const digest2 = createHash11("sha256").update(bytes).digest("hex");
10859
+ if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
10614
10860
  return bytes;
10615
10861
  }
10616
10862
  };
@@ -10699,7 +10945,7 @@ function ensureTrailingSlash(value) {
10699
10945
  }
10700
10946
 
10701
10947
  // src/trial/skill.ts
10702
- import { createHash as createHash11 } from "crypto";
10948
+ import { createHash as createHash12 } from "crypto";
10703
10949
  import { readFile as readFile26, stat as stat13 } from "fs/promises";
10704
10950
  import { join as join43 } from "path";
10705
10951
  import { parse as parseYaml2 } from "yaml";
@@ -10729,7 +10975,7 @@ async function createSkillTrial(driver, resolved, selectors) {
10729
10975
  skill: {
10730
10976
  name: artifact.name,
10731
10977
  relativePath: artifact.relativePath,
10732
- sha256: createHash11("sha256").update(content).digest("hex"),
10978
+ sha256: createHash12("sha256").update(content).digest("hex"),
10733
10979
  frontmatter,
10734
10980
  content
10735
10981
  }
@@ -10748,12 +10994,12 @@ function readSkillFrontmatter(content, artifact) {
10748
10994
  }
10749
10995
 
10750
10996
  // src/lifecycle/fleet-normalize.ts
10751
- import { createHash as createHash12 } from "crypto";
10997
+ import { createHash as createHash13 } from "crypto";
10752
10998
  import { readFile as readFile27, readdir as readdir9, rm as rm9 } from "fs/promises";
10753
10999
  import { homedir as homedir11 } from "os";
10754
- import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve22 } from "path";
11000
+ import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve23 } from "path";
10755
11001
  async function planFleetNormalization(request) {
10756
- const normalizedRequest = normalizeRequest(request);
11002
+ const normalizedRequest = normalizeRequest2(request);
10757
11003
  const destinationScope = await resolveWorkspaceScope({
10758
11004
  fleet: normalizedRequest.destinationFleet,
10759
11005
  globalRoot: normalizedRequest.globalRoot
@@ -10774,8 +11020,8 @@ async function planFleetNormalization(request) {
10774
11020
  if (!sourcePackage || !destinationPackage) {
10775
11021
  throw new Error(`Package '${name}' must be an existing duplicate in both source and destination fleets.`);
10776
11022
  }
10777
- const sourceDeclaration = canonicalJson(sourcePackage);
10778
- const destinationDeclaration = canonicalJson(destinationPackage);
11023
+ const sourceDeclaration = canonicalJson2(sourcePackage);
11024
+ const destinationDeclaration = canonicalJson2(destinationPackage);
10779
11025
  if (sourceDeclaration !== destinationDeclaration) {
10780
11026
  throw new Error(
10781
11027
  `Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
@@ -10812,13 +11058,13 @@ async function planFleetNormalization(request) {
10812
11058
  packages,
10813
11059
  installedState
10814
11060
  };
10815
- return { ...planWithoutDigest, planDigest: sha2564(canonicalJson(planWithoutDigest)) };
11061
+ return { ...planWithoutDigest, planDigest: sha2564(canonicalJson2(planWithoutDigest)) };
10816
11062
  }
10817
11063
  async function applyFleetNormalization(request, hooks = {}) {
10818
11064
  if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
10819
11065
  if (!request.planDigest) throw new Error("Applying fleet normalization requires --plan-digest <digest> from a reviewed dry-run.");
10820
11066
  assertDigest(request.planDigest);
10821
- const normalizedRequest = normalizeRequest(request);
11067
+ const normalizedRequest = normalizeRequest2(request);
10822
11068
  const roots = await normalizationRoots(normalizedRequest);
10823
11069
  const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
10824
11070
  return withOrderedLocks(ordered, async () => {
@@ -10950,14 +11196,14 @@ async function assertNormalizationPostconditions(journal) {
10950
11196
  }
10951
11197
  for (const graphLock of journal.graphLocks) {
10952
11198
  const raw = await readOptionalRecord(graphLock.path);
10953
- const digest = raw ? sha2564(canonicalJson(await readGraphLock(graphLock.path))) : null;
10954
- if (digest !== graphLock.afterDigest) {
11199
+ const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
11200
+ if (digest2 !== graphLock.afterDigest) {
10955
11201
  throw new Error(`Fleet normalization graph-lock postcheck failed: ${graphLock.path}`);
10956
11202
  }
10957
11203
  }
10958
11204
  }
10959
11205
  async function recoverFleetNormalization(request) {
10960
- const normalizedRequest = normalizeRequest(request);
11206
+ const normalizedRequest = normalizeRequest2(request);
10961
11207
  const roots = await normalizationRoots(normalizedRequest);
10962
11208
  const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
10963
11209
  return withOrderedLocks(ordered, async () => {
@@ -11000,7 +11246,7 @@ async function assertSourceFleetPostcondition(request, source, selectedPackages)
11000
11246
  );
11001
11247
  }
11002
11248
  }
11003
- function normalizeRequest(request) {
11249
+ function normalizeRequest2(request) {
11004
11250
  const packages = request.packages === void 0 ? void 0 : sortedUnique6(request.packages);
11005
11251
  if (request.packages !== void 0 && packages?.length === 0) throw new Error("--package requires at least one package name.");
11006
11252
  const artifacts = request.artifacts === void 0 ? void 0 : sortedUnique6(request.artifacts.map((artifact) => normalizeArtifactSelector(artifact)));
@@ -11022,17 +11268,17 @@ function normalizeRequest(request) {
11022
11268
  ...profile ? { profile } : {},
11023
11269
  ...agent ? { agent } : {},
11024
11270
  ...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
11025
- ...request.globalRoot ? { globalRoot: resolve22(request.globalRoot) } : {}
11271
+ ...request.globalRoot ? { globalRoot: resolve23(request.globalRoot) } : {}
11026
11272
  };
11027
11273
  }
11028
11274
  function normalizeOrphanedOwnerRoot(value) {
11029
11275
  const root = value.trim();
11030
11276
  if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
11031
- return resolve22(root);
11277
+ return resolve23(root);
11032
11278
  }
11033
11279
  async function normalizationRoots(request) {
11034
11280
  const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
11035
- const source = request.from === "user" ? resolve22(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
11281
+ const source = request.from === "user" ? resolve23(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
11036
11282
  return { source, destination: destination.root };
11037
11283
  }
11038
11284
  async function withOrderedLocks(roots, fn) {
@@ -11123,10 +11369,10 @@ async function inspectInstalledState(source, destination, packageNames) {
11123
11369
  const destinationGraphAfter = destinationGraph?.lock ?? destinationGraphFromSource(sourceGraph.lock, destinationState.targetFingerprint);
11124
11370
  graphTransfers.push({
11125
11371
  sourceGraphLockPath: sourceGraph.path,
11126
- sourceGraphLockDigest: sha2564(canonicalJson(sourceGraph.lock)),
11372
+ sourceGraphLockDigest: sha2564(canonicalJson2(sourceGraph.lock)),
11127
11373
  destinationGraphLockPath: destinationState.graphLockPath,
11128
- destinationGraphLockDigest: destinationGraph ? sha2564(canonicalJson(destinationGraph.lock)) : null,
11129
- destinationGraphLockAfterDigest: sha2564(canonicalJson(destinationGraphAfter)),
11374
+ destinationGraphLockDigest: destinationGraph ? sha2564(canonicalJson2(destinationGraph.lock)) : null,
11375
+ destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraphAfter)),
11130
11376
  targetKey: sourceGraph.targetKey,
11131
11377
  adapter: destinationState.adapter,
11132
11378
  targetFingerprint: destinationState.targetFingerprint
@@ -11339,7 +11585,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
11339
11585
  )
11340
11586
  });
11341
11587
  if (!selectedArtifacts) {
11342
- const fullDigest = sha2564(canonicalJson(graph.lock));
11588
+ const fullDigest = sha2564(canonicalJson2(graph.lock));
11343
11589
  const existingDestinationGraph = graphCandidates.find((candidate) => candidate.path === destinationState.graphLockPath);
11344
11590
  if (existingDestinationGraph) assertGraphIsSubset(graph, existingDestinationGraph);
11345
11591
  const destinationGraph = destinationGraphFromSource(graph.lock, destinationState.targetFingerprint);
@@ -11347,8 +11593,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
11347
11593
  sourceGraphLockPath: graph.path,
11348
11594
  sourceGraphLockDigest: fullDigest,
11349
11595
  destinationGraphLockPath: destinationState.graphLockPath,
11350
- destinationGraphLockDigest: existingDestinationGraph ? sha2564(canonicalJson(existingDestinationGraph.lock)) : null,
11351
- destinationGraphLockAfterDigest: sha2564(canonicalJson(destinationGraph)),
11596
+ destinationGraphLockDigest: existingDestinationGraph ? sha2564(canonicalJson2(existingDestinationGraph.lock)) : null,
11597
+ destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraph)),
11352
11598
  targetKey: graph.targetKey,
11353
11599
  adapter: graph.adapter,
11354
11600
  targetFingerprint: destinationState.targetFingerprint
@@ -11406,7 +11652,7 @@ function targetKeysForAgent(scope, name) {
11406
11652
  async function runtimeRoots(scope) {
11407
11653
  const roots = /* @__PURE__ */ new Set([scope.root]);
11408
11654
  if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
11409
- roots.add(resolve22(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
11655
+ roots.add(resolve23(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
11410
11656
  }
11411
11657
  for (const [name, agent] of Object.entries(scope.config.agents)) {
11412
11658
  if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
@@ -11443,14 +11689,14 @@ async function collectManifests(roots) {
11443
11689
  }
11444
11690
  continue;
11445
11691
  }
11446
- if (resolve22(parsed.targetRoot) !== resolve22(root)) {
11692
+ if (resolve23(parsed.targetRoot) !== resolve23(root)) {
11447
11693
  throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
11448
11694
  }
11449
11695
  const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
11450
11696
  installationType: parsed.installationType,
11451
11697
  stateKey: parsed.stateKey
11452
11698
  });
11453
- if (resolve22(expectedPath) !== resolve22(path)) {
11699
+ if (resolve23(expectedPath) !== resolve23(path)) {
11454
11700
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
11455
11701
  }
11456
11702
  found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -11471,7 +11717,7 @@ async function collectManifestPaths(paths) {
11471
11717
  installationType: parsed.installationType,
11472
11718
  stateKey: parsed.stateKey
11473
11719
  });
11474
- if (resolve22(expectedPath) !== resolve22(path)) {
11720
+ if (resolve23(expectedPath) !== resolve23(path)) {
11475
11721
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
11476
11722
  }
11477
11723
  found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -11518,7 +11764,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
11518
11764
  const projection = relevantGraphProjection(lock, selected);
11519
11765
  results.push({
11520
11766
  ...candidate,
11521
- digest: sha2564(canonicalJson(projection)),
11767
+ digest: sha2564(canonicalJson2(projection)),
11522
11768
  artifactIdentities: lock.canonical.artifacts.filter((artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(lock, owner, selected))).map(graphArtifactIdentity).sort()
11523
11769
  });
11524
11770
  }
@@ -11554,7 +11800,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
11554
11800
  baseDir: scope.root
11555
11801
  });
11556
11802
  const targetRoot = resolveConfigPath(agent.root, scope.root);
11557
- const installRoot = resolve22(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
11803
+ const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
11558
11804
  const stateKey = stateKeyFor(adapter.name, {
11559
11805
  installationType: agent.installationType,
11560
11806
  targetFingerprint: graph.lock.canonical.targetFingerprint
@@ -11628,7 +11874,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
11628
11874
  targetFingerprint,
11629
11875
  ...identityFleetId ? { fleetId: identityFleetId } : {}
11630
11876
  });
11631
- const installRoot = resolve22(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
11877
+ const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
11632
11878
  return {
11633
11879
  adapter: adapter.name,
11634
11880
  installationType,
@@ -11659,9 +11905,9 @@ async function legacyTargetStateForGraph(scope, graph) {
11659
11905
  };
11660
11906
  }
11661
11907
  function assertGraphIsSubset(source, destination) {
11662
- const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson(root)]));
11908
+ const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
11663
11909
  for (const root of destination.lock.canonical.roots) {
11664
- if (sourceRoots.get(root.rootId) !== canonicalJson(root)) {
11910
+ if (sourceRoots.get(root.rootId) !== canonicalJson2(root)) {
11665
11911
  throw new Error(
11666
11912
  `Partially normalized destination graph diverges at root '${root.rootId}': ${destination.path}`
11667
11913
  );
@@ -11691,7 +11937,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
11691
11937
  function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
11692
11938
  if (existing) {
11693
11939
  const manifest2 = existing.manifest;
11694
- if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve22(manifest2.targetRoot) !== state.installRoot) {
11940
+ if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve23(manifest2.targetRoot) !== state.installRoot) {
11695
11941
  throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
11696
11942
  }
11697
11943
  }
@@ -11751,7 +11997,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
11751
11997
  const content = await readFile27(path, "utf8");
11752
11998
  if (hasMergeRemovalContent(entry.mergeRemoval)) {
11753
11999
  assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
11754
- } else if (resolve22(sourcePath) === resolve22(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
12000
+ } else if (resolve23(sourcePath) === resolve23(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
11755
12001
  assertExactMergeContribution({}, entry.mergeStrategy, content);
11756
12002
  } else {
11757
12003
  throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
@@ -11762,8 +12008,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
11762
12008
  await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
11763
12009
  }
11764
12010
  function renderedEntryPathForRoot(root, entryPath) {
11765
- const normalizedRoot = resolve22(root);
11766
- const candidate = resolve22(normalizedRoot, entryPath);
12011
+ const normalizedRoot = resolve23(root);
12012
+ const candidate = resolve23(normalizedRoot, entryPath);
11767
12013
  if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
11768
12014
  throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
11769
12015
  }
@@ -11854,8 +12100,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
11854
12100
  return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
11855
12101
  }
11856
12102
  function renderedEntryPath(manifest, entry) {
11857
- const root = resolve22(manifest.targetRoot);
11858
- const candidate = resolve22(root, entry.path);
12103
+ const root = resolve23(manifest.targetRoot);
12104
+ const candidate = resolve23(root, entry.path);
11859
12105
  if (candidate !== root && !candidate.startsWith(`${root}/`)) {
11860
12106
  throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
11861
12107
  }
@@ -11874,7 +12120,7 @@ function assertSimpleVerifiableEntry(entry, path) {
11874
12120
  if (entry.kind !== "file" && entry.kind !== "dir") throw new Error(`Unsupported installed entry kind at ${path}.`);
11875
12121
  }
11876
12122
  function entryIdentity(entry) {
11877
- return canonicalJson({
12123
+ return canonicalJson2({
11878
12124
  path: entry.path,
11879
12125
  artifactType: entry.artifactType,
11880
12126
  artifactName: entry.artifactName,
@@ -11892,7 +12138,7 @@ function entryIdentity(entry) {
11892
12138
  });
11893
12139
  }
11894
12140
  function graphArtifactIdentity(artifact) {
11895
- return canonicalJson({
12141
+ return canonicalJson2({
11896
12142
  graphNodeId: artifact.graphNodeId,
11897
12143
  type: artifact.type,
11898
12144
  name: artifact.name,
@@ -11906,7 +12152,7 @@ function graphArtifactIdentity(artifact) {
11906
12152
  });
11907
12153
  }
11908
12154
  function graphEntryIdentity(entry) {
11909
- return canonicalJson({
12155
+ return canonicalJson2({
11910
12156
  graphNodeId: entry.graphNodeId,
11911
12157
  type: entry.artifactType,
11912
12158
  name: entry.artifactName,
@@ -12010,7 +12256,7 @@ async function buildJournalGraphLockStates(plan) {
12010
12256
  for (const transfer of plan.installedState.graphTransfers) {
12011
12257
  const sourceBefore = JSON.parse(await readFile27(transfer.sourceGraphLockPath, "utf8"));
12012
12258
  const sourceLock = await readGraphLock(transfer.sourceGraphLockPath);
12013
- const sourceDigest = sha2564(canonicalJson(sourceLock));
12259
+ const sourceDigest = sha2564(canonicalJson2(sourceLock));
12014
12260
  if (sourceDigest !== transfer.sourceGraphLockDigest) {
12015
12261
  throw new Error(`Source graph lock changed after planning: ${transfer.sourceGraphLockPath}`);
12016
12262
  }
@@ -12028,9 +12274,9 @@ async function buildJournalGraphLockStates(plan) {
12028
12274
  continue;
12029
12275
  }
12030
12276
  const destinationBefore = await readOptionalRecord(transfer.destinationGraphLockPath);
12031
- const destinationBeforeDigest = destinationBefore ? sha2564(canonicalJson(await readGraphLock(transfer.destinationGraphLockPath))) : null;
12277
+ const destinationBeforeDigest = destinationBefore ? sha2564(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
12032
12278
  const destinationAfter = destinationBefore ? await readGraphLock(transfer.destinationGraphLockPath) : destinationGraphFromSource(sourceLock, transfer.targetFingerprint);
12033
- const destinationAfterDigest = sha2564(canonicalJson(destinationAfter));
12279
+ const destinationAfterDigest = sha2564(canonicalJson2(destinationAfter));
12034
12280
  if (destinationBeforeDigest !== transfer.destinationGraphLockDigest || destinationAfterDigest !== transfer.destinationGraphLockAfterDigest) {
12035
12281
  throw new Error(`Destination graph lock changed after planning: ${transfer.destinationGraphLockPath}`);
12036
12282
  }
@@ -12140,8 +12386,8 @@ async function assertJournalRecoverable(journal) {
12140
12386
  }
12141
12387
  for (const graphLock of journal.graphLocks) {
12142
12388
  const raw = await readOptionalRecord(graphLock.path);
12143
- const digest = raw ? sha2564(canonicalJson(await readGraphLock(graphLock.path))) : null;
12144
- if (digest !== graphLock.beforeDigest && digest !== graphLock.afterDigest) {
12389
+ const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
12390
+ if (digest2 !== graphLock.beforeDigest && digest2 !== graphLock.afterDigest) {
12145
12391
  throw new Error(`Cannot recover fleet normalization because graph lock changed outside the transaction: ${graphLock.path}`);
12146
12392
  }
12147
12393
  }
@@ -12193,12 +12439,12 @@ function packageMap(config) {
12193
12439
  return result;
12194
12440
  }
12195
12441
  function configRevision(config) {
12196
- return sha2564(canonicalJson(config));
12442
+ return sha2564(canonicalJson2(config));
12197
12443
  }
12198
12444
  function normalizationJournalPath(sourceRoot, destinationFleet) {
12199
12445
  return join44(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
12200
12446
  }
12201
- function canonicalJson(value) {
12447
+ function canonicalJson2(value) {
12202
12448
  return JSON.stringify(sortValue(value));
12203
12449
  }
12204
12450
  function sortValue(value) {
@@ -12209,7 +12455,7 @@ function sortValue(value) {
12209
12455
  return value;
12210
12456
  }
12211
12457
  function sha2564(value) {
12212
- return createHash12("sha256").update(value).digest("hex");
12458
+ return createHash13("sha256").update(value).digest("hex");
12213
12459
  }
12214
12460
  function assertDigest(value) {
12215
12461
  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
@@ -12247,6 +12493,7 @@ var commandGovernance = {
12247
12493
  "package migrate": "governed-always",
12248
12494
  remember: "governed-always",
12249
12495
  "ownership handoff": "governed-unless-dry-run",
12496
+ "ownership retire-stale": "governed-apply",
12250
12497
  "mcp retire": "governed-apply",
12251
12498
  eject: "governed-always",
12252
12499
  uninstall: "governed-unless-dry-run",
@@ -12663,7 +12910,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
12663
12910
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
12664
12911
  if (options.toFleet) {
12665
12912
  const fleet = await showRegisteredFleet(options.toFleet);
12666
- if (resolve23(fleet.root) !== resolve23(toWorkspaceRoot)) {
12913
+ if (resolve24(fleet.root) !== resolve24(toWorkspaceRoot)) {
12667
12914
  throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
12668
12915
  }
12669
12916
  }
@@ -12688,6 +12935,84 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
12688
12935
  console.log(`Manifest revision: ${result.manifestRevision}`);
12689
12936
  console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
12690
12937
  });
12938
+ 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").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("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
12939
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
12940
+ const fleet = await showRegisteredFleet(options.fleet);
12941
+ const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
12942
+ if (resolve24(fleet.root) !== resolve24(toWorkspaceRoot)) {
12943
+ throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
12944
+ }
12945
+ const targets = await resolveCliTargets(normalizedOptions);
12946
+ if (targets.length !== 1) {
12947
+ throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
12948
+ }
12949
+ const target = targets[0];
12950
+ if (target.fleetId !== options.fleet || resolve24(target.workspaceRoot) !== resolve24(fleet.root)) {
12951
+ throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
12952
+ }
12953
+ const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
12954
+ const adapter = await resolveAdapterForTarget(target, adapterOptions);
12955
+ const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
12956
+ const state = installStateForTarget(target, adapter, adapterOptions, installationType);
12957
+ const request = {
12958
+ targetRoot: state.installRoot,
12959
+ adapter: adapter.name,
12960
+ installationType,
12961
+ sourceStateKey: options.sourceStateKey,
12962
+ destinationStateKey: state.stateKey,
12963
+ fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
12964
+ toWorkspaceRoot,
12965
+ toFleetId: fleet.id,
12966
+ planDigest: options.planDigest,
12967
+ expectedSourceRevision: options.expectedSourceRevision,
12968
+ expectedDestinationRevision: options.expectedDestinationRevision,
12969
+ expectedInventoryRevision: options.expectedInventoryRevision,
12970
+ transport: transportForTarget(target)
12971
+ };
12972
+ const result = options.apply ? await applyRetireStaleOwnership(request) : await planRetireStaleOwnership(request);
12973
+ const targetArgs = options.profile ? ["--profile", options.profile] : target.agentName ? ["--agent", target.agentName] : ["--adapter", adapter.name, "--target-root", target.targetRoot];
12974
+ const applyArgs = [
12975
+ "agentwheel",
12976
+ "ownership",
12977
+ "retire-stale",
12978
+ "--from-workspace-root",
12979
+ request.fromWorkspaceRoot,
12980
+ "--to-workspace-root",
12981
+ request.toWorkspaceRoot,
12982
+ "--source-state-key",
12983
+ request.sourceStateKey,
12984
+ "--fleet",
12985
+ request.toFleetId,
12986
+ ...targetArgs,
12987
+ "--installation-type",
12988
+ installationType,
12989
+ ...options.adapterConfig ? ["--adapter-config", options.adapterConfig] : [],
12990
+ ...options.adapterModule ? ["--adapter-module", options.adapterModule] : [],
12991
+ ...options.allowAdapterCode ? ["--allow-adapter-code"] : [],
12992
+ "--plan-digest",
12993
+ result.planDigest,
12994
+ "--expected-source-revision",
12995
+ result.source.revision,
12996
+ "--expected-destination-revision",
12997
+ result.destination.revision,
12998
+ "--expected-inventory-revision",
12999
+ result.manifestInventoryRevision,
13000
+ "--apply"
13001
+ ];
13002
+ const applyCommand = applyArgs.map(shellQuoteArgument).join(" ");
13003
+ if (options.json) {
13004
+ console.log(JSON.stringify({ ...result, applyCommand }, null, 2));
13005
+ return;
13006
+ }
13007
+ console.log(`${options.apply ? "Retired stale ownership" : "Stale ownership retirement plan"}: ${result.selected.length} entr${result.selected.length === 1 ? "y" : "ies"}`);
13008
+ console.log(`Target: ${result.adapter}/${result.installationType} at ${result.targetRoot}`);
13009
+ console.log(`Source: ${result.source.stateKey} revision ${result.source.revision}`);
13010
+ console.log(`Destination: ${result.destination.stateKey} revision ${result.destination.revision}`);
13011
+ console.log(`Manifest inventory revision: ${result.manifestInventoryRevision}`);
13012
+ for (const entry of result.selected) console.log(`- ${entry.path}: ${entry.sourceEntryDigest} -> ${entry.destinationEntryDigest}`);
13013
+ console.log(`Plan digest: ${result.planDigest}`);
13014
+ if (!options.apply) console.log(`Apply: ${applyCommand}`);
13015
+ });
12691
13016
  var mcpCommand = program.command("mcp").description("operate on MCP runtime configuration");
12692
13017
  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) => {
12693
13018
  if (options.apply && options.dryRun) throw new Error("--apply cannot be combined with --dry-run.");
@@ -13678,7 +14003,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
13678
14003
  selectedPreviousNodeIds,
13679
14004
  selectedRootIds
13680
14005
  );
13681
- const graphLockDigest = createHash13("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14006
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13682
14007
  return {
13683
14008
  ...result,
13684
14009
  bundle: { ...result.bundle, graphLock },
@@ -13804,7 +14129,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
13804
14129
  function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
13805
14130
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
13806
14131
  const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
13807
- const graphLockDigest = createHash13("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14132
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13808
14133
  return {
13809
14134
  ...scoped,
13810
14135
  bundle: { ...scoped.bundle, graphLock },
@@ -13815,7 +14140,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
13815
14140
  function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
13816
14141
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
13817
14142
  const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
13818
- const graphLockDigest = createHash13("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14143
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13819
14144
  return {
13820
14145
  ...scoped,
13821
14146
  bundle: { ...scoped.bundle, graphLock },
@@ -14110,7 +14435,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
14110
14435
  selectedPreviousNodeIds,
14111
14436
  /* @__PURE__ */ new Set([rootId])
14112
14437
  );
14113
- const graphLockDigest = createHash13("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14438
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14114
14439
  return {
14115
14440
  ...scoped,
14116
14441
  bundle: { ...scoped.bundle, graphLock },
@@ -14666,7 +14991,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
14666
14991
  `Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
14667
14992
  );
14668
14993
  }
14669
- const memberRoot = resolve23(workspaceRoot, member.workspace);
14994
+ const memberRoot = resolve24(workspaceRoot, member.workspace);
14670
14995
  const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
14671
14996
  if (memberPolicy?.revisioning.mode === "commit-after-verify") {
14672
14997
  throw new Error(
@@ -15117,11 +15442,14 @@ function looksLikeSourceSpecifier(value) {
15117
15442
  }
15118
15443
  function normalizeCliPath(value) {
15119
15444
  if (value === "~") return homedir12();
15120
- if (value.startsWith("~/")) return resolve23(homedir12(), value.slice(2));
15121
- return resolve23(value);
15445
+ if (value.startsWith("~/")) return resolve24(homedir12(), value.slice(2));
15446
+ return resolve24(value);
15447
+ }
15448
+ function shellQuoteArgument(value) {
15449
+ return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
15122
15450
  }
15123
15451
  function isHomePath(path) {
15124
- return resolve23(path) === resolve23(homedir12());
15452
+ return resolve24(path) === resolve24(homedir12());
15125
15453
  }
15126
15454
  function adapterListFromOption(adapter) {
15127
15455
  if (!adapter) return [];
@@ -15242,7 +15570,7 @@ function withFleetExample(config) {
15242
15570
  };
15243
15571
  }
15244
15572
  async function findAgentwheelPackageRoot(start) {
15245
- let current = resolve23(start);
15573
+ let current = resolve24(start);
15246
15574
  while (true) {
15247
15575
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
15248
15576
  const parent = dirname22(current);
@@ -8,6 +8,7 @@ import {
8
8
  applyLockPath,
9
9
  assertApplyJournalRecoveryAllowed,
10
10
  assertGovernedRuntimeTransportSupported,
11
+ listAllApplyJournals,
11
12
  listApplyJournals,
12
13
  localPathExists,
13
14
  mutationMetadataForApplyJournal,
@@ -17,7 +18,7 @@ import {
17
18
  removeApplyJournal,
18
19
  rollbackCompletedOperations,
19
20
  writeApplyJournal
20
- } from "./chunk-BJEZXIXX.js";
21
+ } from "./chunk-Z4N7TB6Q.js";
21
22
  import "./chunk-7VPI5J5Y.js";
22
23
  export {
23
24
  abortApplyJournal,
@@ -28,6 +29,7 @@ export {
28
29
  applyLockPath,
29
30
  assertApplyJournalRecoveryAllowed,
30
31
  assertGovernedRuntimeTransportSupported,
32
+ listAllApplyJournals,
31
33
  listApplyJournals,
32
34
  localPathExists,
33
35
  mutationMetadataForApplyJournal,
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.20.1",
4
+ "version": "0.20.2",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.1"
8
+ version: "0.20.2"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.1"
8
+ version: "0.20.2"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery