plugnz 0.0.9 → 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 +750 -207
  2. package/package.json +1 -1
package/dist/plugnz.mjs CHANGED
@@ -10252,6 +10252,91 @@ function frame(hash, bytes) {
10252
10252
  hash.update(bytes);
10253
10253
  }
10254
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
+
10255
10340
  // src/state.ts
10256
10341
  var PENDING_OPERATIONS = ["install", "update", "route-migrate", "disable-nonconforming", "retire-orphan", "remove"];
10257
10342
  var JOURNAL_ACTIONS = [...PENDING_OPERATIONS, "unchanged", "retain-prior"];
@@ -10319,39 +10404,41 @@ function validateLifecycleStateDocument(value, minimumStateGeneration) {
10319
10404
  continue;
10320
10405
  const attempt = attemptsById.get(scope.lastAttemptId);
10321
10406
  if (attempt === undefined)
10322
- invalid2(`deployment scope '${scope.id}' references unknown last attempt '${scope.lastAttemptId}'`);
10407
+ invalid3(`deployment scope '${scope.id}' references unknown last attempt '${scope.lastAttemptId}'`);
10323
10408
  if (!attempt.scopeIds.includes(scope.id))
10324
- 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`);
10325
10410
  }
10326
10411
  for (const activation of activations) {
10327
10412
  if (!scopeIds.has(activation.scopeId))
10328
- invalid2(`activation '${activationKey(activation)}' references unknown scope '${activation.scopeId}'`);
10413
+ invalid3(`activation '${activationKey(activation)}' references unknown scope '${activation.scopeId}'`);
10329
10414
  if (activation.pending === undefined)
10330
10415
  continue;
10331
10416
  const attempt = attemptsById.get(activation.pending.attemptId);
10332
10417
  if (attempt === undefined)
10333
- invalid2(`activation '${activationKey(activation)}' references unknown pending attempt '${activation.pending.attemptId}'`);
10418
+ invalid3(`activation '${activationKey(activation)}' references unknown pending attempt '${activation.pending.attemptId}'`);
10334
10419
  if (!attempt.scopeIds.includes(activation.scopeId))
10335
- invalid2(`activation '${activationKey(activation)}' pending attempt does not include its scope`);
10420
+ invalid3(`activation '${activationKey(activation)}' pending attempt does not include its scope`);
10336
10421
  const matchingEntries = attempt.journal.filter((entry) => entry.scopeId === activation.scopeId && entry.packageId === activation.packageId && entry.nativeId === activation.nativeId);
10337
10422
  if (matchingEntries.length !== 1)
10338
- 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`);
10339
10424
  validatePendingSemantics(activation, attempt, matchingEntries[0]);
10340
10425
  }
10341
10426
  for (const attempt of attempts) {
10342
10427
  for (const scopeId of attempt.scopeIds)
10343
10428
  if (!scopeIds.has(scopeId))
10344
- invalid2(`attempt '${attempt.id}' references unknown scope '${scopeId}'`);
10429
+ invalid3(`attempt '${attempt.id}' references unknown scope '${scopeId}'`);
10345
10430
  for (const entry of attempt.journal) {
10346
10431
  operationIds.push(entry.operationId);
10347
10432
  if (!attempt.scopeIds.includes(entry.scopeId))
10348
- 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`);
10349
10436
  }
10350
10437
  }
10351
10438
  unique(operationIds, "journal operation id");
10352
10439
  for (const tombstone of tombstones)
10353
10440
  if (!scopeIds.has(tombstone.scopeId))
10354
- invalid2(`tombstone '${tombstone.id}' references unknown scope '${tombstone.scopeId}'`);
10441
+ invalid3(`tombstone '${tombstone.id}' references unknown scope '${tombstone.scopeId}'`);
10355
10442
  return value;
10356
10443
  }
10357
10444
  function findRecord(records, host, id) {
@@ -10387,9 +10474,9 @@ function parseV1Install(value, index) {
10387
10474
  const rec = asObject(value, "install record");
10388
10475
  const unknown = Object.keys(rec).find((key) => !V1_RECORD_FIELDS.has(key));
10389
10476
  if (unknown !== undefined)
10390
- invalid2(`unsupported install field '${unknown}'`);
10477
+ invalid3(`unsupported install field '${unknown}'`);
10391
10478
  if (typeof rec["host"] !== "string" || typeof rec["id"] !== "string" || typeof rec["source"] !== "string" || typeof rec["sourceSha"] !== "string") {
10392
- invalid2("install record is missing required fields");
10479
+ invalid3("install record is missing required fields");
10393
10480
  }
10394
10481
  const host = rec["host"];
10395
10482
  const id = rec["id"];
@@ -10403,7 +10490,7 @@ function parseV1Install(value, index) {
10403
10490
  optionalLegacyString(rec["installedFingerprint"], "installedFingerprint");
10404
10491
  optionalLegacyString(rec["ownership"], "ownership");
10405
10492
  if (rec["pending"] !== undefined && rec["pending"] !== "install" && rec["pending"] !== "remove")
10406
- invalid2("pending must be install or remove");
10493
+ invalid3("pending must be install or remove");
10407
10494
  const record = { host, id, source, sourceSha };
10408
10495
  if (typeof rec["installedAt"] === "string")
10409
10496
  record.installedAt = rec["installedAt"];
@@ -10440,7 +10527,7 @@ function importV1(records) {
10440
10527
  scopes.set(scopeId, scope);
10441
10528
  }
10442
10529
  const relativeDir = legacyRelativeDir(record);
10443
- 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}`)}`;
10444
10531
  state.activations.push({
10445
10532
  scopeId,
10446
10533
  packageId: record.id,
@@ -10475,7 +10562,7 @@ function importV1(records) {
10475
10562
  mutationStarted: true,
10476
10563
  scopeIds: [scopeId],
10477
10564
  journal: [{
10478
- operationId: `legacy-operation-${digest(attemptId)}`,
10565
+ operationId: `legacy-operation-${digest2(attemptId)}`,
10479
10566
  scopeId,
10480
10567
  packageId: record.id,
10481
10568
  nativeId: record.id,
@@ -10534,7 +10621,7 @@ function validateScope(value, label) {
10534
10621
  const target = validateTarget(rec["target"], `${label}.target`);
10535
10622
  const canonicalId = canonicalScopeId(source, target);
10536
10623
  if (id !== canonicalId)
10537
- 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}'`);
10538
10625
  oneOf(rec["authority"], ["legacy-import", "authoritative"], `${label}.authority`);
10539
10626
  oneOf(rec["lifecycle"], ["active", "retiring", "retired"], `${label}.lifecycle`);
10540
10627
  oneOf(rec["selectorMode"], ["legacy-unknown", "all", "explicit", "retired"], `${label}.selectorMode`);
@@ -10545,15 +10632,15 @@ function validateScope(value, label) {
10545
10632
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10546
10633
  optionalTimestamp(rec["retiredAt"], `${label}.retiredAt`);
10547
10634
  if (rec["authority"] === "legacy-import" && (rec["selectorMode"] !== "legacy-unknown" || desired !== undefined || lastConverged !== undefined))
10548
- 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`);
10549
10636
  if (rec["authority"] === "authoritative" && rec["lifecycle"] !== "retired" && desired === undefined)
10550
- invalid2(`deployment scope '${id}' authoritative lifecycle requires desired state`);
10637
+ invalid3(`deployment scope '${id}' authoritative lifecycle requires desired state`);
10551
10638
  if (desired !== undefined && rec["selectorMode"] !== "all" && rec["selectorMode"] !== "explicit")
10552
- 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`);
10553
10640
  if (rec["lifecycle"] === "retired" && rec["selectorMode"] !== "retired")
10554
- invalid2(`deployment scope '${id}' retired lifecycle requires retired selector mode`);
10641
+ invalid3(`deployment scope '${id}' retired lifecycle requires retired selector mode`);
10555
10642
  if (lastConverged !== undefined && desired !== undefined && lastConverged.generation > desired.generation)
10556
- invalid2(`deployment scope '${id}' last-converged generation exceeds desired generation`);
10643
+ invalid3(`deployment scope '${id}' last-converged generation exceeds desired generation`);
10557
10644
  return value;
10558
10645
  }
10559
10646
  function validateSource(value, label) {
@@ -10570,7 +10657,7 @@ function validateTarget(value, label) {
10570
10657
  return parsePersistedTargetIdentity(value, label);
10571
10658
  } catch (error) {
10572
10659
  if (error instanceof TargetIdentityValidationError)
10573
- invalid2(error.message);
10660
+ invalid3(error.message);
10574
10661
  throw error;
10575
10662
  }
10576
10663
  }
@@ -10583,7 +10670,7 @@ function validateDesired(value, label) {
10583
10670
  timestamp(rec["validatedAt"], `${label}.validatedAt`);
10584
10671
  const packages = array(rec["packages"], `${label}.packages`).map((item, index) => validateDesiredPackage(item, `${label}.packages[${index}]`));
10585
10672
  if (packages.length === 0)
10586
- invalid2(`${label}.packages must not be empty; retire the scope explicitly`);
10673
+ invalid3(`${label}.packages must not be empty; retire the scope explicitly`);
10587
10674
  unique(packages.map((item) => item.packageId), `${label} package identity`);
10588
10675
  unique(packages.map((item) => item.nativeId), `${label} native identity`);
10589
10676
  return value;
@@ -10597,7 +10684,7 @@ function validateDesiredPackage(value, label) {
10597
10684
  const capabilities = stringArray(rec["requiredCapabilities"], `${label}.requiredCapabilities`);
10598
10685
  sortedUniqueRequired(capabilities, `${label}.requiredCapabilities`, true);
10599
10686
  if (typeof rec["adoptionRequested"] !== "boolean")
10600
- invalid2(`${label}.adoptionRequested must be a boolean`);
10687
+ invalid3(`${label}.adoptionRequested must be a boolean`);
10601
10688
  return value;
10602
10689
  }
10603
10690
  function validateActivation(value, label) {
@@ -10612,7 +10699,7 @@ function validateActivation(value, label) {
10612
10699
  const route = validateRoute(rec["route"], `${label}.route`);
10613
10700
  const ownershipObject = asObject(rec["ownership"], `${label}.ownership`);
10614
10701
  if (route.kind !== "legacy-unverified" && ownershipObject["kind"] === "legacy-claim")
10615
- invalid2("legacy ownership cannot use a verified route");
10702
+ invalid3("legacy ownership cannot use a verified route");
10616
10703
  const ownership = validateOwnership(ownershipObject, `${label}.ownership`);
10617
10704
  const fingerprints = validateFingerprints(rec["fingerprints"], `${label}.fingerprints`);
10618
10705
  oneOf(rec["activationState"], ["active", "inactive", "retained", "nonconforming", "unknown"], `${label}.activationState`);
@@ -10625,13 +10712,13 @@ function validateActivation(value, label) {
10625
10712
  optionalTimestamp(rec["createdAt"], `${label}.createdAt`);
10626
10713
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10627
10714
  if (route.kind === "legacy-unverified" !== (ownership.kind === "legacy-claim"))
10628
- invalid2(`${label} legacy route and ownership claim must agree`);
10715
+ invalid3(`${label} legacy route and ownership claim must agree`);
10629
10716
  if (route.kind !== "legacy-unverified" && (rec["sourceRevision"] === undefined || fingerprints.source === undefined || fingerprints.projected === undefined || fingerprints.installed === undefined))
10630
- 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`);
10631
10718
  if (rec["readbackState"] === "verified" && fingerprints.installed === undefined)
10632
- invalid2(`${label} verified readback requires an installed fingerprint`);
10719
+ invalid3(`${label} verified readback requires an installed fingerprint`);
10633
10720
  if (pending === undefined && rec["readbackState"] === "pending")
10634
- invalid2(`${label} pending readback requires a pending operation`);
10721
+ invalid3(`${label} pending readback requires a pending operation`);
10635
10722
  return value;
10636
10723
  }
10637
10724
  function validateRoute(value, label) {
@@ -10691,7 +10778,7 @@ function validatePendingSemantics(activation, attempt, journal) {
10691
10778
  const pending = activation.pending;
10692
10779
  const identity = `activation '${activationKey(activation)}'`;
10693
10780
  if (pending.operation !== journal.action)
10694
- invalid2(`${identity} pending operation must match journal action`);
10781
+ invalid3(`${identity} pending operation must match journal action`);
10695
10782
  const isRetirement = pending.operation === "retire-orphan" || pending.operation === "remove";
10696
10783
  let journalState;
10697
10784
  let attemptPhases;
@@ -10724,11 +10811,11 @@ function validatePendingSemantics(activation, attempt, journal) {
10724
10811
  break;
10725
10812
  }
10726
10813
  if (journal.state !== journalState)
10727
- invalid2(`${identity} pending phase ${pending.phase} requires journal ${journalState}`);
10814
+ invalid3(`${identity} pending phase ${pending.phase} requires journal ${journalState}`);
10728
10815
  if (!attemptPhases.includes(attempt.phase))
10729
- invalid2(`${identity} pending phase ${pending.phase} requires attempt ${attemptPhases.join(" or ")}`);
10816
+ invalid3(`${identity} pending phase ${pending.phase} requires attempt ${attemptPhases.join(" or ")}`);
10730
10817
  if (attempt.mutationStarted !== mutationStarted)
10731
- invalid2(`${identity} ${pending.phase} pending work requires mutationStarted ${String(mutationStarted)}`);
10818
+ invalid3(`${identity} ${pending.phase} pending work requires mutationStarted ${String(mutationStarted)}`);
10732
10819
  }
10733
10820
  function validateAttempt(value, label) {
10734
10821
  const rec = asObject(value, label);
@@ -10738,31 +10825,31 @@ function validateAttempt(value, label) {
10738
10825
  oneOf(rec["phase"], ["accepted", "applying", "readback", "pruning", "finalizing", "completed", "failed", "recovery-required"], `${label}.phase`);
10739
10826
  const mutationStarted = rec["mutationStarted"];
10740
10827
  if (typeof mutationStarted !== "boolean")
10741
- invalid2(`${label}.mutationStarted must be a boolean`);
10828
+ invalid3(`${label}.mutationStarted must be a boolean`);
10742
10829
  const scopeIds = stringArray(rec["scopeIds"], `${label}.scopeIds`);
10743
10830
  if (scopeIds.length === 0)
10744
- invalid2(`${label}.scopeIds must not be empty`);
10831
+ invalid3(`${label}.scopeIds must not be empty`);
10745
10832
  unique(scopeIds, `${label}.scopeIds`);
10746
10833
  const journal = array(rec["journal"], `${label}.journal`).map((item, index) => validateJournal(item, `${label}.journal[${index}]`));
10747
10834
  for (const entry of journal) {
10748
10835
  if (!commandAllowsJournalAction(command, entry.action))
10749
- invalid2(`${label} command ${command} cannot journal action ${entry.action}`);
10836
+ invalid3(`${label} command ${command} cannot journal action ${entry.action}`);
10750
10837
  }
10751
10838
  const journalShowsMutation = journal.some((entry) => MUTATION_STARTED_JOURNAL_STATES.has(entry.state));
10752
10839
  if (journalShowsMutation && !mutationStarted)
10753
- invalid2(`${label} journal shows mutation began but mutationStarted is false`);
10840
+ invalid3(`${label} journal shows mutation began but mutationStarted is false`);
10754
10841
  if (mutationStarted && !journalShowsMutation)
10755
- 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`);
10756
10843
  optionalTimestamp(rec["startedAt"], `${label}.startedAt`);
10757
10844
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10758
10845
  optionalTimestamp(rec["completedAt"], `${label}.completedAt`);
10759
10846
  if (rec["phase"] === "completed" && rec["completedAt"] === undefined)
10760
- invalid2(`${label} completed attempt requires completedAt`);
10847
+ invalid3(`${label} completed attempt requires completedAt`);
10761
10848
  return value;
10762
10849
  }
10763
10850
  function validateJournal(value, label) {
10764
10851
  const rec = asObject(value, label);
10765
- 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");
10766
10853
  requiredString(rec["operationId"], `${label}.operationId`);
10767
10854
  requiredString(rec["scopeId"], `${label}.scopeId`);
10768
10855
  optionalString(rec["packageId"], `${label}.packageId`);
@@ -10771,6 +10858,20 @@ function validateJournal(value, label) {
10771
10858
  oneOf(rec["state"], ["pending", "applying", "applied", "readback-verified", "rollback", "rolled-back", "cleanup-pending", "completed", "failed", "not-attempted"], `${label}.state`);
10772
10859
  if (rec["route"] !== undefined)
10773
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
+ }
10774
10875
  optionalTimestamp(rec["startedAt"], `${label}.startedAt`);
10775
10876
  optionalTimestamp(rec["updatedAt"], `${label}.updatedAt`);
10776
10877
  return value;
@@ -10803,19 +10904,19 @@ function validateTombstone(value, label) {
10803
10904
  const route = validateRoute(rec["route"], `${label}.route`);
10804
10905
  const ownershipObject = asObject(rec["ownership"], `${label}.ownership`);
10805
10906
  if (ownershipObject["kind"] === "legacy-claim")
10806
- invalid2("tombstone ownership must be revalidated before retirement");
10907
+ invalid3("tombstone ownership must be revalidated before retirement");
10807
10908
  const ownership = validateOwnership(ownershipObject, `${label}.ownership`);
10808
10909
  const fingerprints = validateFingerprints(rec["fingerprints"], `${label}.fingerprints`);
10809
10910
  const pins = stringArray(rec["pins"], `${label}.pins`);
10810
10911
  sortedUniqueRequired(pins, `${label}.pins`, true);
10811
10912
  if (rec["retentionState"] !== "plugin-state-retained")
10812
- invalid2(`${label}.retentionState must be plugin-state-retained`);
10913
+ invalid3(`${label}.retentionState must be plugin-state-retained`);
10813
10914
  optionalTimestamp(rec["activatedAt"], `${label}.activatedAt`);
10814
10915
  timestamp(rec["retiredAt"], `${label}.retiredAt`);
10815
10916
  if (route.kind === "legacy-unverified" || ownership.kind === "legacy-claim")
10816
- invalid2("tombstone ownership must be revalidated before retirement");
10917
+ invalid3("tombstone ownership must be revalidated before retirement");
10817
10918
  if (fingerprints.source === undefined || fingerprints.projected === undefined || fingerprints.installed === undefined)
10818
- invalid2(`${label} requires source, projected, and installed fingerprints`);
10919
+ invalid3(`${label} requires source, projected, and installed fingerprints`);
10819
10920
  return value;
10820
10921
  }
10821
10922
  function legacySourceBinding(source) {
@@ -10826,7 +10927,7 @@ function legacySourceBinding(source) {
10826
10927
  return binding;
10827
10928
  }
10828
10929
  if (!isAbsolute4(source))
10829
- invalid2(`legacy local source must be an absolute path: ${source}`);
10930
+ invalid3(`legacy local source must be an absolute path: ${source}`);
10830
10931
  const binding = { kind: "local", locator: source };
10831
10932
  validateCanonicalSourceBinding(binding, "legacy source");
10832
10933
  return binding;
@@ -10835,7 +10936,7 @@ function validateCanonicalSourceBinding(source, label) {
10835
10936
  try {
10836
10937
  validateSourceBinding(source);
10837
10938
  } catch (error) {
10838
- invalid2(`${label}: ${error.message}`);
10939
+ invalid3(`${label}: ${error.message}`);
10839
10940
  }
10840
10941
  }
10841
10942
  function compatibilitySourceUri(source) {
@@ -10848,7 +10949,7 @@ function canonicalScopeId(source, target) {
10848
10949
  try {
10849
10950
  return createDeploymentScopeIdentity(source, target).id;
10850
10951
  } catch (error) {
10851
- invalid2(error.message);
10952
+ invalid3(error.message);
10852
10953
  }
10853
10954
  }
10854
10955
  function legacyRelativeDir(record) {
@@ -10861,7 +10962,7 @@ function legacyRelativeDir(record) {
10861
10962
  return;
10862
10963
  return candidate;
10863
10964
  }
10864
- function digest(value) {
10965
+ function digest2(value) {
10865
10966
  const hash = new CryptoHasher("sha256");
10866
10967
  hash.update(value);
10867
10968
  return hash.digest("hex").slice(0, 24);
@@ -10877,59 +10978,59 @@ function pendingV1(operation) {
10877
10978
  }
10878
10979
  function asObject(value, label) {
10879
10980
  if (typeof value !== "object" || value === null || Array.isArray(value))
10880
- invalid2(`${label} must be an object`);
10981
+ invalid3(`${label} must be an object`);
10881
10982
  return value;
10882
10983
  }
10883
10984
  function exactFields2(record, allowed, label) {
10884
10985
  const allowedSet = new Set(allowed);
10885
10986
  const unknown = Object.keys(record).find((key) => !allowedSet.has(key));
10886
10987
  if (unknown !== undefined)
10887
- invalid2(`unsupported ${label} field '${unknown}'`);
10988
+ invalid3(`unsupported ${label} field '${unknown}'`);
10888
10989
  }
10889
10990
  function array(value, label) {
10890
10991
  if (!Array.isArray(value))
10891
- invalid2(`${label} must be an array`);
10992
+ invalid3(`${label} must be an array`);
10892
10993
  return value;
10893
10994
  }
10894
10995
  function requiredString(value, label) {
10895
10996
  if (typeof value !== "string" || value.trim() === "")
10896
- invalid2(`${label} must be a non-empty string`);
10997
+ invalid3(`${label} must be a non-empty string`);
10897
10998
  return value;
10898
10999
  }
10899
11000
  function optionalString(value, label) {
10900
11001
  if (value !== undefined && (typeof value !== "string" || value.length === 0))
10901
- invalid2(`${label} must be a non-empty string`);
11002
+ invalid3(`${label} must be a non-empty string`);
10902
11003
  }
10903
11004
  function stringArray(value, label) {
10904
11005
  const values = array(value, label);
10905
11006
  if (values.some((item) => typeof item !== "string" || item.length === 0))
10906
- invalid2(`${label} must contain non-empty strings`);
11007
+ invalid3(`${label} must contain non-empty strings`);
10907
11008
  return values;
10908
11009
  }
10909
11010
  function optionalLegacyString(value, label) {
10910
11011
  if (value !== undefined && typeof value !== "string")
10911
- invalid2(`${label} must be a string`);
11012
+ invalid3(`${label} must be a string`);
10912
11013
  }
10913
11014
  function optionalLegacyPins(value) {
10914
11015
  if (value === undefined)
10915
11016
  return;
10916
11017
  if (!Array.isArray(value) || value.some((pin) => typeof pin !== "string" || pin.length === 0))
10917
- invalid2("pins must be non-empty strings");
11018
+ invalid3("pins must be non-empty strings");
10918
11019
  return value;
10919
11020
  }
10920
11021
  function safeInteger(value, label, minimum) {
10921
11022
  if (!Number.isSafeInteger(value) || value < minimum)
10922
- invalid2(`${label} must be a safe integer >= ${minimum}`);
11023
+ invalid3(`${label} must be a safe integer >= ${minimum}`);
10923
11024
  return value;
10924
11025
  }
10925
11026
  function oneOf(value, values, label) {
10926
11027
  if (typeof value !== "string" || !values.includes(value))
10927
- invalid2(`${label} must be one of: ${values.join(", ")}`);
11028
+ invalid3(`${label} must be one of: ${values.join(", ")}`);
10928
11029
  return value;
10929
11030
  }
10930
11031
  function timestamp(value, label) {
10931
11032
  if (typeof value !== "string" || !validTimestamp(value))
10932
- invalid2(`${label} must be an ISO-8601 UTC timestamp`);
11033
+ invalid3(`${label} must be an ISO-8601 UTC timestamp`);
10933
11034
  return value;
10934
11035
  }
10935
11036
  function optionalTimestamp(value, label) {
@@ -10948,21 +11049,21 @@ function relativePath(value, label) {
10948
11049
  return path;
10949
11050
  const segments = path.split("/");
10950
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 === ".."))
10951
- invalid2(`${label} must be a canonical Source-relative path`);
11052
+ invalid3(`${label} must be a canonical Source-relative path`);
10952
11053
  return path;
10953
11054
  }
10954
11055
  function contentAddress(value, label) {
10955
11056
  const key = requiredString(value, label);
10956
11057
  if (!/^sha256:[0-9a-f]{64}$/u.test(key))
10957
- invalid2(`${label} must be sha256 followed by 64 lowercase hexadecimal characters`);
11058
+ invalid3(`${label} must be sha256 followed by 64 lowercase hexadecimal characters`);
10958
11059
  return key;
10959
11060
  }
10960
11061
  function sortedUniqueRequired(values, label, allowEmpty = false) {
10961
11062
  if (!allowEmpty && values.length === 0)
10962
- invalid2(`${label} must not be empty`);
11063
+ invalid3(`${label} must not be empty`);
10963
11064
  const sorted = [...new Set(values)].sort();
10964
11065
  if (sorted.length !== values.length || sorted.some((value, index) => value !== values[index]))
10965
- invalid2(`${label} must be sorted and unique`);
11066
+ invalid3(`${label} must be sorted and unique`);
10966
11067
  }
10967
11068
  function sortedUnique(values) {
10968
11069
  return [...new Set(values)].sort();
@@ -10971,7 +11072,7 @@ function unique(values, label) {
10971
11072
  const seen = new Set;
10972
11073
  for (const value of values) {
10973
11074
  if (seen.has(value))
10974
- invalid2(`duplicate ${label}: ${value}`);
11075
+ invalid3(`duplicate ${label}: ${value}`);
10975
11076
  seen.add(value);
10976
11077
  }
10977
11078
  }
@@ -10981,10 +11082,25 @@ function removeUndefined(record) {
10981
11082
  delete record[key];
10982
11083
  return record;
10983
11084
  }
10984
- function invalid2(message) {
11085
+ function invalid3(message) {
10985
11086
  throw new Error(`Invalid state.json: ${message}`);
10986
11087
  }
10987
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
+
10988
11104
  // src/fingerprint.ts
10989
11105
  import { lstatSync as lstatSync6, readFileSync as readFileSync12, readdirSync as readdirSync7 } from "node:fs";
10990
11106
  import { isAbsolute as isAbsolute5, join as join17 } from "node:path";
@@ -11247,6 +11363,10 @@ function checkStaleness(host, state, out) {
11247
11363
  function runDoctor(hostList = hosts, state = readState()) {
11248
11364
  const findings = [];
11249
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
+ }
11250
11370
  for (const record of state) {
11251
11371
  if (record.pending !== undefined && selected.has(record.host)) {
11252
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` });
@@ -12448,14 +12568,14 @@ function exitCodeForLifecycleReport(report) {
12448
12568
  }
12449
12569
  function parseLifecycleReport(value) {
12450
12570
  if (!isObject2(value)) {
12451
- throw invalid3("lifecycle report must be an object");
12571
+ throw invalid4("lifecycle report must be an object");
12452
12572
  }
12453
12573
  exactFields3(value, ["schemaVersion", "command", "plan", "outcomes", "summary"], "lifecycle report");
12454
12574
  if (value["schemaVersion"] !== LIFECYCLE_REPORT_SCHEMA_VERSION) {
12455
- throw invalid3("lifecycle report schemaVersion must be 1");
12575
+ throw invalid4("lifecycle report schemaVersion must be 1");
12456
12576
  }
12457
12577
  if (!isObject2(value["command"]) || !Array.isArray(value["plan"]) || !Array.isArray(value["outcomes"]) || !isObject2(value["summary"])) {
12458
- throw invalid3("lifecycle report must contain command, plan, outcomes, and summary");
12578
+ throw invalid4("lifecycle report must contain command, plan, outcomes, and summary");
12459
12579
  }
12460
12580
  const command = value["command"];
12461
12581
  const plan = value["plan"];
@@ -12464,19 +12584,19 @@ function parseLifecycleReport(value) {
12464
12584
  assertCommand(command);
12465
12585
  const commandName = command["name"];
12466
12586
  if (!plan.every(isObject2) || !outcomes.every(isObject2))
12467
- throw invalid3("lifecycle plan and outcomes must contain objects");
12587
+ throw invalid4("lifecycle plan and outcomes must contain objects");
12468
12588
  const planned = new Map;
12469
12589
  const plannedPairs = new Set;
12470
12590
  for (const operation of plan) {
12471
12591
  assertPlanOperation(operation, command["sourceSnapshots"], commandName);
12472
12592
  const operationId = operation["operationId"];
12473
12593
  if (typeof operationId !== "string" || operationId.length === 0)
12474
- throw invalid3("every planned operation needs a nonempty operationId");
12594
+ throw invalid4("every planned operation needs a nonempty operationId");
12475
12595
  if (planned.has(operationId))
12476
- throw invalid3(`duplicate planned operation '${operationId}'`);
12596
+ throw invalid4(`duplicate planned operation '${operationId}'`);
12477
12597
  const pair = JSON.stringify([operation["scope"].id, operation["package"]]);
12478
12598
  if (plannedPairs.has(pair))
12479
- throw invalid3(`duplicate planned package pair for '${operation["package"]}'`);
12599
+ throw invalid4(`duplicate planned package pair for '${operation["package"]}'`);
12480
12600
  plannedPairs.add(pair);
12481
12601
  planned.set(operationId, operation);
12482
12602
  }
@@ -12485,7 +12605,7 @@ function parseLifecycleReport(value) {
12485
12605
  assertOutcome(outcome, command["sourceSnapshots"], command["dryRun"], commandName);
12486
12606
  const operationId = outcome["operationId"];
12487
12607
  if (typeof operationId !== "string" || operationId.length === 0)
12488
- throw invalid3("every outcome needs a nonempty operationId");
12608
+ throw invalid4("every outcome needs a nonempty operationId");
12489
12609
  if (seen.has(operationId))
12490
12610
  throw protocol("protocol.duplicate-outcome", `duplicate outcome for planned operation '${operationId}'`);
12491
12611
  seen.add(operationId);
@@ -12516,7 +12636,7 @@ var PLAN_IDENTITY_FIELDS = [
12516
12636
  "action",
12517
12637
  "route"
12518
12638
  ];
12519
- function invalid3(diagnostic) {
12639
+ function invalid4(diagnostic) {
12520
12640
  return protocol("protocol.invalid-report", diagnostic);
12521
12641
  }
12522
12642
  function contradiction(diagnostic) {
@@ -12528,20 +12648,20 @@ function protocol(code, diagnostic) {
12528
12648
  function assertCommand(command) {
12529
12649
  exactFields3(command, ["name", "dryRun", "sourceSnapshots"], "lifecycle command");
12530
12650
  if (!oneOf2(LIFECYCLE_COMMANDS, command["name"]) || typeof command["dryRun"] !== "boolean" || !Array.isArray(command["sourceSnapshots"])) {
12531
- 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");
12532
12652
  }
12533
12653
  const ids = new Set;
12534
12654
  for (const context of command["sourceSnapshots"]) {
12535
12655
  if (!isObject2(context) || !nonempty(context["id"]) || !isObject2(context["reference"]))
12536
- throw invalid3("source snapshot context is malformed");
12656
+ throw invalid4("source snapshot context is malformed");
12537
12657
  exactFields3(context, ["id", "reference"], "source snapshot context");
12538
12658
  if (ids.has(context["id"]))
12539
- throw invalid3(`duplicate source snapshot '${context["id"]}'`);
12659
+ throw invalid4(`duplicate source snapshot '${context["id"]}'`);
12540
12660
  ids.add(context["id"]);
12541
12661
  const reference = context["reference"];
12542
12662
  exactFields3(reference, ["binding", "revision", "fingerprint"], `source snapshot '${context["id"]}' reference`);
12543
12663
  if (!isObject2(reference["binding"]) || !nonempty(reference["revision"]) || !nonempty(reference["fingerprint"]))
12544
- throw invalid3(`source snapshot '${context["id"]}' is malformed`);
12664
+ throw invalid4(`source snapshot '${context["id"]}' is malformed`);
12545
12665
  const binding = reference["binding"];
12546
12666
  assertSourceBinding(binding, `source snapshot '${context["id"]}'`);
12547
12667
  }
@@ -12568,27 +12688,27 @@ var OUTCOME_FIELDS = [
12568
12688
  function assertPlanOperation(operation, snapshots, command, shape = "plan") {
12569
12689
  exactFields3(operation, shape === "plan" ? PLAN_OPERATION_FIELDS : OUTCOME_FIELDS, `lifecycle ${shape}`);
12570
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"])) {
12571
- throw invalid3("planned operation contains a missing or invalid field");
12691
+ throw invalid4("planned operation contains a missing or invalid field");
12572
12692
  }
12573
12693
  assertScope(operation["scope"]);
12574
12694
  if (operation["sourceSnapshotId"] !== null && !snapshots.some((snapshot) => snapshot.id === operation["sourceSnapshotId"])) {
12575
- throw invalid3(`planned operation '${operation["operationId"]}' references an unknown source snapshot`);
12695
+ throw invalid4(`planned operation '${operation["operationId"]}' references an unknown source snapshot`);
12576
12696
  }
12577
12697
  const snapshot = snapshots.find((candidate) => candidate.id === operation["sourceSnapshotId"]);
12578
12698
  if (snapshot !== undefined && !sameSourceBinding(snapshot.reference.binding, operation["scope"].source)) {
12579
- throw invalid3(`planned operation '${operation["operationId"]}' scope contradicts its source snapshot`);
12699
+ throw invalid4(`planned operation '${operation["operationId"]}' scope contradicts its source snapshot`);
12580
12700
  }
12581
12701
  if (operation["coverage"] === "retirement" && operation["action"] !== "retire-orphan" && operation["action"] !== "not-attempted") {
12582
- throw invalid3(`orphan retirement '${operation["operationId"]}' has a non-retirement action`);
12702
+ throw invalid4(`orphan retirement '${operation["operationId"]}' has a non-retirement action`);
12583
12703
  }
12584
12704
  if (operation["coverage"] === "desired-pair" && operation["action"] === "retire-orphan") {
12585
- throw invalid3(`Desired pair '${operation["operationId"]}' has a retirement action`);
12705
+ throw invalid4(`Desired pair '${operation["operationId"]}' has a retirement action`);
12586
12706
  }
12587
12707
  if ((command === "add" || command === "update") && operation["coverage"] !== "desired-pair") {
12588
- throw invalid3(`${command} cannot contain retirement coverage`);
12708
+ throw invalid4(`${command} cannot contain retirement coverage`);
12589
12709
  }
12590
12710
  if ((command === "remove" || command === "retire-source") && operation["coverage"] !== "retirement") {
12591
- throw invalid3(`${command} cannot contain desired-pair coverage`);
12711
+ throw invalid4(`${command} cannot contain desired-pair coverage`);
12592
12712
  }
12593
12713
  if (operation["action"] === "not-attempted" !== (operation["route"] === "none")) {
12594
12714
  throw contradiction(`planned operation '${operation["operationId"]}' must pair action not-attempted with route none`);
@@ -12597,7 +12717,7 @@ function assertPlanOperation(operation, snapshots, command, shape = "plan") {
12597
12717
  function assertOutcome(outcome, snapshots, dryRun, command) {
12598
12718
  assertPlanOperation(outcome, snapshots, command, "outcome");
12599
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"]))) {
12600
- throw invalid3(`outcome '${outcome["operationId"]}' contains a missing or invalid field`);
12720
+ throw invalid4(`outcome '${outcome["operationId"]}' contains a missing or invalid field`);
12601
12721
  }
12602
12722
  const reason = outcome["reason"];
12603
12723
  if (reason !== null)
@@ -12620,7 +12740,7 @@ function assertOutcome(outcome, snapshots, dryRun, command) {
12620
12740
  const notices = outcome["notices"];
12621
12741
  if (notices !== undefined) {
12622
12742
  if (!Array.isArray(notices) || notices.some((item) => typeof item !== "string" || item.length === 0)) {
12623
- throw invalid3(`outcome '${outcome["operationId"]}' notices must be non-empty strings`);
12743
+ throw invalid4(`outcome '${outcome["operationId"]}' notices must be non-empty strings`);
12624
12744
  }
12625
12745
  if (outcome["result"] !== "succeeded")
12626
12746
  throw contradiction(`outcome '${outcome["operationId"]}' can report a dropped pin only after success`);
@@ -12692,7 +12812,7 @@ function isKnownSafeReadbackState(resourceState, activationState) {
12692
12812
  function assertSummary(summary, outcomes, dryRun) {
12693
12813
  exactFields3(summary, ["result", "terminalPhase", "mutationStarted", "changed", "failureCategory", "reason", "recoveryId", "readbackId"], "lifecycle summary");
12694
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"])) {
12695
- throw invalid3("lifecycle summary contains a missing or invalid field");
12815
+ throw invalid4("lifecycle summary contains a missing or invalid field");
12696
12816
  }
12697
12817
  const reason = summary["reason"];
12698
12818
  if (reason !== null)
@@ -12780,21 +12900,21 @@ function assertSummary(summary, outcomes, dryRun) {
12780
12900
  function assertReason(reason) {
12781
12901
  exactFields3(reason, ["category", "code", "diagnostic", "capabilityId", "evidenceId"], "lifecycle reason");
12782
12902
  if (!oneOf2(LIFECYCLE_REASON_CATEGORIES, reason["category"]) || !oneOf2(LIFECYCLE_REASON_CODES, reason["code"]) || !hasText(reason["diagnostic"]) || !nullableText(reason["capabilityId"]) || !nullableText(reason["evidenceId"])) {
12783
- throw invalid3("lifecycle reason contains a missing or invalid field");
12903
+ throw invalid4("lifecycle reason contains a missing or invalid field");
12784
12904
  }
12785
12905
  if (reason["code"].slice(0, reason["code"].indexOf(".")) !== reason["category"]) {
12786
- 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"]}'`);
12787
12907
  }
12788
12908
  if (reason["category"] === "capability" && reason["capabilityId"] === null)
12789
- throw invalid3("capability reason needs a capabilityId");
12909
+ throw invalid4("capability reason needs a capabilityId");
12790
12910
  if (reason["category"] !== "capability" && (reason["capabilityId"] !== null || reason["evidenceId"] !== null)) {
12791
- throw invalid3("only capability reasons may carry capability or evidence identifiers");
12911
+ throw invalid4("only capability reasons may carry capability or evidence identifiers");
12792
12912
  }
12793
12913
  }
12794
12914
  function assertScope(scope) {
12795
12915
  exactFields3(scope, ["id", "source", "target"], "deployment scope");
12796
12916
  if (!nonempty(scope["id"]) || !isObject2(scope["source"]) || !isObject2(scope["target"])) {
12797
- throw invalid3("planned operation has a malformed deployment scope");
12917
+ throw invalid4("planned operation has a malformed deployment scope");
12798
12918
  }
12799
12919
  assertSourceBinding(scope["source"], "planned operation deployment scope");
12800
12920
  exactFields3(scope["target"], ["kind", "instance"], "deployment scope target");
@@ -12802,28 +12922,28 @@ function assertScope(scope) {
12802
12922
  try {
12803
12923
  canonical = createDeploymentScopeIdentity(scope["source"], scope["target"]);
12804
12924
  } catch (error) {
12805
- throw invalid3(`planned operation has an invalid deployment scope: ${unknownErrorDiagnostic(error)}`);
12925
+ throw invalid4(`planned operation has an invalid deployment scope: ${unknownErrorDiagnostic(error)}`);
12806
12926
  }
12807
12927
  if (!sameScope(scope, canonical)) {
12808
- 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");
12809
12929
  }
12810
12930
  }
12811
12931
  function assertSourceBinding(binding, label) {
12812
12932
  if (binding["kind"] === "local") {
12813
12933
  exactFields3(binding, ["kind", "locator"], `${label} Source binding`);
12814
12934
  if (!nonempty(binding["locator"]))
12815
- throw invalid3(`${label} local Source binding needs a locator`);
12935
+ throw invalid4(`${label} local Source binding needs a locator`);
12816
12936
  } else if (binding["kind"] === "git") {
12817
12937
  exactFields3(binding, ["kind", "locator", "ref"], `${label} Source binding`);
12818
12938
  if (!nonempty(binding["locator"]) || !nonempty(binding["ref"]))
12819
- throw invalid3(`${label} git Source binding needs a locator and ref`);
12939
+ throw invalid4(`${label} git Source binding needs a locator and ref`);
12820
12940
  } else {
12821
- throw invalid3(`${label} Source binding has an unknown kind`);
12941
+ throw invalid4(`${label} Source binding has an unknown kind`);
12822
12942
  }
12823
12943
  try {
12824
12944
  validateSourceBinding(binding);
12825
12945
  } catch (error) {
12826
- throw invalid3(`${label} has an invalid Source binding: ${unknownErrorDiagnostic(error)}`);
12946
+ throw invalid4(`${label} has an invalid Source binding: ${unknownErrorDiagnostic(error)}`);
12827
12947
  }
12828
12948
  }
12829
12949
  function sameScope(left, right) {
@@ -12851,7 +12971,7 @@ function exactFields3(record, allowed, label) {
12851
12971
  const fields = new Set(allowed);
12852
12972
  const unknown = Object.keys(record).find((key) => !fields.has(key));
12853
12973
  if (unknown !== undefined)
12854
- throw invalid3(`${label} has unsupported field '${unknown}'`);
12974
+ throw invalid4(`${label} has unsupported field '${unknown}'`);
12855
12975
  }
12856
12976
  function isObject2(value) {
12857
12977
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -12904,21 +13024,21 @@ function inventoryPackageSemantics(plugin) {
12904
13024
  const manifest = manifests[0];
12905
13025
  if (manifest?.mcpServers !== undefined) {
12906
13026
  if (!isRecord4(manifest.mcpServers))
12907
- invalid4("plugin manifest mcpServers must be an object");
13027
+ invalid5("plugin manifest mcpServers must be an object");
12908
13028
  mcp.push(`${manifest.path}#mcpServers`);
12909
13029
  }
12910
13030
  const autoUpdate = [];
12911
13031
  if (manifest !== undefined && manifest.extensions !== undefined) {
12912
13032
  const extension = manifest.extensions;
12913
13033
  if (!isRecord4(extension))
12914
- invalid4("plugin manifest extensions must be an object");
13034
+ invalid5("plugin manifest extensions must be an object");
12915
13035
  const dcode = extension["com.langchain.deepagents.code"];
12916
13036
  if (dcode !== undefined) {
12917
13037
  if (!isRecord4(dcode))
12918
- invalid4("dcode plugin manifest extension must be an object");
13038
+ invalid5("dcode plugin manifest extension must be an object");
12919
13039
  if (Object.hasOwn(dcode, "autoUpdate")) {
12920
13040
  if (typeof dcode["autoUpdate"] !== "boolean")
12921
- invalid4("dcode plugin manifest autoUpdate must be boolean");
13041
+ invalid5("dcode plugin manifest autoUpdate must be boolean");
12922
13042
  autoUpdate.push({
12923
13043
  dialect: "dcode-manifest-extension",
12924
13044
  path: manifest.path,
@@ -12999,7 +13119,7 @@ function requiredSemanticsForOperation(inventory, operation) {
12999
13119
  ]));
13000
13120
  }
13001
13121
  if (inventory === undefined)
13002
- invalid4(`${operation} semantic admission requires Source inventory`);
13122
+ invalid5(`${operation} semantic admission requires Source inventory`);
13003
13123
  const required = new Set(inventory.requiredSemantics);
13004
13124
  required.add("readback");
13005
13125
  required.add("rollback");
@@ -13017,7 +13137,7 @@ function inventorySkillPolicy(root, skill) {
13017
13137
  continue;
13018
13138
  const value = frontmatter[field];
13019
13139
  if (typeof value !== "boolean")
13020
- invalid4(`skill invocation policy ${field} must be boolean: ${skill}`);
13140
+ invalid5(`skill invocation policy ${field} must be boolean: ${skill}`);
13021
13141
  declarations.push({ dialect: "claude-frontmatter", path: skill, field, direction: "model", allowed: !value });
13022
13142
  }
13023
13143
  for (const field of USER_INVOCATION_ALIASES) {
@@ -13025,7 +13145,7 @@ function inventorySkillPolicy(root, skill) {
13025
13145
  continue;
13026
13146
  const value = frontmatter[field];
13027
13147
  if (typeof value !== "boolean")
13028
- invalid4(`skill invocation policy ${field} must be boolean: ${skill}`);
13148
+ invalid5(`skill invocation policy ${field} must be boolean: ${skill}`);
13029
13149
  declarations.push({ dialect: "claude-frontmatter", path: skill, field, direction: "user", allowed: value });
13030
13150
  }
13031
13151
  }
@@ -13035,11 +13155,11 @@ function inventorySkillPolicy(root, skill) {
13035
13155
  const policy = parsed["policy"];
13036
13156
  if (policy !== undefined) {
13037
13157
  if (!isRecord4(policy))
13038
- invalid4(`Codex invocation sidecar policy must be an object: ${sidecar}`);
13158
+ invalid5(`Codex invocation sidecar policy must be an object: ${sidecar}`);
13039
13159
  if (Object.hasOwn(policy, "allow_implicit_invocation")) {
13040
13160
  const value = policy["allow_implicit_invocation"];
13041
13161
  if (typeof value !== "boolean")
13042
- invalid4(`Codex invocation sidecar policy must be boolean: ${sidecar}`);
13162
+ invalid5(`Codex invocation sidecar policy must be boolean: ${sidecar}`);
13043
13163
  declarations.push({
13044
13164
  dialect: "codex-sidecar",
13045
13165
  path: sidecar,
@@ -13060,7 +13180,7 @@ function inventorySkillPolicy(root, skill) {
13060
13180
  function onePolicyValue(declarations, direction, skill) {
13061
13181
  const values = new Set(declarations.filter((entry) => entry.direction === direction).map((entry) => entry.allowed));
13062
13182
  if (values.size > 1)
13063
- invalid4(`conflicting ${direction}-invocation policy declarations: ${skill}`);
13183
+ invalid5(`conflicting ${direction}-invocation policy declarations: ${skill}`);
13064
13184
  return values.values().next().value ?? true;
13065
13185
  }
13066
13186
  function openingFrontmatter(raw, path) {
@@ -13072,7 +13192,7 @@ function openingFrontmatter(raw, path) {
13072
13192
  function requiredOpeningFrontmatter(raw, path) {
13073
13193
  const frontmatter = openingFrontmatter(raw, path);
13074
13194
  if (frontmatter === undefined)
13075
- invalid4(`component frontmatter is required: ${path}`);
13195
+ invalid5(`component frontmatter is required: ${path}`);
13076
13196
  return frontmatter;
13077
13197
  }
13078
13198
  function parseYamlObject(raw, label) {
@@ -13080,10 +13200,10 @@ function parseYamlObject(raw, label) {
13080
13200
  try {
13081
13201
  parsed = yamlParse(raw);
13082
13202
  } catch (error) {
13083
- invalid4(`${label} has invalid YAML (${errorDiagnostic(error)})`);
13203
+ invalid5(`${label} has invalid YAML (${errorDiagnostic(error)})`);
13084
13204
  }
13085
13205
  if (!isRecord4(parsed))
13086
- invalid4(`${label} must be an object`);
13206
+ invalid5(`${label} must be an object`);
13087
13207
  return parsed;
13088
13208
  }
13089
13209
  function parseTomlObject(raw, path) {
@@ -13091,10 +13211,10 @@ function parseTomlObject(raw, path) {
13091
13211
  try {
13092
13212
  parsed = parse(raw);
13093
13213
  } catch (error) {
13094
- invalid4(`command ${path} has invalid TOML (${errorDiagnostic(error)})`);
13214
+ invalid5(`command ${path} has invalid TOML (${errorDiagnostic(error)})`);
13095
13215
  }
13096
13216
  if (!isRecord4(parsed))
13097
- invalid4(`command ${path} must be an object`);
13217
+ invalid5(`command ${path} must be an object`);
13098
13218
  return parsed;
13099
13219
  }
13100
13220
  function parseJsonObject(path, label) {
@@ -13102,10 +13222,10 @@ function parseJsonObject(path, label) {
13102
13222
  try {
13103
13223
  parsed = JSON.parse(readFileSync15(path, "utf8"));
13104
13224
  } catch (error) {
13105
- invalid4(`${label} has invalid JSON (${errorDiagnostic(error)})`);
13225
+ invalid5(`${label} has invalid JSON (${errorDiagnostic(error)})`);
13106
13226
  }
13107
13227
  if (!isRecord4(parsed))
13108
- invalid4(`${label} must be an object`);
13228
+ invalid5(`${label} must be an object`);
13109
13229
  return parsed;
13110
13230
  }
13111
13231
  function readSemanticManifests(root) {
@@ -13130,7 +13250,7 @@ function inventoryHooks(root, files, manifests) {
13130
13250
  return cached;
13131
13251
  const value = parseJsonObject(join21(root, path), `hook configuration ${path}`);
13132
13252
  if (Object.keys(value).some((field) => field !== "hooks") || !isRecord4(value["hooks"])) {
13133
- 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`);
13134
13254
  }
13135
13255
  const observation = validateHookEventMap(value["hooks"], `hook configuration ${path}`);
13136
13256
  documents.set(path, observation);
@@ -13146,7 +13266,7 @@ function inventoryHooks(root, files, manifests) {
13146
13266
  addDocument("hooks/hooks.json", null, "default-file");
13147
13267
  for (const manifest of manifests.filter((candidate) => Object.hasOwn(candidate, "hooks"))) {
13148
13268
  if (manifest.path === ".plugin/plugin.json") {
13149
- 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");
13150
13270
  }
13151
13271
  const declaration = manifest["hooks"];
13152
13272
  const addInline = (value, label, arrayItem) => {
@@ -13154,10 +13274,10 @@ function inventoryHooks(root, files, manifests) {
13154
13274
  let form = arrayItem ? "manifest-inline-array" : "manifest-inline-event-map";
13155
13275
  if (Object.hasOwn(value, "hooks")) {
13156
13276
  if (Object.keys(value).some((field) => field !== "hooks") || !isRecord4(value["hooks"])) {
13157
- invalid4(`inline hook declaration ${label} has an ambiguous hooks wrapper`);
13277
+ invalid5(`inline hook declaration ${label} has an ambiguous hooks wrapper`);
13158
13278
  }
13159
13279
  if (arrayItem)
13160
- 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`);
13161
13281
  eventMap = value["hooks"];
13162
13282
  form = "manifest-inline-wrapped";
13163
13283
  }
@@ -13180,10 +13300,10 @@ function inventoryHooks(root, files, manifests) {
13180
13300
  else if (manifest.path === ".claude-plugin/plugin.json" && isRecord4(item)) {
13181
13301
  addInline(item, `${manifest.path}#hooks[${index}]`, true);
13182
13302
  } else
13183
- invalid4(`plugin manifest ${manifest.path} hooks[${index}] has an unsupported shape`);
13303
+ invalid5(`plugin manifest ${manifest.path} hooks[${index}] has an unsupported shape`);
13184
13304
  }
13185
13305
  } else {
13186
- 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`);
13187
13307
  }
13188
13308
  }
13189
13309
  return {
@@ -13195,54 +13315,54 @@ function inventoryHooks(root, files, manifests) {
13195
13315
  function declaredHookPath(root, declaration) {
13196
13316
  const withoutPrefix = declaration.slice(2);
13197
13317
  if (!declaration.startsWith("./") || declaration.includes("\\") || /^[a-z]:\//iu.test(withoutPrefix)) {
13198
- 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}`);
13199
13319
  }
13200
13320
  const path = portable(relative3(root, join21(root, declaration)));
13201
13321
  if (path === "" || path === ".." || path.startsWith("../") || declaration !== `./${path}`) {
13202
- 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}`);
13203
13323
  }
13204
13324
  const absolute = join21(root, path);
13205
13325
  if (!existsSync16(absolute))
13206
- invalid4(`declared hook configuration does not exist: ${declaration}`);
13326
+ invalid5(`declared hook configuration does not exist: ${declaration}`);
13207
13327
  const stat = lstatSync9(absolute);
13208
13328
  if (stat.isDirectory()) {
13209
13329
  const document = `${path}/hooks.json`;
13210
13330
  const documentPath = join21(root, document);
13211
13331
  if (!existsSync16(documentPath) || !lstatSync9(documentPath).isFile()) {
13212
- invalid4(`declared hook directory must contain hooks.json: ${declaration}`);
13332
+ invalid5(`declared hook directory must contain hooks.json: ${declaration}`);
13213
13333
  }
13214
13334
  return { path: document, directory: true };
13215
13335
  }
13216
13336
  if (!stat.isFile()) {
13217
- 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}`);
13218
13338
  }
13219
13339
  return { path, directory: false };
13220
13340
  }
13221
13341
  function validateHookEventMap(value, label) {
13222
13342
  if (!isRecord4(value))
13223
- invalid4(`${label} must be a hook event object`);
13343
+ invalid5(`${label} must be a hook event object`);
13224
13344
  const events = [];
13225
13345
  const handlerTypes = new Set;
13226
13346
  const groupDeclarations = [];
13227
13347
  const handlerFacets = new Set;
13228
13348
  for (const [event, groups] of Object.entries(value)) {
13229
13349
  if (event.trim() === "" || !Array.isArray(groups))
13230
- invalid4(`${label} event '${event}' must be an array`);
13350
+ invalid5(`${label} event '${event}' must be an array`);
13231
13351
  events.push(event);
13232
13352
  for (const [groupIndex, group] of groups.entries()) {
13233
13353
  if (!isRecord4(group))
13234
- invalid4(`${label} event '${event}' group ${groupIndex} must be an object`);
13354
+ invalid5(`${label} event '${event}' group ${groupIndex} must be an object`);
13235
13355
  if (group["matcher"] !== undefined && typeof group["matcher"] !== "string") {
13236
- invalid4(`${label} event '${event}' group ${groupIndex} matcher must be a string`);
13356
+ invalid5(`${label} event '${event}' group ${groupIndex} matcher must be a string`);
13237
13357
  }
13238
13358
  const matcher = typeof group["matcher"] === "string" ? group["matcher"] : null;
13239
13359
  if (!Array.isArray(group["hooks"]))
13240
- invalid4(`${label} event '${event}' group ${groupIndex} hooks must be an array`);
13360
+ invalid5(`${label} event '${event}' group ${groupIndex} hooks must be an array`);
13241
13361
  const groupHandlerTypes = new Set;
13242
13362
  const groupHandlerFacets = new Set;
13243
13363
  for (const [hookIndex, hook] of group["hooks"].entries()) {
13244
13364
  if (!isRecord4(hook) || typeof hook["type"] !== "string" || hook["type"].trim() === "") {
13245
- 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`);
13246
13366
  }
13247
13367
  validateHookHandler(hook, `${label} event '${event}' group ${groupIndex} hook ${hookIndex}`);
13248
13368
  handlerTypes.add(hook["type"]);
@@ -13311,7 +13431,7 @@ function validateHookHandler(hook, label) {
13311
13431
  return;
13312
13432
  if ((hook["type"] === "prompt" || hook["type"] === "agent") && nonemptyString("prompt"))
13313
13433
  return;
13314
- invalid4(`${label} has an unknown type or is missing required fields`);
13434
+ invalid5(`${label} has an unknown type or is missing required fields`);
13315
13435
  }
13316
13436
  function walkFiles(root) {
13317
13437
  const files = [];
@@ -13322,13 +13442,13 @@ function walkFiles(root) {
13322
13442
  const path = join21(dir, entry);
13323
13443
  const stat = lstatSync9(path);
13324
13444
  if (stat.isSymbolicLink())
13325
- invalid4(`package semantic inventory refuses symlink: ${portable(relative3(root, path))}`);
13445
+ invalid5(`package semantic inventory refuses symlink: ${portable(relative3(root, path))}`);
13326
13446
  if (stat.isDirectory())
13327
13447
  walk(path);
13328
13448
  else if (stat.isFile())
13329
13449
  files.push(portable(relative3(root, path)));
13330
13450
  else
13331
- invalid4(`package semantic inventory refuses unsupported entry: ${portable(relative3(root, path))}`);
13451
+ invalid5(`package semantic inventory refuses unsupported entry: ${portable(relative3(root, path))}`);
13332
13452
  }
13333
13453
  };
13334
13454
  walk(root);
@@ -13344,7 +13464,7 @@ var UNSUPPORTED_COMPONENT_ROOTS = [".codex/commands", ".codex/agents"];
13344
13464
  function inventoryComponentRoots(root) {
13345
13465
  for (const unsupported of UNSUPPORTED_COMPONENT_ROOTS) {
13346
13466
  if (existsSync16(join21(root, unsupported)))
13347
- invalid4(`unsupported component root: ${unsupported}`);
13467
+ invalid5(`unsupported component root: ${unsupported}`);
13348
13468
  }
13349
13469
  const definitions = [];
13350
13470
  const invocationPolicies = [];
@@ -13355,26 +13475,26 @@ function inventoryComponentRoots(root) {
13355
13475
  continue;
13356
13476
  const rootStat = lstatSync9(directory);
13357
13477
  if (!rootStat.isDirectory())
13358
- invalid4(`component root must be a directory: ${spec.root}`);
13478
+ invalid5(`component root must be a directory: ${spec.root}`);
13359
13479
  const entries = readdirSync10(directory).sort();
13360
13480
  if (entries.length === 0)
13361
- invalid4(`component root must not be empty: ${spec.root}`);
13481
+ invalid5(`component root must not be empty: ${spec.root}`);
13362
13482
  const identities = new Set;
13363
13483
  for (const entry of entries) {
13364
13484
  const absolute = join21(directory, entry);
13365
13485
  const path = `${spec.root}/${entry}`;
13366
13486
  const stat = lstatSync9(absolute);
13367
13487
  if (!stat.isFile())
13368
- invalid4(`component root entries must be regular files: ${path}`);
13488
+ invalid5(`component root entries must be regular files: ${path}`);
13369
13489
  const suffix = Object.keys(spec.dialects).find((candidate) => entry.endsWith(candidate));
13370
13490
  if (suffix === undefined)
13371
- invalid4(`unsupported ${spec.kind} dialect: ${path}`);
13491
+ invalid5(`unsupported ${spec.kind} dialect: ${path}`);
13372
13492
  const dialect = spec.dialects[suffix];
13373
13493
  if (dialect === undefined)
13374
- invalid4(`unsupported ${spec.kind} dialect: ${path}`);
13494
+ invalid5(`unsupported ${spec.kind} dialect: ${path}`);
13375
13495
  const parsed = parseComponentDefinition(absolute, path, spec.kind, dialect, entry.slice(0, -suffix.length));
13376
13496
  if (identities.has(parsed.definition.identity)) {
13377
- invalid4(`duplicate ${spec.kind} identity '${parsed.definition.identity}' within ${spec.root}`);
13497
+ invalid5(`duplicate ${spec.kind} identity '${parsed.definition.identity}' within ${spec.root}`);
13378
13498
  }
13379
13499
  identities.add(parsed.definition.identity);
13380
13500
  definitions.push({ ...parsed.definition, root: spec.root });
@@ -13396,16 +13516,16 @@ function parseComponentDefinition(absolute, path, kind, dialect, filenameIdentit
13396
13516
  let body;
13397
13517
  if (dialect === "toml") {
13398
13518
  if (kind !== "command")
13399
- invalid4(`unsupported ${kind} dialect: ${path}`);
13519
+ invalid5(`unsupported ${kind} dialect: ${path}`);
13400
13520
  record = parseTomlObject(raw, path);
13401
13521
  if (typeof record["description"] !== "string" || record["description"].trim() === "" || typeof record["prompt"] !== "string") {
13402
- invalid4(`command TOML needs description and prompt: ${path}`);
13522
+ invalid5(`command TOML needs description and prompt: ${path}`);
13403
13523
  }
13404
13524
  body = record["prompt"];
13405
13525
  } else {
13406
13526
  const frontmatter = requiredOpeningFrontmatter(raw, path);
13407
13527
  if (typeof frontmatter["description"] !== "string" || frontmatter["description"].trim() === "") {
13408
- invalid4(`${kind} description is required: ${path}`);
13528
+ invalid5(`${kind} description is required: ${path}`);
13409
13529
  }
13410
13530
  record = frontmatter;
13411
13531
  const match = OPENING_FRONTMATTER.exec(raw);
@@ -13413,9 +13533,9 @@ function parseComponentDefinition(absolute, path, kind, dialect, filenameIdentit
13413
13533
  }
13414
13534
  const identity = kind === "agent" ? record["name"] : filenameIdentity;
13415
13535
  if (typeof identity !== "string" || identity.trim() === "")
13416
- invalid4(`${kind} identity is required: ${path}`);
13536
+ invalid5(`${kind} identity is required: ${path}`);
13417
13537
  if (kind === "command" && /@\{/u.test(body))
13418
- invalid4(`unsupported command preprocessing: ${path}`);
13538
+ invalid5(`unsupported command preprocessing: ${path}`);
13419
13539
  const declarations = kind === "command" ? componentInvocationDeclarations(record, dialect, path) : [];
13420
13540
  const policy = {
13421
13541
  component: kind,
@@ -13438,7 +13558,7 @@ function componentInvocationDeclarations(record, dialect, path) {
13438
13558
  continue;
13439
13559
  const value = record[field];
13440
13560
  if (typeof value !== "boolean")
13441
- invalid4(`component invocation policy ${field} must be boolean: ${path}`);
13561
+ invalid5(`component invocation policy ${field} must be boolean: ${path}`);
13442
13562
  declarations.push({ dialect: policyDialect, path, field, direction: "model", allowed: !value });
13443
13563
  }
13444
13564
  for (const field of USER_INVOCATION_ALIASES) {
@@ -13446,7 +13566,7 @@ function componentInvocationDeclarations(record, dialect, path) {
13446
13566
  continue;
13447
13567
  const value = record[field];
13448
13568
  if (typeof value !== "boolean")
13449
- invalid4(`component invocation policy ${field} must be boolean: ${path}`);
13569
+ invalid5(`component invocation policy ${field} must be boolean: ${path}`);
13450
13570
  declarations.push({ dialect: policyDialect, path, field, direction: "user", allowed: value });
13451
13571
  }
13452
13572
  onePolicyValue(declarations, "model", path);
@@ -13468,7 +13588,7 @@ function hasAny(record, fields) {
13468
13588
  function isRecord4(value) {
13469
13589
  return typeof value === "object" && value !== null && !Array.isArray(value);
13470
13590
  }
13471
- function invalid4(diagnostic) {
13591
+ function invalid5(diagnostic) {
13472
13592
  throw new SemanticInventoryError(createLifecycleReason("usage", "usage.invalid-argument", diagnostic));
13473
13593
  }
13474
13594
  function errorDiagnostic(error) {
@@ -14226,6 +14346,32 @@ function createLifecycleHostAdapter(definition) {
14226
14346
  const result = await invokePhase("retire", () => definition.retire(prepared));
14227
14347
  return mutationReceipt(prepared.handle, result);
14228
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
+ },
14229
14375
  async readback(handle) {
14230
14376
  assertHandle(definition.id, handle);
14231
14377
  const data = await invokePhase("readback", () => definition.readback(handle));
@@ -14281,6 +14427,68 @@ function assertMutationLeader(handle) {
14281
14427
  throw phaseError("apply-precondition", "internal.invariant", new Error(`operation '${handle.operationId}' is a grouped Native follower; '${handle.mutationInvocationOperationId}' owns the single mutation invocation`));
14282
14428
  }
14283
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
+ }
14284
14492
  function freezeNativeProjectionRequest(request) {
14285
14493
  if ("snapshot" in request) {
14286
14494
  const expectedOperation = request.snapshot.action === "install" ? "install" : "update";
@@ -15058,6 +15266,51 @@ function stringField(value, field) {
15058
15266
  invariant(`${field} must be a string`);
15059
15267
  return result;
15060
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
+ }
15061
15314
 
15062
15315
  // src/hosts/claude-code-writer.ts
15063
15316
  var OWNERSHIP = ".plgnz-install.json";
@@ -19518,9 +19771,9 @@ var ompWriter = {
19518
19771
  const lockFile = join26(pluginsDir3(), "omp-plugins.lock.json");
19519
19772
  const registryFile = join26(pluginsDir3(), "installed_plugins.json");
19520
19773
  const lock = readDoc(lockFile, { plugins: {}, settings: {} }, "lockfile");
19521
- const lockPlugins = object2(lock.plugins, "lockfile plugins");
19774
+ const lockPlugins = object3(lock.plugins, "lockfile plugins");
19522
19775
  const registry = readDoc(registryFile, { version: 2, plugins: {} }, "registry");
19523
- const registryPlugins = object2(registry.plugins, "registry plugins");
19776
+ const registryPlugins = object3(registry.plugins, "registry plugins");
19524
19777
  const rows = userRows(registryPlugins[id]);
19525
19778
  if (rows.length > 0)
19526
19779
  validateLegacy(rows, id, plugin.name, version, opts?.adoptExisting === true);
@@ -19843,7 +20096,7 @@ function readDoc(path, fallback, label) {
19843
20096
  throw new Error(`invalid OMP ${label}: ${path} (${error.message})`);
19844
20097
  }
19845
20098
  }
19846
- function object2(value, label) {
20099
+ function object3(value, label) {
19847
20100
  if (!isDoc(value))
19848
20101
  throw new Error(`invalid OMP ${label}`);
19849
20102
  return value;
@@ -20264,7 +20517,7 @@ async function applyManaged(handle, stagingRoot) {
20264
20517
  const link = managedLink(handle.nativeId);
20265
20518
  const file = lockPath();
20266
20519
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20267
- const plugins = object2(lock.plugins, "lockfile plugins");
20520
+ const plugins = object3(lock.plugins, "lockfile plugins");
20268
20521
  const before = snapshot(file);
20269
20522
  mkdirSafe(dirname9(target));
20270
20523
  mkdirSafe(dirname9(link));
@@ -20294,7 +20547,7 @@ function activationAlreadyCurrent(nativeId, stage, version) {
20294
20547
  if (!existsSync21(target) || !existsSync21(stage) || fingerprintTree(stage) !== fingerprintTree(target) || !linkPointsTo(managedLink(nativeId), target))
20295
20548
  return false;
20296
20549
  const lock = readDoc(lockPath(), { plugins: {}, settings: {} }, "lockfile");
20297
- const plugins = object2(lock.plugins, "lockfile plugins");
20550
+ const plugins = object3(lock.plugins, "lockfile plugins");
20298
20551
  const row = entry(plugins[npmName(nativeId)]);
20299
20552
  return row.enabled === true && row.version === version;
20300
20553
  }
@@ -20302,7 +20555,7 @@ async function disableManaged(handle) {
20302
20555
  const receiptId = `disable:${handle.attemptId}:${handle.operationId}`;
20303
20556
  const file = lockPath();
20304
20557
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20305
- const plugins = object2(lock.plugins, "lockfile plugins");
20558
+ const plugins = object3(lock.plugins, "lockfile plugins");
20306
20559
  const npm = npmName(handle.nativeId);
20307
20560
  const previous = entry(plugins[npm]);
20308
20561
  if (previous.enabled === false)
@@ -20638,6 +20891,7 @@ with FileLock(sys.argv[1]).acquire(timeout=10):
20638
20891
  sys.stdin.read()
20639
20892
  `;
20640
20893
  var nativeLocks = new Map;
20894
+ var recoveringNativeLocks = new Map;
20641
20895
  function nativePython() {
20642
20896
  const override = process.env["OPEN_PLUGIN_DCODE_PYTHON_BIN"];
20643
20897
  if (override) {
@@ -20709,6 +20963,7 @@ async function acquireNativeLock(attemptId, operationId) {
20709
20963
  }
20710
20964
  async function releaseNativeLock(attemptId, operationId) {
20711
20965
  const key = `${attemptId}:${operationId}`, held = nativeLocks.get(key);
20966
+ recoveringNativeLocks.delete(key);
20712
20967
  if (held === undefined)
20713
20968
  return;
20714
20969
  nativeLocks.delete(key);
@@ -20743,7 +20998,7 @@ function observeDcodeTarget(target) {
20743
20998
  throw new Error("unsupported dcode target identity");
20744
20999
  const registry = readRegistry3(dcodeRegistryFile());
20745
21000
  const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
20746
- return Object.entries(object3(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
21001
+ return Object.entries(object4(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
20747
21002
  const rows = rowsFor(raw);
20748
21003
  if (rows.length !== 1)
20749
21004
  throw new Error(`dcode install record ${nativeId} has duplicate rows`);
@@ -20798,7 +21053,7 @@ function captureDcodeRollback(binding, attemptId, operationId) {
20798
21053
  mkdirSync9(dir, { recursive: true });
20799
21054
  const registry = readRegistry3(dcodeRegistryFile());
20800
21055
  const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
20801
- const row = object3(registry.plugins, "registry plugins")[binding.nativeId];
21056
+ const row = object4(registry.plugins, "registry plugins")[binding.nativeId];
20802
21057
  const state = { row: row ?? null, enabled: enabled[binding.nativeId] ?? null, registryExisted: existsSync23(dcodeRegistryFile()), enablementExisted: existsSync23(dcodeEnablementFile()) };
20803
21058
  writeFileSync9(join28(dir, "state.json"), JSON.stringify(state));
20804
21059
  for (const [index, item] of rowsFor(row).entries()) {
@@ -20810,6 +21065,11 @@ function captureDcodeRollback(binding, attemptId, operationId) {
20810
21065
  }
20811
21066
  return dir;
20812
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
+ }
20813
21073
  async function lockPreparation(binding, attemptId, operationId, expectedObservationId) {
20814
21074
  await acquireNativeLock(attemptId, operationId);
20815
21075
  const observed = createTargetInventoryObservation("dcode", { target: binding.target, installations: observeDcodeTarget(binding.target) });
@@ -20840,7 +21100,7 @@ function preflightAdoption(request) {
20840
21100
  if (path === undefined)
20841
21101
  throw new Error("dcode unmanaged adoption has no native root");
20842
21102
  const manifest = declaredManifest(snapshot.packageRoot, snapshot.packageName);
20843
- assessPrior(object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[snapshot.nativeId], snapshot.nativeId, snapshot.nativeGit?.locator ?? `local:${snapshot.scopeId}`, true, snapshot.packageName, manifest.version, snapshot.packageRoot);
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);
20844
21104
  if (fingerprintTree(path) !== installed.installedFingerprint)
20845
21105
  throw new Error("dcode unmanaged adoption changed during preflight");
20846
21106
  }
@@ -20848,7 +21108,7 @@ function setDcodeRow(nativeId, row, enabled) {
20848
21108
  assertManagedPath4(dcodeRegistryFile());
20849
21109
  assertManagedPath4(dcodeEnablementFile());
20850
21110
  const registry = readRegistry3(dcodeRegistryFile()), enablement = readEnablement(dcodeEnablementFile());
20851
- const plugins = { ...object3(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
21111
+ const plugins = { ...object4(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
20852
21112
  if (row === null)
20853
21113
  delete plugins[nativeId];
20854
21114
  else
@@ -20972,13 +21232,13 @@ var dcodeDefinition = {
20972
21232
  },
20973
21233
  disable: async (prepared) => {
20974
21234
  await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
20975
- const row = object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
21235
+ const row = object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
20976
21236
  setDcodeRow(prepared.handle.nativeId, row ?? null, false);
20977
21237
  return { receiptId: `dcode-disable:${prepared.handle.operationId}`, changed: true };
20978
21238
  },
20979
21239
  retire: async (prepared) => {
20980
21240
  await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
20981
- const rows = rowsFor(object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
21241
+ const rows = rowsFor(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
20982
21242
  for (const row of rows) {
20983
21243
  const path = rowInstall(row, prepared.handle.nativeId);
20984
21244
  assertCachePath(path);
@@ -20998,15 +21258,26 @@ var dcodeDefinition = {
20998
21258
  },
20999
21259
  rollback: async (handle) => {
21000
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
+ }
21001
21273
  const target = handle.projectedFingerprint === null ? undefined : lifecycleSlot(handle.nativeId, handle.projectedFingerprint);
21002
21274
  if (target !== undefined)
21003
21275
  assertCachePath(target);
21004
21276
  if (target !== undefined && existsSync23(target) && !handle.prior.contentRoots.some((root) => resolve17(root.path) === resolve17(target))) {
21005
- if (ownership3(target)?.pluginId !== handle.nativeId)
21006
- throw new Error("dcode rollback target is not owned");
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");
21007
21279
  rmSync9(target, { recursive: true });
21008
21280
  }
21009
- const state = readDoc2(join28(handle.rollbackReference, "state.json"), {}, "rollback state");
21010
21281
  for (const [index, row] of rowsFor(state.row).entries()) {
21011
21282
  const path = rowInstall(row, handle.nativeId);
21012
21283
  assertCachePath(path);
@@ -21017,21 +21288,48 @@ var dcodeDefinition = {
21017
21288
  }
21018
21289
  }
21019
21290
  setDcodeRow(handle.nativeId, state.row ?? null, typeof state.enabled === "boolean" ? state.enabled : null);
21020
- if (state.registryExisted === false && Object.keys(object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")).length === 0)
21291
+ if (state.registryExisted === false && Object.keys(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")).length === 0)
21021
21292
  rmSync9(dcodeRegistryFile());
21022
21293
  if (state.enablementExisted === false && Object.keys(boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins")).length === 0)
21023
21294
  rmSync9(dcodeEnablementFile());
21024
21295
  return { receiptId: `dcode-rollback:${handle.operationId}`, changed: true };
21025
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
+ },
21026
21307
  releaseRecovery: async (handle) => {
21027
21308
  await releaseNativeLock(handle.attemptId, handle.operationId);
21028
21309
  },
21029
21310
  cleanup: async (reference, disposition) => {
21030
21311
  try {
21031
21312
  const dir = lifecycleDirectory(reference.attemptId, reference.operationId);
21032
- const next = readDoc2(join28(dir, "rollback", "next-path.json"), {}, "cleanup target");
21033
- if (disposition === "verified-commit" && typeof next.target === "string") {
21034
- const prior = readDoc2(join28(dir, "rollback", "state.json"), {}, "rollback state");
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");
21035
21333
  for (const row of rowsFor(prior.row)) {
21036
21334
  const path = rowInstall(row, reference.operationId);
21037
21335
  assertCachePath(path);
@@ -21067,7 +21365,7 @@ var dcodeWriter = {
21067
21365
  assertManagedPath4(enablementFile);
21068
21366
  const registry = readRegistry3(registryFile);
21069
21367
  const enablement = readEnablement(enablementFile);
21070
- const plugins = object3(registry.plugins, "registry plugins");
21368
+ const plugins = object4(registry.plugins, "registry plugins");
21071
21369
  const enabled = boolObject(enablement.enabledPlugins, "enabledPlugins");
21072
21370
  const manifest = declaredManifest(plugin.dir, plugin.name);
21073
21371
  const observation = probeDcodeVersion();
@@ -21175,7 +21473,7 @@ var dcodeWriter = {
21175
21473
  assertManagedPath4(enablementFile);
21176
21474
  const registry = readRegistry3(registryFile);
21177
21475
  const enablement = readEnablement(enablementFile);
21178
- const plugins = object3(registry.plugins, "registry plugins");
21476
+ const plugins = object4(registry.plugins, "registry plugins");
21179
21477
  const rows = rowsFor(plugins[id]);
21180
21478
  if (rows.length === 0)
21181
21479
  return;
@@ -21269,7 +21567,7 @@ function declaredManifest(dir, expectedName) {
21269
21567
  throw new Error("dcode stage has no supported plugin manifest");
21270
21568
  let parsed;
21271
21569
  try {
21272
- parsed = object3(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21570
+ parsed = object4(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21273
21571
  } catch (error) {
21274
21572
  throw new Error(`invalid dcode manifest: ${manifest} (${error.message})`);
21275
21573
  }
@@ -21292,10 +21590,10 @@ function forceAutoUpdateOff(dir) {
21292
21590
  const manifest = MANIFESTS.map((relative) => join28(dir, relative)).find((file) => existsSync23(file));
21293
21591
  if (manifest === undefined)
21294
21592
  throw new Error("dcode stage has no supported plugin manifest");
21295
- const parsed = object3(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21296
- 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");
21297
21595
  const current = extensions["com.langchain.deepagents.code"];
21298
- const dcodeExtension = current === undefined ? {} : object3(current, "dcode manifest extension");
21596
+ const dcodeExtension = current === undefined ? {} : object4(current, "dcode manifest extension");
21299
21597
  dcodeExtension.autoUpdate = false;
21300
21598
  extensions["com.langchain.deepagents.code"] = dcodeExtension;
21301
21599
  parsed.extensions = extensions;
@@ -21307,7 +21605,7 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
21307
21605
  throw new Error(`dcode readback failed for ${id}`);
21308
21606
  const registry = readRegistry3(registryFile);
21309
21607
  const enablement = readEnablement(enablementFile);
21310
- const rows = rowsFor(object3(registry.plugins, "registry plugins")[id]);
21608
+ const rows = rowsFor(object4(registry.plugins, "registry plugins")[id]);
21311
21609
  if (rows.length !== 1)
21312
21610
  throw new Error(`dcode readback failed for ${id}`);
21313
21611
  const install = rowInstall(rows[0], id);
@@ -21317,7 +21615,7 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
21317
21615
  throw new Error(`dcode managed install is eligible for native auto-update: ${id}`);
21318
21616
  if (declaredManifest(target, id.split("@")[0]).version !== version)
21319
21617
  throw new Error(`dcode readback version mismatch for ${id}`);
21320
- if (boolObject(object3(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
21618
+ if (boolObject(object4(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
21321
21619
  throw new Error(`dcode readback enablement mismatch for ${id}`);
21322
21620
  const marker = ownership3(target);
21323
21621
  if (marker?.kind !== "current" || marker.projectedFingerprint !== projected || marker.sourceRevision !== sourceRevision) {
@@ -21397,7 +21695,7 @@ function readRegistry3(file) {
21397
21695
  const registry = readDoc2(file, { version: 2, plugins: {} }, "registry");
21398
21696
  if (registry.version !== 1 && registry.version !== 2)
21399
21697
  throw new Error(`invalid dcode registry version: ${file}`);
21400
- const plugins = object3(registry.plugins, "registry plugins");
21698
+ const plugins = object4(registry.plugins, "registry plugins");
21401
21699
  for (const [id, value] of Object.entries(plugins)) {
21402
21700
  if (!id || !Array.isArray(value) || value.length === 0)
21403
21701
  throw new Error(`invalid dcode registry record: ${id}`);
@@ -21416,13 +21714,13 @@ function readEnablement(file) {
21416
21714
  boolObject(enablement.enabledPlugins, "enabledPlugins");
21417
21715
  return enablement;
21418
21716
  }
21419
- function object3(value, label) {
21717
+ function object4(value, label) {
21420
21718
  if (typeof value !== "object" || value === null || Array.isArray(value))
21421
21719
  throw new Error(`invalid dcode ${label}`);
21422
21720
  return value;
21423
21721
  }
21424
21722
  function boolObject(value, label) {
21425
- const result = object3(value, label);
21723
+ const result = object4(value, label);
21426
21724
  if (Object.values(result).some((entry) => typeof entry !== "boolean"))
21427
21725
  throw new Error(`invalid dcode ${label}`);
21428
21726
  return result;
@@ -21445,7 +21743,7 @@ function ownership3(dir) {
21445
21743
  if (!existsSync23(file))
21446
21744
  return null;
21447
21745
  try {
21448
- const value = object3(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
21746
+ const value = object4(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
21449
21747
  if (typeof value.source !== "string" || typeof value.pluginId !== "string" || typeof value.fingerprint !== "string")
21450
21748
  throw new Error("marker fields are invalid");
21451
21749
  const projected = value.projectedFingerprint;
@@ -21959,7 +22257,7 @@ import { cpSync as cpSync10, existsSync as existsSync25, lstatSync as lstatSync1
21959
22257
  import { dirname as dirname13, join as join30, resolve as resolve19 } from "node:path";
21960
22258
  import { tmpdir as tmpdir9 } from "node:os";
21961
22259
  var stableId = (plugin) => plugin.marketplace === undefined ? plugin.name : `${plugin.name}@${plugin.marketplace}`;
21962
- var digest2 = (value) => {
22260
+ var digest3 = (value) => {
21963
22261
  const hash = new CryptoHasher("sha256");
21964
22262
  hash.update(value);
21965
22263
  return hash.digest("hex").slice(0, 16);
@@ -21980,7 +22278,7 @@ var grokWriter = {
21980
22278
  if (!registryIsReadable())
21981
22279
  throw new Error("Grok native registry is unreadable or unsupported; refusing mutation");
21982
22280
  const id = stableId(plugin);
21983
- const root = join30(marketplacesRoot(), `plgnz-${digest2(`${resolved.sourceUri}\x00${id}`)}`);
22281
+ const root = join30(marketplacesRoot(), `plgnz-${digest3(`${resolved.sourceUri}\x00${id}`)}`);
21984
22282
  const parent = dirname13(root);
21985
22283
  const prior = canonical(root);
21986
22284
  const priorMarker = prior === undefined ? null : ownership(prior);
@@ -22467,7 +22765,7 @@ function probeGrokVersion() {
22467
22765
  return parseGrokVersionOutput(result.stdout ?? "", result.status);
22468
22766
  }
22469
22767
  function lifecycleMarketplace(scopeId, nativeId) {
22470
- return resolve19(join30(marketplacesRoot(), `plgnz-${digest2(`${scopeId}\x00${nativeId}`)}`));
22768
+ return resolve19(join30(marketplacesRoot(), `plgnz-${digest3(`${scopeId}\x00${nativeId}`)}`));
22471
22769
  }
22472
22770
  function marketplacePluginPath(scopeId, nativeId, packageName) {
22473
22771
  return resolve19(join30(lifecycleMarketplace(scopeId, nativeId), "plugins", packageName));
@@ -25268,7 +25566,7 @@ class SyncManifestValidationError extends Error {
25268
25566
  }
25269
25567
  }
25270
25568
  function parseSyncManifest(value) {
25271
- const manifest = object4(value, "sync manifest");
25569
+ const manifest = object5(value, "sync manifest");
25272
25570
  exactFields5(manifest, ["schemaVersion", "entries"], "sync manifest");
25273
25571
  if (manifest["schemaVersion"] !== SYNC_MANIFEST_SCHEMA_VERSION)
25274
25572
  invalidArgument2("sync manifest schemaVersion must be 1");
@@ -25302,7 +25600,7 @@ function selectManifestPackages(entry, discovered) {
25302
25600
  });
25303
25601
  }
25304
25602
  function parseEntry(value, index) {
25305
- const entry = object4(value, `sync manifest entry ${index}`);
25603
+ const entry = object5(value, `sync manifest entry ${index}`);
25306
25604
  if (entry["operation"] === "sync") {
25307
25605
  exactFields5(entry, ["operation", "source", "target", "selectors"], `sync manifest entry ${index}`);
25308
25606
  const source = parseSourceBinding(entry["source"], `sync manifest entry ${index} source`);
@@ -25380,7 +25678,7 @@ function assertUniqueScopes(entries) {
25380
25678
  }
25381
25679
  }
25382
25680
  function parseSourceBinding(value, label) {
25383
- const binding = object4(value, label);
25681
+ const binding = object5(value, label);
25384
25682
  if (binding["kind"] === "local") {
25385
25683
  exactFields5(binding, ["kind", "locator"], label);
25386
25684
  if (typeof binding["locator"] !== "string")
@@ -25406,7 +25704,7 @@ function parseTarget(value, label) {
25406
25704
  }
25407
25705
  function parseSelector(value, entryIndex, selectorIndex) {
25408
25706
  const label = `sync manifest entry ${entryIndex} selector ${selectorIndex}`;
25409
- const selector = object4(value, label);
25707
+ const selector = object5(value, label);
25410
25708
  exactFields5(selector, ["package", "adoptExisting"], label);
25411
25709
  if (typeof selector["package"] !== "string" || !/^[a-z0-9][a-z0-9._-]*$/iu.test(selector["package"])) {
25412
25710
  invalidArgument2(`${label} package must be a valid package name`);
@@ -25424,7 +25722,7 @@ function validateBinding(source, label) {
25424
25722
  }
25425
25723
  }
25426
25724
  function targetProfile(value, label) {
25427
- const target = object4(value, label);
25725
+ const target = object5(value, label);
25428
25726
  if (typeof target["kind"] !== "string")
25429
25727
  invalidArgument2(`${label} kind must be a string`);
25430
25728
  return targetProfileForKind(target["kind"], label);
@@ -25449,7 +25747,7 @@ function invokeTargetProfile(operation) {
25449
25747
  throw error;
25450
25748
  }
25451
25749
  }
25452
- function object4(value, label) {
25750
+ function object5(value, label) {
25453
25751
  if (typeof value !== "object" || value === null || Array.isArray(value))
25454
25752
  invalidArgument2(`${label} must be an object`);
25455
25753
  return value;
@@ -25480,9 +25778,15 @@ async function planLifecycle(input) {
25480
25778
  if (duplicate !== null)
25481
25779
  return zeroWrite(command, input.dryRun, duplicate);
25482
25780
  const prepared = [];
25483
- 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;
25484
25788
  for (const entry of input.manifest.entries) {
25485
- 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);
25486
25790
  if (result.kind === "defect")
25487
25791
  return zeroWrite(command, input.dryRun, result.reason, result.terminalPhase);
25488
25792
  if (input.removalSelection !== undefined) {
@@ -25503,14 +25807,58 @@ async function planLifecycle(input) {
25503
25807
  prepared.push(result);
25504
25808
  }
25505
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)));
25506
25811
  const scopes = [];
25507
25812
  const operations = [];
25508
25813
  for (const item of prepared) {
25509
- 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);
25510
25848
  if (planned.kind === "defect")
25511
25849
  return zeroWrite(command, input.dryRun, planned.reason, planned.terminalPhase);
25512
- scopes.push(planned.scope);
25513
- 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);
25514
25862
  }
25515
25863
  return frozenPlan(input, command, attemptId, scopes, operations, prepared, loaded.state);
25516
25864
  }
@@ -26289,7 +26637,7 @@ function lifecycleTarget(target) {
26289
26637
  }
26290
26638
  function sourceSnapshotId(frozen) {
26291
26639
  const binding = frozen.snapshot.binding;
26292
- return `snapshot-v1-${digest3([
26640
+ return `snapshot-v1-${digest4([
26293
26641
  binding.kind,
26294
26642
  binding.locator,
26295
26643
  binding.kind === "git" ? binding.ref : "",
@@ -26297,10 +26645,10 @@ function sourceSnapshotId(frozen) {
26297
26645
  ])}`;
26298
26646
  }
26299
26647
  function operationIdentity(coverage, scopeId, packageName, nativeId, fingerprint) {
26300
- return `operation-v1-${digest3([coverage, scopeId, packageName, nativeId, fingerprint])}`;
26648
+ return `operation-v1-${digest4([coverage, scopeId, packageName, nativeId, fingerprint])}`;
26301
26649
  }
26302
26650
  function lifecycleAttemptId(stateGeneration, identities) {
26303
- return `attempt-v1-${digest3([String(stateGeneration), ...identities.flat()])}`;
26651
+ return `attempt-v1-${digest4([String(stateGeneration), ...identities.flat()])}`;
26304
26652
  }
26305
26653
  function lifecycleAttemptIdForPlannedScopes(stateGeneration, scopes) {
26306
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)));
@@ -26310,7 +26658,7 @@ function plannedAttemptIdentity(scopeId, sourceFingerprint, packageIds, retired
26310
26658
  return ["retire-source", scopeId];
26311
26659
  return ["sync", scopeId, sourceFingerprint, ...packageIds];
26312
26660
  }
26313
- function digest3(parts) {
26661
+ function digest4(parts) {
26314
26662
  const hash = new CryptoHasher("sha256");
26315
26663
  for (const part of parts) {
26316
26664
  hash.update(String(part.length));
@@ -26625,6 +26973,7 @@ async function runActivation(plan, operation, hosts, ledger, action) {
26625
26973
  const sealed = await host.adapter.sealActivation(pinned);
26626
26974
  preparation = sealed;
26627
26975
  handle = sealed.handle;
26976
+ ledger.captureOperation(plan, operation, handle);
26628
26977
  ledger.markApplying(plan, operation);
26629
26978
  receipt = await host.adapter.apply(sealed);
26630
26979
  const observation = await host.adapter.readback(receipt.handle);
@@ -26810,6 +27159,7 @@ async function runDisable(plan, operation, hosts, ledger) {
26810
27159
  throw new Error("disable containment selected the wrong operation");
26811
27160
  ledger.acceptJournal(plan, operation, "disable-nonconforming");
26812
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);
26813
27163
  ledger.markApplying(plan, operation);
26814
27164
  receipt = await host.adapter.disable(preparation);
26815
27165
  const observation = await host.adapter.readback(receipt.handle);
@@ -26880,6 +27230,7 @@ async function runRetire(plan, operation, hosts, ledger) {
26880
27230
  activation: prepared.activation
26881
27231
  });
26882
27232
  preparation = sealed;
27233
+ ledger.captureOperation(plan, operation, sealed.handle);
26883
27234
  ledger.markPruning(plan, operation);
26884
27235
  receipt = await host.adapter.retire(sealed);
26885
27236
  const observation = await host.adapter.readback(receipt.handle);
@@ -26897,6 +27248,8 @@ async function runRetire(plan, operation, hosts, ledger) {
26897
27248
  if (preparation !== undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying") {
26898
27249
  return recoverApplied(plan, operation, host, ledger, preparation.handle, reason);
26899
27250
  }
27251
+ if (preparation === undefined)
27252
+ ledger.markNotApplied(plan, operation);
26900
27253
  return stop(operation, reason);
26901
27254
  }
26902
27255
  if (receipt === undefined || verified === undefined) {
@@ -27047,6 +27400,10 @@ class Ledger {
27047
27400
  this.disablePending(plan, operation, "applying");
27048
27401
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "applying", "applying", true));
27049
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
+ }
27050
27407
  markNotApplied(plan, operation) {
27051
27408
  if (operation.action === "disable-nonconforming")
27052
27409
  this.clearDisablePending(operation);
@@ -27120,8 +27477,14 @@ class Ledger {
27120
27477
  this.adoptMovedGeneration();
27121
27478
  if (operation.action === "disable-nonconforming")
27122
27479
  this.clearDisablePending(operation, true);
27480
+ this.clearPending(operation);
27123
27481
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "rolled-back", "failed", true));
27124
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
+ }
27125
27488
  confirmDisable(plan, operation) {
27126
27489
  this.clearDisablePending(operation);
27127
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) };
@@ -27419,7 +27782,7 @@ async function recoverApplied(plan, operation, host, ledger, handle, reason) {
27419
27782
  }
27420
27783
  try {
27421
27784
  if (verified)
27422
- ledger.markRolledBack(plan, operation);
27785
+ ledger.markRollbackVerified(plan, operation);
27423
27786
  else
27424
27787
  ledger.markRollbackRequired(plan, operation);
27425
27788
  } catch (error) {
@@ -27429,7 +27792,11 @@ async function recoverApplied(plan, operation, host, ledger, handle, reason) {
27429
27792
  if (verified) {
27430
27793
  try {
27431
27794
  await host.adapter.cleanup(handle, "verified-rollback");
27432
- } catch {}
27795
+ ledger.markRolledBack(plan, operation);
27796
+ } catch (error) {
27797
+ verified = false;
27798
+ diagnostic += `; rollback cleanup remains pending: ${unknownErrorDiagnostic(error)}`;
27799
+ }
27433
27800
  }
27434
27801
  } finally {
27435
27802
  try {
@@ -27670,17 +28037,186 @@ function absolutePinExecutable(command) {
27670
28037
  return resolve22(found);
27671
28038
  }
27672
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
+
27673
28188
  // src/lifecycle-command.ts
27674
28189
  async function runFrozenLifecycle(command) {
27675
- const plan = await planLifecycle({
28190
+ const retainedSources = new Map;
28191
+ const input = {
27676
28192
  manifest: command.manifest,
27677
28193
  dryRun: command.dryRun,
27678
28194
  validatedAt: command.now,
27679
28195
  hosts: command.hosts,
27680
28196
  ...command.command === undefined ? {} : { command: command.command },
27681
28197
  ...command.transportSource === undefined ? {} : { transportSource: command.transportSource },
27682
- ...command.removalSelection === undefined ? {} : { removalSelection: command.removalSelection }
27683
- });
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
+ }
27684
28220
  const executed = await executeLifecycle({
27685
28221
  plan,
27686
28222
  hosts: command.hosts,
@@ -27795,6 +28331,7 @@ async function runOfflineLifecyclePin(host, plugin, dryRun) {
27795
28331
  const sealed = await adapter.sealActivation(preparation);
27796
28332
  preparation = sealed;
27797
28333
  handle = sealed.handle;
28334
+ attempt.journal[0].sealedOperation = parseSerializedLifecycleOperation(JSON.parse(JSON.stringify(handle)));
27798
28335
  attempt.phase = "applying";
27799
28336
  attempt.mutationStarted = true;
27800
28337
  attempt.journal[0].state = "applying";
@@ -27807,6 +28344,7 @@ async function runOfflineLifecyclePin(host, plugin, dryRun) {
27807
28344
  throw new Error("pin readback lacks exact installed/projected fingerprint");
27808
28345
  attempt.phase = "finalizing";
27809
28346
  attempt.journal[0].state = "readback-verified";
28347
+ attempt.journal[0].settlement = "commit";
27810
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 };
27811
28349
  persist(pinnedActivation);
27812
28350
  committed = true;
@@ -27842,6 +28380,7 @@ async function runOfflineLifecyclePin(host, plugin, dryRun) {
27842
28380
  throw new Error(`rollback verified but its durable receipt is blocked: ${journalError.message}`);
27843
28381
  attempt.phase = "finalizing";
27844
28382
  attempt.journal[0].state = "cleanup-pending";
28383
+ attempt.journal[0].settlement = "rollback";
27845
28384
  persist({ ...activation, pending: { operation: "update", phase: "cleanup", attemptId, startedAt: now } });
27846
28385
  await adapter.cleanup(handle, "verified-rollback");
27847
28386
  attempt.phase = "failed";
@@ -28438,7 +28977,7 @@ function legacyCommandAction(command) {
28438
28977
  // package.json
28439
28978
  var package_default = {
28440
28979
  name: "plugnz",
28441
- version: "0.0.9",
28980
+ version: "0.0.10",
28442
28981
  description: "Install, diagnose and update agent plugins (Agent Plugins spec) and MCP configs across Claude Code, Codex, Cursor, Kimi and more",
28443
28982
  license: "MIT",
28444
28983
  type: "module",
@@ -29868,18 +30407,22 @@ async function main(argv) {
29868
30407
  return 2;
29869
30408
  }
29870
30409
  const state = readState();
30410
+ const recoveries = pendingLifecycleOperations();
29871
30411
  const all = [];
29872
30412
  for (const h of selection.selected) {
29873
30413
  const installed = h.listInstalled();
29874
30414
  const pending = state.filter((record) => record.host === h.id && record.pending !== undefined).map((record) => ({ id: record.id, action: record.pending }));
29875
30415
  if (json) {
29876
- 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 } : {} });
29877
30418
  } else {
29878
30419
  for (const p of installed) {
29879
30420
  console.log(`${h.id} ${p.id} ${p.version || p.sha || "unknown"}`);
29880
30421
  }
29881
30422
  for (const record of pending)
29882
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}`);
29883
30426
  }
29884
30427
  }
29885
30428
  if (json)