create-agdf 0.14.2 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +39 -3
  2. package/generated/.opencode/contracts/control-scaffold.md +17 -2
  3. package/generated/.opencode/contracts/interaction.md +2 -2
  4. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +16 -4
  5. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
  6. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  7. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  8. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  9. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +17 -2
  10. package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
  11. package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
  12. package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
  13. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  14. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  15. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
  16. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  17. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  18. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  19. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  20. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  21. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  22. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
  23. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
  24. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
  25. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
  26. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  28. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  29. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  30. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -0
  31. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +16 -4
  32. package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
  33. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  34. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  35. package/generated/plugins/agdf/submission/openai/release-notes.md +7 -1
  36. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  37. package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +71 -51
  38. package/generated/plugins/copilot/agdf/copilot-skills/agdf-delivery-path-search/SKILL.md +16 -4
  39. package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
  40. package/generated/plugins/copilot/agdf/copilot-skills/contracts/control-scaffold.md +17 -2
  41. package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
  42. package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
  43. package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
  44. package/generated/plugins/copilot/agdf/plugin.json +1 -1
  45. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  46. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  47. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
  48. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  49. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  50. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  51. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  52. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  53. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  54. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
  55. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
  56. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
  57. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
  58. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  59. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  60. package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
  61. package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
  62. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  63. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  64. package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +17 -2
  65. package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
  66. package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
  67. package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
  68. package/generated/submissions/openai/agdf/skills/delivery-path-search/SKILL.md +16 -4
  69. package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
  70. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  71. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  72. package/generated/submissions/openai/agdf/submission/openai/inventory.json +18 -14
  73. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  74. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  75. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  76. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +7 -1
  77. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  78. package/lib/cli/application.js +24 -6
  79. package/lib/cli/command-registry.js +8 -1
  80. package/lib/cli/delivery-path-search-command.js +51 -19
  81. package/lib/cli/parse-args.js +3 -0
  82. package/lib/control-evaluation/doctor.js +13 -0
  83. package/lib/control-evaluation/gate-check.js +64 -13
  84. package/lib/control-evaluation/run-state.js +6 -0
  85. package/lib/control-state/run-identity.js +9 -0
  86. package/lib/control-state/run-state-parser.js +4 -2
  87. package/lib/delivery-path-search/contracts.js +37 -0
  88. package/lib/delivery-path-search/persistence.js +14 -2
  89. package/lib/delivery-path-search/search-engine.js +126 -36
  90. package/lib/delivery-path-search/state-adapter.js +40 -92
  91. package/lib/installers/claude-cache-recovery.js +76 -0
  92. package/lib/installers/copilot-settings.js +146 -16
  93. package/lib/installers/local-marketplace.js +169 -14
  94. package/lib/installers/plugin-installers.js +96 -9
  95. package/lib/interaction-presentation.js +57 -7
  96. package/lib/lifecycle/operations.js +28 -4
  97. package/lib/lifecycle/result.js +4 -4
  98. package/lib/release/profile-history.js +222 -0
  99. package/lib/release/version-bump.js +527 -0
  100. package/lib/release/version-coherence.js +92 -38
  101. package/lib/runtime/distribution-profile-history.js +173 -0
  102. package/lib/runtime/plugin-provenance.js +46 -3
  103. package/package.json +7 -3
@@ -2,15 +2,17 @@ import {
2
2
  cpSync,
3
3
  existsSync,
4
4
  mkdirSync,
5
+ mkdtempSync,
5
6
  readFileSync,
6
7
  rmSync,
7
8
  writeFileSync,
8
9
  } from "node:fs";
9
- import { homedir } from "node:os";
10
+ import { homedir, tmpdir } from "node:os";
10
11
  import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
11
12
  import process from "node:process";
12
13
  import { generatedRoot, packageRoot, pluginDefinition } from "../cli/runtime-context.js";
13
14
  import { renameSyncWithRetry } from "../fs-swap.js";
15
+ import { classifyHistoricalDistributionProfile } from "../runtime/distribution-profile-history.js";
14
16
  import {
15
17
  INSTALLATION_PROVENANCE_FILE,
16
18
  LEGACY_LOCAL_INSTALL_FILE,
@@ -31,6 +33,14 @@ function pathInside(root, candidate) {
31
33
  return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
32
34
  }
33
35
 
36
+ function readOptionalJson(path) {
37
+ try {
38
+ return JSON.parse(readFileSync(path, "utf8"));
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
34
44
  export function digestPluginSource(root, canonicalVersion = pluginDefinition.version) {
35
45
  return digestNormalizedPluginSource(root, canonicalVersion);
36
46
  }
@@ -58,6 +68,59 @@ export function isCodexLocalInstallVersion(version, candidate, sourceDigest = ""
58
68
  return new RegExp(`^${escaped}\\+codex\\.local-[a-f0-9]{12}$`).test(candidate);
59
69
  }
60
70
 
71
+ function localInstallSourceUnstable(detail) {
72
+ const error = new Error(`AGDF local plugin source changed during snapshot capture: ${detail}`);
73
+ error.code = "local_install_source_unstable";
74
+ return error;
75
+ }
76
+
77
+ export function captureLocalPluginSnapshot({
78
+ builtPluginRoot,
79
+ expectedVersion = pluginDefinition.version,
80
+ profileId = "runtime-plugin",
81
+ adapters = {},
82
+ } = {}) {
83
+ if (!builtPluginRoot) throw new Error("AGDF local plugin source root is required.");
84
+ const sourceRoot = resolve(builtPluginRoot);
85
+ const digest = adapters.digest ?? digestPluginSource;
86
+ const createRoot = adapters.createRoot ?? (() => mkdtempSync(join(tmpdir(), "agdf-local-plugin-")));
87
+ const copy = adapters.copy ?? ((source, target) => cpSync(source, target, { recursive: true }));
88
+ const remove = adapters.remove ?? ((root) => rmSync(root, { recursive: true, force: true }));
89
+ const beforeDigest = digest(sourceRoot, expectedVersion);
90
+ let snapshotRoot = null;
91
+ try {
92
+ snapshotRoot = resolve(createRoot());
93
+ const pluginRoot = join(snapshotRoot, "agdf");
94
+ copy(sourceRoot, pluginRoot);
95
+ const snapshotDigest = digest(pluginRoot, expectedVersion);
96
+ const afterDigest = digest(sourceRoot, expectedVersion);
97
+ if (beforeDigest !== snapshotDigest || snapshotDigest !== afterDigest) {
98
+ throw localInstallSourceUnstable(
99
+ `before=${beforeDigest}, snapshot=${snapshotDigest}, after=${afterDigest}`,
100
+ );
101
+ }
102
+ const copilotProfile = profileId === "copilot-runtime-plugin";
103
+ return Object.freeze({
104
+ canonicalVersion: expectedVersion,
105
+ profileId,
106
+ pluginRoot,
107
+ sourceDigest: snapshotDigest,
108
+ codexInstallVersion: copilotProfile
109
+ ? expectedVersion
110
+ : codexLocalInstallVersion(expectedVersion, snapshotDigest),
111
+ cleanup() {
112
+ if (!snapshotRoot) return;
113
+ const ownedRoot = snapshotRoot;
114
+ remove(ownedRoot);
115
+ snapshotRoot = null;
116
+ },
117
+ });
118
+ } catch (error) {
119
+ if (snapshotRoot) remove(snapshotRoot);
120
+ throw error;
121
+ }
122
+ }
123
+
61
124
  function readJson(path, label) {
62
125
  try {
63
126
  return JSON.parse(readFileSync(path, "utf8"));
@@ -160,6 +223,9 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
160
223
  requireInstallationProvenance = false,
161
224
  allowLegacyProvenanceForMigration = false,
162
225
  allowPreProvenanceShape = false,
226
+ allowHistoricalProfilesForMigration = false,
227
+ distributionProfileHistory = null,
228
+ classifyHistoricalProfile = classifyHistoricalDistributionProfile,
163
229
  profileId = "runtime-plugin",
164
230
  } = {}) {
165
231
  const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"), "built plugin definition");
@@ -193,10 +259,18 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
193
259
  runtimeManifest: runtime,
194
260
  pluginVersion: expectedCodexInstallVersion,
195
261
  allowLegacy: true,
262
+ allowHistoricalProfilesForMigration,
263
+ distributionProfileHistory,
264
+ classifyHistoricalProfile,
196
265
  });
197
266
  }
198
- if (profile.status !== "matched" && provenance?.status !== "legacy" && !isPreProvenanceShape) {
199
- throw new Error("Built plugin distribution profile contract is invalid.");
267
+ const supportedHistoricalProfile = provenance?.status === "matched"
268
+ && provenance.profileClassification === "supported_historical";
269
+ if (profile.status !== "matched"
270
+ && provenance?.status !== "legacy"
271
+ && !supportedHistoricalProfile
272
+ && !isPreProvenanceShape) {
273
+ throw new Error(`Built plugin distribution profile contract is invalid (${provenance?.reason ?? profile.reason}).`);
200
274
  }
201
275
  if (!copilotProfile && codex.version !== expectedCodexInstallVersion) {
202
276
  throw new Error(`Built Codex plugin version mismatch: expected ${expectedCodexInstallVersion}, observed ${codex.version}`);
@@ -226,6 +300,9 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
226
300
  runtimeManifest: runtime,
227
301
  pluginVersion: expectedCodexInstallVersion,
228
302
  allowLegacy: allowLegacyProvenanceForMigration,
303
+ allowHistoricalProfilesForMigration,
304
+ distributionProfileHistory,
305
+ classifyHistoricalProfile,
229
306
  profileId,
230
307
  inventoryDigest: copilotInventory?.inventoryDigest ?? null,
231
308
  });
@@ -234,7 +311,11 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
234
311
  throw new Error(`Invalid AGDF installation provenance (${provenance.reason}): ${pluginRoot}`);
235
312
  }
236
313
  }
237
- return definition;
314
+ return {
315
+ definition,
316
+ provenance,
317
+ profileClassification: provenance?.profileClassification ?? "current",
318
+ };
238
319
  }
239
320
 
240
321
  function validateMarketplaceRoot(root, definition = pluginDefinition, { profileId = "runtime-plugin" } = {}) {
@@ -262,7 +343,10 @@ function validateMarketplaceRoot(root, definition = pluginDefinition, { profileI
262
343
  return Object.freeze({ codexShape });
263
344
  }
264
345
 
265
- function classifyExistingMarketplace(stableRoot, marker, { profileId = "runtime-plugin" } = {}) {
346
+ function classifyExistingMarketplace(stableRoot, marker, {
347
+ profileId = "runtime-plugin",
348
+ distributionProfileHistory = null,
349
+ } = {}) {
266
350
  const existingPluginRoot = join(stableRoot, "plugins", MARKETPLACE_ID);
267
351
  const copilotProfile = profileId === "copilot-runtime-plugin";
268
352
  const existingCodexInstallVersion = copilotProfile ? marker.version : marker.codex_install_version ?? marker.version;
@@ -279,15 +363,32 @@ function classifyExistingMarketplace(stableRoot, marker, { profileId = "runtime-
279
363
  const preProvenanceCandidate = !hasCurrentProvenance
280
364
  && !hasLegacyProvenance
281
365
  && !Object.hasOwn(definition, "distributionProfiles");
282
- const existingDefinition = validateBuiltPlugin(
366
+ const existingValidation = validateBuiltPlugin(
283
367
  existingPluginRoot,
284
368
  marker.version,
285
369
  existingCodexInstallVersion,
286
370
  marker.source_digest ?? "",
287
371
  preProvenanceCandidate
288
372
  ? { allowPreProvenanceShape: true }
289
- : { requireInstallationProvenance: true, allowLegacyProvenanceForMigration: true, profileId },
373
+ : {
374
+ requireInstallationProvenance: true,
375
+ allowLegacyProvenanceForMigration: true,
376
+ allowHistoricalProfilesForMigration: !copilotProfile,
377
+ distributionProfileHistory,
378
+ profileId,
379
+ },
290
380
  );
381
+ if (!preProvenanceCandidate) {
382
+ if (!/^[a-f0-9]{64}$/.test(marker.source_digest ?? "")) {
383
+ throw new Error(`Refusing invalid AGDF marketplace ownership source digest: ${stableRoot}`);
384
+ }
385
+ const observedSourceDigest = existingValidation.provenance?.observedSourceDigest
386
+ ?? digestPluginSource(existingPluginRoot, marker.version);
387
+ if (marker.source_digest !== observedSourceDigest) {
388
+ throw new Error(`Refusing AGDF marketplace ownership source digest mismatch: ${stableRoot}`);
389
+ }
390
+ }
391
+ const existingDefinition = existingValidation.definition;
291
392
  const marketplace = validateMarketplaceRoot(stableRoot, existingDefinition, { profileId });
292
393
  if (digestDirectory(existingPluginRoot) !== marker.plugin_digest) {
293
394
  throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
@@ -295,9 +396,19 @@ function classifyExistingMarketplace(stableRoot, marker, { profileId = "runtime-
295
396
  return Object.freeze({
296
397
  classification: preProvenanceCandidate
297
398
  ? "owned_pre_provenance_rebuild"
298
- : "current_or_marker_migration",
399
+ : existingValidation.profileClassification === "supported_historical"
400
+ ? "owned_supported_historical_rebuild"
401
+ : "current_or_marker_migration",
299
402
  definition: existingDefinition,
300
403
  marketplace,
404
+ historicalEvidence: existingValidation.profileClassification === "supported_historical"
405
+ ? {
406
+ releaseVersion: existingValidation.provenance.historicalReleaseVersion,
407
+ contractId: existingValidation.provenance.historicalContractId,
408
+ contractDigest: existingValidation.provenance.historicalContractDigest,
409
+ entryDigest: existingValidation.provenance.historicalEntryDigest,
410
+ }
411
+ : null,
301
412
  });
302
413
  }
303
414
 
@@ -322,13 +433,15 @@ function recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failed
322
433
  if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
323
434
  }
324
435
 
325
- export function prepareLocalMarketplace({
436
+ function prepareLocalMarketplaceFromSource({
326
437
  dataRoot = defaultAgdfDataRoot(),
327
438
  builtPluginRoot,
328
439
  expectedVersion = pluginDefinition.version,
329
440
  codexInstallVersion = expectedVersion,
330
441
  codexRegistrationRevision = null,
331
442
  profileId = "runtime-plugin",
443
+ knownSourceDigest = "",
444
+ sourceStaged = () => {},
332
445
  } = {}) {
333
446
  dataRoot = resolve(dataRoot);
334
447
  const copilotProfile = profileId === "copilot-runtime-plugin";
@@ -342,12 +455,15 @@ export function prepareLocalMarketplace({
342
455
  const backupRoot = `${stableRoot}.backup`;
343
456
  const failedRoot = `${stableRoot}.failed`;
344
457
  if (!pathInside(dataRoot, stableRoot) || dirname(stableRoot) !== parent) throw new Error(`Unsafe AGDF marketplace root: ${stableRoot}`);
345
- const sourceDigest = digestPluginSource(builtPluginRoot, expectedVersion);
458
+ const sourceDigest = knownSourceDigest || digestPluginSource(builtPluginRoot, expectedVersion);
346
459
  if (!copilotProfile && codexInstallVersion !== expectedVersion
347
460
  && !isCodexLocalInstallVersion(expectedVersion, codexInstallVersion, sourceDigest)) {
348
461
  throw new Error(`Invalid AGDF Codex local install version: ${codexInstallVersion}`);
349
462
  }
350
- const targetDefinition = validateBuiltPlugin(builtPluginRoot, expectedVersion, expectedVersion, "", { profileId });
463
+ const targetDefinition = validateBuiltPlugin(builtPluginRoot, expectedVersion, expectedVersion, "", { profileId }).definition;
464
+ const distributionProfileHistory = readOptionalJson(
465
+ join(builtPluginRoot, "meta", "distribution-profile-history.json"),
466
+ );
351
467
  mkdirSync(parent, { recursive: true });
352
468
  recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot);
353
469
 
@@ -365,15 +481,17 @@ export function prepareLocalMarketplace({
365
481
  ?? 0;
366
482
  let existingMarketplace = null;
367
483
  let existingClassification = "none";
484
+ let historicalEvidence = null;
368
485
  if (existing) {
369
486
  let classified;
370
487
  try {
371
- classified = classifyExistingMarketplace(stableRoot, existing, { profileId });
488
+ classified = classifyExistingMarketplace(stableRoot, existing, { profileId, distributionProfileHistory });
372
489
  } catch (error) {
373
490
  throw invalidExistingMarketplace(error);
374
491
  }
375
492
  existingClassification = classified.classification;
376
493
  existingMarketplace = classified.marketplace;
494
+ historicalEvidence = classified.historicalEvidence;
377
495
  }
378
496
  mkdirSync(stageRoot, { recursive: false });
379
497
  try {
@@ -392,6 +510,7 @@ export function prepareLocalMarketplace({
392
510
  });
393
511
  const stagedPluginRoot = join(stageRoot, "plugins", MARKETPLACE_ID);
394
512
  cpSync(builtPluginRoot, stagedPluginRoot, { recursive: true });
513
+ sourceStaged();
395
514
  validateBuiltPlugin(stagedPluginRoot, expectedVersion, expectedVersion, "", { profileId });
396
515
  if (!copilotProfile && codexInstallVersion !== expectedVersion) {
397
516
  const codexManifestPath = join(stagedPluginRoot, ".codex-plugin", "plugin.json");
@@ -450,6 +569,7 @@ export function prepareLocalMarketplace({
450
569
  digest: pluginDigest,
451
570
  runtimeDigest: runtimeManifest.digest,
452
571
  existingClassification,
572
+ historicalEvidence,
453
573
  changed: false,
454
574
  commit() {},
455
575
  rollback() {},
@@ -473,6 +593,7 @@ export function prepareLocalMarketplace({
473
593
  digest: pluginDigest,
474
594
  runtimeDigest: runtimeManifest.digest,
475
595
  existingClassification,
596
+ historicalEvidence,
476
597
  changed: true,
477
598
  commit() {
478
599
  if (closed) return;
@@ -498,8 +619,42 @@ export function prepareLocalMarketplace({
498
619
  }
499
620
  }
500
621
 
622
+ export function prepareLocalMarketplace(options = {}) {
623
+ if (!options.snapshotSource) return prepareLocalMarketplaceFromSource(options);
624
+ if (Object.hasOwn(options, "codexInstallVersion")) {
625
+ throw new Error("AGDF snapshot preparation owns the Codex local install version.");
626
+ }
627
+ const snapshot = captureLocalPluginSnapshot({
628
+ builtPluginRoot: options.builtPluginRoot,
629
+ expectedVersion: options.expectedVersion,
630
+ profileId: options.profileId,
631
+ adapters: options.snapshotAdapters,
632
+ });
633
+ try {
634
+ const { snapshotSource: _snapshotSource, snapshotAdapters: _snapshotAdapters, ...stableOptions } = options;
635
+ return prepareLocalMarketplaceFromSource({
636
+ ...stableOptions,
637
+ builtPluginRoot: snapshot.pluginRoot,
638
+ expectedVersion: snapshot.canonicalVersion,
639
+ profileId: snapshot.profileId,
640
+ codexInstallVersion: snapshot.codexInstallVersion,
641
+ knownSourceDigest: snapshot.sourceDigest,
642
+ sourceStaged: snapshot.cleanup,
643
+ });
644
+ } finally {
645
+ snapshot.cleanup();
646
+ }
647
+ }
648
+
501
649
  export function prepareCopilotMarketplace(options = {}) {
502
- return prepareLocalMarketplace({ ...options, profileId: "copilot-runtime-plugin", codexInstallVersion: options.expectedVersion ?? pluginDefinition.version });
650
+ const { codexInstallVersion, ...copilotOptions } = options;
651
+ return prepareLocalMarketplace({
652
+ ...copilotOptions,
653
+ profileId: "copilot-runtime-plugin",
654
+ ...(!options.snapshotSource
655
+ ? { codexInstallVersion: codexInstallVersion ?? options.expectedVersion ?? pluginDefinition.version }
656
+ : {}),
657
+ });
503
658
  }
504
659
 
505
660
  export function inspectLocalMarketplaceProjection({
@@ -517,7 +672,7 @@ export function inspectLocalMarketplaceProjection({
517
672
  codexInstallVersion,
518
673
  marker.source_digest ?? "",
519
674
  { requireInstallationProvenance: true },
520
- );
675
+ ).definition;
521
676
  validateMarketplaceRoot(root, definition);
522
677
  if (marker.version !== expectedVersion || digestDirectory(pluginRoot) !== marker.plugin_digest) {
523
678
  throw new Error(`Refusing stale or tampered AGDF marketplace projection: ${root}`);
@@ -4,10 +4,22 @@ import { join, resolve } from "node:path";
4
4
  import process from "node:process";
5
5
  import { pluginDefinition } from "../cli/runtime-context.js";
6
6
  import { digestNormalizedPluginSource } from "../runtime/plugin-provenance.js";
7
+ import { recoverClaudeCacheTemp } from "./claude-cache-recovery.js";
7
8
  import { CODEX_REGISTRATION_REVISION, classifyMarketplaceList, inspectLocalMarketplaceProjection, inspectOwnedSharedMarketplaceForCopilotMigration, isCodexLocalInstallVersion, prepareCopilotMarketplace, prepareLocalMarketplace } from "./local-marketplace.js";
8
9
 
9
10
  export const COPILOT_CLI_NPM_PACKAGE = "@github/copilot@1.0.80";
10
11
 
12
+ function historicalEvidenceEntries(transaction) {
13
+ const historical = transaction.historicalEvidence;
14
+ if (!historical) return [];
15
+ return [
16
+ `historical_release:${historical.releaseVersion}`,
17
+ `historical_contract:${historical.contractId}`,
18
+ `historical_contract_digest:${historical.contractDigest}`,
19
+ `historical_entry_digest:${historical.entryDigest}`,
20
+ ];
21
+ }
22
+
11
23
  export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
12
24
  const expectedVersion = pluginDefinition.version;
13
25
  const nativeOutput = [];
@@ -34,12 +46,15 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
34
46
  ...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
35
47
  ...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
36
48
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
49
+ ...(transaction.existingClassification === "owned_supported_historical_rebuild" ? ["marketplace_recovery:owned_supported_historical_rebuild", "loaded_session:fresh_session_required"] : []),
50
+ ...historicalEvidenceEntries(transaction),
37
51
  ...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
38
52
  ],
39
53
  pluginRoot: transaction.pluginRoot ?? null,
40
54
  digest: transaction.digest ?? null,
41
55
  runtimeDigest: transaction.runtimeDigest ?? null,
42
56
  sourceDigest: transaction.sourceDigest ?? null,
57
+ historicalEvidence: transaction.historicalEvidence ?? null,
43
58
  nativeOutput: nativeOutput.filter(Boolean).map(String),
44
59
  };
45
60
  } catch (error) {
@@ -48,11 +63,19 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
48
63
  }
49
64
  }
50
65
 
51
- export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
66
+ export function installClaudeGlobalPlugin({
67
+ exec = execFileSync,
68
+ prepare = prepareLocalMarketplace,
69
+ dataRoot,
70
+ recoverCache = recoverClaudeCacheTemp,
71
+ cacheRecoveryOptions = {},
72
+ } = {}) {
52
73
  const expectedVersion = pluginDefinition.version;
53
74
  const nativeOutput = [];
54
75
  const transaction = prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
55
76
  const migration = { state: "unknown", source: "", addedLocal: false, removedLegacy: false };
77
+ let previousPluginRemoved = false;
78
+ let pluginInstalled = false;
56
79
  try {
57
80
  const marketplaceOutput = runPluginPhase(exec, "claude", ["plugin", "marketplace", "list", "--json"], "marketplace", captureOptions());
58
81
  migrateMarketplace({ surface: "claude", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration });
@@ -63,11 +86,41 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
63
86
  // same-version local source change never reaches the host; reinstall replaces the content.
64
87
  if (alreadyInstalled) {
65
88
  nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "uninstall", "agdf@agdf"], "plugin_operation", captureOptions()));
89
+ previousPluginRemoved = true;
90
+ }
91
+ let cacheRecovery = null;
92
+ try {
93
+ nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
94
+ pluginInstalled = true;
95
+ } catch (installError) {
96
+ cacheRecovery = recoverCache({
97
+ error: installError,
98
+ expectedVersion,
99
+ ...cacheRecoveryOptions,
100
+ });
101
+ if (cacheRecovery.status !== "recovered") {
102
+ installError.evidence = {
103
+ ...(installError.evidence ?? {}),
104
+ claude_cache_recovery: cacheRecovery.reason,
105
+ };
106
+ throw installError;
107
+ }
108
+ try {
109
+ nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
110
+ pluginInstalled = true;
111
+ } catch (retryError) {
112
+ retryError.evidence = {
113
+ ...(retryError.evidence ?? {}),
114
+ claude_cache_recovery: "claude_cache_temp_retry_exhausted",
115
+ };
116
+ throw retryError;
117
+ }
66
118
  }
67
- nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
68
119
  const afterList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
69
120
  const installedVersion = pluginVersionFromList(afterList, "agdf@agdf");
70
- if (installedVersion && installedVersion !== expectedVersion) {
121
+ const historicalRebuild = transaction.existingClassification === "owned_supported_historical_rebuild";
122
+ if ((historicalRebuild && installedVersion !== expectedVersion)
123
+ || (installedVersion && installedVersion !== expectedVersion)) {
71
124
  throw lifecycleAdapterError("version", versionMismatchMessage("Claude Code", "agdf@agdf", expectedVersion, installedVersion, "npx --yes @agdf/cli@latest claude"));
72
125
  }
73
126
  transaction.commit();
@@ -85,16 +138,20 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
85
138
  ...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
86
139
  ...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
87
140
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
141
+ ...(transaction.existingClassification === "owned_supported_historical_rebuild" ? ["marketplace_recovery:owned_supported_historical_rebuild", "loaded_session:fresh_session_required"] : []),
142
+ ...historicalEvidenceEntries(transaction),
143
+ ...(cacheRecovery?.status === "recovered" ? ["claude_cache_temp_recovery:bounded_retry"] : []),
88
144
  ...(installedVersion ? [] : ["host_did_not_expose_version"]),
89
145
  ],
90
146
  pluginRoot: transaction.pluginRoot ?? null,
91
147
  digest: transaction.digest ?? null,
92
148
  runtimeDigest: transaction.runtimeDigest ?? null,
93
149
  sourceDigest: transaction.sourceDigest ?? null,
150
+ historicalEvidence: transaction.historicalEvidence ?? null,
94
151
  nativeOutput: nativeOutput.filter(Boolean).map(String),
95
152
  };
96
153
  } catch (error) {
97
- recoverMarketplace({ surface: "claude", exec, migration, transaction, error });
154
+ recoverMarketplace({ surface: "claude", exec, migration, transaction, error, previousPluginRemoved, pluginInstalled });
98
155
  throw error;
99
156
  }
100
157
  }
@@ -118,7 +175,7 @@ export function installCopilotGlobalPlugin({ exec = execFileSync, packagedCopilo
118
175
  try {
119
176
  before = activeExec("copilot", ["plugin", "list"], captureOptions());
120
177
  } catch (error) {
121
- if (error?.code !== "ENOENT") {
178
+ if (!copilotCliUnavailable(error)) {
122
179
  transaction?.rollback();
123
180
  throw lifecycleAdapterError("verification", commandErrorText(error), { executable: "copilot", args: ["plugin", "list"] });
124
181
  }
@@ -126,7 +183,10 @@ export function installCopilotGlobalPlugin({ exec = execFileSync, packagedCopilo
126
183
  activeExec = (_executable, args, options) => packagedCopilotExec(invocation.executable, [...invocation.args, ...args], options);
127
184
  try {
128
185
  before = activeExec("copilot", ["plugin", "list"], captureOptions());
129
- bootstrapEvidence.push("copilot_cli_not_found", `copilot_cli_npm_package:${COPILOT_CLI_NPM_PACKAGE}`);
186
+ bootstrapEvidence.push(
187
+ error?.code === "ENOENT" ? "copilot_cli_not_found" : "copilot_cli_launcher_unavailable",
188
+ `copilot_cli_npm_package:${COPILOT_CLI_NPM_PACKAGE}`,
189
+ );
130
190
  } catch (bootstrapError) {
131
191
  transaction?.commit();
132
192
  return {
@@ -302,7 +362,15 @@ function migrateMarketplace({ surface, exec, output, root, nativeOutput, migrati
302
362
  }
303
363
  }
304
364
 
305
- function recoverMarketplace({ surface, exec, migration, transaction, error }) {
365
+ function recoverMarketplace({
366
+ surface,
367
+ exec,
368
+ migration,
369
+ transaction,
370
+ error,
371
+ previousPluginRemoved = false,
372
+ pluginInstalled = false,
373
+ }) {
306
374
  const executable = surface === "claude" ? "claude" : "codex";
307
375
  const recovery = [];
308
376
  const attempt = (args) => {
@@ -316,13 +384,19 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
316
384
  }
317
385
  };
318
386
  if (migration?.refreshOwnedLocal) {
387
+ if (surface === "claude" && pluginInstalled) {
388
+ attempt(["plugin", "uninstall", "agdf@agdf"]);
389
+ }
319
390
  const refreshedRegistrationRemoved = migration.addedLocal
320
391
  ? attempt(["plugin", "marketplace", "remove", "agdf", "--json"])
321
392
  : true;
322
- rollbackMarketplaceFilesystem(transaction, recovery);
393
+ const filesystemRestored = rollbackMarketplaceFilesystem(transaction, recovery);
323
394
  if (migration.removedOwnedLocal && refreshedRegistrationRemoved) {
324
395
  attempt(["plugin", "marketplace", "add", migration.source || transaction.root, "--json"]);
325
396
  }
397
+ if (surface === "claude" && previousPluginRemoved && filesystemRestored) {
398
+ attempt(["plugin", "install", "agdf@agdf"]);
399
+ }
326
400
  error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
327
401
  return;
328
402
  }
@@ -336,7 +410,13 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
336
410
  ? ["plugin", "marketplace", "add", migration.source, "--scope", "user"]
337
411
  : ["plugin", "marketplace", "add", migration.source, "--json"]);
338
412
  }
339
- rollbackMarketplaceFilesystem(transaction, recovery);
413
+ if (surface === "claude" && pluginInstalled) {
414
+ attempt(["plugin", "uninstall", "agdf@agdf"]);
415
+ }
416
+ const filesystemRestored = rollbackMarketplaceFilesystem(transaction, recovery);
417
+ if (surface === "claude" && previousPluginRemoved && filesystemRestored) {
418
+ attempt(["plugin", "install", "agdf@agdf"]);
419
+ }
340
420
  error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
341
421
  }
342
422
 
@@ -344,8 +424,10 @@ function rollbackMarketplaceFilesystem(transaction, recovery) {
344
424
  try {
345
425
  transaction.rollback();
346
426
  recovery.push({ filesystem: transaction.root, status: "restored" });
427
+ return true;
347
428
  } catch (rollbackError) {
348
429
  recovery.push({ filesystem: transaction.root, status: "failed", message: rollbackError.message });
430
+ return false;
349
431
  }
350
432
  }
351
433
 
@@ -404,6 +486,11 @@ function commandErrorText(error) {
404
486
  return (error.stderr || error.stdout || error.message || "").toString().trim();
405
487
  }
406
488
 
489
+ function copilotCliUnavailable(error) {
490
+ return error?.code === "ENOENT"
491
+ || /^Cannot find GitHub Copilot CLI(?:\s|$|\()/i.test(commandErrorText(error));
492
+ }
493
+
407
494
  export function pluginListHasPlugin(output, pluginId) {
408
495
  const escapedPluginId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
409
496
  return output
@@ -1,3 +1,5 @@
1
+ import { RUN_ID_PATTERN } from "./control-state/run-identity.js";
2
+
1
3
  const REQUIRED_GATES = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
2
4
  const ARTEFACT_ORDER = ["UR", "PRD", "SD", "TP"];
3
5
  const OUTCOMES = new Set(["approve", "revise", "decline", "cancel", "no_response", "timeout", "empty", "invalid", "stale"]);
@@ -313,17 +315,37 @@ function operationalBreadcrumb(value, labels) {
313
315
  .join(labels.breadcrumbSeparator);
314
316
  }
315
317
 
318
+ export function validateOperationalStatusCardPreconditions(statusCard, {
319
+ registry,
320
+ humanPresentation,
321
+ } = {}) {
322
+ const errors = [];
323
+ if (!plainObject(statusCard)) errors.push("status_card_missing");
324
+ if (humanPresentation !== undefined && !plainObject(humanPresentation)) errors.push("human_presentation_missing");
325
+ if (plainObject(statusCard)) {
326
+ if (!String(statusCard.run_id ?? "").trim()) errors.push("run_id_missing");
327
+ if (!String(statusCard.current_gate ?? "").trim()) errors.push("current_gate_missing");
328
+ let locale = "";
329
+ try {
330
+ locale = resolvePresentationLocale(registry, statusCard.presentation_language);
331
+ } catch {
332
+ locale = "";
333
+ }
334
+ if (!locale) errors.push("locale_unresolved");
335
+ }
336
+ return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
337
+ }
338
+
316
339
  export function renderOperationalStatusCard(statusCard, {
317
340
  registry,
318
341
  humanPresentation,
319
342
  revisionId = "",
320
343
  } = {}) {
321
- if (!plainObject(statusCard) || !plainObject(humanPresentation)) return null;
344
+ if (!validateOperationalStatusCardPreconditions(statusCard, { registry, humanPresentation: humanPresentation ?? null }).valid) return null;
322
345
  const runId = String(statusCard.run_id ?? "").trim();
323
346
  const currentGate = String(statusCard.current_gate ?? "").trim();
324
347
  const revision = String(revisionId ?? "").trim() || "unversioned";
325
348
  const locale = resolvePresentationLocale(registry, statusCard.presentation_language);
326
- if (!runId || !currentGate || !locale) return null;
327
349
 
328
350
  const pack = localePack(registry, locale);
329
351
  const labels = pack.statusCard;
@@ -537,6 +559,37 @@ export function normalizeInteractionOutcome(value, { expectedApproval, stale = f
537
559
  return "invalid";
538
560
  }
539
561
 
562
+ export function validateApprovalOrientationPreconditions({
563
+ statusCard,
564
+ humanPresentation,
565
+ registry,
566
+ requestedLocale,
567
+ } = {}) {
568
+ const errors = [];
569
+ if (!plainObject(statusCard)) return Object.freeze({ valid: false, errors: Object.freeze(["status_card_missing"]) });
570
+ if (humanPresentation !== undefined && !plainObject(humanPresentation)) errors.push("human_presentation_missing");
571
+ const gate = String(statusCard.current_gate ?? "").trim();
572
+ const runId = String(statusCard.run_id ?? "").trim();
573
+ if (!REQUIRED_GATES.includes(gate)) errors.push("gate_not_user_gate");
574
+ if (!RUN_ID_PATTERN.test(runId)) errors.push("run_id_invalid");
575
+ if (statusCard.status !== "open") errors.push("status_not_open");
576
+ if (statusCard.missing_approval !== `Approval: ${gate}`) errors.push("missing_approval_mismatch");
577
+ const nextGate = String(statusCard.next_gate_after_approval ?? "none");
578
+ const nextUserGate = String(statusCard.next_user_gate ?? "").trim();
579
+ const userActionRequired = String(statusCard.user_action_required ?? "").trim();
580
+ const userActionSemanticsValid = (userActionRequired === "no" && nextUserGate === "none")
581
+ || (userActionRequired === "yes" && REQUIRED_GATES.includes(nextUserGate) && nextGate === nextUserGate);
582
+ if (!userActionSemanticsValid) errors.push("user_action_semantics_invalid");
583
+ let locale = "";
584
+ try {
585
+ locale = resolvePresentationLocale(registry, requestedLocale);
586
+ } catch {
587
+ locale = "";
588
+ }
589
+ if (!locale) errors.push("locale_unresolved");
590
+ return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
591
+ }
592
+
540
593
  export function buildApprovalOrientationSnapshot({
541
594
  ready = false,
542
595
  statusCard,
@@ -545,11 +598,11 @@ export function buildApprovalOrientationSnapshot({
545
598
  registry,
546
599
  requestedLocale,
547
600
  } = {}) {
548
- if (!ready || !plainObject(statusCard) || !plainObject(humanPresentation)) return null;
601
+ if (!ready) return null;
602
+ if (!validateApprovalOrientationPreconditions({ statusCard, humanPresentation: humanPresentation ?? null, registry, requestedLocale }).valid) return null;
549
603
  const gate = String(statusCard.current_gate ?? "").trim();
550
604
  const runId = String(statusCard.run_id ?? "").trim();
551
605
  const expectedApproval = `Approval: ${gate}`;
552
- if (!REQUIRED_GATES.includes(gate) || !/^[A-Za-z0-9._-]+$/.test(runId) || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
553
606
 
554
607
  const locale = resolvePresentationLocale(registry, requestedLocale);
555
608
  const pack = localePack(registry, locale);
@@ -560,9 +613,6 @@ export function buildApprovalOrientationSnapshot({
560
613
  const nextGate = String(statusCard.next_gate_after_approval ?? "none");
561
614
  const nextUserGate = String(statusCard.next_user_gate ?? "").trim();
562
615
  const userActionRequired = String(statusCard.user_action_required ?? "").trim();
563
- const userActionSemanticsValid = (userActionRequired === "no" && nextUserGate === "none")
564
- || (userActionRequired === "yes" && REQUIRED_GATES.includes(nextUserGate) && nextGate === nextUserGate);
565
- if (!userActionSemanticsValid) return null;
566
616
  const narration = pack.primary.narration.gates[gate] ?? {};
567
617
  const nextDecision = userActionRequired === "no"
568
618
  ? pack.primary.narration.noAction