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.
- package/README.md +39 -3
- package/generated/.opencode/contracts/control-scaffold.md +17 -2
- package/generated/.opencode/contracts/interaction.md +2 -2
- package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
- package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -0
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +7 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +71 -51
- package/generated/plugins/copilot/agdf/copilot-skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/copilot/agdf/copilot-skills/contracts/control-scaffold.md +17 -2
- package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
- package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/copilot/agdf/plugin.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
- package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/submissions/openai/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +18 -14
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +7 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/cli/application.js +24 -6
- package/lib/cli/command-registry.js +8 -1
- package/lib/cli/delivery-path-search-command.js +51 -19
- package/lib/cli/parse-args.js +3 -0
- package/lib/control-evaluation/doctor.js +13 -0
- package/lib/control-evaluation/gate-check.js +64 -13
- package/lib/control-evaluation/run-state.js +6 -0
- package/lib/control-state/run-identity.js +9 -0
- package/lib/control-state/run-state-parser.js +4 -2
- package/lib/delivery-path-search/contracts.js +37 -0
- package/lib/delivery-path-search/persistence.js +14 -2
- package/lib/delivery-path-search/search-engine.js +126 -36
- package/lib/delivery-path-search/state-adapter.js +40 -92
- package/lib/installers/claude-cache-recovery.js +76 -0
- package/lib/installers/copilot-settings.js +146 -16
- package/lib/installers/local-marketplace.js +169 -14
- package/lib/installers/plugin-installers.js +96 -9
- package/lib/interaction-presentation.js +57 -7
- package/lib/lifecycle/operations.js +28 -4
- package/lib/lifecycle/result.js +4 -4
- package/lib/release/profile-history.js +222 -0
- package/lib/release/version-bump.js +527 -0
- package/lib/release/version-coherence.js +92 -38
- package/lib/runtime/distribution-profile-history.js +173 -0
- package/lib/runtime/plugin-provenance.js +46 -3
- 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
|
-
|
|
199
|
-
|
|
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
|
|
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, {
|
|
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
|
|
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
|
-
: {
|
|
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
|
-
: "
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
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(
|
|
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({
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|