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