plugnz 0.0.8 → 0.0.10

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.
Files changed (2) hide show
  1. package/dist/plugnz.mjs +1553 -245
  2. package/package.json +1 -1
package/dist/plugnz.mjs CHANGED
@@ -9339,10 +9339,23 @@ var dcode = {
9339
9339
  continue;
9340
9340
  const row = entry;
9341
9341
  const path = typeof row.installPath === "string" ? row.installPath : typeof row.install_path === "string" ? row.install_path : undefined;
9342
+ let declaredVersion;
9343
+ if (path && existsSync8(path)) {
9344
+ for (const relative of ["plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json"]) {
9345
+ try {
9346
+ const manifest = JSON.parse(readFileSync6(join11(path, relative), "utf8"));
9347
+ if (typeof manifest.version === "string") {
9348
+ declaredVersion = manifest.version;
9349
+ break;
9350
+ }
9351
+ } catch {}
9352
+ }
9353
+ }
9342
9354
  const at = id.indexOf("@");
9343
9355
  const name = at < 0 ? id : id.slice(0, at);
9344
9356
  const marketplace = at < 0 ? undefined : id.slice(at + 1);
9345
- const plugin = { id, name, ...marketplace ? { marketplace } : {}, ...path && existsSync8(path) ? { path } : {}, ...typeof row.version === "string" ? { version: row.version } : {}, ...enabled.enabledPlugins[id] === true ? { enabled: true } : { enabled: false } };
9357
+ const version = typeof row.version === "string" ? row.version : declaredVersion;
9358
+ const plugin = { id, name, ...marketplace ? { marketplace } : {}, ...path && existsSync8(path) ? { path } : {}, ...version === undefined ? {} : { version }, ...enabled.enabledPlugins[id] === true ? { enabled: true } : { enabled: false } };
9346
9359
  result.push(plugin);
9347
9360
  }
9348
9361
  return result;
@@ -10239,6 +10252,91 @@ function frame(hash, bytes) {
10239
10252
  hash.update(bytes);
10240
10253
  }
10241
10254
 
10255
+ // src/lifecycle-operation-record.ts
10256
+ var FIELDS = ["schemaVersion", "adapterId", "target", "targetObservationId", "operationId", "attemptId", "scopeId", "packageName", "nativeId", "sourceType", "sourceRevision", "sourceLocator", "packageVersion", "action", "route", "detectedVersion", "versionProbeId", "evidenceId", "mutationGroupId", "mutationInvocationOperationId", "artifactId", "projectedFingerprint", "affectedNativeIds", "affectedOperationIds", "prior", "expected", "rollbackReference"];
10257
+ function parseSerializedLifecycleOperation(value) {
10258
+ const row = object2(value);
10259
+ exact(row, FIELDS);
10260
+ if (row.schemaVersion !== 1)
10261
+ invalid2("operation schemaVersion must be 1");
10262
+ for (const field of ["adapterId", "targetObservationId", "operationId", "attemptId", "scopeId", "packageName", "nativeId", "sourceRevision", "detectedVersion", "versionProbeId", "evidenceId", "mutationGroupId", "mutationInvocationOperationId", "rollbackReference"])
10263
+ string(row[field]);
10264
+ parsePersistedTargetIdentity(row.target, "sealed operation target");
10265
+ choice(row.sourceType, ["local", "git"]);
10266
+ choice(row.route, ["native", "managed"]);
10267
+ choice(row.action, ["install", "update", "route-migrate", "disable-nonconforming", "retire-orphan", "remove"]);
10268
+ for (const field of ["sourceLocator", "packageVersion", "artifactId"])
10269
+ if (row[field] !== null)
10270
+ string(row[field]);
10271
+ digest(row.projectedFingerprint);
10272
+ for (const field of ["affectedNativeIds", "affectedOperationIds"]) {
10273
+ if (!Array.isArray(row[field]) || row[field].length === 0)
10274
+ invalid2("affected identities must be nonempty arrays");
10275
+ for (const item of row[field])
10276
+ string(item);
10277
+ }
10278
+ for (const field of ["prior", "expected"])
10279
+ readback(row[field]);
10280
+ return value;
10281
+ }
10282
+ function readback(value) {
10283
+ const row = object2(value);
10284
+ exact(row, ["adapterId", "target", "scopeId", "packageName", "nativeId", "route", "presence", "enablement", "activation", "transition", "installedFingerprint", "contentRoots", "retention"]);
10285
+ for (const field of ["adapterId", "scopeId", "packageName", "nativeId"])
10286
+ string(row[field]);
10287
+ parsePersistedTargetIdentity(row.target, "operation readback target");
10288
+ choice(row.route, ["native", "managed", "none"]);
10289
+ choice(row.presence, ["present", "absent"]);
10290
+ choice(row.enablement, ["enabled", "disabled", "not-applicable", "unknown"]);
10291
+ choice(row.activation, ["active", "inactive", "unknown"]);
10292
+ digest(row.installedFingerprint);
10293
+ const transition = object2(row.transition);
10294
+ exact(transition, ["requirement", "status"]);
10295
+ choice(transition.requirement, ["none", "restart", "reload", "unknown"]);
10296
+ choice(transition.status, ["effective", "pending", "unknown"]);
10297
+ if (!Array.isArray(row.contentRoots))
10298
+ invalid2("content roots must be an array");
10299
+ for (const value of row.contentRoots) {
10300
+ const root = object2(value);
10301
+ exact(root, ["label", "path", "fingerprint"]);
10302
+ string(root.label);
10303
+ string(root.path);
10304
+ digest(root.fingerprint);
10305
+ }
10306
+ const retention = object2(row.retention);
10307
+ exact(retention, ["pluginData", "inactiveMetadata"]);
10308
+ for (const field of ["pluginData", "inactiveMetadata"]) {
10309
+ const resource = object2(retention[field]);
10310
+ exact(resource, ["state", "fingerprint"]);
10311
+ choice(resource.state, ["present", "absent", "missing", "not-observed"]);
10312
+ digest(resource.fingerprint);
10313
+ }
10314
+ }
10315
+ function object2(value) {
10316
+ if (!value || typeof value !== "object" || Array.isArray(value))
10317
+ invalid2("expected object");
10318
+ return value;
10319
+ }
10320
+ function exact(row, fields) {
10321
+ if (Object.keys(row).some((key) => !fields.includes(key)) || fields.some((key) => !(key in row)))
10322
+ invalid2("operation fields do not match schema");
10323
+ }
10324
+ function string(value) {
10325
+ if (typeof value !== "string" || !value || /[\r\n\u0000]/u.test(value))
10326
+ invalid2("expected stable nonempty string");
10327
+ }
10328
+ function choice(value, choices) {
10329
+ if (typeof value !== "string" || !choices.includes(value))
10330
+ invalid2("invalid operation enum");
10331
+ }
10332
+ function digest(value) {
10333
+ if (value !== null && (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)))
10334
+ invalid2("invalid operation fingerprint");
10335
+ }
10336
+ function invalid2(message) {
10337
+ throw new Error(`invalid sealed lifecycle operation: ${message}`);
10338
+ }
10339
+
10242
10340
  // src/state.ts
10243
10341
  var PENDING_OPERATIONS = ["install", "update", "route-migrate", "disable-nonconforming", "retire-orphan", "remove"];
10244
10342
  var JOURNAL_ACTIONS = [...PENDING_OPERATIONS, "unchanged", "retain-prior"];
@@ -10306,39 +10404,41 @@ function validateLifecycleStateDocument(value, minimumStateGeneration) {
10306
10404
  continue;
10307
10405
  const attempt = attemptsById.get(scope.lastAttemptId);
10308
10406
  if (attempt === undefined)
10309
- invalid2(`deployment scope '${scope.id}' references unknown last attempt '${scope.lastAttemptId}'`);
10407
+ invalid3(`deployment scope '${scope.id}' references unknown last attempt '${scope.lastAttemptId}'`);
10310
10408
  if (!attempt.scopeIds.includes(scope.id))
10311
- invalid2(`deployment scope '${scope.id}' last attempt does not include that scope`);
10409
+ invalid3(`deployment scope '${scope.id}' last attempt does not include that scope`);
10312
10410
  }
10313
10411
  for (const activation of activations) {
10314
10412
  if (!scopeIds.has(activation.scopeId))
10315
- invalid2(`activation '${activationKey(activation)}' references unknown scope '${activation.scopeId}'`);
10413
+ invalid3(`activation '${activationKey(activation)}' references unknown scope '${activation.scopeId}'`);
10316
10414
  if (activation.pending === undefined)
10317
10415
  continue;
10318
10416
  const attempt = attemptsById.get(activation.pending.attemptId);
10319
10417
  if (attempt === undefined)
10320
- invalid2(`activation '${activationKey(activation)}' references unknown pending attempt '${activation.pending.attemptId}'`);
10418
+ invalid3(`activation '${activationKey(activation)}' references unknown pending attempt '${activation.pending.attemptId}'`);
10321
10419
  if (!attempt.scopeIds.includes(activation.scopeId))
10322
- invalid2(`activation '${activationKey(activation)}' pending attempt does not include its scope`);
10420
+ invalid3(`activation '${activationKey(activation)}' pending attempt does not include its scope`);
10323
10421
  const matchingEntries = attempt.journal.filter((entry) => entry.scopeId === activation.scopeId && entry.packageId === activation.packageId && entry.nativeId === activation.nativeId);
10324
10422
  if (matchingEntries.length !== 1)
10325
- invalid2(`activation '${activationKey(activation)}' pending attempt must contain exactly one matching package/native journal entry`);
10423
+ invalid3(`activation '${activationKey(activation)}' pending attempt must contain exactly one matching package/native journal entry`);
10326
10424
  validatePendingSemantics(activation, attempt, matchingEntries[0]);
10327
10425
  }
10328
10426
  for (const attempt of attempts) {
10329
10427
  for (const scopeId of attempt.scopeIds)
10330
10428
  if (!scopeIds.has(scopeId))
10331
- invalid2(`attempt '${attempt.id}' references unknown scope '${scopeId}'`);
10429
+ invalid3(`attempt '${attempt.id}' references unknown scope '${scopeId}'`);
10332
10430
  for (const entry of attempt.journal) {
10333
10431
  operationIds.push(entry.operationId);
10334
10432
  if (!attempt.scopeIds.includes(entry.scopeId))
10335
- invalid2(`attempt '${attempt.id}' journal references scope '${entry.scopeId}' outside the attempt`);
10433
+ invalid3(`attempt '${attempt.id}' journal references scope '${entry.scopeId}' outside the attempt`);
10434
+ if (entry.sealedOperation !== undefined && entry.sealedOperation.attemptId !== attempt.id)
10435
+ invalid3(`attempt '${attempt.id}' sealed operation references another attempt`);
10336
10436
  }
10337
10437
  }
10338
10438
  unique(operationIds, "journal operation id");
10339
10439
  for (const tombstone of tombstones)
10340
10440
  if (!scopeIds.has(tombstone.scopeId))
10341
- invalid2(`tombstone '${tombstone.id}' references unknown scope '${tombstone.scopeId}'`);
10441
+ invalid3(`tombstone '${tombstone.id}' references unknown scope '${tombstone.scopeId}'`);
10342
10442
  return value;
10343
10443
  }
10344
10444
  function findRecord(records, host, id) {
@@ -10374,9 +10474,9 @@ function parseV1Install(value, index) {
10374
10474
  const rec = asObject(value, "install record");
10375
10475
  const unknown = Object.keys(rec).find((key) => !V1_RECORD_FIELDS.has(key));
10376
10476
  if (unknown !== undefined)
10377
- invalid2(`unsupported install field '${unknown}'`);
10477
+ invalid3(`unsupported install field '${unknown}'`);
10378
10478
  if (typeof rec["host"] !== "string" || typeof rec["id"] !== "string" || typeof rec["source"] !== "string" || typeof rec["sourceSha"] !== "string") {
10379
- invalid2("install record is missing required fields");
10479
+ invalid3("install record is missing required fields");
10380
10480
  }
10381
10481
  const host = rec["host"];
10382
10482
  const id = rec["id"];
@@ -10390,7 +10490,7 @@ function parseV1Install(value, index) {
10390
10490
  optionalLegacyString(rec["installedFingerprint"], "installedFingerprint");
10391
10491
  optionalLegacyString(rec["ownership"], "ownership");
10392
10492
  if (rec["pending"] !== undefined && rec["pending"] !== "install" && rec["pending"] !== "remove")
10393
- invalid2("pending must be install or remove");
10493
+ invalid3("pending must be install or remove");
10394
10494
  const record = { host, id, source, sourceSha };
10395
10495
  if (typeof rec["installedAt"] === "string")
10396
10496
  record.installedAt = rec["installedAt"];
@@ -10427,7 +10527,7 @@ function importV1(records) {
10427
10527
  scopes.set(scopeId, scope);
10428
10528
  }
10429
10529
  const relativeDir = legacyRelativeDir(record);
10430
- const attemptId = record.pending === undefined ? undefined : `legacy-attempt-${digest(`${scopeId}\x00${record.id}\x00${record.pending}`)}`;
10530
+ const attemptId = record.pending === undefined ? undefined : `legacy-attempt-${digest2(`${scopeId}\x00${record.id}\x00${record.pending}`)}`;
10431
10531
  state.activations.push({
10432
10532
  scopeId,
10433
10533
  packageId: record.id,
@@ -10462,7 +10562,7 @@ function importV1(records) {
10462
10562
  mutationStarted: true,
10463
10563
  scopeIds: [scopeId],
10464
10564
  journal: [{
10465
- operationId: `legacy-operation-${digest(attemptId)}`,
10565
+ operationId: `legacy-operation-${digest2(attemptId)}`,
10466
10566
  scopeId,
10467
10567
  packageId: record.id,
10468
10568
  nativeId: record.id,
@@ -10521,7 +10621,7 @@ function validateScope(value, label) {
10521
10621
  const target = validateTarget(rec["target"], `${label}.target`);
10522
10622
  const canonicalId = canonicalScopeId(source, target);
10523
10623
  if (id !== canonicalId)
10524
- invalid2(`deployment scope '${id}' does not match canonical Source and target identity '${canonicalId}'`);
10624
+ invalid3(`deployment scope '${id}' does not match canonical Source and target identity '${canonicalId}'`);
10525
10625
  oneOf(rec["authority"], ["legacy-import", "authoritative"], `${label}.authority`);
10526
10626
  oneOf(rec["lifecycle"], ["active", "retiring", "retired"], `${label}.lifecycle`);
10527
10627
  oneOf(rec["selectorMode"], ["legacy-unknown", "all", "explicit", "retired"], `${label}.selectorMode`);
@@ -10532,15 +10632,15 @@ function validateScope(value, label) {
10532
10632
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10533
10633
  optionalTimestamp(rec["retiredAt"], `${label}.retiredAt`);
10534
10634
  if (rec["authority"] === "legacy-import" && (rec["selectorMode"] !== "legacy-unknown" || desired !== undefined || lastConverged !== undefined))
10535
- invalid2(`deployment scope '${id}' legacy import cannot carry desired or converged authority`);
10635
+ invalid3(`deployment scope '${id}' legacy import cannot carry desired or converged authority`);
10536
10636
  if (rec["authority"] === "authoritative" && rec["lifecycle"] !== "retired" && desired === undefined)
10537
- invalid2(`deployment scope '${id}' authoritative lifecycle requires desired state`);
10637
+ invalid3(`deployment scope '${id}' authoritative lifecycle requires desired state`);
10538
10638
  if (desired !== undefined && rec["selectorMode"] !== "all" && rec["selectorMode"] !== "explicit")
10539
- invalid2(`deployment scope '${id}' desired state requires all or explicit selector mode`);
10639
+ invalid3(`deployment scope '${id}' desired state requires all or explicit selector mode`);
10540
10640
  if (rec["lifecycle"] === "retired" && rec["selectorMode"] !== "retired")
10541
- invalid2(`deployment scope '${id}' retired lifecycle requires retired selector mode`);
10641
+ invalid3(`deployment scope '${id}' retired lifecycle requires retired selector mode`);
10542
10642
  if (lastConverged !== undefined && desired !== undefined && lastConverged.generation > desired.generation)
10543
- invalid2(`deployment scope '${id}' last-converged generation exceeds desired generation`);
10643
+ invalid3(`deployment scope '${id}' last-converged generation exceeds desired generation`);
10544
10644
  return value;
10545
10645
  }
10546
10646
  function validateSource(value, label) {
@@ -10557,7 +10657,7 @@ function validateTarget(value, label) {
10557
10657
  return parsePersistedTargetIdentity(value, label);
10558
10658
  } catch (error) {
10559
10659
  if (error instanceof TargetIdentityValidationError)
10560
- invalid2(error.message);
10660
+ invalid3(error.message);
10561
10661
  throw error;
10562
10662
  }
10563
10663
  }
@@ -10570,7 +10670,7 @@ function validateDesired(value, label) {
10570
10670
  timestamp(rec["validatedAt"], `${label}.validatedAt`);
10571
10671
  const packages = array(rec["packages"], `${label}.packages`).map((item, index) => validateDesiredPackage(item, `${label}.packages[${index}]`));
10572
10672
  if (packages.length === 0)
10573
- invalid2(`${label}.packages must not be empty; retire the scope explicitly`);
10673
+ invalid3(`${label}.packages must not be empty; retire the scope explicitly`);
10574
10674
  unique(packages.map((item) => item.packageId), `${label} package identity`);
10575
10675
  unique(packages.map((item) => item.nativeId), `${label} native identity`);
10576
10676
  return value;
@@ -10584,7 +10684,7 @@ function validateDesiredPackage(value, label) {
10584
10684
  const capabilities = stringArray(rec["requiredCapabilities"], `${label}.requiredCapabilities`);
10585
10685
  sortedUniqueRequired(capabilities, `${label}.requiredCapabilities`, true);
10586
10686
  if (typeof rec["adoptionRequested"] !== "boolean")
10587
- invalid2(`${label}.adoptionRequested must be a boolean`);
10687
+ invalid3(`${label}.adoptionRequested must be a boolean`);
10588
10688
  return value;
10589
10689
  }
10590
10690
  function validateActivation(value, label) {
@@ -10599,7 +10699,7 @@ function validateActivation(value, label) {
10599
10699
  const route = validateRoute(rec["route"], `${label}.route`);
10600
10700
  const ownershipObject = asObject(rec["ownership"], `${label}.ownership`);
10601
10701
  if (route.kind !== "legacy-unverified" && ownershipObject["kind"] === "legacy-claim")
10602
- invalid2("legacy ownership cannot use a verified route");
10702
+ invalid3("legacy ownership cannot use a verified route");
10603
10703
  const ownership = validateOwnership(ownershipObject, `${label}.ownership`);
10604
10704
  const fingerprints = validateFingerprints(rec["fingerprints"], `${label}.fingerprints`);
10605
10705
  oneOf(rec["activationState"], ["active", "inactive", "retained", "nonconforming", "unknown"], `${label}.activationState`);
@@ -10612,13 +10712,13 @@ function validateActivation(value, label) {
10612
10712
  optionalTimestamp(rec["createdAt"], `${label}.createdAt`);
10613
10713
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10614
10714
  if (route.kind === "legacy-unverified" !== (ownership.kind === "legacy-claim"))
10615
- invalid2(`${label} legacy route and ownership claim must agree`);
10715
+ invalid3(`${label} legacy route and ownership claim must agree`);
10616
10716
  if (route.kind !== "legacy-unverified" && (rec["sourceRevision"] === undefined || fingerprints.source === undefined || fingerprints.projected === undefined || fingerprints.installed === undefined))
10617
- invalid2(`${label} verified route requires source revision plus source, projected, and installed fingerprints`);
10717
+ invalid3(`${label} verified route requires source revision plus source, projected, and installed fingerprints`);
10618
10718
  if (rec["readbackState"] === "verified" && fingerprints.installed === undefined)
10619
- invalid2(`${label} verified readback requires an installed fingerprint`);
10719
+ invalid3(`${label} verified readback requires an installed fingerprint`);
10620
10720
  if (pending === undefined && rec["readbackState"] === "pending")
10621
- invalid2(`${label} pending readback requires a pending operation`);
10721
+ invalid3(`${label} pending readback requires a pending operation`);
10622
10722
  return value;
10623
10723
  }
10624
10724
  function validateRoute(value, label) {
@@ -10678,7 +10778,7 @@ function validatePendingSemantics(activation, attempt, journal) {
10678
10778
  const pending = activation.pending;
10679
10779
  const identity = `activation '${activationKey(activation)}'`;
10680
10780
  if (pending.operation !== journal.action)
10681
- invalid2(`${identity} pending operation must match journal action`);
10781
+ invalid3(`${identity} pending operation must match journal action`);
10682
10782
  const isRetirement = pending.operation === "retire-orphan" || pending.operation === "remove";
10683
10783
  let journalState;
10684
10784
  let attemptPhases;
@@ -10711,11 +10811,11 @@ function validatePendingSemantics(activation, attempt, journal) {
10711
10811
  break;
10712
10812
  }
10713
10813
  if (journal.state !== journalState)
10714
- invalid2(`${identity} pending phase ${pending.phase} requires journal ${journalState}`);
10814
+ invalid3(`${identity} pending phase ${pending.phase} requires journal ${journalState}`);
10715
10815
  if (!attemptPhases.includes(attempt.phase))
10716
- invalid2(`${identity} pending phase ${pending.phase} requires attempt ${attemptPhases.join(" or ")}`);
10816
+ invalid3(`${identity} pending phase ${pending.phase} requires attempt ${attemptPhases.join(" or ")}`);
10717
10817
  if (attempt.mutationStarted !== mutationStarted)
10718
- invalid2(`${identity} ${pending.phase} pending work requires mutationStarted ${String(mutationStarted)}`);
10818
+ invalid3(`${identity} ${pending.phase} pending work requires mutationStarted ${String(mutationStarted)}`);
10719
10819
  }
10720
10820
  function validateAttempt(value, label) {
10721
10821
  const rec = asObject(value, label);
@@ -10725,31 +10825,31 @@ function validateAttempt(value, label) {
10725
10825
  oneOf(rec["phase"], ["accepted", "applying", "readback", "pruning", "finalizing", "completed", "failed", "recovery-required"], `${label}.phase`);
10726
10826
  const mutationStarted = rec["mutationStarted"];
10727
10827
  if (typeof mutationStarted !== "boolean")
10728
- invalid2(`${label}.mutationStarted must be a boolean`);
10828
+ invalid3(`${label}.mutationStarted must be a boolean`);
10729
10829
  const scopeIds = stringArray(rec["scopeIds"], `${label}.scopeIds`);
10730
10830
  if (scopeIds.length === 0)
10731
- invalid2(`${label}.scopeIds must not be empty`);
10831
+ invalid3(`${label}.scopeIds must not be empty`);
10732
10832
  unique(scopeIds, `${label}.scopeIds`);
10733
10833
  const journal = array(rec["journal"], `${label}.journal`).map((item, index) => validateJournal(item, `${label}.journal[${index}]`));
10734
10834
  for (const entry of journal) {
10735
10835
  if (!commandAllowsJournalAction(command, entry.action))
10736
- invalid2(`${label} command ${command} cannot journal action ${entry.action}`);
10836
+ invalid3(`${label} command ${command} cannot journal action ${entry.action}`);
10737
10837
  }
10738
10838
  const journalShowsMutation = journal.some((entry) => MUTATION_STARTED_JOURNAL_STATES.has(entry.state));
10739
10839
  if (journalShowsMutation && !mutationStarted)
10740
- invalid2(`${label} journal shows mutation began but mutationStarted is false`);
10840
+ invalid3(`${label} journal shows mutation began but mutationStarted is false`);
10741
10841
  if (mutationStarted && !journalShowsMutation)
10742
- invalid2(`${label} mutationStarted is true but no journal row shows mutation began`);
10842
+ invalid3(`${label} mutationStarted is true but no journal row shows mutation began`);
10743
10843
  optionalTimestamp(rec["startedAt"], `${label}.startedAt`);
10744
10844
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10745
10845
  optionalTimestamp(rec["completedAt"], `${label}.completedAt`);
10746
10846
  if (rec["phase"] === "completed" && rec["completedAt"] === undefined)
10747
- invalid2(`${label} completed attempt requires completedAt`);
10847
+ invalid3(`${label} completed attempt requires completedAt`);
10748
10848
  return value;
10749
10849
  }
10750
10850
  function validateJournal(value, label) {
10751
10851
  const rec = asObject(value, label);
10752
- exactFields2(rec, ["operationId", "scopeId", "packageId", "nativeId", "action", "state", "route", "startedAt", "updatedAt"], "journal entry");
10852
+ exactFields2(rec, ["operationId", "scopeId", "packageId", "nativeId", "action", "state", "route", "startedAt", "updatedAt", "sealedOperation", "settlement"], "journal entry");
10753
10853
  requiredString(rec["operationId"], `${label}.operationId`);
10754
10854
  requiredString(rec["scopeId"], `${label}.scopeId`);
10755
10855
  optionalString(rec["packageId"], `${label}.packageId`);
@@ -10758,6 +10858,20 @@ function validateJournal(value, label) {
10758
10858
  oneOf(rec["state"], ["pending", "applying", "applied", "readback-verified", "rollback", "rolled-back", "cleanup-pending", "completed", "failed", "not-attempted"], `${label}.state`);
10759
10859
  if (rec["route"] !== undefined)
10760
10860
  validateRoute(rec["route"], `${label}.route`);
10861
+ if (rec["sealedOperation"] !== undefined) {
10862
+ const operation = parseSerializedLifecycleOperation(rec["sealedOperation"]);
10863
+ if (operation.operationId !== rec["operationId"] || operation.scopeId !== rec["scopeId"] || operation.packageName !== rec["packageId"] || operation.nativeId !== rec["nativeId"] || operation.action !== rec["action"])
10864
+ invalid3(`${label} sealed operation does not match journal identity`);
10865
+ }
10866
+ if (rec["settlement"] !== undefined) {
10867
+ oneOf(rec["settlement"], ["commit", "rollback"], `${label}.settlement`);
10868
+ if (rec["sealedOperation"] === undefined || !["readback-verified", "cleanup-pending", "completed", "rolled-back"].includes(String(rec["state"])))
10869
+ invalid3(`${label} settlement requires a sealed, verified receipt`);
10870
+ if (rec["settlement"] === "rollback" && !["cleanup-pending", "rolled-back"].includes(String(rec["state"])))
10871
+ invalid3(`${label} rollback settlement contradicts journal state`);
10872
+ if (rec["settlement"] === "commit" && rec["state"] === "rolled-back")
10873
+ invalid3(`${label} committed settlement cannot be rolled back`);
10874
+ }
10761
10875
  optionalTimestamp(rec["startedAt"], `${label}.startedAt`);
10762
10876
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10763
10877
  return value;
@@ -10790,19 +10904,19 @@ function validateTombstone(value, label) {
10790
10904
  const route = validateRoute(rec["route"], `${label}.route`);
10791
10905
  const ownershipObject = asObject(rec["ownership"], `${label}.ownership`);
10792
10906
  if (ownershipObject["kind"] === "legacy-claim")
10793
- invalid2("tombstone ownership must be revalidated before retirement");
10907
+ invalid3("tombstone ownership must be revalidated before retirement");
10794
10908
  const ownership = validateOwnership(ownershipObject, `${label}.ownership`);
10795
10909
  const fingerprints = validateFingerprints(rec["fingerprints"], `${label}.fingerprints`);
10796
10910
  const pins = stringArray(rec["pins"], `${label}.pins`);
10797
10911
  sortedUniqueRequired(pins, `${label}.pins`, true);
10798
10912
  if (rec["retentionState"] !== "plugin-state-retained")
10799
- invalid2(`${label}.retentionState must be plugin-state-retained`);
10913
+ invalid3(`${label}.retentionState must be plugin-state-retained`);
10800
10914
  optionalTimestamp(rec["activatedAt"], `${label}.activatedAt`);
10801
10915
  timestamp(rec["retiredAt"], `${label}.retiredAt`);
10802
10916
  if (route.kind === "legacy-unverified" || ownership.kind === "legacy-claim")
10803
- invalid2("tombstone ownership must be revalidated before retirement");
10917
+ invalid3("tombstone ownership must be revalidated before retirement");
10804
10918
  if (fingerprints.source === undefined || fingerprints.projected === undefined || fingerprints.installed === undefined)
10805
- invalid2(`${label} requires source, projected, and installed fingerprints`);
10919
+ invalid3(`${label} requires source, projected, and installed fingerprints`);
10806
10920
  return value;
10807
10921
  }
10808
10922
  function legacySourceBinding(source) {
@@ -10813,7 +10927,7 @@ function legacySourceBinding(source) {
10813
10927
  return binding;
10814
10928
  }
10815
10929
  if (!isAbsolute4(source))
10816
- invalid2(`legacy local source must be an absolute path: ${source}`);
10930
+ invalid3(`legacy local source must be an absolute path: ${source}`);
10817
10931
  const binding = { kind: "local", locator: source };
10818
10932
  validateCanonicalSourceBinding(binding, "legacy source");
10819
10933
  return binding;
@@ -10822,7 +10936,7 @@ function validateCanonicalSourceBinding(source, label) {
10822
10936
  try {
10823
10937
  validateSourceBinding(source);
10824
10938
  } catch (error) {
10825
- invalid2(`${label}: ${error.message}`);
10939
+ invalid3(`${label}: ${error.message}`);
10826
10940
  }
10827
10941
  }
10828
10942
  function compatibilitySourceUri(source) {
@@ -10835,7 +10949,7 @@ function canonicalScopeId(source, target) {
10835
10949
  try {
10836
10950
  return createDeploymentScopeIdentity(source, target).id;
10837
10951
  } catch (error) {
10838
- invalid2(error.message);
10952
+ invalid3(error.message);
10839
10953
  }
10840
10954
  }
10841
10955
  function legacyRelativeDir(record) {
@@ -10848,7 +10962,7 @@ function legacyRelativeDir(record) {
10848
10962
  return;
10849
10963
  return candidate;
10850
10964
  }
10851
- function digest(value) {
10965
+ function digest2(value) {
10852
10966
  const hash = new CryptoHasher("sha256");
10853
10967
  hash.update(value);
10854
10968
  return hash.digest("hex").slice(0, 24);
@@ -10864,59 +10978,59 @@ function pendingV1(operation) {
10864
10978
  }
10865
10979
  function asObject(value, label) {
10866
10980
  if (typeof value !== "object" || value === null || Array.isArray(value))
10867
- invalid2(`${label} must be an object`);
10981
+ invalid3(`${label} must be an object`);
10868
10982
  return value;
10869
10983
  }
10870
10984
  function exactFields2(record, allowed, label) {
10871
10985
  const allowedSet = new Set(allowed);
10872
10986
  const unknown = Object.keys(record).find((key) => !allowedSet.has(key));
10873
10987
  if (unknown !== undefined)
10874
- invalid2(`unsupported ${label} field '${unknown}'`);
10988
+ invalid3(`unsupported ${label} field '${unknown}'`);
10875
10989
  }
10876
10990
  function array(value, label) {
10877
10991
  if (!Array.isArray(value))
10878
- invalid2(`${label} must be an array`);
10992
+ invalid3(`${label} must be an array`);
10879
10993
  return value;
10880
10994
  }
10881
10995
  function requiredString(value, label) {
10882
10996
  if (typeof value !== "string" || value.trim() === "")
10883
- invalid2(`${label} must be a non-empty string`);
10997
+ invalid3(`${label} must be a non-empty string`);
10884
10998
  return value;
10885
10999
  }
10886
11000
  function optionalString(value, label) {
10887
11001
  if (value !== undefined && (typeof value !== "string" || value.length === 0))
10888
- invalid2(`${label} must be a non-empty string`);
11002
+ invalid3(`${label} must be a non-empty string`);
10889
11003
  }
10890
11004
  function stringArray(value, label) {
10891
11005
  const values = array(value, label);
10892
11006
  if (values.some((item) => typeof item !== "string" || item.length === 0))
10893
- invalid2(`${label} must contain non-empty strings`);
11007
+ invalid3(`${label} must contain non-empty strings`);
10894
11008
  return values;
10895
11009
  }
10896
11010
  function optionalLegacyString(value, label) {
10897
11011
  if (value !== undefined && typeof value !== "string")
10898
- invalid2(`${label} must be a string`);
11012
+ invalid3(`${label} must be a string`);
10899
11013
  }
10900
11014
  function optionalLegacyPins(value) {
10901
11015
  if (value === undefined)
10902
11016
  return;
10903
11017
  if (!Array.isArray(value) || value.some((pin) => typeof pin !== "string" || pin.length === 0))
10904
- invalid2("pins must be non-empty strings");
11018
+ invalid3("pins must be non-empty strings");
10905
11019
  return value;
10906
11020
  }
10907
11021
  function safeInteger(value, label, minimum) {
10908
11022
  if (!Number.isSafeInteger(value) || value < minimum)
10909
- invalid2(`${label} must be a safe integer >= ${minimum}`);
11023
+ invalid3(`${label} must be a safe integer >= ${minimum}`);
10910
11024
  return value;
10911
11025
  }
10912
11026
  function oneOf(value, values, label) {
10913
11027
  if (typeof value !== "string" || !values.includes(value))
10914
- invalid2(`${label} must be one of: ${values.join(", ")}`);
11028
+ invalid3(`${label} must be one of: ${values.join(", ")}`);
10915
11029
  return value;
10916
11030
  }
10917
11031
  function timestamp(value, label) {
10918
11032
  if (typeof value !== "string" || !validTimestamp(value))
10919
- invalid2(`${label} must be an ISO-8601 UTC timestamp`);
11033
+ invalid3(`${label} must be an ISO-8601 UTC timestamp`);
10920
11034
  return value;
10921
11035
  }
10922
11036
  function optionalTimestamp(value, label) {
@@ -10935,21 +11049,21 @@ function relativePath(value, label) {
10935
11049
  return path;
10936
11050
  const segments = path.split("/");
10937
11051
  if (path.trim() !== path || path.includes("\\") || /[\u0000-\u001f\u007f]/u.test(path) || isAbsolute4(path) || /^[a-z]:\//iu.test(path) || segments.some((segment) => segment === "" || segment === "." || segment === ".."))
10938
- invalid2(`${label} must be a canonical Source-relative path`);
11052
+ invalid3(`${label} must be a canonical Source-relative path`);
10939
11053
  return path;
10940
11054
  }
10941
11055
  function contentAddress(value, label) {
10942
11056
  const key = requiredString(value, label);
10943
11057
  if (!/^sha256:[0-9a-f]{64}$/u.test(key))
10944
- invalid2(`${label} must be sha256 followed by 64 lowercase hexadecimal characters`);
11058
+ invalid3(`${label} must be sha256 followed by 64 lowercase hexadecimal characters`);
10945
11059
  return key;
10946
11060
  }
10947
11061
  function sortedUniqueRequired(values, label, allowEmpty = false) {
10948
11062
  if (!allowEmpty && values.length === 0)
10949
- invalid2(`${label} must not be empty`);
11063
+ invalid3(`${label} must not be empty`);
10950
11064
  const sorted = [...new Set(values)].sort();
10951
11065
  if (sorted.length !== values.length || sorted.some((value, index) => value !== values[index]))
10952
- invalid2(`${label} must be sorted and unique`);
11066
+ invalid3(`${label} must be sorted and unique`);
10953
11067
  }
10954
11068
  function sortedUnique(values) {
10955
11069
  return [...new Set(values)].sort();
@@ -10958,7 +11072,7 @@ function unique(values, label) {
10958
11072
  const seen = new Set;
10959
11073
  for (const value of values) {
10960
11074
  if (seen.has(value))
10961
- invalid2(`duplicate ${label}: ${value}`);
11075
+ invalid3(`duplicate ${label}: ${value}`);
10962
11076
  seen.add(value);
10963
11077
  }
10964
11078
  }
@@ -10968,10 +11082,25 @@ function removeUndefined(record) {
10968
11082
  delete record[key];
10969
11083
  return record;
10970
11084
  }
10971
- function invalid2(message) {
11085
+ function invalid3(message) {
10972
11086
  throw new Error(`Invalid state.json: ${message}`);
10973
11087
  }
10974
11088
 
11089
+ // src/pending-lifecycle.ts
11090
+ var UNRESOLVED = new Set(["pending", "applying", "applied", "readback-verified", "rollback", "cleanup-pending"]);
11091
+ function pendingLifecycleOperations(state = readLifecycleState().state) {
11092
+ return state.attempts.filter((attempt) => attempt.command !== "legacy-recovery").flatMap((attempt) => attempt.journal.filter((row) => UNRESOLVED.has(row.state)).map((row) => ({
11093
+ host: state.scopes.find((scope) => scope.id === row.scopeId).target.kind,
11094
+ scopeId: row.scopeId,
11095
+ attemptId: attempt.id,
11096
+ operationId: row.operationId,
11097
+ id: row.nativeId ?? row.packageId ?? row.operationId,
11098
+ action: row.action,
11099
+ phase: row.state,
11100
+ command: attempt.command
11101
+ })));
11102
+ }
11103
+
10975
11104
  // src/fingerprint.ts
10976
11105
  import { lstatSync as lstatSync6, readFileSync as readFileSync12, readdirSync as readdirSync7 } from "node:fs";
10977
11106
  import { isAbsolute as isAbsolute5, join as join17 } from "node:path";
@@ -11234,6 +11363,10 @@ function checkStaleness(host, state, out) {
11234
11363
  function runDoctor(hostList = hosts, state = readState()) {
11235
11364
  const findings = [];
11236
11365
  const selected = new Set(hostList.map((host) => host.id));
11366
+ for (const pending of pendingLifecycleOperations()) {
11367
+ if (selected.has(pending.host))
11368
+ findings.push({ host: pending.host, mark: "✗", message: `operation '${pending.operationId}' has pending ${pending.action}/${pending.phase} in attempt '${pending.attemptId}' — retry '${pending.command}' for scope '${pending.scopeId}'` });
11369
+ }
11237
11370
  for (const record of state) {
11238
11371
  if (record.pending !== undefined && selected.has(record.host)) {
11239
11372
  findings.push({ host: record.host, mark: "!", message: `install '${record.id}' has pending ${record.pending} intent — retry the ${record.pending === "remove" ? "remove" : "add/update"} operation` });
@@ -12435,14 +12568,14 @@ function exitCodeForLifecycleReport(report) {
12435
12568
  }
12436
12569
  function parseLifecycleReport(value) {
12437
12570
  if (!isObject2(value)) {
12438
- throw invalid3("lifecycle report must be an object");
12571
+ throw invalid4("lifecycle report must be an object");
12439
12572
  }
12440
12573
  exactFields3(value, ["schemaVersion", "command", "plan", "outcomes", "summary"], "lifecycle report");
12441
12574
  if (value["schemaVersion"] !== LIFECYCLE_REPORT_SCHEMA_VERSION) {
12442
- throw invalid3("lifecycle report schemaVersion must be 1");
12575
+ throw invalid4("lifecycle report schemaVersion must be 1");
12443
12576
  }
12444
12577
  if (!isObject2(value["command"]) || !Array.isArray(value["plan"]) || !Array.isArray(value["outcomes"]) || !isObject2(value["summary"])) {
12445
- throw invalid3("lifecycle report must contain command, plan, outcomes, and summary");
12578
+ throw invalid4("lifecycle report must contain command, plan, outcomes, and summary");
12446
12579
  }
12447
12580
  const command = value["command"];
12448
12581
  const plan = value["plan"];
@@ -12451,19 +12584,19 @@ function parseLifecycleReport(value) {
12451
12584
  assertCommand(command);
12452
12585
  const commandName = command["name"];
12453
12586
  if (!plan.every(isObject2) || !outcomes.every(isObject2))
12454
- throw invalid3("lifecycle plan and outcomes must contain objects");
12587
+ throw invalid4("lifecycle plan and outcomes must contain objects");
12455
12588
  const planned = new Map;
12456
12589
  const plannedPairs = new Set;
12457
12590
  for (const operation of plan) {
12458
12591
  assertPlanOperation(operation, command["sourceSnapshots"], commandName);
12459
12592
  const operationId = operation["operationId"];
12460
12593
  if (typeof operationId !== "string" || operationId.length === 0)
12461
- throw invalid3("every planned operation needs a nonempty operationId");
12594
+ throw invalid4("every planned operation needs a nonempty operationId");
12462
12595
  if (planned.has(operationId))
12463
- throw invalid3(`duplicate planned operation '${operationId}'`);
12596
+ throw invalid4(`duplicate planned operation '${operationId}'`);
12464
12597
  const pair = JSON.stringify([operation["scope"].id, operation["package"]]);
12465
12598
  if (plannedPairs.has(pair))
12466
- throw invalid3(`duplicate planned package pair for '${operation["package"]}'`);
12599
+ throw invalid4(`duplicate planned package pair for '${operation["package"]}'`);
12467
12600
  plannedPairs.add(pair);
12468
12601
  planned.set(operationId, operation);
12469
12602
  }
@@ -12472,7 +12605,7 @@ function parseLifecycleReport(value) {
12472
12605
  assertOutcome(outcome, command["sourceSnapshots"], command["dryRun"], commandName);
12473
12606
  const operationId = outcome["operationId"];
12474
12607
  if (typeof operationId !== "string" || operationId.length === 0)
12475
- throw invalid3("every outcome needs a nonempty operationId");
12608
+ throw invalid4("every outcome needs a nonempty operationId");
12476
12609
  if (seen.has(operationId))
12477
12610
  throw protocol("protocol.duplicate-outcome", `duplicate outcome for planned operation '${operationId}'`);
12478
12611
  seen.add(operationId);
@@ -12503,7 +12636,7 @@ var PLAN_IDENTITY_FIELDS = [
12503
12636
  "action",
12504
12637
  "route"
12505
12638
  ];
12506
- function invalid3(diagnostic) {
12639
+ function invalid4(diagnostic) {
12507
12640
  return protocol("protocol.invalid-report", diagnostic);
12508
12641
  }
12509
12642
  function contradiction(diagnostic) {
@@ -12515,20 +12648,20 @@ function protocol(code, diagnostic) {
12515
12648
  function assertCommand(command) {
12516
12649
  exactFields3(command, ["name", "dryRun", "sourceSnapshots"], "lifecycle command");
12517
12650
  if (!oneOf2(LIFECYCLE_COMMANDS, command["name"]) || typeof command["dryRun"] !== "boolean" || !Array.isArray(command["sourceSnapshots"])) {
12518
- throw invalid3("lifecycle command must contain a known name, dryRun boolean, and sourceSnapshots array");
12651
+ throw invalid4("lifecycle command must contain a known name, dryRun boolean, and sourceSnapshots array");
12519
12652
  }
12520
12653
  const ids = new Set;
12521
12654
  for (const context of command["sourceSnapshots"]) {
12522
12655
  if (!isObject2(context) || !nonempty(context["id"]) || !isObject2(context["reference"]))
12523
- throw invalid3("source snapshot context is malformed");
12656
+ throw invalid4("source snapshot context is malformed");
12524
12657
  exactFields3(context, ["id", "reference"], "source snapshot context");
12525
12658
  if (ids.has(context["id"]))
12526
- throw invalid3(`duplicate source snapshot '${context["id"]}'`);
12659
+ throw invalid4(`duplicate source snapshot '${context["id"]}'`);
12527
12660
  ids.add(context["id"]);
12528
12661
  const reference = context["reference"];
12529
12662
  exactFields3(reference, ["binding", "revision", "fingerprint"], `source snapshot '${context["id"]}' reference`);
12530
12663
  if (!isObject2(reference["binding"]) || !nonempty(reference["revision"]) || !nonempty(reference["fingerprint"]))
12531
- throw invalid3(`source snapshot '${context["id"]}' is malformed`);
12664
+ throw invalid4(`source snapshot '${context["id"]}' is malformed`);
12532
12665
  const binding = reference["binding"];
12533
12666
  assertSourceBinding(binding, `source snapshot '${context["id"]}'`);
12534
12667
  }
@@ -12555,27 +12688,27 @@ var OUTCOME_FIELDS = [
12555
12688
  function assertPlanOperation(operation, snapshots, command, shape = "plan") {
12556
12689
  exactFields3(operation, shape === "plan" ? PLAN_OPERATION_FIELDS : OUTCOME_FIELDS, `lifecycle ${shape}`);
12557
12690
  if (!nonempty(operation["operationId"]) || !oneOf2(LIFECYCLE_COVERAGE_KINDS, operation["coverage"]) || !isObject2(operation["scope"]) || !nullableString(operation["sourceSnapshotId"]) || !nonempty(operation["package"]) || !nullableString(operation["nativeId"]) || !oneOf2(LIFECYCLE_PLAN_ACTIONS, operation["action"]) || !oneOf2(LIFECYCLE_ROUTES, operation["route"])) {
12558
- throw invalid3("planned operation contains a missing or invalid field");
12691
+ throw invalid4("planned operation contains a missing or invalid field");
12559
12692
  }
12560
12693
  assertScope(operation["scope"]);
12561
12694
  if (operation["sourceSnapshotId"] !== null && !snapshots.some((snapshot) => snapshot.id === operation["sourceSnapshotId"])) {
12562
- throw invalid3(`planned operation '${operation["operationId"]}' references an unknown source snapshot`);
12695
+ throw invalid4(`planned operation '${operation["operationId"]}' references an unknown source snapshot`);
12563
12696
  }
12564
12697
  const snapshot = snapshots.find((candidate) => candidate.id === operation["sourceSnapshotId"]);
12565
12698
  if (snapshot !== undefined && !sameSourceBinding(snapshot.reference.binding, operation["scope"].source)) {
12566
- throw invalid3(`planned operation '${operation["operationId"]}' scope contradicts its source snapshot`);
12699
+ throw invalid4(`planned operation '${operation["operationId"]}' scope contradicts its source snapshot`);
12567
12700
  }
12568
12701
  if (operation["coverage"] === "retirement" && operation["action"] !== "retire-orphan" && operation["action"] !== "not-attempted") {
12569
- throw invalid3(`orphan retirement '${operation["operationId"]}' has a non-retirement action`);
12702
+ throw invalid4(`orphan retirement '${operation["operationId"]}' has a non-retirement action`);
12570
12703
  }
12571
12704
  if (operation["coverage"] === "desired-pair" && operation["action"] === "retire-orphan") {
12572
- throw invalid3(`Desired pair '${operation["operationId"]}' has a retirement action`);
12705
+ throw invalid4(`Desired pair '${operation["operationId"]}' has a retirement action`);
12573
12706
  }
12574
12707
  if ((command === "add" || command === "update") && operation["coverage"] !== "desired-pair") {
12575
- throw invalid3(`${command} cannot contain retirement coverage`);
12708
+ throw invalid4(`${command} cannot contain retirement coverage`);
12576
12709
  }
12577
12710
  if ((command === "remove" || command === "retire-source") && operation["coverage"] !== "retirement") {
12578
- throw invalid3(`${command} cannot contain desired-pair coverage`);
12711
+ throw invalid4(`${command} cannot contain desired-pair coverage`);
12579
12712
  }
12580
12713
  if (operation["action"] === "not-attempted" !== (operation["route"] === "none")) {
12581
12714
  throw contradiction(`planned operation '${operation["operationId"]}' must pair action not-attempted with route none`);
@@ -12584,7 +12717,7 @@ function assertPlanOperation(operation, snapshots, command, shape = "plan") {
12584
12717
  function assertOutcome(outcome, snapshots, dryRun, command) {
12585
12718
  assertPlanOperation(outcome, snapshots, command, "outcome");
12586
12719
  if (!oneOf2(LIFECYCLE_OPERATION_RESULTS, outcome["result"]) || !oneOf2(LIFECYCLE_RESOURCE_STATES, outcome["resourceState"]) || !oneOf2(LIFECYCLE_ACTIVATION_STATES, outcome["activationState"]) || typeof outcome["changed"] !== "boolean" || !(outcome["reason"] === null || isObject2(outcome["reason"]))) {
12587
- throw invalid3(`outcome '${outcome["operationId"]}' contains a missing or invalid field`);
12720
+ throw invalid4(`outcome '${outcome["operationId"]}' contains a missing or invalid field`);
12588
12721
  }
12589
12722
  const reason = outcome["reason"];
12590
12723
  if (reason !== null)
@@ -12607,7 +12740,7 @@ function assertOutcome(outcome, snapshots, dryRun, command) {
12607
12740
  const notices = outcome["notices"];
12608
12741
  if (notices !== undefined) {
12609
12742
  if (!Array.isArray(notices) || notices.some((item) => typeof item !== "string" || item.length === 0)) {
12610
- throw invalid3(`outcome '${outcome["operationId"]}' notices must be non-empty strings`);
12743
+ throw invalid4(`outcome '${outcome["operationId"]}' notices must be non-empty strings`);
12611
12744
  }
12612
12745
  if (outcome["result"] !== "succeeded")
12613
12746
  throw contradiction(`outcome '${outcome["operationId"]}' can report a dropped pin only after success`);
@@ -12627,7 +12760,13 @@ function assertOutcome(outcome, snapshots, dryRun, command) {
12627
12760
  }
12628
12761
  const containment = outcome["action"] === "retain-prior" || outcome["action"] === "disable-nonconforming";
12629
12762
  const appliedDisablement = outcome["action"] === "disable-nonconforming" && outcome["changed"] === true;
12630
- if (containment && !dryRun) {
12763
+ if (outcome["action"] === "disable-nonconforming" && outcome["result"] === "succeeded") {
12764
+ throw contradiction(`disablement '${outcome["operationId"]}' cannot claim Desired convergence`);
12765
+ }
12766
+ if (!dryRun && outcome["action"] === "disable-nonconforming" && outcome["resourceState"] === "retained" && outcome["activationState"] === "inactive" && outcome["changed"] !== true) {
12767
+ throw contradiction(`disablement '${outcome["operationId"]}' cannot claim inactive containment without a verified mutation`);
12768
+ }
12769
+ if (containment && !dryRun && (outcome["action"] === "retain-prior" || isObject2(reason) && reason["category"] === "capability")) {
12631
12770
  const expectedResourceState = "retained";
12632
12771
  const expectedActivationState = outcome["action"] === "retain-prior" ? "retained-prior" : appliedDisablement ? "inactive" : "active-nonconforming";
12633
12772
  if (outcome["result"] !== "failed" || outcome["resourceState"] !== expectedResourceState || outcome["activationState"] !== expectedActivationState) {
@@ -12673,7 +12812,7 @@ function isKnownSafeReadbackState(resourceState, activationState) {
12673
12812
  function assertSummary(summary, outcomes, dryRun) {
12674
12813
  exactFields3(summary, ["result", "terminalPhase", "mutationStarted", "changed", "failureCategory", "reason", "recoveryId", "readbackId"], "lifecycle summary");
12675
12814
  if (!oneOf2(LIFECYCLE_COMMAND_RESULTS, summary["result"]) || !oneOf2(LIFECYCLE_TERMINAL_PHASES, summary["terminalPhase"]) || typeof summary["mutationStarted"] !== "boolean" || typeof summary["changed"] !== "boolean" || !(summary["failureCategory"] === null || oneOf2(LIFECYCLE_REASON_CATEGORIES, summary["failureCategory"])) || !(summary["reason"] === null || isObject2(summary["reason"])) || !nullableString(summary["recoveryId"]) || !nullableString(summary["readbackId"])) {
12676
- throw invalid3("lifecycle summary contains a missing or invalid field");
12815
+ throw invalid4("lifecycle summary contains a missing or invalid field");
12677
12816
  }
12678
12817
  const reason = summary["reason"];
12679
12818
  if (reason !== null)
@@ -12761,21 +12900,21 @@ function assertSummary(summary, outcomes, dryRun) {
12761
12900
  function assertReason(reason) {
12762
12901
  exactFields3(reason, ["category", "code", "diagnostic", "capabilityId", "evidenceId"], "lifecycle reason");
12763
12902
  if (!oneOf2(LIFECYCLE_REASON_CATEGORIES, reason["category"]) || !oneOf2(LIFECYCLE_REASON_CODES, reason["code"]) || !hasText(reason["diagnostic"]) || !nullableText(reason["capabilityId"]) || !nullableText(reason["evidenceId"])) {
12764
- throw invalid3("lifecycle reason contains a missing or invalid field");
12903
+ throw invalid4("lifecycle reason contains a missing or invalid field");
12765
12904
  }
12766
12905
  if (reason["code"].slice(0, reason["code"].indexOf(".")) !== reason["category"]) {
12767
- throw invalid3(`reason code '${reason["code"]}' does not belong to category '${reason["category"]}'`);
12906
+ throw invalid4(`reason code '${reason["code"]}' does not belong to category '${reason["category"]}'`);
12768
12907
  }
12769
12908
  if (reason["category"] === "capability" && reason["capabilityId"] === null)
12770
- throw invalid3("capability reason needs a capabilityId");
12909
+ throw invalid4("capability reason needs a capabilityId");
12771
12910
  if (reason["category"] !== "capability" && (reason["capabilityId"] !== null || reason["evidenceId"] !== null)) {
12772
- throw invalid3("only capability reasons may carry capability or evidence identifiers");
12911
+ throw invalid4("only capability reasons may carry capability or evidence identifiers");
12773
12912
  }
12774
12913
  }
12775
12914
  function assertScope(scope) {
12776
12915
  exactFields3(scope, ["id", "source", "target"], "deployment scope");
12777
12916
  if (!nonempty(scope["id"]) || !isObject2(scope["source"]) || !isObject2(scope["target"])) {
12778
- throw invalid3("planned operation has a malformed deployment scope");
12917
+ throw invalid4("planned operation has a malformed deployment scope");
12779
12918
  }
12780
12919
  assertSourceBinding(scope["source"], "planned operation deployment scope");
12781
12920
  exactFields3(scope["target"], ["kind", "instance"], "deployment scope target");
@@ -12783,28 +12922,28 @@ function assertScope(scope) {
12783
12922
  try {
12784
12923
  canonical = createDeploymentScopeIdentity(scope["source"], scope["target"]);
12785
12924
  } catch (error) {
12786
- throw invalid3(`planned operation has an invalid deployment scope: ${unknownErrorDiagnostic(error)}`);
12925
+ throw invalid4(`planned operation has an invalid deployment scope: ${unknownErrorDiagnostic(error)}`);
12787
12926
  }
12788
12927
  if (!sameScope(scope, canonical)) {
12789
- throw invalid3("planned operation deployment scope id contradicts its Source and target identity");
12928
+ throw invalid4("planned operation deployment scope id contradicts its Source and target identity");
12790
12929
  }
12791
12930
  }
12792
12931
  function assertSourceBinding(binding, label) {
12793
12932
  if (binding["kind"] === "local") {
12794
12933
  exactFields3(binding, ["kind", "locator"], `${label} Source binding`);
12795
12934
  if (!nonempty(binding["locator"]))
12796
- throw invalid3(`${label} local Source binding needs a locator`);
12935
+ throw invalid4(`${label} local Source binding needs a locator`);
12797
12936
  } else if (binding["kind"] === "git") {
12798
12937
  exactFields3(binding, ["kind", "locator", "ref"], `${label} Source binding`);
12799
12938
  if (!nonempty(binding["locator"]) || !nonempty(binding["ref"]))
12800
- throw invalid3(`${label} git Source binding needs a locator and ref`);
12939
+ throw invalid4(`${label} git Source binding needs a locator and ref`);
12801
12940
  } else {
12802
- throw invalid3(`${label} Source binding has an unknown kind`);
12941
+ throw invalid4(`${label} Source binding has an unknown kind`);
12803
12942
  }
12804
12943
  try {
12805
12944
  validateSourceBinding(binding);
12806
12945
  } catch (error) {
12807
- throw invalid3(`${label} has an invalid Source binding: ${unknownErrorDiagnostic(error)}`);
12946
+ throw invalid4(`${label} has an invalid Source binding: ${unknownErrorDiagnostic(error)}`);
12808
12947
  }
12809
12948
  }
12810
12949
  function sameScope(left, right) {
@@ -12832,7 +12971,7 @@ function exactFields3(record, allowed, label) {
12832
12971
  const fields = new Set(allowed);
12833
12972
  const unknown = Object.keys(record).find((key) => !fields.has(key));
12834
12973
  if (unknown !== undefined)
12835
- throw invalid3(`${label} has unsupported field '${unknown}'`);
12974
+ throw invalid4(`${label} has unsupported field '${unknown}'`);
12836
12975
  }
12837
12976
  function isObject2(value) {
12838
12977
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -12885,21 +13024,21 @@ function inventoryPackageSemantics(plugin) {
12885
13024
  const manifest = manifests[0];
12886
13025
  if (manifest?.mcpServers !== undefined) {
12887
13026
  if (!isRecord4(manifest.mcpServers))
12888
- invalid4("plugin manifest mcpServers must be an object");
13027
+ invalid5("plugin manifest mcpServers must be an object");
12889
13028
  mcp.push(`${manifest.path}#mcpServers`);
12890
13029
  }
12891
13030
  const autoUpdate = [];
12892
13031
  if (manifest !== undefined && manifest.extensions !== undefined) {
12893
13032
  const extension = manifest.extensions;
12894
13033
  if (!isRecord4(extension))
12895
- invalid4("plugin manifest extensions must be an object");
13034
+ invalid5("plugin manifest extensions must be an object");
12896
13035
  const dcode = extension["com.langchain.deepagents.code"];
12897
13036
  if (dcode !== undefined) {
12898
13037
  if (!isRecord4(dcode))
12899
- invalid4("dcode plugin manifest extension must be an object");
13038
+ invalid5("dcode plugin manifest extension must be an object");
12900
13039
  if (Object.hasOwn(dcode, "autoUpdate")) {
12901
13040
  if (typeof dcode["autoUpdate"] !== "boolean")
12902
- invalid4("dcode plugin manifest autoUpdate must be boolean");
13041
+ invalid5("dcode plugin manifest autoUpdate must be boolean");
12903
13042
  autoUpdate.push({
12904
13043
  dialect: "dcode-manifest-extension",
12905
13044
  path: manifest.path,
@@ -12980,7 +13119,7 @@ function requiredSemanticsForOperation(inventory, operation) {
12980
13119
  ]));
12981
13120
  }
12982
13121
  if (inventory === undefined)
12983
- invalid4(`${operation} semantic admission requires Source inventory`);
13122
+ invalid5(`${operation} semantic admission requires Source inventory`);
12984
13123
  const required = new Set(inventory.requiredSemantics);
12985
13124
  required.add("readback");
12986
13125
  required.add("rollback");
@@ -12998,7 +13137,7 @@ function inventorySkillPolicy(root, skill) {
12998
13137
  continue;
12999
13138
  const value = frontmatter[field];
13000
13139
  if (typeof value !== "boolean")
13001
- invalid4(`skill invocation policy ${field} must be boolean: ${skill}`);
13140
+ invalid5(`skill invocation policy ${field} must be boolean: ${skill}`);
13002
13141
  declarations.push({ dialect: "claude-frontmatter", path: skill, field, direction: "model", allowed: !value });
13003
13142
  }
13004
13143
  for (const field of USER_INVOCATION_ALIASES) {
@@ -13006,7 +13145,7 @@ function inventorySkillPolicy(root, skill) {
13006
13145
  continue;
13007
13146
  const value = frontmatter[field];
13008
13147
  if (typeof value !== "boolean")
13009
- invalid4(`skill invocation policy ${field} must be boolean: ${skill}`);
13148
+ invalid5(`skill invocation policy ${field} must be boolean: ${skill}`);
13010
13149
  declarations.push({ dialect: "claude-frontmatter", path: skill, field, direction: "user", allowed: value });
13011
13150
  }
13012
13151
  }
@@ -13016,11 +13155,11 @@ function inventorySkillPolicy(root, skill) {
13016
13155
  const policy = parsed["policy"];
13017
13156
  if (policy !== undefined) {
13018
13157
  if (!isRecord4(policy))
13019
- invalid4(`Codex invocation sidecar policy must be an object: ${sidecar}`);
13158
+ invalid5(`Codex invocation sidecar policy must be an object: ${sidecar}`);
13020
13159
  if (Object.hasOwn(policy, "allow_implicit_invocation")) {
13021
13160
  const value = policy["allow_implicit_invocation"];
13022
13161
  if (typeof value !== "boolean")
13023
- invalid4(`Codex invocation sidecar policy must be boolean: ${sidecar}`);
13162
+ invalid5(`Codex invocation sidecar policy must be boolean: ${sidecar}`);
13024
13163
  declarations.push({
13025
13164
  dialect: "codex-sidecar",
13026
13165
  path: sidecar,
@@ -13041,7 +13180,7 @@ function inventorySkillPolicy(root, skill) {
13041
13180
  function onePolicyValue(declarations, direction, skill) {
13042
13181
  const values = new Set(declarations.filter((entry) => entry.direction === direction).map((entry) => entry.allowed));
13043
13182
  if (values.size > 1)
13044
- invalid4(`conflicting ${direction}-invocation policy declarations: ${skill}`);
13183
+ invalid5(`conflicting ${direction}-invocation policy declarations: ${skill}`);
13045
13184
  return values.values().next().value ?? true;
13046
13185
  }
13047
13186
  function openingFrontmatter(raw, path) {
@@ -13053,7 +13192,7 @@ function openingFrontmatter(raw, path) {
13053
13192
  function requiredOpeningFrontmatter(raw, path) {
13054
13193
  const frontmatter = openingFrontmatter(raw, path);
13055
13194
  if (frontmatter === undefined)
13056
- invalid4(`component frontmatter is required: ${path}`);
13195
+ invalid5(`component frontmatter is required: ${path}`);
13057
13196
  return frontmatter;
13058
13197
  }
13059
13198
  function parseYamlObject(raw, label) {
@@ -13061,10 +13200,10 @@ function parseYamlObject(raw, label) {
13061
13200
  try {
13062
13201
  parsed = yamlParse(raw);
13063
13202
  } catch (error) {
13064
- invalid4(`${label} has invalid YAML (${errorDiagnostic(error)})`);
13203
+ invalid5(`${label} has invalid YAML (${errorDiagnostic(error)})`);
13065
13204
  }
13066
13205
  if (!isRecord4(parsed))
13067
- invalid4(`${label} must be an object`);
13206
+ invalid5(`${label} must be an object`);
13068
13207
  return parsed;
13069
13208
  }
13070
13209
  function parseTomlObject(raw, path) {
@@ -13072,10 +13211,10 @@ function parseTomlObject(raw, path) {
13072
13211
  try {
13073
13212
  parsed = parse(raw);
13074
13213
  } catch (error) {
13075
- invalid4(`command ${path} has invalid TOML (${errorDiagnostic(error)})`);
13214
+ invalid5(`command ${path} has invalid TOML (${errorDiagnostic(error)})`);
13076
13215
  }
13077
13216
  if (!isRecord4(parsed))
13078
- invalid4(`command ${path} must be an object`);
13217
+ invalid5(`command ${path} must be an object`);
13079
13218
  return parsed;
13080
13219
  }
13081
13220
  function parseJsonObject(path, label) {
@@ -13083,10 +13222,10 @@ function parseJsonObject(path, label) {
13083
13222
  try {
13084
13223
  parsed = JSON.parse(readFileSync15(path, "utf8"));
13085
13224
  } catch (error) {
13086
- invalid4(`${label} has invalid JSON (${errorDiagnostic(error)})`);
13225
+ invalid5(`${label} has invalid JSON (${errorDiagnostic(error)})`);
13087
13226
  }
13088
13227
  if (!isRecord4(parsed))
13089
- invalid4(`${label} must be an object`);
13228
+ invalid5(`${label} must be an object`);
13090
13229
  return parsed;
13091
13230
  }
13092
13231
  function readSemanticManifests(root) {
@@ -13111,7 +13250,7 @@ function inventoryHooks(root, files, manifests) {
13111
13250
  return cached;
13112
13251
  const value = parseJsonObject(join21(root, path), `hook configuration ${path}`);
13113
13252
  if (Object.keys(value).some((field) => field !== "hooks") || !isRecord4(value["hooks"])) {
13114
- invalid4(`hook configuration ${path} must contain only a top-level hooks object`);
13253
+ invalid5(`hook configuration ${path} must contain only a top-level hooks object`);
13115
13254
  }
13116
13255
  const observation = validateHookEventMap(value["hooks"], `hook configuration ${path}`);
13117
13256
  documents.set(path, observation);
@@ -13127,7 +13266,7 @@ function inventoryHooks(root, files, manifests) {
13127
13266
  addDocument("hooks/hooks.json", null, "default-file");
13128
13267
  for (const manifest of manifests.filter((candidate) => Object.hasOwn(candidate, "hooks"))) {
13129
13268
  if (manifest.path === ".plugin/plugin.json") {
13130
- invalid4("hook declarations in .plugin/plugin.json have no source-backed native contract");
13269
+ invalid5("hook declarations in .plugin/plugin.json have no source-backed native contract");
13131
13270
  }
13132
13271
  const declaration = manifest["hooks"];
13133
13272
  const addInline = (value, label, arrayItem) => {
@@ -13135,10 +13274,10 @@ function inventoryHooks(root, files, manifests) {
13135
13274
  let form = arrayItem ? "manifest-inline-array" : "manifest-inline-event-map";
13136
13275
  if (Object.hasOwn(value, "hooks")) {
13137
13276
  if (Object.keys(value).some((field) => field !== "hooks") || !isRecord4(value["hooks"])) {
13138
- invalid4(`inline hook declaration ${label} has an ambiguous hooks wrapper`);
13277
+ invalid5(`inline hook declaration ${label} has an ambiguous hooks wrapper`);
13139
13278
  }
13140
13279
  if (arrayItem)
13141
- invalid4(`inline hook declaration ${label} cannot use a file-document wrapper inside an array`);
13280
+ invalid5(`inline hook declaration ${label} cannot use a file-document wrapper inside an array`);
13142
13281
  eventMap = value["hooks"];
13143
13282
  form = "manifest-inline-wrapped";
13144
13283
  }
@@ -13161,10 +13300,10 @@ function inventoryHooks(root, files, manifests) {
13161
13300
  else if (manifest.path === ".claude-plugin/plugin.json" && isRecord4(item)) {
13162
13301
  addInline(item, `${manifest.path}#hooks[${index}]`, true);
13163
13302
  } else
13164
- invalid4(`plugin manifest ${manifest.path} hooks[${index}] has an unsupported shape`);
13303
+ invalid5(`plugin manifest ${manifest.path} hooks[${index}] has an unsupported shape`);
13165
13304
  }
13166
13305
  } else {
13167
- invalid4(`plugin manifest ${manifest.path} hooks must be a path, object, or non-empty supported array`);
13306
+ invalid5(`plugin manifest ${manifest.path} hooks must be a path, object, or non-empty supported array`);
13168
13307
  }
13169
13308
  }
13170
13309
  return {
@@ -13176,54 +13315,54 @@ function inventoryHooks(root, files, manifests) {
13176
13315
  function declaredHookPath(root, declaration) {
13177
13316
  const withoutPrefix = declaration.slice(2);
13178
13317
  if (!declaration.startsWith("./") || declaration.includes("\\") || /^[a-z]:\//iu.test(withoutPrefix)) {
13179
- invalid4(`declared hook path must start with './' and use portable separators: ${declaration}`);
13318
+ invalid5(`declared hook path must start with './' and use portable separators: ${declaration}`);
13180
13319
  }
13181
13320
  const path = portable(relative3(root, join21(root, declaration)));
13182
13321
  if (path === "" || path === ".." || path.startsWith("../") || declaration !== `./${path}`) {
13183
- invalid4(`declared hook path must be a canonical file inside the plugin root: ${declaration}`);
13322
+ invalid5(`declared hook path must be a canonical file inside the plugin root: ${declaration}`);
13184
13323
  }
13185
13324
  const absolute = join21(root, path);
13186
13325
  if (!existsSync16(absolute))
13187
- invalid4(`declared hook configuration does not exist: ${declaration}`);
13326
+ invalid5(`declared hook configuration does not exist: ${declaration}`);
13188
13327
  const stat = lstatSync9(absolute);
13189
13328
  if (stat.isDirectory()) {
13190
13329
  const document = `${path}/hooks.json`;
13191
13330
  const documentPath = join21(root, document);
13192
13331
  if (!existsSync16(documentPath) || !lstatSync9(documentPath).isFile()) {
13193
- invalid4(`declared hook directory must contain hooks.json: ${declaration}`);
13332
+ invalid5(`declared hook directory must contain hooks.json: ${declaration}`);
13194
13333
  }
13195
13334
  return { path: document, directory: true };
13196
13335
  }
13197
13336
  if (!stat.isFile()) {
13198
- invalid4(`declared hook configuration must be a regular file or native hook directory: ${declaration}`);
13337
+ invalid5(`declared hook configuration must be a regular file or native hook directory: ${declaration}`);
13199
13338
  }
13200
13339
  return { path, directory: false };
13201
13340
  }
13202
13341
  function validateHookEventMap(value, label) {
13203
13342
  if (!isRecord4(value))
13204
- invalid4(`${label} must be a hook event object`);
13343
+ invalid5(`${label} must be a hook event object`);
13205
13344
  const events = [];
13206
13345
  const handlerTypes = new Set;
13207
13346
  const groupDeclarations = [];
13208
13347
  const handlerFacets = new Set;
13209
13348
  for (const [event, groups] of Object.entries(value)) {
13210
13349
  if (event.trim() === "" || !Array.isArray(groups))
13211
- invalid4(`${label} event '${event}' must be an array`);
13350
+ invalid5(`${label} event '${event}' must be an array`);
13212
13351
  events.push(event);
13213
13352
  for (const [groupIndex, group] of groups.entries()) {
13214
13353
  if (!isRecord4(group))
13215
- invalid4(`${label} event '${event}' group ${groupIndex} must be an object`);
13354
+ invalid5(`${label} event '${event}' group ${groupIndex} must be an object`);
13216
13355
  if (group["matcher"] !== undefined && typeof group["matcher"] !== "string") {
13217
- invalid4(`${label} event '${event}' group ${groupIndex} matcher must be a string`);
13356
+ invalid5(`${label} event '${event}' group ${groupIndex} matcher must be a string`);
13218
13357
  }
13219
13358
  const matcher = typeof group["matcher"] === "string" ? group["matcher"] : null;
13220
13359
  if (!Array.isArray(group["hooks"]))
13221
- invalid4(`${label} event '${event}' group ${groupIndex} hooks must be an array`);
13360
+ invalid5(`${label} event '${event}' group ${groupIndex} hooks must be an array`);
13222
13361
  const groupHandlerTypes = new Set;
13223
13362
  const groupHandlerFacets = new Set;
13224
13363
  for (const [hookIndex, hook] of group["hooks"].entries()) {
13225
13364
  if (!isRecord4(hook) || typeof hook["type"] !== "string" || hook["type"].trim() === "") {
13226
- invalid4(`${label} event '${event}' group ${groupIndex} hook ${hookIndex} needs a non-empty type`);
13365
+ invalid5(`${label} event '${event}' group ${groupIndex} hook ${hookIndex} needs a non-empty type`);
13227
13366
  }
13228
13367
  validateHookHandler(hook, `${label} event '${event}' group ${groupIndex} hook ${hookIndex}`);
13229
13368
  handlerTypes.add(hook["type"]);
@@ -13292,7 +13431,7 @@ function validateHookHandler(hook, label) {
13292
13431
  return;
13293
13432
  if ((hook["type"] === "prompt" || hook["type"] === "agent") && nonemptyString("prompt"))
13294
13433
  return;
13295
- invalid4(`${label} has an unknown type or is missing required fields`);
13434
+ invalid5(`${label} has an unknown type or is missing required fields`);
13296
13435
  }
13297
13436
  function walkFiles(root) {
13298
13437
  const files = [];
@@ -13303,13 +13442,13 @@ function walkFiles(root) {
13303
13442
  const path = join21(dir, entry);
13304
13443
  const stat = lstatSync9(path);
13305
13444
  if (stat.isSymbolicLink())
13306
- invalid4(`package semantic inventory refuses symlink: ${portable(relative3(root, path))}`);
13445
+ invalid5(`package semantic inventory refuses symlink: ${portable(relative3(root, path))}`);
13307
13446
  if (stat.isDirectory())
13308
13447
  walk(path);
13309
13448
  else if (stat.isFile())
13310
13449
  files.push(portable(relative3(root, path)));
13311
13450
  else
13312
- invalid4(`package semantic inventory refuses unsupported entry: ${portable(relative3(root, path))}`);
13451
+ invalid5(`package semantic inventory refuses unsupported entry: ${portable(relative3(root, path))}`);
13313
13452
  }
13314
13453
  };
13315
13454
  walk(root);
@@ -13325,7 +13464,7 @@ var UNSUPPORTED_COMPONENT_ROOTS = [".codex/commands", ".codex/agents"];
13325
13464
  function inventoryComponentRoots(root) {
13326
13465
  for (const unsupported of UNSUPPORTED_COMPONENT_ROOTS) {
13327
13466
  if (existsSync16(join21(root, unsupported)))
13328
- invalid4(`unsupported component root: ${unsupported}`);
13467
+ invalid5(`unsupported component root: ${unsupported}`);
13329
13468
  }
13330
13469
  const definitions = [];
13331
13470
  const invocationPolicies = [];
@@ -13336,26 +13475,26 @@ function inventoryComponentRoots(root) {
13336
13475
  continue;
13337
13476
  const rootStat = lstatSync9(directory);
13338
13477
  if (!rootStat.isDirectory())
13339
- invalid4(`component root must be a directory: ${spec.root}`);
13478
+ invalid5(`component root must be a directory: ${spec.root}`);
13340
13479
  const entries = readdirSync10(directory).sort();
13341
13480
  if (entries.length === 0)
13342
- invalid4(`component root must not be empty: ${spec.root}`);
13481
+ invalid5(`component root must not be empty: ${spec.root}`);
13343
13482
  const identities = new Set;
13344
13483
  for (const entry of entries) {
13345
13484
  const absolute = join21(directory, entry);
13346
13485
  const path = `${spec.root}/${entry}`;
13347
13486
  const stat = lstatSync9(absolute);
13348
13487
  if (!stat.isFile())
13349
- invalid4(`component root entries must be regular files: ${path}`);
13488
+ invalid5(`component root entries must be regular files: ${path}`);
13350
13489
  const suffix = Object.keys(spec.dialects).find((candidate) => entry.endsWith(candidate));
13351
13490
  if (suffix === undefined)
13352
- invalid4(`unsupported ${spec.kind} dialect: ${path}`);
13491
+ invalid5(`unsupported ${spec.kind} dialect: ${path}`);
13353
13492
  const dialect = spec.dialects[suffix];
13354
13493
  if (dialect === undefined)
13355
- invalid4(`unsupported ${spec.kind} dialect: ${path}`);
13494
+ invalid5(`unsupported ${spec.kind} dialect: ${path}`);
13356
13495
  const parsed = parseComponentDefinition(absolute, path, spec.kind, dialect, entry.slice(0, -suffix.length));
13357
13496
  if (identities.has(parsed.definition.identity)) {
13358
- invalid4(`duplicate ${spec.kind} identity '${parsed.definition.identity}' within ${spec.root}`);
13497
+ invalid5(`duplicate ${spec.kind} identity '${parsed.definition.identity}' within ${spec.root}`);
13359
13498
  }
13360
13499
  identities.add(parsed.definition.identity);
13361
13500
  definitions.push({ ...parsed.definition, root: spec.root });
@@ -13377,16 +13516,16 @@ function parseComponentDefinition(absolute, path, kind, dialect, filenameIdentit
13377
13516
  let body;
13378
13517
  if (dialect === "toml") {
13379
13518
  if (kind !== "command")
13380
- invalid4(`unsupported ${kind} dialect: ${path}`);
13519
+ invalid5(`unsupported ${kind} dialect: ${path}`);
13381
13520
  record = parseTomlObject(raw, path);
13382
13521
  if (typeof record["description"] !== "string" || record["description"].trim() === "" || typeof record["prompt"] !== "string") {
13383
- invalid4(`command TOML needs description and prompt: ${path}`);
13522
+ invalid5(`command TOML needs description and prompt: ${path}`);
13384
13523
  }
13385
13524
  body = record["prompt"];
13386
13525
  } else {
13387
13526
  const frontmatter = requiredOpeningFrontmatter(raw, path);
13388
13527
  if (typeof frontmatter["description"] !== "string" || frontmatter["description"].trim() === "") {
13389
- invalid4(`${kind} description is required: ${path}`);
13528
+ invalid5(`${kind} description is required: ${path}`);
13390
13529
  }
13391
13530
  record = frontmatter;
13392
13531
  const match = OPENING_FRONTMATTER.exec(raw);
@@ -13394,9 +13533,9 @@ function parseComponentDefinition(absolute, path, kind, dialect, filenameIdentit
13394
13533
  }
13395
13534
  const identity = kind === "agent" ? record["name"] : filenameIdentity;
13396
13535
  if (typeof identity !== "string" || identity.trim() === "")
13397
- invalid4(`${kind} identity is required: ${path}`);
13536
+ invalid5(`${kind} identity is required: ${path}`);
13398
13537
  if (kind === "command" && /@\{/u.test(body))
13399
- invalid4(`unsupported command preprocessing: ${path}`);
13538
+ invalid5(`unsupported command preprocessing: ${path}`);
13400
13539
  const declarations = kind === "command" ? componentInvocationDeclarations(record, dialect, path) : [];
13401
13540
  const policy = {
13402
13541
  component: kind,
@@ -13419,7 +13558,7 @@ function componentInvocationDeclarations(record, dialect, path) {
13419
13558
  continue;
13420
13559
  const value = record[field];
13421
13560
  if (typeof value !== "boolean")
13422
- invalid4(`component invocation policy ${field} must be boolean: ${path}`);
13561
+ invalid5(`component invocation policy ${field} must be boolean: ${path}`);
13423
13562
  declarations.push({ dialect: policyDialect, path, field, direction: "model", allowed: !value });
13424
13563
  }
13425
13564
  for (const field of USER_INVOCATION_ALIASES) {
@@ -13427,7 +13566,7 @@ function componentInvocationDeclarations(record, dialect, path) {
13427
13566
  continue;
13428
13567
  const value = record[field];
13429
13568
  if (typeof value !== "boolean")
13430
- invalid4(`component invocation policy ${field} must be boolean: ${path}`);
13569
+ invalid5(`component invocation policy ${field} must be boolean: ${path}`);
13431
13570
  declarations.push({ dialect: policyDialect, path, field, direction: "user", allowed: value });
13432
13571
  }
13433
13572
  onePolicyValue(declarations, "model", path);
@@ -13449,7 +13588,7 @@ function hasAny(record, fields) {
13449
13588
  function isRecord4(value) {
13450
13589
  return typeof value === "object" && value !== null && !Array.isArray(value);
13451
13590
  }
13452
- function invalid4(diagnostic) {
13591
+ function invalid5(diagnostic) {
13453
13592
  throw new SemanticInventoryError(createLifecycleReason("usage", "usage.invalid-argument", diagnostic));
13454
13593
  }
13455
13594
  function errorDiagnostic(error) {
@@ -13546,7 +13685,8 @@ var dcode0183Managed = createCapabilityEvidenceProfile({
13546
13685
  },
13547
13686
  evidence: [
13548
13687
  "docs/evidence/dcode-native-update-0.1.83-20261009.json",
13549
- "docs/research/dcode-session-findings-2026-10-09.md"
13688
+ "docs/research/dcode-session-findings-2026-10-09.md",
13689
+ "docs/evidence/dcode-managed-lifecycle-20261011.md"
13550
13690
  ]
13551
13691
  });
13552
13692
  var capabilityEvidenceProfiles = Object.freeze([
@@ -14206,6 +14346,32 @@ function createLifecycleHostAdapter(definition) {
14206
14346
  const result = await invokePhase("retire", () => definition.retire(prepared));
14207
14347
  return mutationReceipt(prepared.handle, result);
14208
14348
  },
14349
+ async beginRecovery(handle) {
14350
+ assertHandle(definition.id, handle);
14351
+ if (definition.beginRecovery === undefined)
14352
+ throw phaseError("rollback", "recovery.failed", new Error(`adapter '${definition.id}' cross-process native CAS fence is unverified; recovery remains pending`));
14353
+ try {
14354
+ await definition.beginRecovery(handle);
14355
+ const version = await definition.probeVersion(handle.target);
14356
+ if (version.kind !== "detected" || version.version !== handle.detectedVersion || version.probeId !== handle.versionProbeId)
14357
+ throw new Error("recovery native version/probe differs from the sealed operation");
14358
+ const observation = deepFreeze({ ...normalizeReadback(await definition.readback(handle)), phase: "observed" });
14359
+ try {
14360
+ assertReadbackMatch(handle.prior, observation, "recovery prior");
14361
+ return { kind: "prior", observation };
14362
+ } catch {
14363
+ assertReadbackMatch(handle.expected, observation, "recovery candidate");
14364
+ return { kind: "expected", observation };
14365
+ }
14366
+ } catch (error) {
14367
+ try {
14368
+ await definition.releaseRecovery?.(handle);
14369
+ } catch (releaseError) {
14370
+ throw phaseError("rollback", "recovery.failed", new Error(`${unknownErrorDiagnostic(error)}; releasing recovery fence failed: ${unknownErrorDiagnostic(releaseError)}`));
14371
+ }
14372
+ throw phaseError("rollback", "recovery.failed", error);
14373
+ }
14374
+ },
14209
14375
  async readback(handle) {
14210
14376
  assertHandle(definition.id, handle);
14211
14377
  const data = await invokePhase("readback", () => definition.readback(handle));
@@ -14228,6 +14394,12 @@ function createLifecycleHostAdapter(definition) {
14228
14394
  assertReadbackMatch(handle.prior, observation, "rollback");
14229
14395
  return deepFreeze({ phase: "rollback-verified", handle, observation });
14230
14396
  },
14397
+ async releaseRecovery(handle) {
14398
+ assertHandle(definition.id, handle);
14399
+ await invokePhase("cleanup", async () => {
14400
+ await definition.releaseRecovery?.(handle);
14401
+ });
14402
+ },
14231
14403
  async cleanup(target, disposition) {
14232
14404
  const reference = cleanupReference(definition.id, target);
14233
14405
  if (isPreparationProjection(target) && disposition !== "aborted-preparation") {
@@ -14255,6 +14427,68 @@ function assertMutationLeader(handle) {
14255
14427
  throw phaseError("apply-precondition", "internal.invariant", new Error(`operation '${handle.operationId}' is a grouped Native follower; '${handle.mutationInvocationOperationId}' owns the single mutation invocation`));
14256
14428
  }
14257
14429
  }
14430
+ function hydrateDurableLifecycleOperation(value) {
14431
+ const input = record(value, "durable lifecycle operation");
14432
+ exactFields4(input, [
14433
+ "schemaVersion",
14434
+ "adapterId",
14435
+ "target",
14436
+ "targetObservationId",
14437
+ "operationId",
14438
+ "attemptId",
14439
+ "scopeId",
14440
+ "packageName",
14441
+ "nativeId",
14442
+ "sourceType",
14443
+ "sourceRevision",
14444
+ "sourceLocator",
14445
+ "packageVersion",
14446
+ "action",
14447
+ "route",
14448
+ "detectedVersion",
14449
+ "versionProbeId",
14450
+ "evidenceId",
14451
+ "mutationGroupId",
14452
+ "mutationInvocationOperationId",
14453
+ "artifactId",
14454
+ "projectedFingerprint",
14455
+ "affectedNativeIds",
14456
+ "affectedOperationIds",
14457
+ "prior",
14458
+ "expected",
14459
+ "rollbackReference"
14460
+ ], "durable lifecycle operation");
14461
+ if (input["schemaVersion"] !== 1)
14462
+ invariant("durable lifecycle operation schemaVersion must be 1");
14463
+ return durableHandle({
14464
+ adapterId: stringField(input, "adapterId"),
14465
+ target: input["target"],
14466
+ targetObservationId: stringField(input, "targetObservationId"),
14467
+ operationId: stringField(input, "operationId"),
14468
+ attemptId: stringField(input, "attemptId"),
14469
+ scopeId: stringField(input, "scopeId"),
14470
+ packageName: stringField(input, "packageName"),
14471
+ nativeId: stringField(input, "nativeId"),
14472
+ sourceType: sourceTypeField(input["sourceType"]),
14473
+ sourceRevision: stringField(input, "sourceRevision"),
14474
+ sourceLocator: nullableString2(input["sourceLocator"], "sourceLocator"),
14475
+ packageVersion: nullableString2(input["packageVersion"], "packageVersion"),
14476
+ action: actionField(input["action"]),
14477
+ route: routeField(input["route"]),
14478
+ detectedVersion: stringField(input, "detectedVersion"),
14479
+ versionProbeId: stringField(input, "versionProbeId"),
14480
+ evidenceId: stringField(input, "evidenceId"),
14481
+ mutationGroupId: stringField(input, "mutationGroupId"),
14482
+ mutationInvocationOperationId: stringField(input, "mutationInvocationOperationId"),
14483
+ affectedNativeIds: stringArrayField(input["affectedNativeIds"], "affectedNativeIds"),
14484
+ affectedOperationIds: stringArrayField(input["affectedOperationIds"], "affectedOperationIds"),
14485
+ artifactId: nullableString2(input["artifactId"], "artifactId"),
14486
+ projectedFingerprint: nullableString2(input["projectedFingerprint"], "projectedFingerprint"),
14487
+ prior: readbackField(input["prior"], "prior"),
14488
+ expected: readbackField(input["expected"], "expected"),
14489
+ rollbackReference: stringField(input, "rollbackReference")
14490
+ });
14491
+ }
14258
14492
  function freezeNativeProjectionRequest(request) {
14259
14493
  if ("snapshot" in request) {
14260
14494
  const expectedOperation = request.snapshot.action === "install" ? "install" : "update";
@@ -15032,6 +15266,51 @@ function stringField(value, field) {
15032
15266
  invariant(`${field} must be a string`);
15033
15267
  return result;
15034
15268
  }
15269
+ function readbackField(value, label) {
15270
+ const input = record(value, `${label} lifecycle readback`);
15271
+ exactFields4(input, [
15272
+ "adapterId",
15273
+ "target",
15274
+ "scopeId",
15275
+ "packageName",
15276
+ "nativeId",
15277
+ "route",
15278
+ "presence",
15279
+ "enablement",
15280
+ "activation",
15281
+ "transition",
15282
+ "installedFingerprint",
15283
+ "contentRoots",
15284
+ "retention"
15285
+ ], `${label} lifecycle readback`);
15286
+ return input;
15287
+ }
15288
+ function nullableString2(value, label) {
15289
+ if (value !== null && typeof value !== "string")
15290
+ invariant(`${label} must be a string or null`);
15291
+ return value;
15292
+ }
15293
+ function stringArrayField(value, label) {
15294
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string"))
15295
+ invariant(`${label} must be a string array`);
15296
+ return [...value];
15297
+ }
15298
+ function actionField(value) {
15299
+ if (!["install", "update", "route-migrate", "disable-nonconforming", "retire-orphan", "remove"].includes(String(value))) {
15300
+ invariant("durable lifecycle action is invalid");
15301
+ }
15302
+ return value;
15303
+ }
15304
+ function routeField(value) {
15305
+ if (value !== "native" && value !== "managed")
15306
+ invariant("durable lifecycle route is invalid");
15307
+ return value;
15308
+ }
15309
+ function sourceTypeField(value) {
15310
+ if (value !== "local" && value !== "git")
15311
+ invariant("durable lifecycle Source type is invalid");
15312
+ return value;
15313
+ }
15035
15314
 
15036
15315
  // src/hosts/claude-code-writer.ts
15037
15316
  var OWNERSHIP = ".plgnz-install.json";
@@ -19492,9 +19771,9 @@ var ompWriter = {
19492
19771
  const lockFile = join26(pluginsDir3(), "omp-plugins.lock.json");
19493
19772
  const registryFile = join26(pluginsDir3(), "installed_plugins.json");
19494
19773
  const lock = readDoc(lockFile, { plugins: {}, settings: {} }, "lockfile");
19495
- const lockPlugins = object2(lock.plugins, "lockfile plugins");
19774
+ const lockPlugins = object3(lock.plugins, "lockfile plugins");
19496
19775
  const registry = readDoc(registryFile, { version: 2, plugins: {} }, "registry");
19497
- const registryPlugins = object2(registry.plugins, "registry plugins");
19776
+ const registryPlugins = object3(registry.plugins, "registry plugins");
19498
19777
  const rows = userRows(registryPlugins[id]);
19499
19778
  if (rows.length > 0)
19500
19779
  validateLegacy(rows, id, plugin.name, version, opts?.adoptExisting === true);
@@ -19817,7 +20096,7 @@ function readDoc(path, fallback, label) {
19817
20096
  throw new Error(`invalid OMP ${label}: ${path} (${error.message})`);
19818
20097
  }
19819
20098
  }
19820
- function object2(value, label) {
20099
+ function object3(value, label) {
19821
20100
  if (!isDoc(value))
19822
20101
  throw new Error(`invalid OMP ${label}`);
19823
20102
  return value;
@@ -20238,7 +20517,7 @@ async function applyManaged(handle, stagingRoot) {
20238
20517
  const link = managedLink(handle.nativeId);
20239
20518
  const file = lockPath();
20240
20519
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20241
- const plugins = object2(lock.plugins, "lockfile plugins");
20520
+ const plugins = object3(lock.plugins, "lockfile plugins");
20242
20521
  const before = snapshot(file);
20243
20522
  mkdirSafe(dirname9(target));
20244
20523
  mkdirSafe(dirname9(link));
@@ -20268,7 +20547,7 @@ function activationAlreadyCurrent(nativeId, stage, version) {
20268
20547
  if (!existsSync21(target) || !existsSync21(stage) || fingerprintTree(stage) !== fingerprintTree(target) || !linkPointsTo(managedLink(nativeId), target))
20269
20548
  return false;
20270
20549
  const lock = readDoc(lockPath(), { plugins: {}, settings: {} }, "lockfile");
20271
- const plugins = object2(lock.plugins, "lockfile plugins");
20550
+ const plugins = object3(lock.plugins, "lockfile plugins");
20272
20551
  const row = entry(plugins[npmName(nativeId)]);
20273
20552
  return row.enabled === true && row.version === version;
20274
20553
  }
@@ -20276,7 +20555,7 @@ async function disableManaged(handle) {
20276
20555
  const receiptId = `disable:${handle.attemptId}:${handle.operationId}`;
20277
20556
  const file = lockPath();
20278
20557
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20279
- const plugins = object2(lock.plugins, "lockfile plugins");
20558
+ const plugins = object3(lock.plugins, "lockfile plugins");
20280
20559
  const npm = npmName(handle.nativeId);
20281
20560
  const previous = entry(plugins[npm]);
20282
20561
  if (previous.enabled === false)
@@ -20598,10 +20877,476 @@ function assertUnder(root, path) {
20598
20877
  import { cpSync as cpSync8, existsSync as existsSync23, lstatSync as lstatSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync9, readFileSync as readFileSync22, readdirSync as readdirSync17, realpathSync as realpathSync3, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "node:fs";
20599
20878
  import { basename as basename5, dirname as dirname11, join as join28, resolve as resolve17 } from "node:path";
20600
20879
  import { tmpdir as tmpdir7 } from "node:os";
20880
+ import { spawn as spawnChild } from "node:child_process";
20601
20881
  var MARKER7 = ".plgnz-install.json";
20602
20882
  var MANIFESTS = ["plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json"];
20603
20883
  var READBACK_FAULT = ".plgnz-dcode-readback-fault";
20884
+ var NATIVE_LOCK_SCRIPT = `import sys
20885
+ from importlib.metadata import version
20886
+ from filelock import FileLock
20887
+ if version('deepagents-code') != '0.1.83':
20888
+ raise RuntimeError('unverified dcode lock runtime')
20889
+ with FileLock(sys.argv[1]).acquire(timeout=10):
20890
+ print('DCODE_LOCK_0.1.83', flush=True)
20891
+ sys.stdin.read()
20892
+ `;
20893
+ var nativeLocks = new Map;
20894
+ var recoveringNativeLocks = new Map;
20895
+ function nativePython() {
20896
+ const override = process.env["OPEN_PLUGIN_DCODE_PYTHON_BIN"];
20897
+ if (override) {
20898
+ assertIdentity2(basename5(override), "Python binary");
20899
+ return override;
20900
+ }
20901
+ const binary = dcodeBinary();
20902
+ if (binary === undefined)
20903
+ throw new Error("dcode native lock runtime is unavailable");
20904
+ const shebang = readFileSync22(binary, "utf8").split(/\r?\n/, 1)[0] ?? "";
20905
+ const match = /^#!(\/[^\s]+python(?:\d+(?:\.\d+)?)?)$/u.exec(shebang);
20906
+ if (match?.[1] === undefined || !existsSync23(match[1]))
20907
+ throw new Error("dcode native Python interpreter is unverified; set OPEN_PLUGIN_DCODE_PYTHON_BIN to its verified interpreter");
20908
+ return match[1];
20909
+ }
20910
+ function probeLifecycleVersion() {
20911
+ const observation = probeDcodeVersion();
20912
+ if (observation.kind !== "detected" || observation.version !== "0.1.83")
20913
+ return observation;
20914
+ const result = spawnSync([nativePython(), "-c", "from importlib.metadata import version; import filelock; assert version('deepagents-code') == '0.1.83'; print('DCODE_LOCK_0.1.83')"], { stdout: "pipe", stderr: "pipe", timeout: 1e4 });
20915
+ if (result.exitCode !== 0 || new TextDecoder().decode(result.stdout).trim() !== "DCODE_LOCK_0.1.83")
20916
+ throw new Error(`dcode native lock runtime is unverified: ${new TextDecoder().decode(result.stderr).trim()}`);
20917
+ return observation;
20918
+ }
20919
+ async function acquireNativeLock(attemptId, operationId) {
20920
+ const key = `${attemptId}:${operationId}`;
20921
+ const existing = nativeLocks.get(key);
20922
+ if (existing !== undefined) {
20923
+ if (!existing.isAlive())
20924
+ throw new Error("dcode native lock was lost before verification");
20925
+ return;
20926
+ }
20927
+ const file = join28(dcodeRoot(), "plugins", ".mutation.lock");
20928
+ assertManagedPath4(file);
20929
+ mkdirSync9(dirname11(file), { recursive: true });
20930
+ const child = spawnChild(nativePython(), ["-c", NATIVE_LOCK_SCRIPT, file], { env: { ...process.env, HOME: process.env["OPEN_PLUGIN_HOME"] || process.env["HOME"], DEEPAGENTS_HOME: dcodeRoot() }, stdio: ["pipe", "pipe", "pipe"] });
20931
+ let alive = true;
20932
+ const exited = new Promise((resolveExit) => child.on("exit", (code) => {
20933
+ alive = false;
20934
+ resolveExit(code);
20935
+ }));
20936
+ await new Promise((resolveLock, rejectLock) => {
20937
+ let output = "", diagnostic = "", settled = false;
20938
+ const finish = (error) => {
20939
+ if (settled)
20940
+ return;
20941
+ settled = true;
20942
+ clearTimeout(timer);
20943
+ if (error) {
20944
+ child.kill("SIGKILL");
20945
+ rejectLock(error);
20946
+ } else
20947
+ resolveLock();
20948
+ };
20949
+ const timer = setTimeout(() => finish(new Error("dcode native lock acquisition timed out")), 12000);
20950
+ child.stdout.on("data", (data) => {
20951
+ output += new TextDecoder().decode(data);
20952
+ if (output.includes(`DCODE_LOCK_0.1.83
20953
+ `))
20954
+ finish();
20955
+ });
20956
+ child.stderr.on("data", (data) => {
20957
+ diagnostic += new TextDecoder().decode(data);
20958
+ });
20959
+ child.on("error", (error) => finish(new Error(`dcode native lock failed: ${error.message}`)));
20960
+ child.on("exit", (code) => finish(new Error(`dcode native lock failed (${code}): ${diagnostic.trim()}`)));
20961
+ });
20962
+ nativeLocks.set(key, { child, exited, isAlive: () => alive });
20963
+ }
20964
+ async function releaseNativeLock(attemptId, operationId) {
20965
+ const key = `${attemptId}:${operationId}`, held = nativeLocks.get(key);
20966
+ recoveringNativeLocks.delete(key);
20967
+ if (held === undefined)
20968
+ return;
20969
+ nativeLocks.delete(key);
20970
+ held.child.stdin.end();
20971
+ let timer;
20972
+ try {
20973
+ const code = await Promise.race([held.exited, new Promise((_, reject) => {
20974
+ timer = setTimeout(() => {
20975
+ held.child.kill("SIGKILL");
20976
+ reject(new Error("dcode native lock release timed out"));
20977
+ }, 2000);
20978
+ })]);
20979
+ if (code !== 0)
20980
+ throw new Error(`dcode native lock process failed during mutation (${code})`);
20981
+ } finally {
20982
+ if (timer !== undefined)
20983
+ clearTimeout(timer);
20984
+ }
20985
+ }
20986
+ function lifecycleDirectory(attemptId, operationId) {
20987
+ const hash = new CryptoHasher("sha256");
20988
+ hash.update(`${attemptId}\x00${operationId}`);
20989
+ const dir = join28(dcodeCacheRoot(), "plgnz-operations", hash.digest("hex"));
20990
+ assertCachePath(dir);
20991
+ return dir;
20992
+ }
20993
+ function lifecycleSlot(nativeId, fingerprint) {
20994
+ return managedSlot(nativeId, fingerprint);
20995
+ }
20996
+ function observeDcodeTarget(target) {
20997
+ if (target.kind !== "dcode" || target.instance !== "default")
20998
+ throw new Error("unsupported dcode target identity");
20999
+ const registry = readRegistry3(dcodeRegistryFile());
21000
+ const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
21001
+ return Object.entries(object4(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
21002
+ const rows = rowsFor(raw);
21003
+ if (rows.length !== 1)
21004
+ throw new Error(`dcode install record ${nativeId} has duplicate rows`);
21005
+ const path = rowInstall(rows[0], nativeId);
21006
+ assertCachePath(path);
21007
+ const present = existsSync23(path);
21008
+ const marker = present ? ownership3(path) : null;
21009
+ const body = present ? readDoc2(join28(path, MARKER7), {}, "ownership marker") : {};
21010
+ const owned = marker?.pluginId === nativeId;
21011
+ const fingerprint = present ? fingerprintTree(path) : null;
21012
+ return {
21013
+ nativeId,
21014
+ packageName: nativeId.split("@")[0],
21015
+ ownership: owned ? { kind: "owned", proof: body.proof === "adopted" ? "adopted" : "created", scopeId: typeof body.scopeId === "string" ? body.scopeId : "dcode:default", proofId: `dcode-marker:${nativeId}` } : { kind: "unmanaged" },
21016
+ presence: present ? "present" : "absent",
21017
+ enablement: enabled[nativeId] === true ? "enabled" : "disabled",
21018
+ activation: enabled[nativeId] === true && present ? "active" : "inactive",
21019
+ installedFingerprint: fingerprint,
21020
+ installedVersion: present ? declaredManifest(path, nativeId.split("@")[0]).version : null,
21021
+ source: owned ? { type: body.sourceType === "git" ? "git" : "local", immutableRevision: marker.kind === "current" ? marker.sourceRevision : marker.fingerprint, locator: body.sourceType === "git" ? marker.source : null } : null,
21022
+ contentRoots: fingerprint === null ? [] : [{ label: "managed", path, fingerprint }]
21023
+ };
21024
+ });
21025
+ }
21026
+ function dcodeLifecycleReadback(binding) {
21027
+ if (existsSync23(join28(dcodeStateDir(), READBACK_FAULT)))
21028
+ throw new Error(`dcode readback failed for ${binding.nativeId}`);
21029
+ const installations = observeDcodeTarget(binding.target);
21030
+ const row = installations.find((item) => item.nativeId === binding.nativeId);
21031
+ const present = row?.presence === "present";
21032
+ const enabled = row?.enablement === "enabled" && present;
21033
+ const dataRoot = join28(dcodeRoot(), "plugins", "data");
21034
+ assertManagedPath4(dataRoot);
21035
+ return {
21036
+ adapterId: binding.adapterId,
21037
+ target: binding.target,
21038
+ scopeId: binding.scopeId,
21039
+ packageName: binding.packageName,
21040
+ nativeId: binding.nativeId,
21041
+ route: binding.route ?? (present ? "managed" : "none"),
21042
+ presence: present ? "present" : "absent",
21043
+ enablement: enabled ? "enabled" : "disabled",
21044
+ activation: enabled ? "active" : "inactive",
21045
+ transition: { requirement: present ? "restart" : "none", status: "effective" },
21046
+ installedFingerprint: present ? row.installedFingerprint : null,
21047
+ contentRoots: present ? row.contentRoots : [],
21048
+ retention: { pluginData: existsSync23(dataRoot) ? { state: "present", fingerprint: fingerprintTree(dataRoot) } : { state: "absent", fingerprint: null }, inactiveMetadata: { state: "absent", fingerprint: null } }
21049
+ };
21050
+ }
21051
+ function captureDcodeRollback(binding, attemptId, operationId) {
21052
+ const dir = join28(lifecycleDirectory(attemptId, operationId), "rollback");
21053
+ mkdirSync9(dir, { recursive: true });
21054
+ const registry = readRegistry3(dcodeRegistryFile());
21055
+ const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
21056
+ const row = object4(registry.plugins, "registry plugins")[binding.nativeId];
21057
+ const state = { row: row ?? null, enabled: enabled[binding.nativeId] ?? null, registryExisted: existsSync23(dcodeRegistryFile()), enablementExisted: existsSync23(dcodeEnablementFile()) };
21058
+ writeFileSync9(join28(dir, "state.json"), JSON.stringify(state));
21059
+ for (const [index, item] of rowsFor(row).entries()) {
21060
+ const path = rowInstall(item, binding.nativeId);
21061
+ assertCachePath(path);
21062
+ assertNoSymlinks6(path);
21063
+ if (existsSync23(path))
21064
+ cpSync8(path, join28(dir, `content-${index}`), { recursive: true });
21065
+ }
21066
+ return dir;
21067
+ }
21068
+ function assertRecoveryRollbackMetadata(handle, state) {
21069
+ const rows = rowsFor(state.row);
21070
+ if (rows.length !== handle.prior.contentRoots.length || rows.some((row, index) => resolve17(rowInstall(row, handle.nativeId)) !== resolve17(handle.prior.contentRoots[index].path)) || handle.prior.presence === "present" && state.enabled === true !== (handle.prior.enablement === "enabled"))
21071
+ throw new Error("dcode rollback registry snapshot differs from the sealed prior; recovery refused");
21072
+ }
21073
+ async function lockPreparation(binding, attemptId, operationId, expectedObservationId) {
21074
+ await acquireNativeLock(attemptId, operationId);
21075
+ const observed = createTargetInventoryObservation("dcode", { target: binding.target, installations: observeDcodeTarget(binding.target) });
21076
+ if (observed.observationId !== expectedObservationId)
21077
+ throw new Error("dcode native inventory changed before locked preparation; refusing stale rollback authority");
21078
+ }
21079
+ async function captureOwnedPreparation(binding, attemptId, operationId, expectedObservationId, capture) {
21080
+ try {
21081
+ await lockPreparation(binding, attemptId, operationId, expectedObservationId);
21082
+ return capture();
21083
+ } catch (error) {
21084
+ try {
21085
+ await releaseNativeLock(attemptId, operationId);
21086
+ } catch (releaseError) {
21087
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; dcode preparation lock release failed: ${releaseError instanceof Error ? releaseError.message : String(releaseError)}`);
21088
+ }
21089
+ throw error;
21090
+ }
21091
+ }
21092
+ function preflightAdoption(request) {
21093
+ if (request.operation !== "install" && request.operation !== "update")
21094
+ return;
21095
+ const snapshot = request.snapshot;
21096
+ const installed = request.targetObservation.installations.find((item) => item.nativeId === snapshot.nativeId);
21097
+ if (installed?.ownership.kind !== "unmanaged" || installed.presence !== "present")
21098
+ return;
21099
+ const path = installed.contentRoots[0]?.path;
21100
+ if (path === undefined)
21101
+ throw new Error("dcode unmanaged adoption has no native root");
21102
+ const manifest = declaredManifest(snapshot.packageRoot, snapshot.packageName);
21103
+ assessPrior(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[snapshot.nativeId], snapshot.nativeId, snapshot.nativeGit?.locator ?? `local:${snapshot.scopeId}`, true, snapshot.packageName, manifest.version, snapshot.packageRoot);
21104
+ if (fingerprintTree(path) !== installed.installedFingerprint)
21105
+ throw new Error("dcode unmanaged adoption changed during preflight");
21106
+ }
21107
+ function setDcodeRow(nativeId, row, enabled) {
21108
+ assertManagedPath4(dcodeRegistryFile());
21109
+ assertManagedPath4(dcodeEnablementFile());
21110
+ const registry = readRegistry3(dcodeRegistryFile()), enablement = readEnablement(dcodeEnablementFile());
21111
+ const plugins = { ...object4(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
21112
+ if (row === null)
21113
+ delete plugins[nativeId];
21114
+ else
21115
+ plugins[nativeId] = row;
21116
+ if (enabled === null)
21117
+ delete flags[nativeId];
21118
+ else
21119
+ flags[nativeId] = enabled;
21120
+ writeDoc2(dcodeRegistryFile(), { ...registry, plugins });
21121
+ writeDoc2(dcodeEnablementFile(), { ...enablement, enabledPlugins: flags });
21122
+ }
21123
+ var dcodeDefinition = {
21124
+ id: "dcode",
21125
+ evidenceProfiles: capabilityEvidenceProfiles.filter((profile) => profile.host === "dcode").flatMap((profile) => [profile, createCapabilityEvidenceProfile({ ...profile, operations: ["disable"], semantics: { ...profile.semantics, "reversible-disable": "supported" }, evidence: [...profile.evidence, "docs/evidence/dcode-managed-lifecycle-20261011.md"] })]),
21126
+ probeVersion: async () => probeLifecycleVersion(),
21127
+ observeTarget: async (target) => ({ target, installations: observeDcodeTarget(target) }),
21128
+ observeNativeMutationScope: async () => ({ kind: "unavailable" }),
21129
+ observeNativeProjection: async (request) => {
21130
+ preflightAdoption(request);
21131
+ return { kind: "requires-managed", reasonId: "dcode-frozen-projection-and-updater-isolation" };
21132
+ },
21133
+ revalidateTargetPrecondition: async (handle) => {
21134
+ await acquireNativeLock(handle.attemptId, handle.operationId);
21135
+ return { version: probeDcodeVersion(), targetObservationId: createTargetInventoryObservation("dcode", { target: handle.target, installations: observeDcodeTarget(handle.target) }).observationId };
21136
+ },
21137
+ stageActivation: async (request) => {
21138
+ const dir = lifecycleDirectory(request.snapshot.attemptId, request.snapshot.operationId);
21139
+ const stagingRoot = join28(dir, "stage");
21140
+ if (existsSync23(stagingRoot))
21141
+ throw new Error("dcode preparation stage already exists");
21142
+ mkdirSync9(stagingRoot, { recursive: true });
21143
+ const manifest = declaredManifest(request.snapshot.packageRoot, request.snapshot.packageName);
21144
+ try {
21145
+ stagePlugin5(request.snapshot.packageRoot, stagingRoot, request.snapshot.packageName, manifest.version);
21146
+ } catch (error) {
21147
+ rmSync9(dir, { recursive: true, force: true });
21148
+ throw error;
21149
+ }
21150
+ return { stagingRoot, stagingId: `${request.snapshot.attemptId}:${request.snapshot.operationId}` };
21151
+ },
21152
+ applyLifecycleDirectives: async (projection) => {
21153
+ forceAutoUpdateOff(projection.stagingRoot);
21154
+ const prior = observeDcodeTarget(projection.target).find((item) => item.nativeId === projection.nativeId);
21155
+ writeFileSync9(join28(projection.stagingRoot, MARKER7), JSON.stringify({ source: projection.sourceLocator ?? `local:${projection.scopeId}`, pluginId: projection.nativeId, fingerprint: projection.sourceRevision, sourceRevision: projection.sourceRevision, projectedFingerprint: projectionDigest(projection.stagingRoot), scopeId: projection.scopeId, sourceType: projection.sourceType, proof: prior?.ownership.kind === "unmanaged" ? "adopted" : "created" }));
21156
+ return ["dcode.managed-unversioned-registry"];
21157
+ },
21158
+ applyPins: async (projection) => {
21159
+ const found = new Set;
21160
+ for (const candidate of dcodeMcpCandidates()) {
21161
+ const file = join28(projection.stagingRoot, candidate.kind === "spec" ? candidate.file : candidate.manifest);
21162
+ if (!existsSync23(file))
21163
+ continue;
21164
+ const body = readDoc2(file, {}, "MCP document");
21165
+ if (typeof body.mcpServers !== "object" || body.mcpServers === null || Array.isArray(body.mcpServers))
21166
+ continue;
21167
+ let changed = false;
21168
+ for (const pin of projection.pins) {
21169
+ const server = body.mcpServers[pin.server];
21170
+ if (typeof server !== "object" || server === null || Array.isArray(server))
21171
+ continue;
21172
+ server.command = pin.executable;
21173
+ found.add(pin.server);
21174
+ changed = true;
21175
+ }
21176
+ if (changed)
21177
+ writeFileSync9(file, JSON.stringify(body, null, 2));
21178
+ }
21179
+ if (projection.pins.some((pin) => !found.has(pin.server)))
21180
+ throw new Error("dcode resolved pin is absent from staged MCP configuration");
21181
+ const marker = readDoc2(join28(projection.stagingRoot, MARKER7), {}, "ownership marker");
21182
+ marker.projectedFingerprint = projectionDigest(projection.stagingRoot);
21183
+ writeFileSync9(join28(projection.stagingRoot, MARKER7), JSON.stringify(marker));
21184
+ return [...found];
21185
+ },
21186
+ captureActivationPreparation: async (projection, fingerprint) => {
21187
+ await lockPreparation(projection, projection.attemptId, projection.operationId, projection.targetObservationId);
21188
+ const prior = dcodeLifecycleReadback(projection);
21189
+ const target = lifecycleSlot(projection.nativeId, fingerprint);
21190
+ assertCachePath(target);
21191
+ const installed = observeDcodeTarget(projection.target).find((item) => item.nativeId === projection.nativeId);
21192
+ if (installed?.ownership.kind === "owned" && installed.ownership.scopeId !== projection.scopeId)
21193
+ throw new Error("dcode scope ownership conflict");
21194
+ if (installed?.ownership.kind === "unmanaged") {
21195
+ const path = installed.contentRoots[0]?.path;
21196
+ if (path === undefined || !sameTree7(projection.snapshot.packageRoot, path))
21197
+ throw new Error("dcode legacy content differs; refusing adoption");
21198
+ const manifest = declaredManifest(path, projection.packageName);
21199
+ if (manifest.version !== projection.packageVersion)
21200
+ throw new Error("dcode legacy version differs; refusing adoption");
21201
+ }
21202
+ if (existsSync23(target) && ownership3(target)?.pluginId !== projection.nativeId)
21203
+ throw new Error("dcode lifecycle target is not owned");
21204
+ const rollbackReference = captureDcodeRollback(projection, projection.attemptId, projection.operationId);
21205
+ writeFileSync9(join28(rollbackReference, "next-path.json"), JSON.stringify({ target }));
21206
+ return { prior, expected: { ...prior, route: "managed", presence: "present", enablement: "enabled", activation: "active", transition: { requirement: "restart", status: "effective" }, installedFingerprint: fingerprint, contentRoots: [{ label: "managed", path: target, fingerprint }] }, rollbackReference, rollbackCoverageOperationIds: projection.affectedOperationIds };
21207
+ },
21208
+ captureDisablePreparation: async (request) => {
21209
+ const binding = { adapterId: "dcode", ...request.activation };
21210
+ return captureOwnedPreparation(binding, request.attemptId, request.operationId, request.selection.targetObservationId, () => ({ prior: dcodeLifecycleReadback(binding), rollbackReference: captureDcodeRollback(binding, request.attemptId, request.operationId), rollbackCoverageOperationIds: request.selection.affectedOperationIds, transition: { requirement: "restart", status: "effective" } }));
21211
+ },
21212
+ captureRetirementPreparation: async (request) => {
21213
+ const binding = { adapterId: "dcode", ...request.activation };
21214
+ return captureOwnedPreparation(binding, request.attemptId, request.operationId, request.selection.targetObservationId, () => ({ prior: dcodeLifecycleReadback(binding), rollbackReference: captureDcodeRollback(binding, request.attemptId, request.operationId), rollbackCoverageOperationIds: request.selection.affectedOperationIds, transition: { requirement: "none", status: "effective" } }));
21215
+ },
21216
+ apply: async (prepared) => {
21217
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
21218
+ const target = lifecycleSlot(prepared.handle.nativeId, prepared.handle.projectedFingerprint);
21219
+ assertCachePath(target);
21220
+ mkdirSync9(dirname11(target), { recursive: true });
21221
+ if (existsSync23(target)) {
21222
+ const marker = ownership3(target);
21223
+ if (marker?.pluginId !== prepared.handle.nativeId)
21224
+ throw new Error("dcode lifecycle activation would overwrite foreign content");
21225
+ if (fingerprintTree(target) !== prepared.handle.projectedFingerprint)
21226
+ throw new Error("dcode immutable projection slot has diverged");
21227
+ } else {
21228
+ renameSync8(prepared.stagingRoot, target);
21229
+ }
21230
+ setDcodeRow(prepared.handle.nativeId, [{ installPath: target }], true);
21231
+ return { receiptId: `dcode-apply:${prepared.handle.operationId}`, changed: true };
21232
+ },
21233
+ disable: async (prepared) => {
21234
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
21235
+ const row = object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
21236
+ setDcodeRow(prepared.handle.nativeId, row ?? null, false);
21237
+ return { receiptId: `dcode-disable:${prepared.handle.operationId}`, changed: true };
21238
+ },
21239
+ retire: async (prepared) => {
21240
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
21241
+ const rows = rowsFor(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
21242
+ for (const row of rows) {
21243
+ const path = rowInstall(row, prepared.handle.nativeId);
21244
+ assertCachePath(path);
21245
+ if (ownership3(path)?.pluginId !== prepared.handle.nativeId)
21246
+ throw new Error("dcode retirement has no ownership proof");
21247
+ }
21248
+ setDcodeRow(prepared.handle.nativeId, null, null);
21249
+ for (const row of rows)
21250
+ rmSync9(rowInstall(row, prepared.handle.nativeId), { recursive: true, force: true });
21251
+ return { receiptId: `dcode-retire:${prepared.handle.operationId}`, changed: true };
21252
+ },
21253
+ readback: async (handle) => {
21254
+ const held = nativeLocks.get(`${handle.attemptId}:${handle.operationId}`);
21255
+ if (held !== undefined && !held.isAlive())
21256
+ throw new Error("dcode native lock was lost before readback");
21257
+ return dcodeLifecycleReadback(handle);
21258
+ },
21259
+ rollback: async (handle) => {
21260
+ await acquireNativeLock(handle.attemptId, handle.operationId);
21261
+ if (!existsSync23(join28(handle.rollbackReference, "state.json")))
21262
+ throw new Error("dcode rollback snapshot is missing; recovery refused");
21263
+ const state = readDoc2(join28(handle.rollbackReference, "state.json"), {}, "rollback state");
21264
+ if (recoveringNativeLocks.has(`${handle.attemptId}:${handle.operationId}`))
21265
+ assertRecoveryRollbackMetadata(handle, state);
21266
+ for (const [index, root] of handle.prior.contentRoots.entries()) {
21267
+ const backup = join28(handle.rollbackReference, `content-${index}`);
21268
+ if (!existsSync23(backup) || fingerprintTree(backup) !== root.fingerprint)
21269
+ throw new Error("dcode rollback snapshot bytes do not match the sealed prior");
21270
+ if (recoveringNativeLocks.has(`${handle.attemptId}:${handle.operationId}`) && existsSync23(root.path) && fingerprintTree(root.path) !== root.fingerprint)
21271
+ throw new Error("dcode prior root changed outside the sealed operation; refusing stale overwrite");
21272
+ }
21273
+ const target = handle.projectedFingerprint === null ? undefined : lifecycleSlot(handle.nativeId, handle.projectedFingerprint);
21274
+ if (target !== undefined)
21275
+ assertCachePath(target);
21276
+ if (target !== undefined && existsSync23(target) && !handle.prior.contentRoots.some((root) => resolve17(root.path) === resolve17(target))) {
21277
+ if (ownership3(target)?.pluginId !== handle.nativeId || recoveringNativeLocks.has(`${handle.attemptId}:${handle.operationId}`) && fingerprintTree(target) !== handle.projectedFingerprint)
21278
+ throw new Error("dcode rollback candidate differs from the sealed operation; refusing stale overwrite");
21279
+ rmSync9(target, { recursive: true });
21280
+ }
21281
+ for (const [index, row] of rowsFor(state.row).entries()) {
21282
+ const path = rowInstall(row, handle.nativeId);
21283
+ assertCachePath(path);
21284
+ const backup = join28(handle.rollbackReference, `content-${index}`);
21285
+ if (existsSync23(backup)) {
21286
+ mkdirSync9(dirname11(path), { recursive: true });
21287
+ cpSync8(backup, path, { recursive: true });
21288
+ }
21289
+ }
21290
+ setDcodeRow(handle.nativeId, state.row ?? null, typeof state.enabled === "boolean" ? state.enabled : null);
21291
+ if (state.registryExisted === false && Object.keys(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")).length === 0)
21292
+ rmSync9(dcodeRegistryFile());
21293
+ if (state.enablementExisted === false && Object.keys(boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins")).length === 0)
21294
+ rmSync9(dcodeEnablementFile());
21295
+ return { receiptId: `dcode-rollback:${handle.operationId}`, changed: true };
21296
+ },
21297
+ beginRecovery: async (handle) => {
21298
+ const reference = join28(lifecycleDirectory(handle.attemptId, handle.operationId), "rollback");
21299
+ if (resolve17(handle.rollbackReference) !== resolve17(reference))
21300
+ throw new Error("dcode recovery reference escapes the operation-owned artifact directory");
21301
+ assertManagedPath4(reference);
21302
+ for (const root of [...handle.prior.contentRoots, ...handle.expected.contentRoots])
21303
+ assertCachePath(root.path);
21304
+ await acquireNativeLock(handle.attemptId, handle.operationId);
21305
+ recoveringNativeLocks.set(`${handle.attemptId}:${handle.operationId}`, handle);
21306
+ },
21307
+ releaseRecovery: async (handle) => {
21308
+ await releaseNativeLock(handle.attemptId, handle.operationId);
21309
+ },
21310
+ cleanup: async (reference, disposition) => {
21311
+ try {
21312
+ const dir = lifecycleDirectory(reference.attemptId, reference.operationId);
21313
+ const recovering = recoveringNativeLocks.get(`${reference.attemptId}:${reference.operationId}`);
21314
+ const nextFile = join28(dir, "rollback", "next-path.json");
21315
+ const priorFile = join28(dir, "rollback", "state.json");
21316
+ const next = readDoc2(nextFile, {}, "cleanup target");
21317
+ if (disposition === "verified-commit" && recovering !== undefined) {
21318
+ const retiredRoots = recovering.prior.contentRoots.filter((root) => !recovering.expected.contentRoots.some((next) => resolve17(next.path) === resolve17(root.path)));
21319
+ if (existsSync23(priorFile))
21320
+ assertRecoveryRollbackMetadata(recovering, readDoc2(priorFile, {}, "rollback state"));
21321
+ else if (retiredRoots.some((root) => existsSync23(root.path)))
21322
+ throw new Error("dcode cleanup prior metadata is missing; recovery refused");
21323
+ if (existsSync23(nextFile) && (typeof next.target !== "string" || !recovering.expected.contentRoots.some((root) => resolve17(root.path) === resolve17(next.target))))
21324
+ throw new Error("dcode cleanup target differs from the sealed expected root; recovery refused");
21325
+ for (const root of retiredRoots) {
21326
+ if (existsSync23(root.path) && fingerprintTree(root.path) !== root.fingerprint)
21327
+ throw new Error("dcode cleanup prior root changed; refusing stale deletion");
21328
+ }
21329
+ for (const root of retiredRoots)
21330
+ rmSync9(root.path, { recursive: true, force: true });
21331
+ } else if (disposition === "verified-commit" && typeof next.target === "string") {
21332
+ const prior = readDoc2(priorFile, {}, "rollback state");
21333
+ for (const row of rowsFor(prior.row)) {
21334
+ const path = rowInstall(row, reference.operationId);
21335
+ assertCachePath(path);
21336
+ if (resolve17(path) !== resolve17(next.target))
21337
+ rmSync9(path, { recursive: true, force: true });
21338
+ }
21339
+ }
21340
+ rmSync9(dir, { recursive: true, force: true });
21341
+ } finally {
21342
+ await releaseNativeLock(reference.attemptId, reference.operationId);
21343
+ }
21344
+ return { cleanupId: `dcode-cleanup:${reference.operationId}`, completed: true };
21345
+ }
21346
+ };
21347
+ var dcodeLifecycle = createLifecycleHostAdapter(dcodeDefinition);
20604
21348
  var dcodeWriter = {
21349
+ lifecyclePinAdapter: dcodeLifecycle,
20605
21350
  ...dcode,
20606
21351
  supportsAdoption: true,
20607
21352
  plannedNativeId: (plugin) => `${plugin.name}@${plugin.marketplace || "local"}`,
@@ -20620,7 +21365,7 @@ var dcodeWriter = {
20620
21365
  assertManagedPath4(enablementFile);
20621
21366
  const registry = readRegistry3(registryFile);
20622
21367
  const enablement = readEnablement(enablementFile);
20623
- const plugins = object3(registry.plugins, "registry plugins");
21368
+ const plugins = object4(registry.plugins, "registry plugins");
20624
21369
  const enabled = boolObject(enablement.enabledPlugins, "enabledPlugins");
20625
21370
  const manifest = declaredManifest(plugin.dir, plugin.name);
20626
21371
  const observation = probeDcodeVersion();
@@ -20664,7 +21409,7 @@ var dcodeWriter = {
20664
21409
  throw new Error(`dcode cache target is unowned; refusing to replace it: ${target}`);
20665
21410
  const former = prior.kind === "none" ? undefined : prior.path;
20666
21411
  const unchanged = (former === undefined || resolve17(former) === resolve17(target)) && slot?.kind === "current" && slot.fingerprint === sourceFingerprint && slot.projectedFingerprint === projected && slot.sourceRevision === resolved.sha && sameTree7(stage, target);
20667
- const nextRegistry = { ...registry, plugins: { ...plugins, [id]: [{ installPath: target, version: manifest.version }] } };
21412
+ const nextRegistry = { ...registry, plugins: { ...plugins, [id]: [{ installPath: target }] } };
20668
21413
  const nextEnablement = { ...enablement, enabledPlugins: { ...enabled, [id]: true } };
20669
21414
  if (opts?.dryRun) {
20670
21415
  console.log(`[dcode] would activate directory: ${target}`);
@@ -20715,6 +21460,8 @@ var dcodeWriter = {
20715
21460
  assertCachePath(plugin.path);
20716
21461
  if (ownership3(plugin.path)?.pluginId !== plugin.id)
20717
21462
  return { changes: [], refusals: [] };
21463
+ if (opts?.dryRun !== true)
21464
+ throw new Error("dcode pin requires the durable lifecycle pin workflow; direct in-place pinning is refused");
20718
21465
  return pinPluginMcpFiles(plugin.path, dcodeMcpCandidates(), opts);
20719
21466
  },
20720
21467
  async remove(id) {
@@ -20726,7 +21473,7 @@ var dcodeWriter = {
20726
21473
  assertManagedPath4(enablementFile);
20727
21474
  const registry = readRegistry3(registryFile);
20728
21475
  const enablement = readEnablement(enablementFile);
20729
- const plugins = object3(registry.plugins, "registry plugins");
21476
+ const plugins = object4(registry.plugins, "registry plugins");
20730
21477
  const rows = rowsFor(plugins[id]);
20731
21478
  if (rows.length === 0)
20732
21479
  return;
@@ -20820,7 +21567,7 @@ function declaredManifest(dir, expectedName) {
20820
21567
  throw new Error("dcode stage has no supported plugin manifest");
20821
21568
  let parsed;
20822
21569
  try {
20823
- parsed = object3(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21570
+ parsed = object4(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
20824
21571
  } catch (error) {
20825
21572
  throw new Error(`invalid dcode manifest: ${manifest} (${error.message})`);
20826
21573
  }
@@ -20843,10 +21590,10 @@ function forceAutoUpdateOff(dir) {
20843
21590
  const manifest = MANIFESTS.map((relative) => join28(dir, relative)).find((file) => existsSync23(file));
20844
21591
  if (manifest === undefined)
20845
21592
  throw new Error("dcode stage has no supported plugin manifest");
20846
- const parsed = object3(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
20847
- const extensions = parsed.extensions === undefined ? {} : object3(parsed.extensions, "manifest extensions");
21593
+ const parsed = object4(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21594
+ const extensions = parsed.extensions === undefined ? {} : object4(parsed.extensions, "manifest extensions");
20848
21595
  const current = extensions["com.langchain.deepagents.code"];
20849
- const dcodeExtension = current === undefined ? {} : object3(current, "dcode manifest extension");
21596
+ const dcodeExtension = current === undefined ? {} : object4(current, "dcode manifest extension");
20850
21597
  dcodeExtension.autoUpdate = false;
20851
21598
  extensions["com.langchain.deepagents.code"] = dcodeExtension;
20852
21599
  parsed.extensions = extensions;
@@ -20858,15 +21605,17 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
20858
21605
  throw new Error(`dcode readback failed for ${id}`);
20859
21606
  const registry = readRegistry3(registryFile);
20860
21607
  const enablement = readEnablement(enablementFile);
20861
- const rows = rowsFor(object3(registry.plugins, "registry plugins")[id]);
21608
+ const rows = rowsFor(object4(registry.plugins, "registry plugins")[id]);
20862
21609
  if (rows.length !== 1)
20863
21610
  throw new Error(`dcode readback failed for ${id}`);
20864
21611
  const install = rowInstall(rows[0], id);
20865
21612
  if (resolve17(install) !== resolve17(target))
20866
21613
  throw new Error(`dcode readback path mismatch for ${id}`);
20867
- if (rows[0].version !== version)
21614
+ if (rows[0].version !== undefined && rows[0].version !== null)
21615
+ throw new Error(`dcode managed install is eligible for native auto-update: ${id}`);
21616
+ if (declaredManifest(target, id.split("@")[0]).version !== version)
20868
21617
  throw new Error(`dcode readback version mismatch for ${id}`);
20869
- if (boolObject(object3(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
21618
+ if (boolObject(object4(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
20870
21619
  throw new Error(`dcode readback enablement mismatch for ${id}`);
20871
21620
  const marker = ownership3(target);
20872
21621
  if (marker?.kind !== "current" || marker.projectedFingerprint !== projected || marker.sourceRevision !== sourceRevision) {
@@ -20946,7 +21695,7 @@ function readRegistry3(file) {
20946
21695
  const registry = readDoc2(file, { version: 2, plugins: {} }, "registry");
20947
21696
  if (registry.version !== 1 && registry.version !== 2)
20948
21697
  throw new Error(`invalid dcode registry version: ${file}`);
20949
- const plugins = object3(registry.plugins, "registry plugins");
21698
+ const plugins = object4(registry.plugins, "registry plugins");
20950
21699
  for (const [id, value] of Object.entries(plugins)) {
20951
21700
  if (!id || !Array.isArray(value) || value.length === 0)
20952
21701
  throw new Error(`invalid dcode registry record: ${id}`);
@@ -20965,13 +21714,13 @@ function readEnablement(file) {
20965
21714
  boolObject(enablement.enabledPlugins, "enabledPlugins");
20966
21715
  return enablement;
20967
21716
  }
20968
- function object3(value, label) {
21717
+ function object4(value, label) {
20969
21718
  if (typeof value !== "object" || value === null || Array.isArray(value))
20970
21719
  throw new Error(`invalid dcode ${label}`);
20971
21720
  return value;
20972
21721
  }
20973
21722
  function boolObject(value, label) {
20974
- const result = object3(value, label);
21723
+ const result = object4(value, label);
20975
21724
  if (Object.values(result).some((entry) => typeof entry !== "boolean"))
20976
21725
  throw new Error(`invalid dcode ${label}`);
20977
21726
  return result;
@@ -20994,7 +21743,7 @@ function ownership3(dir) {
20994
21743
  if (!existsSync23(file))
20995
21744
  return null;
20996
21745
  try {
20997
- const value = object3(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
21746
+ const value = object4(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
20998
21747
  if (typeof value.source !== "string" || typeof value.pluginId !== "string" || typeof value.fingerprint !== "string")
20999
21748
  throw new Error("marker fields are invalid");
21000
21749
  const projected = value.projectedFingerprint;
@@ -21508,7 +22257,7 @@ import { cpSync as cpSync10, existsSync as existsSync25, lstatSync as lstatSync1
21508
22257
  import { dirname as dirname13, join as join30, resolve as resolve19 } from "node:path";
21509
22258
  import { tmpdir as tmpdir9 } from "node:os";
21510
22259
  var stableId = (plugin) => plugin.marketplace === undefined ? plugin.name : `${plugin.name}@${plugin.marketplace}`;
21511
- var digest2 = (value) => {
22260
+ var digest3 = (value) => {
21512
22261
  const hash = new CryptoHasher("sha256");
21513
22262
  hash.update(value);
21514
22263
  return hash.digest("hex").slice(0, 16);
@@ -21529,7 +22278,7 @@ var grokWriter = {
21529
22278
  if (!registryIsReadable())
21530
22279
  throw new Error("Grok native registry is unreadable or unsupported; refusing mutation");
21531
22280
  const id = stableId(plugin);
21532
- const root = join30(marketplacesRoot(), `plgnz-${digest2(`${resolved.sourceUri}\x00${id}`)}`);
22281
+ const root = join30(marketplacesRoot(), `plgnz-${digest3(`${resolved.sourceUri}\x00${id}`)}`);
21533
22282
  const parent = dirname13(root);
21534
22283
  const prior = canonical(root);
21535
22284
  const priorMarker = prior === undefined ? null : ownership(prior);
@@ -22016,7 +22765,7 @@ function probeGrokVersion() {
22016
22765
  return parseGrokVersionOutput(result.stdout ?? "", result.status);
22017
22766
  }
22018
22767
  function lifecycleMarketplace(scopeId, nativeId) {
22019
- return resolve19(join30(marketplacesRoot(), `plgnz-${digest2(`${scopeId}\x00${nativeId}`)}`));
22768
+ return resolve19(join30(marketplacesRoot(), `plgnz-${digest3(`${scopeId}\x00${nativeId}`)}`));
22020
22769
  }
22021
22770
  function marketplacePluginPath(scopeId, nativeId, packageName) {
22022
22771
  return resolve19(join30(lifecycleMarketplace(scopeId, nativeId), "plugins", packageName));
@@ -24817,7 +25566,7 @@ class SyncManifestValidationError extends Error {
24817
25566
  }
24818
25567
  }
24819
25568
  function parseSyncManifest(value) {
24820
- const manifest = object4(value, "sync manifest");
25569
+ const manifest = object5(value, "sync manifest");
24821
25570
  exactFields5(manifest, ["schemaVersion", "entries"], "sync manifest");
24822
25571
  if (manifest["schemaVersion"] !== SYNC_MANIFEST_SCHEMA_VERSION)
24823
25572
  invalidArgument2("sync manifest schemaVersion must be 1");
@@ -24851,7 +25600,7 @@ function selectManifestPackages(entry, discovered) {
24851
25600
  });
24852
25601
  }
24853
25602
  function parseEntry(value, index) {
24854
- const entry = object4(value, `sync manifest entry ${index}`);
25603
+ const entry = object5(value, `sync manifest entry ${index}`);
24855
25604
  if (entry["operation"] === "sync") {
24856
25605
  exactFields5(entry, ["operation", "source", "target", "selectors"], `sync manifest entry ${index}`);
24857
25606
  const source = parseSourceBinding(entry["source"], `sync manifest entry ${index} source`);
@@ -24929,7 +25678,7 @@ function assertUniqueScopes(entries) {
24929
25678
  }
24930
25679
  }
24931
25680
  function parseSourceBinding(value, label) {
24932
- const binding = object4(value, label);
25681
+ const binding = object5(value, label);
24933
25682
  if (binding["kind"] === "local") {
24934
25683
  exactFields5(binding, ["kind", "locator"], label);
24935
25684
  if (typeof binding["locator"] !== "string")
@@ -24955,7 +25704,7 @@ function parseTarget(value, label) {
24955
25704
  }
24956
25705
  function parseSelector(value, entryIndex, selectorIndex) {
24957
25706
  const label = `sync manifest entry ${entryIndex} selector ${selectorIndex}`;
24958
- const selector = object4(value, label);
25707
+ const selector = object5(value, label);
24959
25708
  exactFields5(selector, ["package", "adoptExisting"], label);
24960
25709
  if (typeof selector["package"] !== "string" || !/^[a-z0-9][a-z0-9._-]*$/iu.test(selector["package"])) {
24961
25710
  invalidArgument2(`${label} package must be a valid package name`);
@@ -24973,7 +25722,7 @@ function validateBinding(source, label) {
24973
25722
  }
24974
25723
  }
24975
25724
  function targetProfile(value, label) {
24976
- const target = object4(value, label);
25725
+ const target = object5(value, label);
24977
25726
  if (typeof target["kind"] !== "string")
24978
25727
  invalidArgument2(`${label} kind must be a string`);
24979
25728
  return targetProfileForKind(target["kind"], label);
@@ -24998,7 +25747,7 @@ function invokeTargetProfile(operation) {
24998
25747
  throw error;
24999
25748
  }
25000
25749
  }
25001
- function object4(value, label) {
25750
+ function object5(value, label) {
25002
25751
  if (typeof value !== "object" || value === null || Array.isArray(value))
25003
25752
  invalidArgument2(`${label} must be an object`);
25004
25753
  return value;
@@ -25029,22 +25778,87 @@ async function planLifecycle(input) {
25029
25778
  if (duplicate !== null)
25030
25779
  return zeroWrite(command, input.dryRun, duplicate);
25031
25780
  const prepared = [];
25032
- const transportCache = new Map;
25781
+ const transportCache = input.retainedSources ?? new Map;
25782
+ const planningState = input.recoveryPreflight ? { ...loaded.state, activations: loaded.state.activations.map((row) => {
25783
+ if (row.pending === undefined || !loaded.state.attempts.some((attempt) => attempt.command !== "legacy-recovery" && attempt.id === row.pending.attemptId && attempt.journal.some((entry) => entry.scopeId === row.scopeId && entry.packageId === row.packageId && entry.nativeId === row.nativeId && (entry.sealedOperation !== undefined || entry.state === "pending"))))
25784
+ return row;
25785
+ const { pending: _pending, ...kept } = row;
25786
+ return kept;
25787
+ }) } : loaded.state;
25033
25788
  for (const entry of input.manifest.entries) {
25034
- const result = await prepareEntry(entry, input.hosts, loaded.state, command, input.transportSource, transportCache);
25789
+ const result = await prepareEntry(entry, input.hosts, planningState, command, input.transportSource, transportCache);
25035
25790
  if (result.kind === "defect")
25036
25791
  return zeroWrite(command, input.dryRun, result.reason, result.terminalPhase);
25037
- prepared.push(result);
25792
+ if (input.removalSelection !== undefined) {
25793
+ if (command !== "remove" || result.kind !== "retire-source") {
25794
+ return zeroWrite(command, input.dryRun, createLifecycleReason("internal", "internal.invariant", "package retirement selection is valid only for manual removal"));
25795
+ }
25796
+ const selected = input.removalSelection.filter((row) => row.scopeId === result.scope.id);
25797
+ if (selected.length === 0 || selected.some((row) => !loaded.state.activations.some((activation) => activation.scopeId === row.scopeId && activation.packageId === row.packageId && activation.nativeId === row.nativeId))) {
25798
+ return zeroWrite(command, input.dryRun, createLifecycleReason("internal", "internal.invariant", "manual removal selection no longer matches recorded ownership"));
25799
+ }
25800
+ const retirementPackages = selected.map(({ packageId, nativeId }) => ({ packageId, nativeId })).sort((left, right) => left.packageId.localeCompare(right.packageId) || left.nativeId.localeCompare(right.nativeId));
25801
+ prepared.push({
25802
+ ...result,
25803
+ retirementPackages,
25804
+ identity: ["remove-packages", result.scope.id, ...retirementPackages.flatMap((row) => [row.packageId, row.nativeId])]
25805
+ });
25806
+ } else
25807
+ prepared.push(result);
25038
25808
  }
25039
25809
  const attemptId = lifecycleAttemptId(loaded.state.stateGeneration, prepared.map((item) => item.identity));
25810
+ const recovering = input.recoveryPreflight && loaded.state.attempts.some((attempt) => attempt.command !== "legacy-recovery" && attempt.journal.some((row) => prepared.some((item) => item.scope.id === row.scopeId) && ["pending", "applying", "applied", "readback-verified", "rollback", "cleanup-pending"].includes(row.state)));
25040
25811
  const scopes = [];
25041
25812
  const operations = [];
25042
25813
  for (const item of prepared) {
25043
- const planned = await planPrepared(item, attemptId, input.validatedAt, loaded.state, command);
25814
+ const pendingJournals = recovering ? loaded.state.attempts.filter((attempt) => attempt.command !== "legacy-recovery").flatMap((attempt) => attempt.journal.filter((row) => row.scopeId === item.scope.id && ["pending", "applying", "applied", "readback-verified", "rollback", "cleanup-pending"].includes(row.state))) : [];
25815
+ const pendingNativeIds = new Set(pendingJournals.map((row) => row.nativeId));
25816
+ const transitional = [];
25817
+ const transitionalDesired = [];
25818
+ const blockedReason = createLifecycleReason("runtime", "runtime.operation-failed", "not attempted before pending lifecycle recovery settles");
25819
+ let classifiable = item;
25820
+ if (item.kind === "sync" && pendingNativeIds.size > 0) {
25821
+ const selectedPackages = [];
25822
+ for (const selected of item.selected) {
25823
+ const nativeId = plannedNativeIdFor(item, selected.plugin);
25824
+ if (nativeId instanceof Error)
25825
+ return zeroWrite(command, input.dryRun, createLifecycleReason("internal", "internal.defect", unknownErrorDiagnostic(nativeId)));
25826
+ if (!pendingNativeIds.has(nativeId)) {
25827
+ selectedPackages.push(selected);
25828
+ continue;
25829
+ }
25830
+ try {
25831
+ const inventory = inventoryPackageSemantics(selected.plugin);
25832
+ transitionalDesired.push({
25833
+ packageId: selected.plugin.name,
25834
+ nativeId,
25835
+ sourceRelativeDir: selected.plugin.relativeDir || ".",
25836
+ requiredCapabilities: [...requiredSemanticsForOperation(inventory, "install")].sort(),
25837
+ adoptionRequested: selected.adoptionRequested
25838
+ });
25839
+ } catch (error) {
25840
+ return zeroWrite(command, input.dryRun, error instanceof SemanticInventoryError ? error.reason : createLifecycleReason("internal", "internal.defect", unknownErrorDiagnostic(error)));
25841
+ }
25842
+ transitional.push(classified(operationIdentity("desired-pair", item.scope.id, selected.plugin.name, nativeId, `${item.frozen.snapshot.fingerprint}\x00${attemptId}`), "desired-pair", item.scope, sourceSnapshotId(item.frozen), selected.plugin.name, nativeId, "not-attempted", "none", blockedReason).operation);
25843
+ }
25844
+ classifiable = { ...item, selected: selectedPackages };
25845
+ }
25846
+ const classifiableState = pendingNativeIds.size === 0 ? loaded.state : { ...loaded.state, activations: loaded.state.activations.filter((row) => row.scopeId !== item.scope.id || !pendingNativeIds.has(row.nativeId)) };
25847
+ const planned = await planPrepared(classifiable, attemptId, input.validatedAt, classifiableState, command);
25044
25848
  if (planned.kind === "defect")
25045
25849
  return zeroWrite(command, input.dryRun, planned.reason, planned.terminalPhase);
25046
- scopes.push(planned.scope);
25047
- operations.push(...planned.operations);
25850
+ for (const journal of pendingJournals) {
25851
+ if (journal.packageId === undefined || journal.nativeId === undefined || transitional.some((row) => row.operation.nativeId === journal.nativeId))
25852
+ continue;
25853
+ if (item.kind === "retire-source" && (item.retirementPackages === undefined || item.retirementPackages.some((row) => row.nativeId === journal.nativeId)) || item.kind === "sync" && command === "sync" && loaded.state.activations.some((row) => row.scopeId === item.scope.id && row.nativeId === journal.nativeId)) {
25854
+ transitional.push(classified(operationIdentity("retirement", item.scope.id, journal.packageId, journal.nativeId, attemptId), "retirement", item.scope, item.kind === "sync" ? sourceSnapshotId(item.frozen) : null, journal.packageId, journal.nativeId, "not-attempted", "none", blockedReason).operation);
25855
+ }
25856
+ }
25857
+ scopes.push(item.kind === "sync" && transitionalDesired.length > 0 ? {
25858
+ ...planned.scope,
25859
+ desired: desiredGeneration(loaded.state.scopes.find((row) => row.id === item.scope.id), item.frozen.snapshot.revision, item.frozen.snapshot.fingerprint, item.selected.map((selected) => [...planned.scope.desired.packages, ...transitionalDesired].find((row) => row.packageId === selected.plugin.name)), input.validatedAt)
25860
+ } : planned.scope);
25861
+ operations.push(...planned.operations, ...transitional);
25048
25862
  }
25049
25863
  return frozenPlan(input, command, attemptId, scopes, operations, prepared, loaded.state);
25050
25864
  }
@@ -25080,6 +25894,10 @@ async function prepareSync(entry, hosts, state, command, transportSource, transp
25080
25894
  throw error;
25081
25895
  }
25082
25896
  const scope = deploymentScopeForSyncManifestEntry(entry);
25897
+ const recordedScope = state.scopes.find((row) => row.id === scope.id);
25898
+ if (recordedScope?.target.context !== undefined && JSON.stringify(canonicalTargetContext(recordedScope.target.context)) !== JSON.stringify(entry.target.context === undefined ? undefined : canonicalTargetContext(entry.target.context))) {
25899
+ return defect(createLifecycleReason("internal", "internal.invariant", `target context changed for recorded scope '${scope.id}'; use a distinct target instance or retire the recorded scope explicitly`));
25900
+ }
25083
25901
  const pending = pendingDefect(state, scope.id, new Set(selected.map((item) => item.plugin.name)), command);
25084
25902
  if (pending !== null)
25085
25903
  return defect(pending);
@@ -25229,6 +26047,7 @@ async function planSync(item, attemptId, validatedAt, state, command) {
25229
26047
  kind: "planned",
25230
26048
  scope: {
25231
26049
  scope: item.scope,
26050
+ target: item.observation?.target ?? item.entry.target,
25232
26051
  selectorMode: item.entry.selectors === undefined ? "all" : "explicit",
25233
26052
  desired: desiredGeneration(existing, item.frozen.snapshot.revision, item.frozen.snapshot.fingerprint, packages, validatedAt),
25234
26053
  prune: blocks || retirements.operations.some((row) => row.operation.action !== "retire-orphan") ? "blocked" : "planned"
@@ -25308,7 +26127,7 @@ async function planOmissions(item, attemptId, state, desired) {
25308
26127
  return { kind: "omissions", operations };
25309
26128
  }
25310
26129
  async function planRetire(item, attemptId, state) {
25311
- const activations = state.activations.filter((activation) => activation.scopeId === item.recorded.id).sort((left, right) => left.packageId < right.packageId ? -1 : left.packageId > right.packageId ? 1 : 0);
26130
+ const activations = state.activations.filter((activation) => activation.scopeId === item.recorded.id && (item.retirementPackages === undefined || item.retirementPackages.some((row) => row.packageId === activation.packageId && row.nativeId === activation.nativeId))).sort((left, right) => left.packageId < right.packageId ? -1 : left.packageId > right.packageId ? 1 : 0);
25312
26131
  const operations = [];
25313
26132
  for (const activation of activations) {
25314
26133
  const installation = item.observation?.installations.find((row) => row.nativeId === activation.nativeId);
@@ -25328,8 +26147,10 @@ async function planRetire(item, attemptId, state) {
25328
26147
  kind: "planned",
25329
26148
  scope: {
25330
26149
  scope: item.scope,
26150
+ target: item.observation?.target ?? item.recorded.target,
25331
26151
  selectorMode: "retired",
25332
26152
  desired: null,
26153
+ ...item.retirementPackages === undefined ? {} : { retirementPackages: item.retirementPackages },
25333
26154
  prune: operations.length > 0 && operations.every((row) => row.operation.action === "retire-orphan") ? "planned" : "blocked"
25334
26155
  },
25335
26156
  operations
@@ -25816,7 +26637,7 @@ function lifecycleTarget(target) {
25816
26637
  }
25817
26638
  function sourceSnapshotId(frozen) {
25818
26639
  const binding = frozen.snapshot.binding;
25819
- return `snapshot-v1-${digest3([
26640
+ return `snapshot-v1-${digest4([
25820
26641
  binding.kind,
25821
26642
  binding.locator,
25822
26643
  binding.kind === "git" ? binding.ref : "",
@@ -25824,20 +26645,20 @@ function sourceSnapshotId(frozen) {
25824
26645
  ])}`;
25825
26646
  }
25826
26647
  function operationIdentity(coverage, scopeId, packageName, nativeId, fingerprint) {
25827
- return `operation-v1-${digest3([coverage, scopeId, packageName, nativeId, fingerprint])}`;
26648
+ return `operation-v1-${digest4([coverage, scopeId, packageName, nativeId, fingerprint])}`;
25828
26649
  }
25829
26650
  function lifecycleAttemptId(stateGeneration, identities) {
25830
- return `attempt-v1-${digest3([String(stateGeneration), ...identities.flat()])}`;
26651
+ return `attempt-v1-${digest4([String(stateGeneration), ...identities.flat()])}`;
25831
26652
  }
25832
26653
  function lifecycleAttemptIdForPlannedScopes(stateGeneration, scopes) {
25833
- return lifecycleAttemptId(stateGeneration, scopes.map((planned) => plannedAttemptIdentity(planned.scope.id, planned.desired?.sourceFingerprint, planned.desired?.packages.map((pkg) => pkg.packageId), planned.selectorMode === "retired" || planned.desired === null)));
26654
+ return lifecycleAttemptId(stateGeneration, scopes.map((planned) => planned.retirementPackages !== undefined ? ["remove-packages", planned.scope.id, ...planned.retirementPackages.flatMap((row) => [row.packageId, row.nativeId])] : plannedAttemptIdentity(planned.scope.id, planned.desired?.sourceFingerprint, planned.desired?.packages.map((pkg) => pkg.packageId), planned.selectorMode === "retired" || planned.desired === null)));
25834
26655
  }
25835
26656
  function plannedAttemptIdentity(scopeId, sourceFingerprint, packageIds, retired = false) {
25836
26657
  if (retired || sourceFingerprint === undefined || packageIds === undefined)
25837
26658
  return ["retire-source", scopeId];
25838
26659
  return ["sync", scopeId, sourceFingerprint, ...packageIds];
25839
26660
  }
25840
- function digest3(parts) {
26661
+ function digest4(parts) {
25841
26662
  const hash = new CryptoHasher("sha256");
25842
26663
  for (const part of parts) {
25843
26664
  hash.update(String(part.length));
@@ -26093,8 +26914,9 @@ async function runOperation(plan, operation, hosts, ledger) {
26093
26914
  return succeeded(operation, false);
26094
26915
  case "retire-orphan":
26095
26916
  return runRetire(plan, operation, hosts, ledger);
26096
- case "route-migrate":
26097
26917
  case "disable-nonconforming":
26918
+ return runDisable(plan, operation, hosts, ledger);
26919
+ case "route-migrate":
26098
26920
  case "retain-prior":
26099
26921
  case "not-attempted":
26100
26922
  return stop(operation, createLifecycleReason("internal", "internal.invariant", `operation '${operation.operationId}' is not applied by this execution slice`));
@@ -26151,12 +26973,19 @@ async function runActivation(plan, operation, hosts, ledger, action) {
26151
26973
  const sealed = await host.adapter.sealActivation(pinned);
26152
26974
  preparation = sealed;
26153
26975
  handle = sealed.handle;
26976
+ ledger.captureOperation(plan, operation, handle);
26154
26977
  ledger.markApplying(plan, operation);
26155
26978
  receipt = await host.adapter.apply(sealed);
26156
26979
  const observation = await host.adapter.readback(receipt.handle);
26157
26980
  verified = host.adapter.verify(receipt.handle, observation);
26158
26981
  } catch (error) {
26159
26982
  const reason = thrownReason(error);
26983
+ if (receipt === undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying" && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
26984
+ ledger.markNotApplied(plan, operation);
26985
+ if (preparation !== undefined)
26986
+ await abortPreparation(host, preparation);
26987
+ return stop(operation, reason);
26988
+ }
26160
26989
  if (handle !== undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying") {
26161
26990
  return recoverApplied(plan, operation, host, ledger, handle, reason);
26162
26991
  }
@@ -26230,7 +27059,7 @@ async function prepareActivation(plan, operation, host, nativeId, action, record
26230
27059
  if (plugin === undefined || packageFingerprint === undefined) {
26231
27060
  return refused(createLifecycleReason("internal", "internal.invariant", `${action} '${operation.package}' is missing from the frozen source`));
26232
27061
  }
26233
- const target = lifecycleTarget2(operation);
27062
+ const target = lifecycleTarget2(plan, operation);
26234
27063
  const observation = await host.adapter.observeTarget(target);
26235
27064
  const version = await host.adapter.probeVersion(observation.target);
26236
27065
  if (version.kind !== "detected") {
@@ -26314,6 +27143,51 @@ function sealActivationSnapshot(operation, attemptId, nativeId, frozen, plugin,
26314
27143
  throw new Error(`frozen snapshot action '${snapshot.action}' is not ${action}`);
26315
27144
  return snapshot;
26316
27145
  }
27146
+ async function runDisable(plan, operation, hosts, ledger) {
27147
+ const host = hosts.find((candidate) => candidate.kinds.includes(operation.scope.target.kind));
27148
+ const reason = plan.report.outcomes.find((row) => row.operationId === operation.operationId)?.reason;
27149
+ if (host === undefined || operation.nativeId === null || reason === undefined || reason === null) {
27150
+ return stop(operation, createLifecycleReason("internal", "internal.invariant", "disable containment lost its adapter, identity or desired failure reason"));
27151
+ }
27152
+ let preparation;
27153
+ let receipt;
27154
+ try {
27155
+ const prepared = await prepareOwnedMutation(plan, operation, host, operation.nativeId, ledger, "disable");
27156
+ if (prepared.kind === "refused")
27157
+ return stop(operation, prepared.reason);
27158
+ if (prepared.selection.operation !== "disable")
27159
+ throw new Error("disable containment selected the wrong operation");
27160
+ ledger.acceptJournal(plan, operation, "disable-nonconforming");
27161
+ preparation = await host.adapter.prepareDisable({ operationId: operation.operationId, attemptId: plan.attemptId, selection: { ...prepared.selection, operation: "disable" }, activation: prepared.activation });
27162
+ ledger.captureOperation(plan, operation, preparation.handle);
27163
+ ledger.markApplying(plan, operation);
27164
+ receipt = await host.adapter.disable(preparation);
27165
+ const observation = await host.adapter.readback(receipt.handle);
27166
+ const verified = host.adapter.verify(receipt.handle, observation);
27167
+ ledger.confirmDisable(plan, operation);
27168
+ try {
27169
+ await host.adapter.cleanup(verified.handle, "verified-commit");
27170
+ } catch {
27171
+ ledger.markCleanupPending(plan, operation, "disable-nonconforming");
27172
+ return pendingCleanup(operation);
27173
+ }
27174
+ ledger.completeContainment(plan, operation);
27175
+ return { stop: true, reason, outcome: { ...operation, result: "failed", resourceState: "retained", activationState: "inactive", changed: receipt.changed, reason } };
27176
+ } catch (error) {
27177
+ const failure = thrownReason(error);
27178
+ if (receipt === undefined && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
27179
+ if (ledger.journalState(plan.attemptId, operation.operationId) !== undefined)
27180
+ ledger.markNotApplied(plan, operation);
27181
+ if (preparation !== undefined)
27182
+ await abortPreparation(host, preparation);
27183
+ return stop(operation, failure);
27184
+ }
27185
+ if (preparation !== undefined && (receipt !== undefined || ledger.journalState(plan.attemptId, operation.operationId) === "applying")) {
27186
+ return recoverApplied(plan, operation, host, ledger, preparation.handle, failure);
27187
+ }
27188
+ return stop(operation, failure);
27189
+ }
27190
+ }
26317
27191
  async function runRetire(plan, operation, hosts, ledger) {
26318
27192
  if (ledger.journalState(plan.attemptId, operation.operationId) === "completed")
26319
27193
  return retired(operation, false);
@@ -26331,17 +27205,20 @@ async function runRetire(plan, operation, hosts, ledger) {
26331
27205
  }
26332
27206
  let prepared;
26333
27207
  try {
26334
- prepared = await prepareRetirement(plan, operation, host, nativeId, ledger);
27208
+ prepared = await prepareOwnedMutation(plan, operation, host, nativeId, ledger);
26335
27209
  } catch (error) {
26336
27210
  return stop(operation, thrownReason(error));
26337
27211
  }
26338
27212
  if (prepared.kind === "refused")
26339
27213
  return stop(operation, prepared.reason);
27214
+ if (prepared.selection.operation !== "retire")
27215
+ return stop(operation, createLifecycleReason("internal", "internal.invariant", "retirement selected the wrong operation"));
26340
27216
  try {
26341
27217
  ledger.acceptJournal(plan, operation, "retire-orphan");
26342
27218
  } catch (error) {
26343
27219
  return stop(operation, thrownReason(error));
26344
27220
  }
27221
+ let preparation;
26345
27222
  let receipt;
26346
27223
  let verified;
26347
27224
  try {
@@ -26349,17 +27226,30 @@ async function runRetire(plan, operation, hosts, ledger) {
26349
27226
  operationId: operation.operationId,
26350
27227
  attemptId: plan.attemptId,
26351
27228
  action: "retire-orphan",
26352
- selection: prepared.selection,
27229
+ selection: { ...prepared.selection, operation: "retire" },
26353
27230
  activation: prepared.activation
26354
27231
  });
27232
+ preparation = sealed;
27233
+ ledger.captureOperation(plan, operation, sealed.handle);
26355
27234
  ledger.markPruning(plan, operation);
26356
27235
  receipt = await host.adapter.retire(sealed);
26357
27236
  const observation = await host.adapter.readback(receipt.handle);
26358
27237
  verified = host.adapter.verify(receipt.handle, observation);
26359
27238
  } catch (error) {
26360
27239
  const reason = thrownReason(error);
27240
+ if (receipt === undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying" && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
27241
+ ledger.markNotApplied(plan, operation);
27242
+ if (preparation !== undefined)
27243
+ await abortPreparation(host, preparation);
27244
+ return stop(operation, reason);
27245
+ }
26361
27246
  if (receipt !== undefined)
26362
27247
  return recoverApplied(plan, operation, host, ledger, receipt.handle, reason);
27248
+ if (preparation !== undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying") {
27249
+ return recoverApplied(plan, operation, host, ledger, preparation.handle, reason);
27250
+ }
27251
+ if (preparation === undefined)
27252
+ ledger.markNotApplied(plan, operation);
26363
27253
  return stop(operation, reason);
26364
27254
  }
26365
27255
  if (receipt === undefined || verified === undefined) {
@@ -26379,12 +27269,13 @@ async function runRetire(plan, operation, hosts, ledger) {
26379
27269
  ledger.markCompleted(plan, operation);
26380
27270
  return retired(operation, receipt.changed);
26381
27271
  }
26382
- async function prepareRetirement(plan, operation, host, nativeId, ledger) {
27272
+ async function prepareOwnedMutation(plan, operation, host, nativeId, ledger, capabilityOperation = "retire") {
27273
+ const operationLabel = capabilityOperation === "disable" ? "disablement" : "retirement";
26383
27274
  const recorded = ledger.activation(operation.scope.id, operation.package, nativeId);
26384
27275
  if (recorded === undefined || recorded.route.kind === "legacy-unverified" || recorded.ownership.kind === "legacy-claim" || recorded.sourceRevision === undefined) {
26385
27276
  return refused(createLifecycleReason("internal", "internal.invariant", `ownership of '${operation.package}' was not revalidated`));
26386
27277
  }
26387
- const target = lifecycleTarget2(operation);
27278
+ const target = lifecycleTarget2(plan, operation);
26388
27279
  const observation = await host.adapter.observeTarget(target);
26389
27280
  const installation = observation.installations.find((row) => row.nativeId === nativeId);
26390
27281
  if (installation === undefined || installation.presence !== "present" || installation.ownership.kind !== "owned" || installation.ownership.scopeId !== operation.scope.id) {
@@ -26392,10 +27283,10 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26392
27283
  }
26393
27284
  const version = await host.adapter.probeVersion(observation.target);
26394
27285
  if (version.kind !== "detected") {
26395
- return refused(createLifecycleReason("runtime", "runtime.operation-failed", `retirement '${operation.package}' lost its detected target version`));
27286
+ return refused(createLifecycleReason("runtime", "runtime.operation-failed", `${operationLabel} '${operation.package}' lost its detected target version`));
26396
27287
  }
26397
27288
  if (recorded.fingerprints.installed !== installation.installedFingerprint) {
26398
- return refused(createLifecycleReason("internal", "internal.invariant", `retirement '${operation.package}' fingerprint does not match the owned install`));
27289
+ return refused(createLifecycleReason("internal", "internal.invariant", `${operationLabel} '${operation.package}' fingerprint does not match the owned install`));
26399
27290
  }
26400
27291
  const owned = ownedActivation(operation.scope, operation.scope.source, recorded, installation, observation.target);
26401
27292
  if (owned === null) {
@@ -26404,27 +27295,27 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26404
27295
  const planCoverage = createLifecyclePlanCoverage(observation, [{
26405
27296
  nativeId,
26406
27297
  operationId: operation.operationId,
26407
- operation: "retire",
27298
+ operation: capabilityOperation,
26408
27299
  mutationGroupId: operation.operationId,
26409
27300
  authorization: "observed-owned"
26410
27301
  }]);
26411
27302
  const nativeScope = await host.adapter.observeNativeMutationScope({
26412
27303
  targetObservation: observation,
26413
- operation: "retire",
27304
+ operation: capabilityOperation,
26414
27305
  packageName: owned.packageName,
26415
27306
  nativeId,
26416
27307
  sourceType: owned.sourceType
26417
27308
  });
26418
27309
  const nativeProjection = await host.adapter.observeNativeProjection({
26419
27310
  targetObservation: observation,
26420
- operation: "retire",
27311
+ operation: capabilityOperation,
26421
27312
  operationId: operation.operationId,
26422
27313
  attemptId: plan.attemptId,
26423
27314
  activation: owned
26424
27315
  });
26425
27316
  const decision = host.adapter.decideRoute({
26426
27317
  target: observation.target,
26427
- operation: "retire",
27318
+ operation: capabilityOperation,
26428
27319
  operationId: operation.operationId,
26429
27320
  attemptId: plan.attemptId,
26430
27321
  scopeId: operation.scope.id,
@@ -26438,13 +27329,13 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26438
27329
  planCoverage,
26439
27330
  activation: owned
26440
27331
  });
26441
- if (decision.kind !== "selected" || decision.operation !== "retire" || decision.route !== operation.route) {
26442
- return refused(createLifecycleReason("runtime", "runtime.operation-failed", `retirement '${operation.package}' kept frozen route '${operation.route}'`));
27332
+ if (decision.kind !== "selected" || decision.operation !== capabilityOperation || decision.route !== operation.route) {
27333
+ return refused(createLifecycleReason("runtime", "runtime.operation-failed", `${operationLabel} '${operation.package}' kept frozen route '${operation.route}'`));
26443
27334
  }
26444
27335
  return { kind: "ready", selection: decision, activation: owned, recorded };
26445
27336
  }
26446
- function lifecycleTarget2(operation) {
26447
- return { kind: operation.scope.target.kind, instance: operation.scope.target.instance };
27337
+ function lifecycleTarget2(plan, operation) {
27338
+ return plan.scopes.find((row) => row.scope.id === operation.scope.id)?.target ?? { kind: operation.scope.target.kind, instance: operation.scope.target.instance };
26448
27339
  }
26449
27340
 
26450
27341
  class Ledger {
@@ -26505,8 +27396,22 @@ class Ledger {
26505
27396
  return this.state.activations.find((row) => row.scopeId === scopeId && row.packageId === packageId && row.nativeId === nativeId);
26506
27397
  }
26507
27398
  markApplying(plan, operation) {
27399
+ if (operation.action === "disable-nonconforming")
27400
+ this.disablePending(plan, operation, "applying");
26508
27401
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "applying", "applying", true));
26509
27402
  }
27403
+ captureOperation(plan, operation, handle) {
27404
+ const sealedOperation = parseSerializedLifecycleOperation(JSON.parse(JSON.stringify(handle)));
27405
+ this.replaceAttempt(plan, (attempt) => ({ ...attempt, journal: attempt.journal.map((row) => row.operationId === operation.operationId ? { ...row, sealedOperation } : row) }));
27406
+ }
27407
+ markNotApplied(plan, operation) {
27408
+ if (operation.action === "disable-nonconforming")
27409
+ this.clearDisablePending(operation);
27410
+ this.replaceAttempt(plan, (attempt) => {
27411
+ const next = this.journaled(attempt, operation.operationId, "not-attempted", "failed", false);
27412
+ return { ...next, mutationStarted: next.journal.some((row) => mutationBegan(row.state)) };
27413
+ });
27414
+ }
26510
27415
  markPruning(plan, operation) {
26511
27416
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "applying", "pruning", true));
26512
27417
  }
@@ -26570,13 +27475,42 @@ class Ledger {
26570
27475
  }
26571
27476
  markRolledBack(plan, operation) {
26572
27477
  this.adoptMovedGeneration();
27478
+ if (operation.action === "disable-nonconforming")
27479
+ this.clearDisablePending(operation, true);
27480
+ this.clearPending(operation);
26573
27481
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "rolled-back", "failed", true));
26574
27482
  }
27483
+ markRollbackVerified(plan, operation) {
27484
+ this.adoptMovedGeneration();
27485
+ this.state = { ...this.state, activations: this.state.activations.map((row) => row.scopeId === operation.scope.id && row.packageId === operation.package ? { ...row, pending: { operation: operation.action, phase: "cleanup", attemptId: plan.attemptId, startedAt: this.now }, readbackState: "verified" } : row) };
27486
+ this.replaceAttempt(plan, (attempt) => ({ ...this.journaled(attempt, operation.operationId, "cleanup-pending", "finalizing", true), journal: attempt.journal.map((row) => row.operationId === operation.operationId ? { ...row, state: "cleanup-pending", settlement: "rollback", updatedAt: this.now } : row) }));
27487
+ }
27488
+ confirmDisable(plan, operation) {
27489
+ this.clearDisablePending(operation);
27490
+ this.state = { ...this.state, activations: this.state.activations.map((row) => row.scopeId === operation.scope.id && row.packageId === operation.package ? { ...row, activationState: "inactive", readbackState: "verified", readbackAt: this.now, updatedAt: this.now } : row) };
27491
+ this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "readback-verified", "readback", true));
27492
+ }
27493
+ completeContainment(plan, operation) {
27494
+ this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "completed", "failed", true));
27495
+ }
26575
27496
  markRollbackRequired(plan, operation) {
26576
27497
  this.adoptMovedGeneration();
26577
27498
  this.retargetBareAlias(plan, operation);
27499
+ if (operation.action === "disable-nonconforming")
27500
+ this.disablePending(plan, operation, "rollback");
26578
27501
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "rollback", "recovery-required", true));
26579
27502
  }
27503
+ disablePending(plan, operation, phase) {
27504
+ this.state = { ...this.state, activations: this.state.activations.map((row) => row.scopeId === operation.scope.id && row.packageId === operation.package ? { ...row, pending: { operation: "disable-nonconforming", phase, attemptId: plan.attemptId, startedAt: this.now } } : row) };
27505
+ }
27506
+ clearDisablePending(operation, rolledBack = false) {
27507
+ this.state = { ...this.state, activations: this.state.activations.map((row) => {
27508
+ if (row.scopeId !== operation.scope.id || row.packageId !== operation.package)
27509
+ return row;
27510
+ const { pending: _pending, ...prior } = row;
27511
+ return rolledBack ? { ...prior, activationState: "nonconforming", readbackState: "unverified" } : prior;
27512
+ }) };
27513
+ }
26580
27514
  markCompleted(plan, operation) {
26581
27515
  const desiredPairs = plan.operations.filter((row) => row.operation.scope.id === operation.scope.id && row.operation.coverage === "desired-pair");
26582
27516
  const converged = desiredPairs.every((row) => row.operation.operationId === operation.operationId || row.operation.action === "unchanged" || this.journalState(plan.attemptId, row.operation.operationId) === "completed");
@@ -26595,7 +27529,13 @@ class Ledger {
26595
27529
  if (scope.authority !== "authoritative")
26596
27530
  return scope;
26597
27531
  const planned = plan.scopes.find((row) => row.scope.id === scope.id);
27532
+ if (planned?.retirementPackages !== undefined)
27533
+ return scope;
26598
27534
  if (planned?.selectorMode === "retired") {
27535
+ if (this.state.activations.some((row) => row.scopeId === scope.id))
27536
+ return scope;
27537
+ if (plan.operations.some((row) => row.operation.scope.id === scope.id && row.operation.operationId !== operation.operationId && this.journalState(plan.attemptId, row.operation.operationId) !== "completed"))
27538
+ return scope;
26599
27539
  const { desired: _desired, lastConverged: _converged, ...kept } = scope;
26600
27540
  return {
26601
27541
  ...kept,
@@ -26663,7 +27603,7 @@ class Ledger {
26663
27603
  if (existing !== undefined && existing.authority !== "authoritative")
26664
27604
  return existing;
26665
27605
  const selectorMode = planned.selectorMode;
26666
- const target = { kind: planned.scope.target.kind, instance: planned.scope.target.instance };
27606
+ const target = planned.target ?? { kind: planned.scope.target.kind, instance: planned.scope.target.instance };
26667
27607
  if (existing === undefined) {
26668
27608
  return {
26669
27609
  id: planned.scope.id,
@@ -26829,19 +27769,44 @@ function succeeded(operation, changed, notices = []) {
26829
27769
  };
26830
27770
  }
26831
27771
  async function recoverApplied(plan, operation, host, ledger, handle, reason) {
27772
+ let verified = false;
27773
+ let diagnostic = "";
26832
27774
  try {
26833
- await host.adapter.rollback(handle);
26834
- const observation = await host.adapter.readback(handle);
26835
- host.adapter.verifyRollback(handle, observation);
26836
- } catch {
26837
- ledger.markRollbackRequired(plan, operation);
26838
- return pendingRecovery(operation);
27775
+ try {
27776
+ await host.adapter.rollback(handle);
27777
+ const observation = await host.adapter.readback(handle);
27778
+ host.adapter.verifyRollback(handle, observation);
27779
+ verified = true;
27780
+ } catch (error) {
27781
+ diagnostic = `; rollback verification failed: ${unknownErrorDiagnostic(error)}`;
27782
+ }
27783
+ try {
27784
+ if (verified)
27785
+ ledger.markRollbackVerified(plan, operation);
27786
+ else
27787
+ ledger.markRollbackRequired(plan, operation);
27788
+ } catch (error) {
27789
+ verified = false;
27790
+ diagnostic += `; recovery ledger could not be persisted: ${unknownErrorDiagnostic(error)}`;
27791
+ }
27792
+ if (verified) {
27793
+ try {
27794
+ await host.adapter.cleanup(handle, "verified-rollback");
27795
+ ledger.markRolledBack(plan, operation);
27796
+ } catch (error) {
27797
+ verified = false;
27798
+ diagnostic += `; rollback cleanup remains pending: ${unknownErrorDiagnostic(error)}`;
27799
+ }
27800
+ }
27801
+ } finally {
27802
+ try {
27803
+ await host.adapter.releaseRecovery(handle);
27804
+ } catch (error) {
27805
+ verified = false;
27806
+ diagnostic += `; recovery resources could not be released: ${unknownErrorDiagnostic(error)}`;
27807
+ }
26839
27808
  }
26840
- ledger.markRolledBack(plan, operation);
26841
- try {
26842
- await host.adapter.cleanup(handle, "verified-rollback");
26843
- } catch {}
26844
- return reportedRollback(operation, reason);
27809
+ return verified ? reportedRollback(operation, reason) : pendingRecovery(operation, `; original failure: ${reason.diagnostic}${diagnostic}`);
26845
27810
  }
26846
27811
  async function abortPreparation(host, preparation) {
26847
27812
  try {
@@ -26869,8 +27834,8 @@ function pendingReadback(operation, reason) {
26869
27834
  }
26870
27835
  };
26871
27836
  }
26872
- function pendingRecovery(operation) {
26873
- const reason = createLifecycleReason("recovery", "recovery.required", `rollback for '${operation.operationId}' did not verify`);
27837
+ function pendingRecovery(operation, diagnostic = "") {
27838
+ const reason = createLifecycleReason("recovery", "recovery.required", `rollback for '${operation.operationId}' did not verify${diagnostic}`);
26874
27839
  return {
26875
27840
  stop: true,
26876
27841
  reason,
@@ -26906,8 +27871,8 @@ function pendingCleanup(operation) {
26906
27871
  outcome: {
26907
27872
  ...operation,
26908
27873
  result: "pending",
26909
- resourceState: "present",
26910
- activationState: "active-conforming",
27874
+ resourceState: operation.action === "disable-nonconforming" ? "retained" : "present",
27875
+ activationState: operation.action === "disable-nonconforming" ? "inactive" : "active-conforming",
26911
27876
  changed: true,
26912
27877
  reason
26913
27878
  }
@@ -26986,7 +27951,7 @@ function summaryFor(outcomes) {
26986
27951
  const failed = outcomes.find((outcome) => outcome.result !== "succeeded");
26987
27952
  const changed = outcomes.some((outcome) => outcome.changed);
26988
27953
  const pending = outcomes.find((outcome) => outcome.result === "pending");
26989
- const readback = outcomes.find((outcome) => outcome.reason?.category === "readback");
27954
+ const readback = outcomes.find((outcome) => outcome.reason?.category === "readback" || outcome.action === "disable-nonconforming" && outcome.changed);
26990
27955
  const reapplied = outcomes.some((outcome) => outcome.result === "succeeded" && outcome.action === "update");
26991
27956
  const mutationStarted = changed || pending !== undefined || reapplied;
26992
27957
  if (failed === undefined) {
@@ -27072,16 +28037,186 @@ function absolutePinExecutable(command) {
27072
28037
  return resolve22(found);
27073
28038
  }
27074
28039
 
28040
+ // src/lifecycle-recovery.ts
28041
+ var OPEN = new Set(["pending", "applying", "applied", "readback-verified", "rollback", "cleanup-pending"]);
28042
+ async function recoverLifecycleJournals(plan, hosts, now) {
28043
+ let state = readLifecycleState().state;
28044
+ const scopeIds = new Set(plan.scopes.map((row) => row.scope.id));
28045
+ const journals = state.attempts.filter((attempt) => attempt.command !== "legacy-recovery").flatMap((attempt) => attempt.journal.filter((row) => scopeIds.has(row.scopeId) && OPEN.has(row.state)).map((row) => ({ attempt, row })));
28046
+ if (journals.length === 0)
28047
+ return false;
28048
+ const incompatible = journals.find(({ attempt }) => attempt.command !== plan.report.command.name);
28049
+ if (incompatible !== undefined)
28050
+ return {
28051
+ operation: null,
28052
+ changed: false,
28053
+ settled: [],
28054
+ reason: createLifecycleReason("internal", "internal.invariant", `pending operation '${incompatible.row.operationId}' belongs to '${incompatible.attempt.command}' attempt '${incompatible.attempt.id}'; cross-verb recovery is unverified. Retry the original '${incompatible.attempt.command}' command for scope '${incompatible.row.scopeId}' before '${plan.report.command.name}'. Pending proof and native state were retained.`)
28055
+ };
28056
+ let pending = journals[0].row;
28057
+ let changed = false;
28058
+ const settled = [];
28059
+ const operationFor = (row) => ({
28060
+ operationId: row.operationId,
28061
+ scope: plan.scopes.find((scope) => scope.scope.id === row.scopeId).scope,
28062
+ sourceSnapshotId: null,
28063
+ package: row.packageId ?? "unknown",
28064
+ nativeId: row.nativeId ?? null,
28065
+ coverage: row.action === "retire-orphan" || row.action === "remove" ? "retirement" : "desired-pair",
28066
+ action: row.action === "remove" ? "retire-orphan" : row.action,
28067
+ route: row.route?.kind === "native" ? "native" : "managed"
28068
+ });
28069
+ try {
28070
+ for (const source of plan.frozenSources.values())
28071
+ if (fingerprintTree(source.snapshotDir) !== source.snapshot.fingerprint)
28072
+ throw new Error("retained Source drifted before recovery");
28073
+ const prepared = journals.map(({ attempt, row }) => {
28074
+ if (row.state === "pending" && row.sealedOperation === undefined)
28075
+ return { row, attemptId: attempt.id, handle: null, host: null };
28076
+ if (row.sealedOperation === undefined)
28077
+ throw new Error(`journal '${row.operationId}' has no durable sealed operation; recovery remains unverified`);
28078
+ const handle = hydrateDurableLifecycleOperation(row.sealedOperation);
28079
+ const scope = state.scopes.find((scope) => scope.id === row.scopeId);
28080
+ if (handle.attemptId !== attempt.id || handle.target.kind !== scope?.target.kind || handle.target.instance !== scope.target.instance || canonicalTargetContextBytes(handle.target.context) !== canonicalTargetContextBytes(scope.target.context))
28081
+ throw new Error("recovery target/attempt differs from recorded authority");
28082
+ const host = hosts.find((host) => host.adapter.id === handle.adapterId && host.kinds.includes(handle.target.kind));
28083
+ if (host === undefined)
28084
+ throw new Error(`recovery adapter '${handle.adapterId}' is unavailable`);
28085
+ return { row, attemptId: attempt.id, handle, host };
28086
+ });
28087
+ const persist = (row, settled, finished) => {
28088
+ if (readLifecycleState().state.stateGeneration !== state.stateGeneration)
28089
+ throw new Error("recovery ledger changed concurrently; refusing stale commit");
28090
+ const next = {
28091
+ ...state,
28092
+ stateGeneration: state.stateGeneration + 1,
28093
+ activations: state.activations.map((activation) => {
28094
+ if (activation.scopeId !== row.scopeId || activation.nativeId !== row.nativeId || activation.packageId !== row.packageId)
28095
+ return activation;
28096
+ const { pending: _pending, ...kept } = activation;
28097
+ return finished ? { ...kept, readbackState: "verified", readbackAt: now, updatedAt: now } : { ...kept, pending: { operation: row.action, phase: "cleanup", attemptId: row.sealedOperation.attemptId, startedAt: now }, readbackState: "verified" };
28098
+ }),
28099
+ attempts: state.attempts.map((attempt) => {
28100
+ if (attempt.id !== row.sealedOperation.attemptId)
28101
+ return attempt;
28102
+ const journal = attempt.journal.map((entry) => entry.operationId === row.operationId ? { ...entry, state: finished ? settled === "rollback" ? "rolled-back" : "completed" : "cleanup-pending", settlement: settled, updatedAt: now } : entry);
28103
+ const unresolved = journal.some((entry) => OPEN.has(entry.state));
28104
+ const failed = journal.some((entry) => entry.state === "rolled-back" || entry.state === "failed" || entry.state === "not-attempted" || entry.action === "disable-nonconforming");
28105
+ return { ...attempt, phase: unresolved ? "finalizing" : failed ? "failed" : "completed", ...!unresolved && !failed ? { completedAt: now } : {}, updatedAt: now, journal };
28106
+ })
28107
+ };
28108
+ if (finished && settled === "commit")
28109
+ next.scopes = next.scopes.map((scope) => {
28110
+ const requested = plan.scopes.find((item) => item.scope.id === scope.id);
28111
+ if (scope.id !== row.scopeId || requested?.selectorMode !== "retired" || requested.retirementPackages !== undefined || next.activations.some((activation) => activation.scopeId === scope.id) || next.attempts.some((attempt) => attempt.journal.some((entry) => entry.scopeId === scope.id && OPEN.has(entry.state))))
28112
+ return scope;
28113
+ const { desired: _desired, lastConverged: _converged, ...kept } = scope;
28114
+ return { ...kept, lifecycle: "retired", selectorMode: "retired", retiredAt: now, updatedAt: now };
28115
+ });
28116
+ writeLifecycleState(next, { globalPreflight: "succeeded" });
28117
+ state = next;
28118
+ };
28119
+ for (const { row, attemptId, handle, host } of prepared) {
28120
+ pending = row;
28121
+ changed = false;
28122
+ if (handle === null || host === null) {
28123
+ if (readLifecycleState().state.stateGeneration !== state.stateGeneration)
28124
+ throw new Error("recovery ledger changed concurrently; refusing stale accepted-intent settlement");
28125
+ const next = {
28126
+ ...state,
28127
+ stateGeneration: state.stateGeneration + 1,
28128
+ activations: state.activations.map((activation) => {
28129
+ if (activation.pending?.attemptId !== attemptId || activation.scopeId !== row.scopeId || activation.packageId !== row.packageId || activation.nativeId !== row.nativeId)
28130
+ return activation;
28131
+ const { pending: _pending, ...kept } = activation;
28132
+ return { ...kept, readbackState: activation.readbackState === "pending" ? "unverified" : activation.readbackState, updatedAt: now };
28133
+ }),
28134
+ attempts: state.attempts.map((attempt) => attempt.id !== attemptId ? attempt : { ...attempt, phase: "failed", updatedAt: now, journal: attempt.journal.map((entry) => entry.operationId !== row.operationId ? entry : { ...entry, state: "not-attempted", updatedAt: now }) })
28135
+ };
28136
+ writeLifecycleState(next, { globalPreflight: "succeeded" });
28137
+ state = next;
28138
+ settled.push({
28139
+ ...operationFor(row),
28140
+ action: "not-attempted",
28141
+ route: "none",
28142
+ result: "not-attempted",
28143
+ resourceState: "unknown",
28144
+ activationState: "unknown",
28145
+ changed: false,
28146
+ reason: createLifecycleReason("runtime", "runtime.operation-failed", `original accepted operation '${row.operationId}' settled without native apply; unsealed preparation remains retained`)
28147
+ });
28148
+ continue;
28149
+ }
28150
+ let receipt;
28151
+ try {
28152
+ const observed = await host.adapter.beginRecovery(handle);
28153
+ const committed = row.settlement === "commit" || row.settlement === undefined && (row.state === "readback-verified" || row.state === "cleanup-pending");
28154
+ let verifiedObservation = observed.observation;
28155
+ if (committed) {
28156
+ if (observed.kind !== "expected")
28157
+ throw new Error("committed recovery candidate changed; refusing stale cleanup");
28158
+ } else {
28159
+ if (row.settlement !== "rollback")
28160
+ changed = (await host.adapter.rollback(handle)).changed;
28161
+ verifiedObservation = await host.adapter.readback(handle);
28162
+ host.adapter.verifyRollback(handle, verifiedObservation);
28163
+ }
28164
+ const settlement = committed ? "commit" : "rollback";
28165
+ persist(row, settlement, false);
28166
+ await host.adapter.cleanup(handle, committed ? "verified-commit" : "verified-rollback");
28167
+ persist(row, settlement, true);
28168
+ const retired = committed && (row.action === "retire-orphan" || row.action === "remove");
28169
+ receipt = {
28170
+ ...operationFor(row),
28171
+ result: retired ? "succeeded" : "failed",
28172
+ resourceState: verifiedObservation.presence === "absent" ? "absent" : committed ? "present" : "retained",
28173
+ activationState: verifiedObservation.presence === "absent" || verifiedObservation.activation === "inactive" ? "inactive" : verifiedObservation.activation === "active" ? "retained-prior" : "unknown",
28174
+ changed,
28175
+ reason: retired ? null : createLifecycleReason("runtime", "runtime.operation-failed", `original operation '${row.operationId}' settled by verified ${settlement}; current Desired has not been attempted`)
28176
+ };
28177
+ } finally {
28178
+ await host.adapter.releaseRecovery(handle);
28179
+ }
28180
+ settled.push(receipt);
28181
+ }
28182
+ return true;
28183
+ } catch (error) {
28184
+ return { changed, settled, reason: createLifecycleReason("recovery", "recovery.required", `cross-process recovery of operation '${pending.operationId}' remains pending: ${unknownErrorDiagnostic(error)}`), operation: operationFor(pending) };
28185
+ }
28186
+ }
28187
+
27075
28188
  // src/lifecycle-command.ts
27076
28189
  async function runFrozenLifecycle(command) {
27077
- const plan = await planLifecycle({
28190
+ const retainedSources = new Map;
28191
+ const input = {
27078
28192
  manifest: command.manifest,
27079
28193
  dryRun: command.dryRun,
27080
28194
  validatedAt: command.now,
27081
28195
  hosts: command.hosts,
27082
28196
  ...command.command === undefined ? {} : { command: command.command },
27083
- ...command.transportSource === undefined ? {} : { transportSource: command.transportSource }
27084
- });
28197
+ ...command.transportSource === undefined ? {} : { transportSource: command.transportSource },
28198
+ ...command.removalSelection === undefined ? {} : { removalSelection: command.removalSelection },
28199
+ retainedSources
28200
+ };
28201
+ let plan = await planLifecycle({ ...input, recoveryPreflight: !command.dryRun });
28202
+ if (!command.dryRun && plan.kind === "frozen") {
28203
+ const recovery = await recoverLifecycleJournals(plan, command.hosts, command.now);
28204
+ if (typeof recovery !== "boolean") {
28205
+ const { reason, operation, changed, settled } = recovery;
28206
+ const blocked = createLifecycleReason("runtime", "runtime.operation-failed", `not attempted while recovery remains unresolved: ${reason.diagnostic}`);
28207
+ const operations = plan.report.plan.filter((row) => ![...settled, ...operation === null ? [] : [operation]].some((recovered) => row.scope.id === recovered.scope.id && row.package === recovered.package && row.nativeId === recovered.nativeId && row.coverage === recovered.coverage)).map((row) => row.action === "retain-prior" ? { ...row, action: "not-attempted", route: "none" } : row);
28208
+ const outcomes = operations.map((row) => ({ ...row, result: "not-attempted", resourceState: "unknown", activationState: "unknown", changed: false, reason: operation === null ? reason : blocked }));
28209
+ if (operation === null) {
28210
+ const report = parseLifecycleReport({ ...plan.report, plan: operations, outcomes, summary: { result: "incomplete", terminalPhase: "preflight", mutationStarted: false, changed: false, failureCategory: reason.category, reason, recoveryId: null, readbackId: null } });
28211
+ return { report, exitCode: exitCodeForLifecycleReport(report) };
28212
+ }
28213
+ const recoveredOutcomes = [{ ...operation, result: "pending", resourceState: changed ? "potentially-changed" : "unknown", activationState: "unknown", changed, reason }, ...settled];
28214
+ const report = parseLifecycleReport({ ...plan.report, plan: [operation, ...settled.map(({ result: _result, resourceState: _resource, activationState: _activation, changed: _changed, reason: _reason, ...row }) => row), ...operations], outcomes: [...recoveredOutcomes, ...outcomes], summary: { result: "incomplete", terminalPhase: "finalize", mutationStarted: true, changed: recoveredOutcomes.some((row) => row.changed), failureCategory: "recovery", reason, recoveryId: operation.operationId, readbackId: recoveredOutcomes.find((row) => row.changed && row.action === "disable-nonconforming")?.operationId ?? null } });
28215
+ return { report, exitCode: exitCodeForLifecycleReport(report) };
28216
+ }
28217
+ if (recovery)
28218
+ plan = await planLifecycle(input);
28219
+ }
27085
28220
  const executed = await executeLifecycle({
27086
28221
  plan,
27087
28222
  hosts: command.hosts,
@@ -27103,6 +28238,7 @@ function plannerHost(kind, adapter, plannedNativeId, announceDryRun) {
27103
28238
  var lifecyclePlannerHostKinds = [
27104
28239
  "claude-code",
27105
28240
  "codex",
28241
+ "dcode",
27106
28242
  "kimi",
27107
28243
  "cursor",
27108
28244
  "omp",
@@ -27114,6 +28250,7 @@ function createLifecyclePlannerHosts() {
27114
28250
  return [
27115
28251
  plannerHost("claude-code", createClaudeCodeLifecycleHost(), claudeCodeWriter.plannedNativeId),
27116
28252
  plannerHost("codex", codexLifecycle, codexWriter.plannedNativeId),
28253
+ plannerHost("dcode", dcodeLifecycle, dcodeWriter.plannedNativeId),
27117
28254
  plannerHost("kimi", createLifecycleHostAdapter(kimiLifecycle), kimiWriter.plannedNativeId),
27118
28255
  plannerHost("cursor", cursorManagedLifecycle, cursorWriter.plannedNativeId, announceCursorDryRun),
27119
28256
  plannerHost("omp", ompLifecycle, ompWriter.plannedNativeId),
@@ -27123,6 +28260,164 @@ function createLifecyclePlannerHosts() {
27123
28260
  ];
27124
28261
  }
27125
28262
 
28263
+ // src/pin-lifecycle.ts
28264
+ import { cpSync as cpSync13, mkdtempSync as mkdtempSync14, rmSync as rmSync15 } from "node:fs";
28265
+ import { tmpdir as tmpdir12 } from "node:os";
28266
+ import { basename as basename9, join as join35, relative as relative8 } from "node:path";
28267
+ async function runOfflineLifecyclePin(host, plugin, dryRun) {
28268
+ const adapter = host.lifecyclePinAdapter;
28269
+ if (adapter === undefined || plugin.path === undefined)
28270
+ throw new Error("offline lifecycle pin has no adapter or active root");
28271
+ let state = readLifecycleState().state;
28272
+ const matches = state.activations.filter((row) => row.nativeId === plugin.id && state.scopes.some((scope) => scope.id === row.scopeId && scope.target.kind === host.id));
28273
+ if (matches.length !== 1)
28274
+ throw new Error("offline lifecycle pin requires one recorded owned activation");
28275
+ const activation = matches[0];
28276
+ const scope = state.scopes.find((row) => row.id === activation.scopeId);
28277
+ if (activation.ownership.kind === "legacy-claim" || activation.route.kind === "legacy-unverified" || activation.sourceRevision === undefined || activation.pending !== undefined)
28278
+ throw new Error("offline lifecycle pin requires verified ownership with no pending recovery");
28279
+ if (activation.activationState !== "active" || activation.readbackState !== "verified")
28280
+ throw new Error("offline lifecycle pin requires a verified active installation");
28281
+ const preview = await host.pin(plugin, { dryRun: true });
28282
+ if (preview.refusals.length !== 0)
28283
+ return { changes: [], refusals: preview.refusals };
28284
+ if (preview.changes.length === 0)
28285
+ return preview;
28286
+ const now = new Date().toISOString();
28287
+ const frozenRoot = mkdtempSync14(join35(tmpdir12(), "plgnz-offline-pin-"));
28288
+ const attemptId = `pin-${basename9(frozenRoot)}`;
28289
+ const operationId = `${attemptId}:${plugin.id}`;
28290
+ let handle;
28291
+ let preparation;
28292
+ let accepted = false, applying = false, committed = false;
28293
+ const attempt = { id: attemptId, command: "update", phase: "accepted", mutationStarted: false, scopeIds: [scope.id], journal: [{ operationId, scopeId: scope.id, packageId: activation.packageId, nativeId: activation.nativeId, action: "update", state: "pending", route: activation.route, startedAt: now, updatedAt: now }], startedAt: now, updatedAt: now };
28294
+ const persist = (nextActivation) => {
28295
+ if (readLifecycleState().state.stateGeneration !== state.stateGeneration)
28296
+ throw new Error("pin ledger changed concurrently; refusing stale commit");
28297
+ const next = { ...state, stateGeneration: state.stateGeneration + 1, activations: state.activations.map((row) => row.scopeId === activation.scopeId && row.nativeId === activation.nativeId ? nextActivation : row), attempts: [...state.attempts.filter((row) => row.id !== attemptId), { ...attempt, journal: attempt.journal.map((row) => ({ ...row })) }] };
28298
+ writeLifecycleState(next, { globalPreflight: "succeeded" });
28299
+ state = next;
28300
+ };
28301
+ const pending = (phase) => persist({ ...activation, pending: { operation: "update", phase, attemptId, startedAt: now }, readbackState: "pending" });
28302
+ try {
28303
+ cpSync13(plugin.path, frozenRoot, { recursive: true });
28304
+ const packageFingerprint = fingerprintTree(frozenRoot);
28305
+ if (packageFingerprint !== activation.fingerprints.installed)
28306
+ throw new Error("pin active bytes differ from recorded readback");
28307
+ const target = { kind: scope.target.kind, instance: scope.target.instance };
28308
+ const observed = await adapter.observeTarget(target);
28309
+ const installed = observed.installations.find((row) => row.nativeId === plugin.id);
28310
+ if (installed?.ownership.kind !== "owned" || installed.ownership.scopeId !== scope.id || installed.installedFingerprint !== packageFingerprint || installed.enablement !== "enabled")
28311
+ throw new Error("pin native identity or bytes differ from the recorded owned activation");
28312
+ const version = await adapter.probeVersion(target);
28313
+ const sourceType = scope.source.kind;
28314
+ const inventory = inventoryPackageSemantics({ dir: frozenRoot, name: activation.packageId, ...plugin.version === undefined ? {} : { version: plugin.version }, contentFingerprint: packageFingerprint });
28315
+ const snapshot = createFrozenPackageSnapshot({ operationId, attemptId, scopeId: scope.id, target, action: "update", packageName: activation.packageId, nativeId: plugin.id, sourceType, immutableRevision: activation.sourceRevision, snapshotRoot: frozenRoot, packageRoot: frozenRoot, relativePackagePath: ".", snapshotFingerprint: packageFingerprint, packageFingerprint, ...sourceType === "git" ? { nativeGit: { locator: scope.source.locator, resolvedRevision: activation.sourceRevision } } : {}, inventory });
28316
+ const pins = createResolvedLifecyclePins(preview.changes.map((change) => ({ server: change.server, executable: change.to })).sort((left, right) => left.server < right.server ? -1 : left.server > right.server ? 1 : 0));
28317
+ const nativeScope = await adapter.observeNativeMutationScope({ targetObservation: observed, operation: "update", packageName: activation.packageId, nativeId: plugin.id, sourceType });
28318
+ const nativeProjection = await adapter.observeNativeProjection({ targetObservation: observed, operation: "update", snapshot, pins });
28319
+ const selection = adapter.decideRoute({ target, operationId, attemptId, scopeId: scope.id, packageName: activation.packageId, nativeId: plugin.id, version, sourceType, targetObservation: observed, nativeScope, nativeProjection, planCoverage: createLifecyclePlanCoverage(observed, [{ nativeId: plugin.id, operationId, operation: "update", mutationGroupId: operationId, authorization: "observed-owned" }]), operation: "update", snapshot, pins });
28320
+ if (selection.kind !== "selected")
28321
+ throw new Error(`pin capability ${selection.status}: ${selection.gaps.map((gap) => gap.capabilityId).join(", ")}`);
28322
+ if (selection.route !== "managed")
28323
+ throw new Error("offline lifecycle pin requires a frozen Managed projection");
28324
+ if (dryRun)
28325
+ return preview;
28326
+ pending("accepted");
28327
+ accepted = true;
28328
+ preparation = await adapter.stageActivation({ selection, snapshot, pins });
28329
+ preparation = await adapter.applyLifecycleDirectives(preparation);
28330
+ preparation = await adapter.applyPins(preparation);
28331
+ const sealed = await adapter.sealActivation(preparation);
28332
+ preparation = sealed;
28333
+ handle = sealed.handle;
28334
+ attempt.journal[0].sealedOperation = parseSerializedLifecycleOperation(JSON.parse(JSON.stringify(handle)));
28335
+ attempt.phase = "applying";
28336
+ attempt.mutationStarted = true;
28337
+ attempt.journal[0].state = "applying";
28338
+ pending("applying");
28339
+ applying = true;
28340
+ await adapter.apply(sealed);
28341
+ const observation = await adapter.readback(handle);
28342
+ adapter.verify(handle, observation);
28343
+ if (observation.installedFingerprint === null || handle.projectedFingerprint === null)
28344
+ throw new Error("pin readback lacks exact installed/projected fingerprint");
28345
+ attempt.phase = "finalizing";
28346
+ attempt.journal[0].state = "readback-verified";
28347
+ attempt.journal[0].settlement = "commit";
28348
+ const pinnedActivation = { ...activation, route: { kind: handle.route, evidenceKey: { kind: "capability-profile", key: handle.evidenceId } }, fingerprints: { ...activation.fingerprints, projected: handle.projectedFingerprint, installed: observation.installedFingerprint }, pins: [...new Set([...activation.pins, ...pins.map((pin) => pin.server)])].sort(), readbackState: "verified", readbackAt: now, updatedAt: now };
28349
+ persist(pinnedActivation);
28350
+ committed = true;
28351
+ try {
28352
+ await adapter.cleanup(handle, "verified-commit");
28353
+ } catch (error) {
28354
+ attempt.journal[0].state = "cleanup-pending";
28355
+ persist({ ...pinnedActivation, pending: { operation: "update", phase: "cleanup", attemptId, startedAt: now } });
28356
+ await adapter.releaseRecovery(handle);
28357
+ throw error;
28358
+ }
28359
+ attempt.phase = "completed";
28360
+ attempt.journal[0].state = "completed";
28361
+ attempt.completedAt = now;
28362
+ persist(pinnedActivation);
28363
+ const activatedRoot = observation.contentRoots[0]?.path;
28364
+ return { ...preview, changes: preview.changes.map((change) => ({ ...change, file: activatedRoot === undefined ? change.file : join35(activatedRoot, relative8(plugin.path, change.file)) })) };
28365
+ } catch (error) {
28366
+ if (!committed && handle !== undefined && applying) {
28367
+ if (!(error instanceof LifecycleHostPhaseError && error.mutationStarted === false)) {
28368
+ attempt.phase = "recovery-required";
28369
+ attempt.journal[0].state = "rollback";
28370
+ try {
28371
+ let journalError;
28372
+ try {
28373
+ pending("rollback");
28374
+ } catch (writeError) {
28375
+ journalError = writeError;
28376
+ }
28377
+ await adapter.rollback(handle);
28378
+ adapter.verifyRollback(handle, await adapter.readback(handle));
28379
+ if (journalError !== undefined)
28380
+ throw new Error(`rollback verified but its durable receipt is blocked: ${journalError.message}`);
28381
+ attempt.phase = "finalizing";
28382
+ attempt.journal[0].state = "cleanup-pending";
28383
+ attempt.journal[0].settlement = "rollback";
28384
+ persist({ ...activation, pending: { operation: "update", phase: "cleanup", attemptId, startedAt: now } });
28385
+ await adapter.cleanup(handle, "verified-rollback");
28386
+ attempt.phase = "failed";
28387
+ attempt.journal[0].state = "rolled-back";
28388
+ persist(activation);
28389
+ } catch (recoveryError) {
28390
+ await adapter.releaseRecovery(handle);
28391
+ throw new Error(`pin failed: ${error.message}; recovery remains pending: ${recoveryError.message}`);
28392
+ }
28393
+ } else {
28394
+ attempt.phase = "failed";
28395
+ attempt.journal[0].state = "not-attempted";
28396
+ attempt.mutationStarted = false;
28397
+ persist(activation);
28398
+ await adapter.cleanup(handle, "aborted-preparation");
28399
+ }
28400
+ } else if (!committed && accepted) {
28401
+ try {
28402
+ if (preparation !== undefined)
28403
+ await adapter.cleanup(preparation, "aborted-preparation");
28404
+ } finally {
28405
+ attempt.phase = "failed";
28406
+ attempt.journal[0].state = "not-attempted";
28407
+ persist(activation);
28408
+ }
28409
+ }
28410
+ throw error;
28411
+ } finally {
28412
+ try {
28413
+ if (handle !== undefined)
28414
+ await adapter.releaseRecovery(handle);
28415
+ } finally {
28416
+ rmSync15(frozenRoot, { recursive: true, force: true });
28417
+ }
28418
+ }
28419
+ }
28420
+
27126
28421
  // src/pin.ts
27127
28422
  function defaultPinTargets(all = writers) {
27128
28423
  return all.filter((w) => w.gui);
@@ -27133,13 +28428,20 @@ async function runPin(options = {}) {
27133
28428
  const selected = selectTargets(candidates, options);
27134
28429
  const findings = [];
27135
28430
  let ledgerChanged = false;
28431
+ const changedRecords = new Set;
27136
28432
  for (const host of selected) {
27137
28433
  if (!host.detect())
27138
28434
  continue;
27139
28435
  for (const plugin of host.listInstalled()) {
27140
28436
  if (plugin.path === undefined)
27141
28437
  continue;
27142
- const outcome = await host.pin(plugin, { dryRun: options.dryRun });
28438
+ let outcome;
28439
+ try {
28440
+ outcome = host.lifecyclePinAdapter === undefined ? await host.pin(plugin, { dryRun: options.dryRun }) : await runOfflineLifecyclePin(host, plugin, options.dryRun === true);
28441
+ } catch (error) {
28442
+ findings.push({ host: host.id, mark: "✗", message: `plugin '${plugin.id}' pin failed — ${error.message}` });
28443
+ continue;
28444
+ }
27143
28445
  const prefix = options.dryRun === true ? "[dry-run] " : "";
27144
28446
  for (const change of outcome.changes) {
27145
28447
  findings.push({
@@ -27157,6 +28459,8 @@ async function runPin(options = {}) {
27157
28459
  }
27158
28460
  if (outcome.changes.length === 0)
27159
28461
  continue;
28462
+ if (host.lifecyclePinAdapter !== undefined)
28463
+ continue;
27160
28464
  const record = findRecord(state, host.id, plugin.id);
27161
28465
  if (record === undefined) {
27162
28466
  findings.push({
@@ -27168,6 +28472,7 @@ async function runPin(options = {}) {
27168
28472
  }
27169
28473
  if (options.dryRun === true)
27170
28474
  continue;
28475
+ changedRecords.add(record);
27171
28476
  const pins = new Set(record.pins ?? []);
27172
28477
  for (const change of outcome.changes)
27173
28478
  pins.add(change.server);
@@ -27185,10 +28490,10 @@ async function runPin(options = {}) {
27185
28490
  }
27186
28491
  }
27187
28492
  if (ledgerChanged)
27188
- persistRecordedPins(state, options.state !== undefined);
28493
+ persistRecordedPins(state, options.state !== undefined, changedRecords);
27189
28494
  return { findings, exitCode: findings.some((f) => f.mark === "✗") ? 1 : 0 };
27190
28495
  }
27191
- function persistRecordedPins(records, legacyLedger) {
28496
+ function persistRecordedPins(records, legacyLedger, changedRecords) {
27192
28497
  const loaded = legacyLedger ? null : readLifecycleState();
27193
28498
  if (loaded === null || loaded.sourceVersion !== 2) {
27194
28499
  writeState(records);
@@ -27200,7 +28505,7 @@ function persistRecordedPins(records, legacyLedger) {
27200
28505
  stateGeneration: loaded.state.stateGeneration + 1,
27201
28506
  activations: loaded.state.activations.map((activation) => {
27202
28507
  const scope = scopes.get(activation.scopeId);
27203
- const record = records.find((candidate) => candidate.host === scope?.target.kind && candidate.id === activation.nativeId);
28508
+ const record = records.find((candidate) => changedRecords.has(candidate) && candidate.host === scope?.target.kind && candidate.id === activation.nativeId);
27204
28509
  if (record === undefined)
27205
28510
  return activation;
27206
28511
  return {
@@ -27672,7 +28977,7 @@ function legacyCommandAction(command) {
27672
28977
  // package.json
27673
28978
  var package_default = {
27674
28979
  name: "plugnz",
27675
- version: "0.0.8",
28980
+ version: "0.0.10",
27676
28981
  description: "Install, diagnose and update agent plugins (Agent Plugins spec) and MCP configs across Claude Code, Codex, Cursor, Kimi and more",
27677
28982
  license: "MIT",
27678
28983
  type: "module",
@@ -28681,19 +29986,13 @@ async function runRecordedLifecycleRemove(flags, target, state, selected) {
28681
29986
  const alias = writer?.persistedNativeIdMayAlias?.(activation.nativeId, target) === true;
28682
29987
  if (target !== activation.packageId && target !== activation.nativeId && !alias)
28683
29988
  return [];
28684
- return [{ scope }];
29989
+ return [{ scope, activation }];
28685
29990
  });
28686
29991
  if (matches.length === 0 || matches.some((match) => !plannerKinds.has(match.scope.target.kind)))
28687
29992
  return null;
28688
29993
  const byScope = new Map;
28689
29994
  for (const match of matches)
28690
29995
  byScope.set(match.scope.id, match.scope);
28691
- for (const scopeId of byScope.keys()) {
28692
- const inScope = state.activations.filter((activation) => activation.scopeId === scopeId).length;
28693
- const selectedInScope = matches.filter((match) => match.scope.id === scopeId).length;
28694
- if (inScope !== selectedInScope)
28695
- return null;
28696
- }
28697
29996
  const perHost = new Map;
28698
29997
  for (const match of matches) {
28699
29998
  const count = (perHost.get(match.scope.target.kind) ?? 0) + 1;
@@ -28726,7 +30025,12 @@ async function runRecordedLifecycleRemove(flags, target, state, selected) {
28726
30025
  dryRun: flags.dryRun,
28727
30026
  hosts: createLifecyclePlannerHosts(),
28728
30027
  now: new Date().toISOString(),
28729
- command: "remove"
30028
+ command: "remove",
30029
+ removalSelection: matches.map(({ activation }) => ({
30030
+ scopeId: activation.scopeId,
30031
+ packageId: activation.packageId,
30032
+ nativeId: activation.nativeId
30033
+ }))
28730
30034
  });
28731
30035
  return executed.report;
28732
30036
  }
@@ -29103,18 +30407,22 @@ async function main(argv) {
29103
30407
  return 2;
29104
30408
  }
29105
30409
  const state = readState();
30410
+ const recoveries = pendingLifecycleOperations();
29106
30411
  const all = [];
29107
30412
  for (const h of selection.selected) {
29108
30413
  const installed = h.listInstalled();
29109
30414
  const pending = state.filter((record) => record.host === h.id && record.pending !== undefined).map((record) => ({ id: record.id, action: record.pending }));
29110
30415
  if (json) {
29111
- all.push({ host: h.id, plugins: installed, ...pending.length > 0 ? { pending } : {} });
30416
+ const recovery = recoveries.filter((row) => row.host === h.id);
30417
+ all.push({ host: h.id, plugins: installed, ...pending.length > 0 ? { pending } : {}, ...recovery.length > 0 ? { recovery } : {} });
29112
30418
  } else {
29113
30419
  for (const p of installed) {
29114
30420
  console.log(`${h.id} ${p.id} ${p.version || p.sha || "unknown"}`);
29115
30421
  }
29116
30422
  for (const record of pending)
29117
30423
  console.log(`${h.id} ${record.id} pending:${record.action}`);
30424
+ for (const row of recoveries.filter((row) => row.host === h.id))
30425
+ console.log(`${h.id} ${row.id} recovery:${row.phase} operation:${row.operationId} retry:${row.command} scope:${row.scopeId}`);
29118
30426
  }
29119
30427
  }
29120
30428
  if (json)