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