plugnz 0.0.8 → 0.0.9

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 +820 -55
  2. package/package.json +1 -1
package/dist/plugnz.mjs CHANGED
@@ -9339,10 +9339,23 @@ var dcode = {
9339
9339
  continue;
9340
9340
  const row = entry;
9341
9341
  const path = typeof row.installPath === "string" ? row.installPath : typeof row.install_path === "string" ? row.install_path : undefined;
9342
+ let declaredVersion;
9343
+ if (path && existsSync8(path)) {
9344
+ for (const relative of ["plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json"]) {
9345
+ try {
9346
+ const manifest = JSON.parse(readFileSync6(join11(path, relative), "utf8"));
9347
+ if (typeof manifest.version === "string") {
9348
+ declaredVersion = manifest.version;
9349
+ break;
9350
+ }
9351
+ } catch {}
9352
+ }
9353
+ }
9342
9354
  const at = id.indexOf("@");
9343
9355
  const name = at < 0 ? id : id.slice(0, at);
9344
9356
  const marketplace = at < 0 ? undefined : id.slice(at + 1);
9345
- const plugin = { id, name, ...marketplace ? { marketplace } : {}, ...path && existsSync8(path) ? { path } : {}, ...typeof row.version === "string" ? { version: row.version } : {}, ...enabled.enabledPlugins[id] === true ? { enabled: true } : { enabled: false } };
9357
+ const version = typeof row.version === "string" ? row.version : declaredVersion;
9358
+ const plugin = { id, name, ...marketplace ? { marketplace } : {}, ...path && existsSync8(path) ? { path } : {}, ...version === undefined ? {} : { version }, ...enabled.enabledPlugins[id] === true ? { enabled: true } : { enabled: false } };
9346
9359
  result.push(plugin);
9347
9360
  }
9348
9361
  return result;
@@ -12627,7 +12640,13 @@ function assertOutcome(outcome, snapshots, dryRun, command) {
12627
12640
  }
12628
12641
  const containment = outcome["action"] === "retain-prior" || outcome["action"] === "disable-nonconforming";
12629
12642
  const appliedDisablement = outcome["action"] === "disable-nonconforming" && outcome["changed"] === true;
12630
- if (containment && !dryRun) {
12643
+ if (outcome["action"] === "disable-nonconforming" && outcome["result"] === "succeeded") {
12644
+ throw contradiction(`disablement '${outcome["operationId"]}' cannot claim Desired convergence`);
12645
+ }
12646
+ if (!dryRun && outcome["action"] === "disable-nonconforming" && outcome["resourceState"] === "retained" && outcome["activationState"] === "inactive" && outcome["changed"] !== true) {
12647
+ throw contradiction(`disablement '${outcome["operationId"]}' cannot claim inactive containment without a verified mutation`);
12648
+ }
12649
+ if (containment && !dryRun && (outcome["action"] === "retain-prior" || isObject2(reason) && reason["category"] === "capability")) {
12631
12650
  const expectedResourceState = "retained";
12632
12651
  const expectedActivationState = outcome["action"] === "retain-prior" ? "retained-prior" : appliedDisablement ? "inactive" : "active-nonconforming";
12633
12652
  if (outcome["result"] !== "failed" || outcome["resourceState"] !== expectedResourceState || outcome["activationState"] !== expectedActivationState) {
@@ -13546,7 +13565,8 @@ var dcode0183Managed = createCapabilityEvidenceProfile({
13546
13565
  },
13547
13566
  evidence: [
13548
13567
  "docs/evidence/dcode-native-update-0.1.83-20261009.json",
13549
- "docs/research/dcode-session-findings-2026-10-09.md"
13568
+ "docs/research/dcode-session-findings-2026-10-09.md",
13569
+ "docs/evidence/dcode-managed-lifecycle-20261011.md"
13550
13570
  ]
13551
13571
  });
13552
13572
  var capabilityEvidenceProfiles = Object.freeze([
@@ -14228,6 +14248,12 @@ function createLifecycleHostAdapter(definition) {
14228
14248
  assertReadbackMatch(handle.prior, observation, "rollback");
14229
14249
  return deepFreeze({ phase: "rollback-verified", handle, observation });
14230
14250
  },
14251
+ async releaseRecovery(handle) {
14252
+ assertHandle(definition.id, handle);
14253
+ await invokePhase("cleanup", async () => {
14254
+ await definition.releaseRecovery?.(handle);
14255
+ });
14256
+ },
14231
14257
  async cleanup(target, disposition) {
14232
14258
  const reference = cleanupReference(definition.id, target);
14233
14259
  if (isPreparationProjection(target) && disposition !== "aborted-preparation") {
@@ -20598,10 +20624,431 @@ function assertUnder(root, path) {
20598
20624
  import { cpSync as cpSync8, existsSync as existsSync23, lstatSync as lstatSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync9, readFileSync as readFileSync22, readdirSync as readdirSync17, realpathSync as realpathSync3, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "node:fs";
20599
20625
  import { basename as basename5, dirname as dirname11, join as join28, resolve as resolve17 } from "node:path";
20600
20626
  import { tmpdir as tmpdir7 } from "node:os";
20627
+ import { spawn as spawnChild } from "node:child_process";
20601
20628
  var MARKER7 = ".plgnz-install.json";
20602
20629
  var MANIFESTS = ["plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json"];
20603
20630
  var READBACK_FAULT = ".plgnz-dcode-readback-fault";
20631
+ var NATIVE_LOCK_SCRIPT = `import sys
20632
+ from importlib.metadata import version
20633
+ from filelock import FileLock
20634
+ if version('deepagents-code') != '0.1.83':
20635
+ raise RuntimeError('unverified dcode lock runtime')
20636
+ with FileLock(sys.argv[1]).acquire(timeout=10):
20637
+ print('DCODE_LOCK_0.1.83', flush=True)
20638
+ sys.stdin.read()
20639
+ `;
20640
+ var nativeLocks = new Map;
20641
+ function nativePython() {
20642
+ const override = process.env["OPEN_PLUGIN_DCODE_PYTHON_BIN"];
20643
+ if (override) {
20644
+ assertIdentity2(basename5(override), "Python binary");
20645
+ return override;
20646
+ }
20647
+ const binary = dcodeBinary();
20648
+ if (binary === undefined)
20649
+ throw new Error("dcode native lock runtime is unavailable");
20650
+ const shebang = readFileSync22(binary, "utf8").split(/\r?\n/, 1)[0] ?? "";
20651
+ const match = /^#!(\/[^\s]+python(?:\d+(?:\.\d+)?)?)$/u.exec(shebang);
20652
+ if (match?.[1] === undefined || !existsSync23(match[1]))
20653
+ throw new Error("dcode native Python interpreter is unverified; set OPEN_PLUGIN_DCODE_PYTHON_BIN to its verified interpreter");
20654
+ return match[1];
20655
+ }
20656
+ function probeLifecycleVersion() {
20657
+ const observation = probeDcodeVersion();
20658
+ if (observation.kind !== "detected" || observation.version !== "0.1.83")
20659
+ return observation;
20660
+ const result = spawnSync([nativePython(), "-c", "from importlib.metadata import version; import filelock; assert version('deepagents-code') == '0.1.83'; print('DCODE_LOCK_0.1.83')"], { stdout: "pipe", stderr: "pipe", timeout: 1e4 });
20661
+ if (result.exitCode !== 0 || new TextDecoder().decode(result.stdout).trim() !== "DCODE_LOCK_0.1.83")
20662
+ throw new Error(`dcode native lock runtime is unverified: ${new TextDecoder().decode(result.stderr).trim()}`);
20663
+ return observation;
20664
+ }
20665
+ async function acquireNativeLock(attemptId, operationId) {
20666
+ const key = `${attemptId}:${operationId}`;
20667
+ const existing = nativeLocks.get(key);
20668
+ if (existing !== undefined) {
20669
+ if (!existing.isAlive())
20670
+ throw new Error("dcode native lock was lost before verification");
20671
+ return;
20672
+ }
20673
+ const file = join28(dcodeRoot(), "plugins", ".mutation.lock");
20674
+ assertManagedPath4(file);
20675
+ mkdirSync9(dirname11(file), { recursive: true });
20676
+ const child = spawnChild(nativePython(), ["-c", NATIVE_LOCK_SCRIPT, file], { env: { ...process.env, HOME: process.env["OPEN_PLUGIN_HOME"] || process.env["HOME"], DEEPAGENTS_HOME: dcodeRoot() }, stdio: ["pipe", "pipe", "pipe"] });
20677
+ let alive = true;
20678
+ const exited = new Promise((resolveExit) => child.on("exit", (code) => {
20679
+ alive = false;
20680
+ resolveExit(code);
20681
+ }));
20682
+ await new Promise((resolveLock, rejectLock) => {
20683
+ let output = "", diagnostic = "", settled = false;
20684
+ const finish = (error) => {
20685
+ if (settled)
20686
+ return;
20687
+ settled = true;
20688
+ clearTimeout(timer);
20689
+ if (error) {
20690
+ child.kill("SIGKILL");
20691
+ rejectLock(error);
20692
+ } else
20693
+ resolveLock();
20694
+ };
20695
+ const timer = setTimeout(() => finish(new Error("dcode native lock acquisition timed out")), 12000);
20696
+ child.stdout.on("data", (data) => {
20697
+ output += new TextDecoder().decode(data);
20698
+ if (output.includes(`DCODE_LOCK_0.1.83
20699
+ `))
20700
+ finish();
20701
+ });
20702
+ child.stderr.on("data", (data) => {
20703
+ diagnostic += new TextDecoder().decode(data);
20704
+ });
20705
+ child.on("error", (error) => finish(new Error(`dcode native lock failed: ${error.message}`)));
20706
+ child.on("exit", (code) => finish(new Error(`dcode native lock failed (${code}): ${diagnostic.trim()}`)));
20707
+ });
20708
+ nativeLocks.set(key, { child, exited, isAlive: () => alive });
20709
+ }
20710
+ async function releaseNativeLock(attemptId, operationId) {
20711
+ const key = `${attemptId}:${operationId}`, held = nativeLocks.get(key);
20712
+ if (held === undefined)
20713
+ return;
20714
+ nativeLocks.delete(key);
20715
+ held.child.stdin.end();
20716
+ let timer;
20717
+ try {
20718
+ const code = await Promise.race([held.exited, new Promise((_, reject) => {
20719
+ timer = setTimeout(() => {
20720
+ held.child.kill("SIGKILL");
20721
+ reject(new Error("dcode native lock release timed out"));
20722
+ }, 2000);
20723
+ })]);
20724
+ if (code !== 0)
20725
+ throw new Error(`dcode native lock process failed during mutation (${code})`);
20726
+ } finally {
20727
+ if (timer !== undefined)
20728
+ clearTimeout(timer);
20729
+ }
20730
+ }
20731
+ function lifecycleDirectory(attemptId, operationId) {
20732
+ const hash = new CryptoHasher("sha256");
20733
+ hash.update(`${attemptId}\x00${operationId}`);
20734
+ const dir = join28(dcodeCacheRoot(), "plgnz-operations", hash.digest("hex"));
20735
+ assertCachePath(dir);
20736
+ return dir;
20737
+ }
20738
+ function lifecycleSlot(nativeId, fingerprint) {
20739
+ return managedSlot(nativeId, fingerprint);
20740
+ }
20741
+ function observeDcodeTarget(target) {
20742
+ if (target.kind !== "dcode" || target.instance !== "default")
20743
+ throw new Error("unsupported dcode target identity");
20744
+ const registry = readRegistry3(dcodeRegistryFile());
20745
+ const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
20746
+ return Object.entries(object3(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
20747
+ const rows = rowsFor(raw);
20748
+ if (rows.length !== 1)
20749
+ throw new Error(`dcode install record ${nativeId} has duplicate rows`);
20750
+ const path = rowInstall(rows[0], nativeId);
20751
+ assertCachePath(path);
20752
+ const present = existsSync23(path);
20753
+ const marker = present ? ownership3(path) : null;
20754
+ const body = present ? readDoc2(join28(path, MARKER7), {}, "ownership marker") : {};
20755
+ const owned = marker?.pluginId === nativeId;
20756
+ const fingerprint = present ? fingerprintTree(path) : null;
20757
+ return {
20758
+ nativeId,
20759
+ packageName: nativeId.split("@")[0],
20760
+ ownership: owned ? { kind: "owned", proof: body.proof === "adopted" ? "adopted" : "created", scopeId: typeof body.scopeId === "string" ? body.scopeId : "dcode:default", proofId: `dcode-marker:${nativeId}` } : { kind: "unmanaged" },
20761
+ presence: present ? "present" : "absent",
20762
+ enablement: enabled[nativeId] === true ? "enabled" : "disabled",
20763
+ activation: enabled[nativeId] === true && present ? "active" : "inactive",
20764
+ installedFingerprint: fingerprint,
20765
+ installedVersion: present ? declaredManifest(path, nativeId.split("@")[0]).version : null,
20766
+ source: owned ? { type: body.sourceType === "git" ? "git" : "local", immutableRevision: marker.kind === "current" ? marker.sourceRevision : marker.fingerprint, locator: body.sourceType === "git" ? marker.source : null } : null,
20767
+ contentRoots: fingerprint === null ? [] : [{ label: "managed", path, fingerprint }]
20768
+ };
20769
+ });
20770
+ }
20771
+ function dcodeLifecycleReadback(binding) {
20772
+ if (existsSync23(join28(dcodeStateDir(), READBACK_FAULT)))
20773
+ throw new Error(`dcode readback failed for ${binding.nativeId}`);
20774
+ const installations = observeDcodeTarget(binding.target);
20775
+ const row = installations.find((item) => item.nativeId === binding.nativeId);
20776
+ const present = row?.presence === "present";
20777
+ const enabled = row?.enablement === "enabled" && present;
20778
+ const dataRoot = join28(dcodeRoot(), "plugins", "data");
20779
+ assertManagedPath4(dataRoot);
20780
+ return {
20781
+ adapterId: binding.adapterId,
20782
+ target: binding.target,
20783
+ scopeId: binding.scopeId,
20784
+ packageName: binding.packageName,
20785
+ nativeId: binding.nativeId,
20786
+ route: binding.route ?? (present ? "managed" : "none"),
20787
+ presence: present ? "present" : "absent",
20788
+ enablement: enabled ? "enabled" : "disabled",
20789
+ activation: enabled ? "active" : "inactive",
20790
+ transition: { requirement: present ? "restart" : "none", status: "effective" },
20791
+ installedFingerprint: present ? row.installedFingerprint : null,
20792
+ contentRoots: present ? row.contentRoots : [],
20793
+ retention: { pluginData: existsSync23(dataRoot) ? { state: "present", fingerprint: fingerprintTree(dataRoot) } : { state: "absent", fingerprint: null }, inactiveMetadata: { state: "absent", fingerprint: null } }
20794
+ };
20795
+ }
20796
+ function captureDcodeRollback(binding, attemptId, operationId) {
20797
+ const dir = join28(lifecycleDirectory(attemptId, operationId), "rollback");
20798
+ mkdirSync9(dir, { recursive: true });
20799
+ const registry = readRegistry3(dcodeRegistryFile());
20800
+ const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
20801
+ const row = object3(registry.plugins, "registry plugins")[binding.nativeId];
20802
+ const state = { row: row ?? null, enabled: enabled[binding.nativeId] ?? null, registryExisted: existsSync23(dcodeRegistryFile()), enablementExisted: existsSync23(dcodeEnablementFile()) };
20803
+ writeFileSync9(join28(dir, "state.json"), JSON.stringify(state));
20804
+ for (const [index, item] of rowsFor(row).entries()) {
20805
+ const path = rowInstall(item, binding.nativeId);
20806
+ assertCachePath(path);
20807
+ assertNoSymlinks6(path);
20808
+ if (existsSync23(path))
20809
+ cpSync8(path, join28(dir, `content-${index}`), { recursive: true });
20810
+ }
20811
+ return dir;
20812
+ }
20813
+ async function lockPreparation(binding, attemptId, operationId, expectedObservationId) {
20814
+ await acquireNativeLock(attemptId, operationId);
20815
+ const observed = createTargetInventoryObservation("dcode", { target: binding.target, installations: observeDcodeTarget(binding.target) });
20816
+ if (observed.observationId !== expectedObservationId)
20817
+ throw new Error("dcode native inventory changed before locked preparation; refusing stale rollback authority");
20818
+ }
20819
+ async function captureOwnedPreparation(binding, attemptId, operationId, expectedObservationId, capture) {
20820
+ try {
20821
+ await lockPreparation(binding, attemptId, operationId, expectedObservationId);
20822
+ return capture();
20823
+ } catch (error) {
20824
+ try {
20825
+ await releaseNativeLock(attemptId, operationId);
20826
+ } catch (releaseError) {
20827
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; dcode preparation lock release failed: ${releaseError instanceof Error ? releaseError.message : String(releaseError)}`);
20828
+ }
20829
+ throw error;
20830
+ }
20831
+ }
20832
+ function preflightAdoption(request) {
20833
+ if (request.operation !== "install" && request.operation !== "update")
20834
+ return;
20835
+ const snapshot = request.snapshot;
20836
+ const installed = request.targetObservation.installations.find((item) => item.nativeId === snapshot.nativeId);
20837
+ if (installed?.ownership.kind !== "unmanaged" || installed.presence !== "present")
20838
+ return;
20839
+ const path = installed.contentRoots[0]?.path;
20840
+ if (path === undefined)
20841
+ throw new Error("dcode unmanaged adoption has no native root");
20842
+ 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);
20844
+ if (fingerprintTree(path) !== installed.installedFingerprint)
20845
+ throw new Error("dcode unmanaged adoption changed during preflight");
20846
+ }
20847
+ function setDcodeRow(nativeId, row, enabled) {
20848
+ assertManagedPath4(dcodeRegistryFile());
20849
+ assertManagedPath4(dcodeEnablementFile());
20850
+ const registry = readRegistry3(dcodeRegistryFile()), enablement = readEnablement(dcodeEnablementFile());
20851
+ const plugins = { ...object3(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
20852
+ if (row === null)
20853
+ delete plugins[nativeId];
20854
+ else
20855
+ plugins[nativeId] = row;
20856
+ if (enabled === null)
20857
+ delete flags[nativeId];
20858
+ else
20859
+ flags[nativeId] = enabled;
20860
+ writeDoc2(dcodeRegistryFile(), { ...registry, plugins });
20861
+ writeDoc2(dcodeEnablementFile(), { ...enablement, enabledPlugins: flags });
20862
+ }
20863
+ var dcodeDefinition = {
20864
+ id: "dcode",
20865
+ evidenceProfiles: capabilityEvidenceProfiles.filter((profile) => profile.host === "dcode").flatMap((profile) => [profile, createCapabilityEvidenceProfile({ ...profile, operations: ["disable"], semantics: { ...profile.semantics, "reversible-disable": "supported" }, evidence: [...profile.evidence, "docs/evidence/dcode-managed-lifecycle-20261011.md"] })]),
20866
+ probeVersion: async () => probeLifecycleVersion(),
20867
+ observeTarget: async (target) => ({ target, installations: observeDcodeTarget(target) }),
20868
+ observeNativeMutationScope: async () => ({ kind: "unavailable" }),
20869
+ observeNativeProjection: async (request) => {
20870
+ preflightAdoption(request);
20871
+ return { kind: "requires-managed", reasonId: "dcode-frozen-projection-and-updater-isolation" };
20872
+ },
20873
+ revalidateTargetPrecondition: async (handle) => {
20874
+ await acquireNativeLock(handle.attemptId, handle.operationId);
20875
+ return { version: probeDcodeVersion(), targetObservationId: createTargetInventoryObservation("dcode", { target: handle.target, installations: observeDcodeTarget(handle.target) }).observationId };
20876
+ },
20877
+ stageActivation: async (request) => {
20878
+ const dir = lifecycleDirectory(request.snapshot.attemptId, request.snapshot.operationId);
20879
+ const stagingRoot = join28(dir, "stage");
20880
+ if (existsSync23(stagingRoot))
20881
+ throw new Error("dcode preparation stage already exists");
20882
+ mkdirSync9(stagingRoot, { recursive: true });
20883
+ const manifest = declaredManifest(request.snapshot.packageRoot, request.snapshot.packageName);
20884
+ try {
20885
+ stagePlugin5(request.snapshot.packageRoot, stagingRoot, request.snapshot.packageName, manifest.version);
20886
+ } catch (error) {
20887
+ rmSync9(dir, { recursive: true, force: true });
20888
+ throw error;
20889
+ }
20890
+ return { stagingRoot, stagingId: `${request.snapshot.attemptId}:${request.snapshot.operationId}` };
20891
+ },
20892
+ applyLifecycleDirectives: async (projection) => {
20893
+ forceAutoUpdateOff(projection.stagingRoot);
20894
+ const prior = observeDcodeTarget(projection.target).find((item) => item.nativeId === projection.nativeId);
20895
+ writeFileSync9(join28(projection.stagingRoot, MARKER7), JSON.stringify({ source: projection.sourceLocator ?? `local:${projection.scopeId}`, pluginId: projection.nativeId, fingerprint: projection.sourceRevision, sourceRevision: projection.sourceRevision, projectedFingerprint: projectionDigest(projection.stagingRoot), scopeId: projection.scopeId, sourceType: projection.sourceType, proof: prior?.ownership.kind === "unmanaged" ? "adopted" : "created" }));
20896
+ return ["dcode.managed-unversioned-registry"];
20897
+ },
20898
+ applyPins: async (projection) => {
20899
+ const found = new Set;
20900
+ for (const candidate of dcodeMcpCandidates()) {
20901
+ const file = join28(projection.stagingRoot, candidate.kind === "spec" ? candidate.file : candidate.manifest);
20902
+ if (!existsSync23(file))
20903
+ continue;
20904
+ const body = readDoc2(file, {}, "MCP document");
20905
+ if (typeof body.mcpServers !== "object" || body.mcpServers === null || Array.isArray(body.mcpServers))
20906
+ continue;
20907
+ let changed = false;
20908
+ for (const pin of projection.pins) {
20909
+ const server = body.mcpServers[pin.server];
20910
+ if (typeof server !== "object" || server === null || Array.isArray(server))
20911
+ continue;
20912
+ server.command = pin.executable;
20913
+ found.add(pin.server);
20914
+ changed = true;
20915
+ }
20916
+ if (changed)
20917
+ writeFileSync9(file, JSON.stringify(body, null, 2));
20918
+ }
20919
+ if (projection.pins.some((pin) => !found.has(pin.server)))
20920
+ throw new Error("dcode resolved pin is absent from staged MCP configuration");
20921
+ const marker = readDoc2(join28(projection.stagingRoot, MARKER7), {}, "ownership marker");
20922
+ marker.projectedFingerprint = projectionDigest(projection.stagingRoot);
20923
+ writeFileSync9(join28(projection.stagingRoot, MARKER7), JSON.stringify(marker));
20924
+ return [...found];
20925
+ },
20926
+ captureActivationPreparation: async (projection, fingerprint) => {
20927
+ await lockPreparation(projection, projection.attemptId, projection.operationId, projection.targetObservationId);
20928
+ const prior = dcodeLifecycleReadback(projection);
20929
+ const target = lifecycleSlot(projection.nativeId, fingerprint);
20930
+ assertCachePath(target);
20931
+ const installed = observeDcodeTarget(projection.target).find((item) => item.nativeId === projection.nativeId);
20932
+ if (installed?.ownership.kind === "owned" && installed.ownership.scopeId !== projection.scopeId)
20933
+ throw new Error("dcode scope ownership conflict");
20934
+ if (installed?.ownership.kind === "unmanaged") {
20935
+ const path = installed.contentRoots[0]?.path;
20936
+ if (path === undefined || !sameTree7(projection.snapshot.packageRoot, path))
20937
+ throw new Error("dcode legacy content differs; refusing adoption");
20938
+ const manifest = declaredManifest(path, projection.packageName);
20939
+ if (manifest.version !== projection.packageVersion)
20940
+ throw new Error("dcode legacy version differs; refusing adoption");
20941
+ }
20942
+ if (existsSync23(target) && ownership3(target)?.pluginId !== projection.nativeId)
20943
+ throw new Error("dcode lifecycle target is not owned");
20944
+ const rollbackReference = captureDcodeRollback(projection, projection.attemptId, projection.operationId);
20945
+ writeFileSync9(join28(rollbackReference, "next-path.json"), JSON.stringify({ target }));
20946
+ return { prior, expected: { ...prior, route: "managed", presence: "present", enablement: "enabled", activation: "active", transition: { requirement: "restart", status: "effective" }, installedFingerprint: fingerprint, contentRoots: [{ label: "managed", path: target, fingerprint }] }, rollbackReference, rollbackCoverageOperationIds: projection.affectedOperationIds };
20947
+ },
20948
+ captureDisablePreparation: async (request) => {
20949
+ const binding = { adapterId: "dcode", ...request.activation };
20950
+ return captureOwnedPreparation(binding, request.attemptId, request.operationId, request.selection.targetObservationId, () => ({ prior: dcodeLifecycleReadback(binding), rollbackReference: captureDcodeRollback(binding, request.attemptId, request.operationId), rollbackCoverageOperationIds: request.selection.affectedOperationIds, transition: { requirement: "restart", status: "effective" } }));
20951
+ },
20952
+ captureRetirementPreparation: async (request) => {
20953
+ const binding = { adapterId: "dcode", ...request.activation };
20954
+ return captureOwnedPreparation(binding, request.attemptId, request.operationId, request.selection.targetObservationId, () => ({ prior: dcodeLifecycleReadback(binding), rollbackReference: captureDcodeRollback(binding, request.attemptId, request.operationId), rollbackCoverageOperationIds: request.selection.affectedOperationIds, transition: { requirement: "none", status: "effective" } }));
20955
+ },
20956
+ apply: async (prepared) => {
20957
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
20958
+ const target = lifecycleSlot(prepared.handle.nativeId, prepared.handle.projectedFingerprint);
20959
+ assertCachePath(target);
20960
+ mkdirSync9(dirname11(target), { recursive: true });
20961
+ if (existsSync23(target)) {
20962
+ const marker = ownership3(target);
20963
+ if (marker?.pluginId !== prepared.handle.nativeId)
20964
+ throw new Error("dcode lifecycle activation would overwrite foreign content");
20965
+ if (fingerprintTree(target) !== prepared.handle.projectedFingerprint)
20966
+ throw new Error("dcode immutable projection slot has diverged");
20967
+ } else {
20968
+ renameSync8(prepared.stagingRoot, target);
20969
+ }
20970
+ setDcodeRow(prepared.handle.nativeId, [{ installPath: target }], true);
20971
+ return { receiptId: `dcode-apply:${prepared.handle.operationId}`, changed: true };
20972
+ },
20973
+ disable: async (prepared) => {
20974
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
20975
+ const row = object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
20976
+ setDcodeRow(prepared.handle.nativeId, row ?? null, false);
20977
+ return { receiptId: `dcode-disable:${prepared.handle.operationId}`, changed: true };
20978
+ },
20979
+ retire: async (prepared) => {
20980
+ await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
20981
+ const rows = rowsFor(object3(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
20982
+ for (const row of rows) {
20983
+ const path = rowInstall(row, prepared.handle.nativeId);
20984
+ assertCachePath(path);
20985
+ if (ownership3(path)?.pluginId !== prepared.handle.nativeId)
20986
+ throw new Error("dcode retirement has no ownership proof");
20987
+ }
20988
+ setDcodeRow(prepared.handle.nativeId, null, null);
20989
+ for (const row of rows)
20990
+ rmSync9(rowInstall(row, prepared.handle.nativeId), { recursive: true, force: true });
20991
+ return { receiptId: `dcode-retire:${prepared.handle.operationId}`, changed: true };
20992
+ },
20993
+ readback: async (handle) => {
20994
+ const held = nativeLocks.get(`${handle.attemptId}:${handle.operationId}`);
20995
+ if (held !== undefined && !held.isAlive())
20996
+ throw new Error("dcode native lock was lost before readback");
20997
+ return dcodeLifecycleReadback(handle);
20998
+ },
20999
+ rollback: async (handle) => {
21000
+ await acquireNativeLock(handle.attemptId, handle.operationId);
21001
+ const target = handle.projectedFingerprint === null ? undefined : lifecycleSlot(handle.nativeId, handle.projectedFingerprint);
21002
+ if (target !== undefined)
21003
+ assertCachePath(target);
21004
+ 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");
21007
+ rmSync9(target, { recursive: true });
21008
+ }
21009
+ const state = readDoc2(join28(handle.rollbackReference, "state.json"), {}, "rollback state");
21010
+ for (const [index, row] of rowsFor(state.row).entries()) {
21011
+ const path = rowInstall(row, handle.nativeId);
21012
+ assertCachePath(path);
21013
+ const backup = join28(handle.rollbackReference, `content-${index}`);
21014
+ if (existsSync23(backup)) {
21015
+ mkdirSync9(dirname11(path), { recursive: true });
21016
+ cpSync8(backup, path, { recursive: true });
21017
+ }
21018
+ }
21019
+ 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)
21021
+ rmSync9(dcodeRegistryFile());
21022
+ if (state.enablementExisted === false && Object.keys(boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins")).length === 0)
21023
+ rmSync9(dcodeEnablementFile());
21024
+ return { receiptId: `dcode-rollback:${handle.operationId}`, changed: true };
21025
+ },
21026
+ releaseRecovery: async (handle) => {
21027
+ await releaseNativeLock(handle.attemptId, handle.operationId);
21028
+ },
21029
+ cleanup: async (reference, disposition) => {
21030
+ try {
21031
+ 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");
21035
+ for (const row of rowsFor(prior.row)) {
21036
+ const path = rowInstall(row, reference.operationId);
21037
+ assertCachePath(path);
21038
+ if (resolve17(path) !== resolve17(next.target))
21039
+ rmSync9(path, { recursive: true, force: true });
21040
+ }
21041
+ }
21042
+ rmSync9(dir, { recursive: true, force: true });
21043
+ } finally {
21044
+ await releaseNativeLock(reference.attemptId, reference.operationId);
21045
+ }
21046
+ return { cleanupId: `dcode-cleanup:${reference.operationId}`, completed: true };
21047
+ }
21048
+ };
21049
+ var dcodeLifecycle = createLifecycleHostAdapter(dcodeDefinition);
20604
21050
  var dcodeWriter = {
21051
+ lifecyclePinAdapter: dcodeLifecycle,
20605
21052
  ...dcode,
20606
21053
  supportsAdoption: true,
20607
21054
  plannedNativeId: (plugin) => `${plugin.name}@${plugin.marketplace || "local"}`,
@@ -20664,7 +21111,7 @@ var dcodeWriter = {
20664
21111
  throw new Error(`dcode cache target is unowned; refusing to replace it: ${target}`);
20665
21112
  const former = prior.kind === "none" ? undefined : prior.path;
20666
21113
  const unchanged = (former === undefined || resolve17(former) === resolve17(target)) && slot?.kind === "current" && slot.fingerprint === sourceFingerprint && slot.projectedFingerprint === projected && slot.sourceRevision === resolved.sha && sameTree7(stage, target);
20667
- const nextRegistry = { ...registry, plugins: { ...plugins, [id]: [{ installPath: target, version: manifest.version }] } };
21114
+ const nextRegistry = { ...registry, plugins: { ...plugins, [id]: [{ installPath: target }] } };
20668
21115
  const nextEnablement = { ...enablement, enabledPlugins: { ...enabled, [id]: true } };
20669
21116
  if (opts?.dryRun) {
20670
21117
  console.log(`[dcode] would activate directory: ${target}`);
@@ -20715,6 +21162,8 @@ var dcodeWriter = {
20715
21162
  assertCachePath(plugin.path);
20716
21163
  if (ownership3(plugin.path)?.pluginId !== plugin.id)
20717
21164
  return { changes: [], refusals: [] };
21165
+ if (opts?.dryRun !== true)
21166
+ throw new Error("dcode pin requires the durable lifecycle pin workflow; direct in-place pinning is refused");
20718
21167
  return pinPluginMcpFiles(plugin.path, dcodeMcpCandidates(), opts);
20719
21168
  },
20720
21169
  async remove(id) {
@@ -20864,7 +21313,9 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
20864
21313
  const install = rowInstall(rows[0], id);
20865
21314
  if (resolve17(install) !== resolve17(target))
20866
21315
  throw new Error(`dcode readback path mismatch for ${id}`);
20867
- if (rows[0].version !== version)
21316
+ if (rows[0].version !== undefined && rows[0].version !== null)
21317
+ throw new Error(`dcode managed install is eligible for native auto-update: ${id}`);
21318
+ if (declaredManifest(target, id.split("@")[0]).version !== version)
20868
21319
  throw new Error(`dcode readback version mismatch for ${id}`);
20869
21320
  if (boolObject(object3(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
20870
21321
  throw new Error(`dcode readback enablement mismatch for ${id}`);
@@ -25034,7 +25485,22 @@ async function planLifecycle(input) {
25034
25485
  const result = await prepareEntry(entry, input.hosts, loaded.state, command, input.transportSource, transportCache);
25035
25486
  if (result.kind === "defect")
25036
25487
  return zeroWrite(command, input.dryRun, result.reason, result.terminalPhase);
25037
- prepared.push(result);
25488
+ if (input.removalSelection !== undefined) {
25489
+ if (command !== "remove" || result.kind !== "retire-source") {
25490
+ return zeroWrite(command, input.dryRun, createLifecycleReason("internal", "internal.invariant", "package retirement selection is valid only for manual removal"));
25491
+ }
25492
+ const selected = input.removalSelection.filter((row) => row.scopeId === result.scope.id);
25493
+ if (selected.length === 0 || selected.some((row) => !loaded.state.activations.some((activation) => activation.scopeId === row.scopeId && activation.packageId === row.packageId && activation.nativeId === row.nativeId))) {
25494
+ return zeroWrite(command, input.dryRun, createLifecycleReason("internal", "internal.invariant", "manual removal selection no longer matches recorded ownership"));
25495
+ }
25496
+ const retirementPackages = selected.map(({ packageId, nativeId }) => ({ packageId, nativeId })).sort((left, right) => left.packageId.localeCompare(right.packageId) || left.nativeId.localeCompare(right.nativeId));
25497
+ prepared.push({
25498
+ ...result,
25499
+ retirementPackages,
25500
+ identity: ["remove-packages", result.scope.id, ...retirementPackages.flatMap((row) => [row.packageId, row.nativeId])]
25501
+ });
25502
+ } else
25503
+ prepared.push(result);
25038
25504
  }
25039
25505
  const attemptId = lifecycleAttemptId(loaded.state.stateGeneration, prepared.map((item) => item.identity));
25040
25506
  const scopes = [];
@@ -25080,6 +25546,10 @@ async function prepareSync(entry, hosts, state, command, transportSource, transp
25080
25546
  throw error;
25081
25547
  }
25082
25548
  const scope = deploymentScopeForSyncManifestEntry(entry);
25549
+ const recordedScope = state.scopes.find((row) => row.id === scope.id);
25550
+ if (recordedScope?.target.context !== undefined && JSON.stringify(canonicalTargetContext(recordedScope.target.context)) !== JSON.stringify(entry.target.context === undefined ? undefined : canonicalTargetContext(entry.target.context))) {
25551
+ return defect(createLifecycleReason("internal", "internal.invariant", `target context changed for recorded scope '${scope.id}'; use a distinct target instance or retire the recorded scope explicitly`));
25552
+ }
25083
25553
  const pending = pendingDefect(state, scope.id, new Set(selected.map((item) => item.plugin.name)), command);
25084
25554
  if (pending !== null)
25085
25555
  return defect(pending);
@@ -25229,6 +25699,7 @@ async function planSync(item, attemptId, validatedAt, state, command) {
25229
25699
  kind: "planned",
25230
25700
  scope: {
25231
25701
  scope: item.scope,
25702
+ target: item.observation?.target ?? item.entry.target,
25232
25703
  selectorMode: item.entry.selectors === undefined ? "all" : "explicit",
25233
25704
  desired: desiredGeneration(existing, item.frozen.snapshot.revision, item.frozen.snapshot.fingerprint, packages, validatedAt),
25234
25705
  prune: blocks || retirements.operations.some((row) => row.operation.action !== "retire-orphan") ? "blocked" : "planned"
@@ -25308,7 +25779,7 @@ async function planOmissions(item, attemptId, state, desired) {
25308
25779
  return { kind: "omissions", operations };
25309
25780
  }
25310
25781
  async function planRetire(item, attemptId, state) {
25311
- const activations = state.activations.filter((activation) => activation.scopeId === item.recorded.id).sort((left, right) => left.packageId < right.packageId ? -1 : left.packageId > right.packageId ? 1 : 0);
25782
+ const activations = state.activations.filter((activation) => activation.scopeId === item.recorded.id && (item.retirementPackages === undefined || item.retirementPackages.some((row) => row.packageId === activation.packageId && row.nativeId === activation.nativeId))).sort((left, right) => left.packageId < right.packageId ? -1 : left.packageId > right.packageId ? 1 : 0);
25312
25783
  const operations = [];
25313
25784
  for (const activation of activations) {
25314
25785
  const installation = item.observation?.installations.find((row) => row.nativeId === activation.nativeId);
@@ -25328,8 +25799,10 @@ async function planRetire(item, attemptId, state) {
25328
25799
  kind: "planned",
25329
25800
  scope: {
25330
25801
  scope: item.scope,
25802
+ target: item.observation?.target ?? item.recorded.target,
25331
25803
  selectorMode: "retired",
25332
25804
  desired: null,
25805
+ ...item.retirementPackages === undefined ? {} : { retirementPackages: item.retirementPackages },
25333
25806
  prune: operations.length > 0 && operations.every((row) => row.operation.action === "retire-orphan") ? "planned" : "blocked"
25334
25807
  },
25335
25808
  operations
@@ -25830,7 +26303,7 @@ function lifecycleAttemptId(stateGeneration, identities) {
25830
26303
  return `attempt-v1-${digest3([String(stateGeneration), ...identities.flat()])}`;
25831
26304
  }
25832
26305
  function lifecycleAttemptIdForPlannedScopes(stateGeneration, scopes) {
25833
- return lifecycleAttemptId(stateGeneration, scopes.map((planned) => plannedAttemptIdentity(planned.scope.id, planned.desired?.sourceFingerprint, planned.desired?.packages.map((pkg) => pkg.packageId), planned.selectorMode === "retired" || planned.desired === null)));
26306
+ return lifecycleAttemptId(stateGeneration, scopes.map((planned) => planned.retirementPackages !== undefined ? ["remove-packages", planned.scope.id, ...planned.retirementPackages.flatMap((row) => [row.packageId, row.nativeId])] : plannedAttemptIdentity(planned.scope.id, planned.desired?.sourceFingerprint, planned.desired?.packages.map((pkg) => pkg.packageId), planned.selectorMode === "retired" || planned.desired === null)));
25834
26307
  }
25835
26308
  function plannedAttemptIdentity(scopeId, sourceFingerprint, packageIds, retired = false) {
25836
26309
  if (retired || sourceFingerprint === undefined || packageIds === undefined)
@@ -26093,8 +26566,9 @@ async function runOperation(plan, operation, hosts, ledger) {
26093
26566
  return succeeded(operation, false);
26094
26567
  case "retire-orphan":
26095
26568
  return runRetire(plan, operation, hosts, ledger);
26096
- case "route-migrate":
26097
26569
  case "disable-nonconforming":
26570
+ return runDisable(plan, operation, hosts, ledger);
26571
+ case "route-migrate":
26098
26572
  case "retain-prior":
26099
26573
  case "not-attempted":
26100
26574
  return stop(operation, createLifecycleReason("internal", "internal.invariant", `operation '${operation.operationId}' is not applied by this execution slice`));
@@ -26157,6 +26631,12 @@ async function runActivation(plan, operation, hosts, ledger, action) {
26157
26631
  verified = host.adapter.verify(receipt.handle, observation);
26158
26632
  } catch (error) {
26159
26633
  const reason = thrownReason(error);
26634
+ if (receipt === undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying" && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
26635
+ ledger.markNotApplied(plan, operation);
26636
+ if (preparation !== undefined)
26637
+ await abortPreparation(host, preparation);
26638
+ return stop(operation, reason);
26639
+ }
26160
26640
  if (handle !== undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying") {
26161
26641
  return recoverApplied(plan, operation, host, ledger, handle, reason);
26162
26642
  }
@@ -26230,7 +26710,7 @@ async function prepareActivation(plan, operation, host, nativeId, action, record
26230
26710
  if (plugin === undefined || packageFingerprint === undefined) {
26231
26711
  return refused(createLifecycleReason("internal", "internal.invariant", `${action} '${operation.package}' is missing from the frozen source`));
26232
26712
  }
26233
- const target = lifecycleTarget2(operation);
26713
+ const target = lifecycleTarget2(plan, operation);
26234
26714
  const observation = await host.adapter.observeTarget(target);
26235
26715
  const version = await host.adapter.probeVersion(observation.target);
26236
26716
  if (version.kind !== "detected") {
@@ -26314,6 +26794,50 @@ function sealActivationSnapshot(operation, attemptId, nativeId, frozen, plugin,
26314
26794
  throw new Error(`frozen snapshot action '${snapshot.action}' is not ${action}`);
26315
26795
  return snapshot;
26316
26796
  }
26797
+ async function runDisable(plan, operation, hosts, ledger) {
26798
+ const host = hosts.find((candidate) => candidate.kinds.includes(operation.scope.target.kind));
26799
+ const reason = plan.report.outcomes.find((row) => row.operationId === operation.operationId)?.reason;
26800
+ if (host === undefined || operation.nativeId === null || reason === undefined || reason === null) {
26801
+ return stop(operation, createLifecycleReason("internal", "internal.invariant", "disable containment lost its adapter, identity or desired failure reason"));
26802
+ }
26803
+ let preparation;
26804
+ let receipt;
26805
+ try {
26806
+ const prepared = await prepareOwnedMutation(plan, operation, host, operation.nativeId, ledger, "disable");
26807
+ if (prepared.kind === "refused")
26808
+ return stop(operation, prepared.reason);
26809
+ if (prepared.selection.operation !== "disable")
26810
+ throw new Error("disable containment selected the wrong operation");
26811
+ ledger.acceptJournal(plan, operation, "disable-nonconforming");
26812
+ preparation = await host.adapter.prepareDisable({ operationId: operation.operationId, attemptId: plan.attemptId, selection: { ...prepared.selection, operation: "disable" }, activation: prepared.activation });
26813
+ ledger.markApplying(plan, operation);
26814
+ receipt = await host.adapter.disable(preparation);
26815
+ const observation = await host.adapter.readback(receipt.handle);
26816
+ const verified = host.adapter.verify(receipt.handle, observation);
26817
+ ledger.confirmDisable(plan, operation);
26818
+ try {
26819
+ await host.adapter.cleanup(verified.handle, "verified-commit");
26820
+ } catch {
26821
+ ledger.markCleanupPending(plan, operation, "disable-nonconforming");
26822
+ return pendingCleanup(operation);
26823
+ }
26824
+ ledger.completeContainment(plan, operation);
26825
+ return { stop: true, reason, outcome: { ...operation, result: "failed", resourceState: "retained", activationState: "inactive", changed: receipt.changed, reason } };
26826
+ } catch (error) {
26827
+ const failure = thrownReason(error);
26828
+ if (receipt === undefined && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
26829
+ if (ledger.journalState(plan.attemptId, operation.operationId) !== undefined)
26830
+ ledger.markNotApplied(plan, operation);
26831
+ if (preparation !== undefined)
26832
+ await abortPreparation(host, preparation);
26833
+ return stop(operation, failure);
26834
+ }
26835
+ if (preparation !== undefined && (receipt !== undefined || ledger.journalState(plan.attemptId, operation.operationId) === "applying")) {
26836
+ return recoverApplied(plan, operation, host, ledger, preparation.handle, failure);
26837
+ }
26838
+ return stop(operation, failure);
26839
+ }
26840
+ }
26317
26841
  async function runRetire(plan, operation, hosts, ledger) {
26318
26842
  if (ledger.journalState(plan.attemptId, operation.operationId) === "completed")
26319
26843
  return retired(operation, false);
@@ -26331,17 +26855,20 @@ async function runRetire(plan, operation, hosts, ledger) {
26331
26855
  }
26332
26856
  let prepared;
26333
26857
  try {
26334
- prepared = await prepareRetirement(plan, operation, host, nativeId, ledger);
26858
+ prepared = await prepareOwnedMutation(plan, operation, host, nativeId, ledger);
26335
26859
  } catch (error) {
26336
26860
  return stop(operation, thrownReason(error));
26337
26861
  }
26338
26862
  if (prepared.kind === "refused")
26339
26863
  return stop(operation, prepared.reason);
26864
+ if (prepared.selection.operation !== "retire")
26865
+ return stop(operation, createLifecycleReason("internal", "internal.invariant", "retirement selected the wrong operation"));
26340
26866
  try {
26341
26867
  ledger.acceptJournal(plan, operation, "retire-orphan");
26342
26868
  } catch (error) {
26343
26869
  return stop(operation, thrownReason(error));
26344
26870
  }
26871
+ let preparation;
26345
26872
  let receipt;
26346
26873
  let verified;
26347
26874
  try {
@@ -26349,17 +26876,27 @@ async function runRetire(plan, operation, hosts, ledger) {
26349
26876
  operationId: operation.operationId,
26350
26877
  attemptId: plan.attemptId,
26351
26878
  action: "retire-orphan",
26352
- selection: prepared.selection,
26879
+ selection: { ...prepared.selection, operation: "retire" },
26353
26880
  activation: prepared.activation
26354
26881
  });
26882
+ preparation = sealed;
26355
26883
  ledger.markPruning(plan, operation);
26356
26884
  receipt = await host.adapter.retire(sealed);
26357
26885
  const observation = await host.adapter.readback(receipt.handle);
26358
26886
  verified = host.adapter.verify(receipt.handle, observation);
26359
26887
  } catch (error) {
26360
26888
  const reason = thrownReason(error);
26889
+ if (receipt === undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying" && error instanceof LifecycleHostPhaseError && !error.mutationStarted) {
26890
+ ledger.markNotApplied(plan, operation);
26891
+ if (preparation !== undefined)
26892
+ await abortPreparation(host, preparation);
26893
+ return stop(operation, reason);
26894
+ }
26361
26895
  if (receipt !== undefined)
26362
26896
  return recoverApplied(plan, operation, host, ledger, receipt.handle, reason);
26897
+ if (preparation !== undefined && ledger.journalState(plan.attemptId, operation.operationId) === "applying") {
26898
+ return recoverApplied(plan, operation, host, ledger, preparation.handle, reason);
26899
+ }
26363
26900
  return stop(operation, reason);
26364
26901
  }
26365
26902
  if (receipt === undefined || verified === undefined) {
@@ -26379,12 +26916,13 @@ async function runRetire(plan, operation, hosts, ledger) {
26379
26916
  ledger.markCompleted(plan, operation);
26380
26917
  return retired(operation, receipt.changed);
26381
26918
  }
26382
- async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26919
+ async function prepareOwnedMutation(plan, operation, host, nativeId, ledger, capabilityOperation = "retire") {
26920
+ const operationLabel = capabilityOperation === "disable" ? "disablement" : "retirement";
26383
26921
  const recorded = ledger.activation(operation.scope.id, operation.package, nativeId);
26384
26922
  if (recorded === undefined || recorded.route.kind === "legacy-unverified" || recorded.ownership.kind === "legacy-claim" || recorded.sourceRevision === undefined) {
26385
26923
  return refused(createLifecycleReason("internal", "internal.invariant", `ownership of '${operation.package}' was not revalidated`));
26386
26924
  }
26387
- const target = lifecycleTarget2(operation);
26925
+ const target = lifecycleTarget2(plan, operation);
26388
26926
  const observation = await host.adapter.observeTarget(target);
26389
26927
  const installation = observation.installations.find((row) => row.nativeId === nativeId);
26390
26928
  if (installation === undefined || installation.presence !== "present" || installation.ownership.kind !== "owned" || installation.ownership.scopeId !== operation.scope.id) {
@@ -26392,10 +26930,10 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26392
26930
  }
26393
26931
  const version = await host.adapter.probeVersion(observation.target);
26394
26932
  if (version.kind !== "detected") {
26395
- return refused(createLifecycleReason("runtime", "runtime.operation-failed", `retirement '${operation.package}' lost its detected target version`));
26933
+ return refused(createLifecycleReason("runtime", "runtime.operation-failed", `${operationLabel} '${operation.package}' lost its detected target version`));
26396
26934
  }
26397
26935
  if (recorded.fingerprints.installed !== installation.installedFingerprint) {
26398
- return refused(createLifecycleReason("internal", "internal.invariant", `retirement '${operation.package}' fingerprint does not match the owned install`));
26936
+ return refused(createLifecycleReason("internal", "internal.invariant", `${operationLabel} '${operation.package}' fingerprint does not match the owned install`));
26399
26937
  }
26400
26938
  const owned = ownedActivation(operation.scope, operation.scope.source, recorded, installation, observation.target);
26401
26939
  if (owned === null) {
@@ -26404,27 +26942,27 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26404
26942
  const planCoverage = createLifecyclePlanCoverage(observation, [{
26405
26943
  nativeId,
26406
26944
  operationId: operation.operationId,
26407
- operation: "retire",
26945
+ operation: capabilityOperation,
26408
26946
  mutationGroupId: operation.operationId,
26409
26947
  authorization: "observed-owned"
26410
26948
  }]);
26411
26949
  const nativeScope = await host.adapter.observeNativeMutationScope({
26412
26950
  targetObservation: observation,
26413
- operation: "retire",
26951
+ operation: capabilityOperation,
26414
26952
  packageName: owned.packageName,
26415
26953
  nativeId,
26416
26954
  sourceType: owned.sourceType
26417
26955
  });
26418
26956
  const nativeProjection = await host.adapter.observeNativeProjection({
26419
26957
  targetObservation: observation,
26420
- operation: "retire",
26958
+ operation: capabilityOperation,
26421
26959
  operationId: operation.operationId,
26422
26960
  attemptId: plan.attemptId,
26423
26961
  activation: owned
26424
26962
  });
26425
26963
  const decision = host.adapter.decideRoute({
26426
26964
  target: observation.target,
26427
- operation: "retire",
26965
+ operation: capabilityOperation,
26428
26966
  operationId: operation.operationId,
26429
26967
  attemptId: plan.attemptId,
26430
26968
  scopeId: operation.scope.id,
@@ -26438,13 +26976,13 @@ async function prepareRetirement(plan, operation, host, nativeId, ledger) {
26438
26976
  planCoverage,
26439
26977
  activation: owned
26440
26978
  });
26441
- if (decision.kind !== "selected" || decision.operation !== "retire" || decision.route !== operation.route) {
26442
- return refused(createLifecycleReason("runtime", "runtime.operation-failed", `retirement '${operation.package}' kept frozen route '${operation.route}'`));
26979
+ if (decision.kind !== "selected" || decision.operation !== capabilityOperation || decision.route !== operation.route) {
26980
+ return refused(createLifecycleReason("runtime", "runtime.operation-failed", `${operationLabel} '${operation.package}' kept frozen route '${operation.route}'`));
26443
26981
  }
26444
26982
  return { kind: "ready", selection: decision, activation: owned, recorded };
26445
26983
  }
26446
- function lifecycleTarget2(operation) {
26447
- return { kind: operation.scope.target.kind, instance: operation.scope.target.instance };
26984
+ function lifecycleTarget2(plan, operation) {
26985
+ return plan.scopes.find((row) => row.scope.id === operation.scope.id)?.target ?? { kind: operation.scope.target.kind, instance: operation.scope.target.instance };
26448
26986
  }
26449
26987
 
26450
26988
  class Ledger {
@@ -26505,8 +27043,18 @@ class Ledger {
26505
27043
  return this.state.activations.find((row) => row.scopeId === scopeId && row.packageId === packageId && row.nativeId === nativeId);
26506
27044
  }
26507
27045
  markApplying(plan, operation) {
27046
+ if (operation.action === "disable-nonconforming")
27047
+ this.disablePending(plan, operation, "applying");
26508
27048
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "applying", "applying", true));
26509
27049
  }
27050
+ markNotApplied(plan, operation) {
27051
+ if (operation.action === "disable-nonconforming")
27052
+ this.clearDisablePending(operation);
27053
+ this.replaceAttempt(plan, (attempt) => {
27054
+ const next = this.journaled(attempt, operation.operationId, "not-attempted", "failed", false);
27055
+ return { ...next, mutationStarted: next.journal.some((row) => mutationBegan(row.state)) };
27056
+ });
27057
+ }
26510
27058
  markPruning(plan, operation) {
26511
27059
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "applying", "pruning", true));
26512
27060
  }
@@ -26570,13 +27118,36 @@ class Ledger {
26570
27118
  }
26571
27119
  markRolledBack(plan, operation) {
26572
27120
  this.adoptMovedGeneration();
27121
+ if (operation.action === "disable-nonconforming")
27122
+ this.clearDisablePending(operation, true);
26573
27123
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "rolled-back", "failed", true));
26574
27124
  }
27125
+ confirmDisable(plan, operation) {
27126
+ this.clearDisablePending(operation);
27127
+ 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) };
27128
+ this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "readback-verified", "readback", true));
27129
+ }
27130
+ completeContainment(plan, operation) {
27131
+ this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "completed", "failed", true));
27132
+ }
26575
27133
  markRollbackRequired(plan, operation) {
26576
27134
  this.adoptMovedGeneration();
26577
27135
  this.retargetBareAlias(plan, operation);
27136
+ if (operation.action === "disable-nonconforming")
27137
+ this.disablePending(plan, operation, "rollback");
26578
27138
  this.replaceAttempt(plan, (attempt) => this.journaled(attempt, operation.operationId, "rollback", "recovery-required", true));
26579
27139
  }
27140
+ disablePending(plan, operation, phase) {
27141
+ this.state = { ...this.state, activations: this.state.activations.map((row) => row.scopeId === operation.scope.id && row.packageId === operation.package ? { ...row, pending: { operation: "disable-nonconforming", phase, attemptId: plan.attemptId, startedAt: this.now } } : row) };
27142
+ }
27143
+ clearDisablePending(operation, rolledBack = false) {
27144
+ this.state = { ...this.state, activations: this.state.activations.map((row) => {
27145
+ if (row.scopeId !== operation.scope.id || row.packageId !== operation.package)
27146
+ return row;
27147
+ const { pending: _pending, ...prior } = row;
27148
+ return rolledBack ? { ...prior, activationState: "nonconforming", readbackState: "unverified" } : prior;
27149
+ }) };
27150
+ }
26580
27151
  markCompleted(plan, operation) {
26581
27152
  const desiredPairs = plan.operations.filter((row) => row.operation.scope.id === operation.scope.id && row.operation.coverage === "desired-pair");
26582
27153
  const converged = desiredPairs.every((row) => row.operation.operationId === operation.operationId || row.operation.action === "unchanged" || this.journalState(plan.attemptId, row.operation.operationId) === "completed");
@@ -26595,7 +27166,13 @@ class Ledger {
26595
27166
  if (scope.authority !== "authoritative")
26596
27167
  return scope;
26597
27168
  const planned = plan.scopes.find((row) => row.scope.id === scope.id);
27169
+ if (planned?.retirementPackages !== undefined)
27170
+ return scope;
26598
27171
  if (planned?.selectorMode === "retired") {
27172
+ if (this.state.activations.some((row) => row.scopeId === scope.id))
27173
+ return scope;
27174
+ if (plan.operations.some((row) => row.operation.scope.id === scope.id && row.operation.operationId !== operation.operationId && this.journalState(plan.attemptId, row.operation.operationId) !== "completed"))
27175
+ return scope;
26599
27176
  const { desired: _desired, lastConverged: _converged, ...kept } = scope;
26600
27177
  return {
26601
27178
  ...kept,
@@ -26663,7 +27240,7 @@ class Ledger {
26663
27240
  if (existing !== undefined && existing.authority !== "authoritative")
26664
27241
  return existing;
26665
27242
  const selectorMode = planned.selectorMode;
26666
- const target = { kind: planned.scope.target.kind, instance: planned.scope.target.instance };
27243
+ const target = planned.target ?? { kind: planned.scope.target.kind, instance: planned.scope.target.instance };
26667
27244
  if (existing === undefined) {
26668
27245
  return {
26669
27246
  id: planned.scope.id,
@@ -26829,19 +27406,40 @@ function succeeded(operation, changed, notices = []) {
26829
27406
  };
26830
27407
  }
26831
27408
  async function recoverApplied(plan, operation, host, ledger, handle, reason) {
27409
+ let verified = false;
27410
+ let diagnostic = "";
26832
27411
  try {
26833
- await host.adapter.rollback(handle);
26834
- const observation = await host.adapter.readback(handle);
26835
- host.adapter.verifyRollback(handle, observation);
26836
- } catch {
26837
- ledger.markRollbackRequired(plan, operation);
26838
- return pendingRecovery(operation);
27412
+ try {
27413
+ await host.adapter.rollback(handle);
27414
+ const observation = await host.adapter.readback(handle);
27415
+ host.adapter.verifyRollback(handle, observation);
27416
+ verified = true;
27417
+ } catch (error) {
27418
+ diagnostic = `; rollback verification failed: ${unknownErrorDiagnostic(error)}`;
27419
+ }
27420
+ try {
27421
+ if (verified)
27422
+ ledger.markRolledBack(plan, operation);
27423
+ else
27424
+ ledger.markRollbackRequired(plan, operation);
27425
+ } catch (error) {
27426
+ verified = false;
27427
+ diagnostic += `; recovery ledger could not be persisted: ${unknownErrorDiagnostic(error)}`;
27428
+ }
27429
+ if (verified) {
27430
+ try {
27431
+ await host.adapter.cleanup(handle, "verified-rollback");
27432
+ } catch {}
27433
+ }
27434
+ } finally {
27435
+ try {
27436
+ await host.adapter.releaseRecovery(handle);
27437
+ } catch (error) {
27438
+ verified = false;
27439
+ diagnostic += `; recovery resources could not be released: ${unknownErrorDiagnostic(error)}`;
27440
+ }
26839
27441
  }
26840
- ledger.markRolledBack(plan, operation);
26841
- try {
26842
- await host.adapter.cleanup(handle, "verified-rollback");
26843
- } catch {}
26844
- return reportedRollback(operation, reason);
27442
+ return verified ? reportedRollback(operation, reason) : pendingRecovery(operation, `; original failure: ${reason.diagnostic}${diagnostic}`);
26845
27443
  }
26846
27444
  async function abortPreparation(host, preparation) {
26847
27445
  try {
@@ -26869,8 +27467,8 @@ function pendingReadback(operation, reason) {
26869
27467
  }
26870
27468
  };
26871
27469
  }
26872
- function pendingRecovery(operation) {
26873
- const reason = createLifecycleReason("recovery", "recovery.required", `rollback for '${operation.operationId}' did not verify`);
27470
+ function pendingRecovery(operation, diagnostic = "") {
27471
+ const reason = createLifecycleReason("recovery", "recovery.required", `rollback for '${operation.operationId}' did not verify${diagnostic}`);
26874
27472
  return {
26875
27473
  stop: true,
26876
27474
  reason,
@@ -26906,8 +27504,8 @@ function pendingCleanup(operation) {
26906
27504
  outcome: {
26907
27505
  ...operation,
26908
27506
  result: "pending",
26909
- resourceState: "present",
26910
- activationState: "active-conforming",
27507
+ resourceState: operation.action === "disable-nonconforming" ? "retained" : "present",
27508
+ activationState: operation.action === "disable-nonconforming" ? "inactive" : "active-conforming",
26911
27509
  changed: true,
26912
27510
  reason
26913
27511
  }
@@ -26986,7 +27584,7 @@ function summaryFor(outcomes) {
26986
27584
  const failed = outcomes.find((outcome) => outcome.result !== "succeeded");
26987
27585
  const changed = outcomes.some((outcome) => outcome.changed);
26988
27586
  const pending = outcomes.find((outcome) => outcome.result === "pending");
26989
- const readback = outcomes.find((outcome) => outcome.reason?.category === "readback");
27587
+ const readback = outcomes.find((outcome) => outcome.reason?.category === "readback" || outcome.action === "disable-nonconforming" && outcome.changed);
26990
27588
  const reapplied = outcomes.some((outcome) => outcome.result === "succeeded" && outcome.action === "update");
26991
27589
  const mutationStarted = changed || pending !== undefined || reapplied;
26992
27590
  if (failed === undefined) {
@@ -27080,7 +27678,8 @@ async function runFrozenLifecycle(command) {
27080
27678
  validatedAt: command.now,
27081
27679
  hosts: command.hosts,
27082
27680
  ...command.command === undefined ? {} : { command: command.command },
27083
- ...command.transportSource === undefined ? {} : { transportSource: command.transportSource }
27681
+ ...command.transportSource === undefined ? {} : { transportSource: command.transportSource },
27682
+ ...command.removalSelection === undefined ? {} : { removalSelection: command.removalSelection }
27084
27683
  });
27085
27684
  const executed = await executeLifecycle({
27086
27685
  plan,
@@ -27103,6 +27702,7 @@ function plannerHost(kind, adapter, plannedNativeId, announceDryRun) {
27103
27702
  var lifecyclePlannerHostKinds = [
27104
27703
  "claude-code",
27105
27704
  "codex",
27705
+ "dcode",
27106
27706
  "kimi",
27107
27707
  "cursor",
27108
27708
  "omp",
@@ -27114,6 +27714,7 @@ function createLifecyclePlannerHosts() {
27114
27714
  return [
27115
27715
  plannerHost("claude-code", createClaudeCodeLifecycleHost(), claudeCodeWriter.plannedNativeId),
27116
27716
  plannerHost("codex", codexLifecycle, codexWriter.plannedNativeId),
27717
+ plannerHost("dcode", dcodeLifecycle, dcodeWriter.plannedNativeId),
27117
27718
  plannerHost("kimi", createLifecycleHostAdapter(kimiLifecycle), kimiWriter.plannedNativeId),
27118
27719
  plannerHost("cursor", cursorManagedLifecycle, cursorWriter.plannedNativeId, announceCursorDryRun),
27119
27720
  plannerHost("omp", ompLifecycle, ompWriter.plannedNativeId),
@@ -27123,6 +27724,161 @@ function createLifecyclePlannerHosts() {
27123
27724
  ];
27124
27725
  }
27125
27726
 
27727
+ // src/pin-lifecycle.ts
27728
+ import { cpSync as cpSync13, mkdtempSync as mkdtempSync14, rmSync as rmSync15 } from "node:fs";
27729
+ import { tmpdir as tmpdir12 } from "node:os";
27730
+ import { basename as basename9, join as join35, relative as relative8 } from "node:path";
27731
+ async function runOfflineLifecyclePin(host, plugin, dryRun) {
27732
+ const adapter = host.lifecyclePinAdapter;
27733
+ if (adapter === undefined || plugin.path === undefined)
27734
+ throw new Error("offline lifecycle pin has no adapter or active root");
27735
+ let state = readLifecycleState().state;
27736
+ const matches = state.activations.filter((row) => row.nativeId === plugin.id && state.scopes.some((scope) => scope.id === row.scopeId && scope.target.kind === host.id));
27737
+ if (matches.length !== 1)
27738
+ throw new Error("offline lifecycle pin requires one recorded owned activation");
27739
+ const activation = matches[0];
27740
+ const scope = state.scopes.find((row) => row.id === activation.scopeId);
27741
+ if (activation.ownership.kind === "legacy-claim" || activation.route.kind === "legacy-unverified" || activation.sourceRevision === undefined || activation.pending !== undefined)
27742
+ throw new Error("offline lifecycle pin requires verified ownership with no pending recovery");
27743
+ if (activation.activationState !== "active" || activation.readbackState !== "verified")
27744
+ throw new Error("offline lifecycle pin requires a verified active installation");
27745
+ const preview = await host.pin(plugin, { dryRun: true });
27746
+ if (preview.refusals.length !== 0)
27747
+ return { changes: [], refusals: preview.refusals };
27748
+ if (preview.changes.length === 0)
27749
+ return preview;
27750
+ const now = new Date().toISOString();
27751
+ const frozenRoot = mkdtempSync14(join35(tmpdir12(), "plgnz-offline-pin-"));
27752
+ const attemptId = `pin-${basename9(frozenRoot)}`;
27753
+ const operationId = `${attemptId}:${plugin.id}`;
27754
+ let handle;
27755
+ let preparation;
27756
+ let accepted = false, applying = false, committed = false;
27757
+ const attempt = { id: attemptId, command: "update", phase: "accepted", mutationStarted: false, scopeIds: [scope.id], journal: [{ operationId, scopeId: scope.id, packageId: activation.packageId, nativeId: activation.nativeId, action: "update", state: "pending", route: activation.route, startedAt: now, updatedAt: now }], startedAt: now, updatedAt: now };
27758
+ const persist = (nextActivation) => {
27759
+ if (readLifecycleState().state.stateGeneration !== state.stateGeneration)
27760
+ throw new Error("pin ledger changed concurrently; refusing stale commit");
27761
+ const next = { ...state, stateGeneration: state.stateGeneration + 1, activations: state.activations.map((row) => row.scopeId === activation.scopeId && row.nativeId === activation.nativeId ? nextActivation : row), attempts: [...state.attempts.filter((row) => row.id !== attemptId), { ...attempt, journal: attempt.journal.map((row) => ({ ...row })) }] };
27762
+ writeLifecycleState(next, { globalPreflight: "succeeded" });
27763
+ state = next;
27764
+ };
27765
+ const pending = (phase) => persist({ ...activation, pending: { operation: "update", phase, attemptId, startedAt: now }, readbackState: "pending" });
27766
+ try {
27767
+ cpSync13(plugin.path, frozenRoot, { recursive: true });
27768
+ const packageFingerprint = fingerprintTree(frozenRoot);
27769
+ if (packageFingerprint !== activation.fingerprints.installed)
27770
+ throw new Error("pin active bytes differ from recorded readback");
27771
+ const target = { kind: scope.target.kind, instance: scope.target.instance };
27772
+ const observed = await adapter.observeTarget(target);
27773
+ const installed = observed.installations.find((row) => row.nativeId === plugin.id);
27774
+ if (installed?.ownership.kind !== "owned" || installed.ownership.scopeId !== scope.id || installed.installedFingerprint !== packageFingerprint || installed.enablement !== "enabled")
27775
+ throw new Error("pin native identity or bytes differ from the recorded owned activation");
27776
+ const version = await adapter.probeVersion(target);
27777
+ const sourceType = scope.source.kind;
27778
+ const inventory = inventoryPackageSemantics({ dir: frozenRoot, name: activation.packageId, ...plugin.version === undefined ? {} : { version: plugin.version }, contentFingerprint: packageFingerprint });
27779
+ const snapshot = createFrozenPackageSnapshot({ operationId, attemptId, scopeId: scope.id, target, action: "update", packageName: activation.packageId, nativeId: plugin.id, sourceType, immutableRevision: activation.sourceRevision, snapshotRoot: frozenRoot, packageRoot: frozenRoot, relativePackagePath: ".", snapshotFingerprint: packageFingerprint, packageFingerprint, ...sourceType === "git" ? { nativeGit: { locator: scope.source.locator, resolvedRevision: activation.sourceRevision } } : {}, inventory });
27780
+ const pins = createResolvedLifecyclePins(preview.changes.map((change) => ({ server: change.server, executable: change.to })).sort((left, right) => left.server < right.server ? -1 : left.server > right.server ? 1 : 0));
27781
+ const nativeScope = await adapter.observeNativeMutationScope({ targetObservation: observed, operation: "update", packageName: activation.packageId, nativeId: plugin.id, sourceType });
27782
+ const nativeProjection = await adapter.observeNativeProjection({ targetObservation: observed, operation: "update", snapshot, pins });
27783
+ const selection = adapter.decideRoute({ target, operationId, attemptId, scopeId: scope.id, packageName: activation.packageId, nativeId: plugin.id, version, sourceType, targetObservation: observed, nativeScope, nativeProjection, planCoverage: createLifecyclePlanCoverage(observed, [{ nativeId: plugin.id, operationId, operation: "update", mutationGroupId: operationId, authorization: "observed-owned" }]), operation: "update", snapshot, pins });
27784
+ if (selection.kind !== "selected")
27785
+ throw new Error(`pin capability ${selection.status}: ${selection.gaps.map((gap) => gap.capabilityId).join(", ")}`);
27786
+ if (selection.route !== "managed")
27787
+ throw new Error("offline lifecycle pin requires a frozen Managed projection");
27788
+ if (dryRun)
27789
+ return preview;
27790
+ pending("accepted");
27791
+ accepted = true;
27792
+ preparation = await adapter.stageActivation({ selection, snapshot, pins });
27793
+ preparation = await adapter.applyLifecycleDirectives(preparation);
27794
+ preparation = await adapter.applyPins(preparation);
27795
+ const sealed = await adapter.sealActivation(preparation);
27796
+ preparation = sealed;
27797
+ handle = sealed.handle;
27798
+ attempt.phase = "applying";
27799
+ attempt.mutationStarted = true;
27800
+ attempt.journal[0].state = "applying";
27801
+ pending("applying");
27802
+ applying = true;
27803
+ await adapter.apply(sealed);
27804
+ const observation = await adapter.readback(handle);
27805
+ adapter.verify(handle, observation);
27806
+ if (observation.installedFingerprint === null || handle.projectedFingerprint === null)
27807
+ throw new Error("pin readback lacks exact installed/projected fingerprint");
27808
+ attempt.phase = "finalizing";
27809
+ attempt.journal[0].state = "readback-verified";
27810
+ 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
+ persist(pinnedActivation);
27812
+ committed = true;
27813
+ try {
27814
+ await adapter.cleanup(handle, "verified-commit");
27815
+ } catch (error) {
27816
+ attempt.journal[0].state = "cleanup-pending";
27817
+ persist({ ...pinnedActivation, pending: { operation: "update", phase: "cleanup", attemptId, startedAt: now } });
27818
+ await adapter.releaseRecovery(handle);
27819
+ throw error;
27820
+ }
27821
+ attempt.phase = "completed";
27822
+ attempt.journal[0].state = "completed";
27823
+ attempt.completedAt = now;
27824
+ persist(pinnedActivation);
27825
+ const activatedRoot = observation.contentRoots[0]?.path;
27826
+ return { ...preview, changes: preview.changes.map((change) => ({ ...change, file: activatedRoot === undefined ? change.file : join35(activatedRoot, relative8(plugin.path, change.file)) })) };
27827
+ } catch (error) {
27828
+ if (!committed && handle !== undefined && applying) {
27829
+ if (!(error instanceof LifecycleHostPhaseError && error.mutationStarted === false)) {
27830
+ attempt.phase = "recovery-required";
27831
+ attempt.journal[0].state = "rollback";
27832
+ try {
27833
+ let journalError;
27834
+ try {
27835
+ pending("rollback");
27836
+ } catch (writeError) {
27837
+ journalError = writeError;
27838
+ }
27839
+ await adapter.rollback(handle);
27840
+ adapter.verifyRollback(handle, await adapter.readback(handle));
27841
+ if (journalError !== undefined)
27842
+ throw new Error(`rollback verified but its durable receipt is blocked: ${journalError.message}`);
27843
+ attempt.phase = "finalizing";
27844
+ attempt.journal[0].state = "cleanup-pending";
27845
+ persist({ ...activation, pending: { operation: "update", phase: "cleanup", attemptId, startedAt: now } });
27846
+ await adapter.cleanup(handle, "verified-rollback");
27847
+ attempt.phase = "failed";
27848
+ attempt.journal[0].state = "rolled-back";
27849
+ persist(activation);
27850
+ } catch (recoveryError) {
27851
+ await adapter.releaseRecovery(handle);
27852
+ throw new Error(`pin failed: ${error.message}; recovery remains pending: ${recoveryError.message}`);
27853
+ }
27854
+ } else {
27855
+ attempt.phase = "failed";
27856
+ attempt.journal[0].state = "not-attempted";
27857
+ attempt.mutationStarted = false;
27858
+ persist(activation);
27859
+ await adapter.cleanup(handle, "aborted-preparation");
27860
+ }
27861
+ } else if (!committed && accepted) {
27862
+ try {
27863
+ if (preparation !== undefined)
27864
+ await adapter.cleanup(preparation, "aborted-preparation");
27865
+ } finally {
27866
+ attempt.phase = "failed";
27867
+ attempt.journal[0].state = "not-attempted";
27868
+ persist(activation);
27869
+ }
27870
+ }
27871
+ throw error;
27872
+ } finally {
27873
+ try {
27874
+ if (handle !== undefined)
27875
+ await adapter.releaseRecovery(handle);
27876
+ } finally {
27877
+ rmSync15(frozenRoot, { recursive: true, force: true });
27878
+ }
27879
+ }
27880
+ }
27881
+
27126
27882
  // src/pin.ts
27127
27883
  function defaultPinTargets(all = writers) {
27128
27884
  return all.filter((w) => w.gui);
@@ -27133,13 +27889,20 @@ async function runPin(options = {}) {
27133
27889
  const selected = selectTargets(candidates, options);
27134
27890
  const findings = [];
27135
27891
  let ledgerChanged = false;
27892
+ const changedRecords = new Set;
27136
27893
  for (const host of selected) {
27137
27894
  if (!host.detect())
27138
27895
  continue;
27139
27896
  for (const plugin of host.listInstalled()) {
27140
27897
  if (plugin.path === undefined)
27141
27898
  continue;
27142
- const outcome = await host.pin(plugin, { dryRun: options.dryRun });
27899
+ let outcome;
27900
+ try {
27901
+ outcome = host.lifecyclePinAdapter === undefined ? await host.pin(plugin, { dryRun: options.dryRun }) : await runOfflineLifecyclePin(host, plugin, options.dryRun === true);
27902
+ } catch (error) {
27903
+ findings.push({ host: host.id, mark: "✗", message: `plugin '${plugin.id}' pin failed — ${error.message}` });
27904
+ continue;
27905
+ }
27143
27906
  const prefix = options.dryRun === true ? "[dry-run] " : "";
27144
27907
  for (const change of outcome.changes) {
27145
27908
  findings.push({
@@ -27157,6 +27920,8 @@ async function runPin(options = {}) {
27157
27920
  }
27158
27921
  if (outcome.changes.length === 0)
27159
27922
  continue;
27923
+ if (host.lifecyclePinAdapter !== undefined)
27924
+ continue;
27160
27925
  const record = findRecord(state, host.id, plugin.id);
27161
27926
  if (record === undefined) {
27162
27927
  findings.push({
@@ -27168,6 +27933,7 @@ async function runPin(options = {}) {
27168
27933
  }
27169
27934
  if (options.dryRun === true)
27170
27935
  continue;
27936
+ changedRecords.add(record);
27171
27937
  const pins = new Set(record.pins ?? []);
27172
27938
  for (const change of outcome.changes)
27173
27939
  pins.add(change.server);
@@ -27185,10 +27951,10 @@ async function runPin(options = {}) {
27185
27951
  }
27186
27952
  }
27187
27953
  if (ledgerChanged)
27188
- persistRecordedPins(state, options.state !== undefined);
27954
+ persistRecordedPins(state, options.state !== undefined, changedRecords);
27189
27955
  return { findings, exitCode: findings.some((f) => f.mark === "✗") ? 1 : 0 };
27190
27956
  }
27191
- function persistRecordedPins(records, legacyLedger) {
27957
+ function persistRecordedPins(records, legacyLedger, changedRecords) {
27192
27958
  const loaded = legacyLedger ? null : readLifecycleState();
27193
27959
  if (loaded === null || loaded.sourceVersion !== 2) {
27194
27960
  writeState(records);
@@ -27200,7 +27966,7 @@ function persistRecordedPins(records, legacyLedger) {
27200
27966
  stateGeneration: loaded.state.stateGeneration + 1,
27201
27967
  activations: loaded.state.activations.map((activation) => {
27202
27968
  const scope = scopes.get(activation.scopeId);
27203
- const record = records.find((candidate) => candidate.host === scope?.target.kind && candidate.id === activation.nativeId);
27969
+ const record = records.find((candidate) => changedRecords.has(candidate) && candidate.host === scope?.target.kind && candidate.id === activation.nativeId);
27204
27970
  if (record === undefined)
27205
27971
  return activation;
27206
27972
  return {
@@ -27672,7 +28438,7 @@ function legacyCommandAction(command) {
27672
28438
  // package.json
27673
28439
  var package_default = {
27674
28440
  name: "plugnz",
27675
- version: "0.0.8",
28441
+ version: "0.0.9",
27676
28442
  description: "Install, diagnose and update agent plugins (Agent Plugins spec) and MCP configs across Claude Code, Codex, Cursor, Kimi and more",
27677
28443
  license: "MIT",
27678
28444
  type: "module",
@@ -28681,19 +29447,13 @@ async function runRecordedLifecycleRemove(flags, target, state, selected) {
28681
29447
  const alias = writer?.persistedNativeIdMayAlias?.(activation.nativeId, target) === true;
28682
29448
  if (target !== activation.packageId && target !== activation.nativeId && !alias)
28683
29449
  return [];
28684
- return [{ scope }];
29450
+ return [{ scope, activation }];
28685
29451
  });
28686
29452
  if (matches.length === 0 || matches.some((match) => !plannerKinds.has(match.scope.target.kind)))
28687
29453
  return null;
28688
29454
  const byScope = new Map;
28689
29455
  for (const match of matches)
28690
29456
  byScope.set(match.scope.id, match.scope);
28691
- for (const scopeId of byScope.keys()) {
28692
- const inScope = state.activations.filter((activation) => activation.scopeId === scopeId).length;
28693
- const selectedInScope = matches.filter((match) => match.scope.id === scopeId).length;
28694
- if (inScope !== selectedInScope)
28695
- return null;
28696
- }
28697
29457
  const perHost = new Map;
28698
29458
  for (const match of matches) {
28699
29459
  const count = (perHost.get(match.scope.target.kind) ?? 0) + 1;
@@ -28726,7 +29486,12 @@ async function runRecordedLifecycleRemove(flags, target, state, selected) {
28726
29486
  dryRun: flags.dryRun,
28727
29487
  hosts: createLifecyclePlannerHosts(),
28728
29488
  now: new Date().toISOString(),
28729
- command: "remove"
29489
+ command: "remove",
29490
+ removalSelection: matches.map(({ activation }) => ({
29491
+ scopeId: activation.scopeId,
29492
+ packageId: activation.packageId,
29493
+ nativeId: activation.nativeId
29494
+ }))
28730
29495
  });
28731
29496
  return executed.report;
28732
29497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugnz",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Install, diagnose and update agent plugins (Agent Plugins spec) and MCP configs across Claude Code, Codex, Cursor, Kimi and more",
5
5
  "license": "MIT",
6
6
  "type": "module",