create-agdf 0.13.6 → 0.13.8
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 +26 -0
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/hooks/hooks.json +3 -2
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +24 -1
- package/generated/plugins/agdf/runtime/agdf-session-check.js +92 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +24 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +71 -0
- 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 +19 -2
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +24 -1
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
- 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 +12 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
- package/lib/cli/application.js +228 -13
- package/lib/cli/command-registry.js +9 -0
- package/lib/cli/parse-args.js +17 -0
- package/lib/installers/local-marketplace.js +85 -21
- package/lib/installers/opencode.js +21 -5
- package/lib/installers/plugin-installers.js +10 -0
- package/lib/lifecycle/presentation.js +43 -2
- package/lib/lifecycle/result.js +9 -0
- package/lib/lifecycle/status.js +5 -1
- package/lib/runtime-check-consent/adapters.js +42 -0
- package/lib/runtime-check-consent/claude-settings.js +71 -0
- package/lib/runtime-check-consent/contract.js +71 -0
- package/lib/runtime-check-consent/coordinator.js +37 -0
- package/lib/runtime-check-consent/service.js +181 -0
- package/lib/runtime-check-consent/state.js +90 -0
- package/opencode-plugin.js +30 -15
- package/package.json +2 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from "node:fs";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
11
11
|
import process from "node:process";
|
|
12
12
|
import { generatedRoot, packageRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
13
13
|
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
@@ -40,6 +40,11 @@ function semverBase(version) {
|
|
|
40
40
|
return version.split("+")[0];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function isSemanticVersion(version) {
|
|
44
|
+
return typeof version === "string"
|
|
45
|
+
&& /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(version);
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
export function codexLocalInstallVersion(version, sourceDigest) {
|
|
44
49
|
if (!/^[a-f0-9]{64}$/.test(sourceDigest)) throw new Error("AGDF local source digest must be a deterministic SHA-256 value.");
|
|
45
50
|
return `${semverBase(version)}+codex.local-${sourceDigest.slice(0, 12)}`;
|
|
@@ -60,15 +65,20 @@ function readJson(path, label) {
|
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
function pathForPlatform(platform = process.platform) {
|
|
69
|
+
return platform === "win32" ? win32 : posix;
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
export function defaultAgdfDataRoot({ env = process.env, platform = process.platform, home = homedir() } = {}) {
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
if (platform === "
|
|
67
|
-
return join(env.
|
|
73
|
+
const targetPath = pathForPlatform(platform);
|
|
74
|
+
if (env.AGDF_DATA_DIR) return targetPath.resolve(env.AGDF_DATA_DIR);
|
|
75
|
+
if (platform === "darwin") return targetPath.join(home, "Library", "Application Support", "agdf");
|
|
76
|
+
if (platform === "win32") return targetPath.join(env.LOCALAPPDATA || env.APPDATA || targetPath.join(home, "AppData", "Local"), "agdf");
|
|
77
|
+
return targetPath.join(env.XDG_DATA_HOME || targetPath.join(home, ".local", "share"), "agdf");
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
export function localMarketplaceRoot(options = {}) {
|
|
71
|
-
return join(defaultAgdfDataRoot(options), "marketplaces", MARKETPLACE_ID);
|
|
81
|
+
return pathForPlatform(options.platform).join(defaultAgdfDataRoot(options), "marketplaces", MARKETPLACE_ID);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
function ownership(root, { allowBuilding = false } = {}) {
|
|
@@ -134,6 +144,7 @@ function claudeMarketplace(definition = pluginDefinition) {
|
|
|
134
144
|
function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVersion = expectedVersion, sourceDigest = "", {
|
|
135
145
|
requireInstallationProvenance = false,
|
|
136
146
|
allowLegacyProvenanceForMigration = false,
|
|
147
|
+
allowPreProvenanceShape = false,
|
|
137
148
|
} = {}) {
|
|
138
149
|
const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"), "built plugin definition");
|
|
139
150
|
const runtime = readJson(join(pluginRoot, "runtime", "runtime-manifest.json"), "built runtime manifest");
|
|
@@ -150,8 +161,11 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
150
161
|
if (definition.version !== expectedVersion || runtime.version !== expectedVersion) {
|
|
151
162
|
throw new Error(`Built plugin version mismatch: expected ${expectedVersion}, definition ${definition.version}, runtime ${runtime.version}`);
|
|
152
163
|
}
|
|
164
|
+
const profile = validateDistributionProfiles(definition);
|
|
165
|
+
const isPreProvenanceShape = allowPreProvenanceShape
|
|
166
|
+
&& !Object.hasOwn(definition, "distributionProfiles");
|
|
153
167
|
let provenance = null;
|
|
154
|
-
if (
|
|
168
|
+
if (profile.status !== "matched"
|
|
155
169
|
&& requireInstallationProvenance
|
|
156
170
|
&& allowLegacyProvenanceForMigration) {
|
|
157
171
|
provenance = inspectInstallationProvenance(pluginRoot, {
|
|
@@ -161,7 +175,7 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
161
175
|
allowLegacy: true,
|
|
162
176
|
});
|
|
163
177
|
}
|
|
164
|
-
if (
|
|
178
|
+
if (profile.status !== "matched" && provenance?.status !== "legacy" && !isPreProvenanceShape) {
|
|
165
179
|
throw new Error("Built plugin distribution profile contract is invalid.");
|
|
166
180
|
}
|
|
167
181
|
if (codex.version !== expectedCodexInstallVersion) {
|
|
@@ -212,6 +226,49 @@ function validateMarketplaceRoot(root, definition = pluginDefinition) {
|
|
|
212
226
|
return Object.freeze({ codexShape });
|
|
213
227
|
}
|
|
214
228
|
|
|
229
|
+
function classifyExistingMarketplace(stableRoot, marker) {
|
|
230
|
+
const existingPluginRoot = join(stableRoot, "plugins", MARKETPLACE_ID);
|
|
231
|
+
const existingCodexInstallVersion = marker.codex_install_version ?? marker.version;
|
|
232
|
+
if (!isSemanticVersion(marker.version)
|
|
233
|
+
|| !/^[a-f0-9]{64}$/.test(marker.plugin_digest ?? "")) {
|
|
234
|
+
throw new Error(`Refusing invalid AGDF marketplace ownership evidence: ${stableRoot}`);
|
|
235
|
+
}
|
|
236
|
+
const definition = readJson(
|
|
237
|
+
join(existingPluginRoot, "meta", "agdf-plugin.definition.json"),
|
|
238
|
+
"built plugin definition",
|
|
239
|
+
);
|
|
240
|
+
const hasCurrentProvenance = existsSync(join(existingPluginRoot, INSTALLATION_PROVENANCE_FILE));
|
|
241
|
+
const hasLegacyProvenance = existsSync(join(existingPluginRoot, LEGACY_LOCAL_INSTALL_FILE));
|
|
242
|
+
const preProvenanceCandidate = !hasCurrentProvenance
|
|
243
|
+
&& !hasLegacyProvenance
|
|
244
|
+
&& !Object.hasOwn(definition, "distributionProfiles");
|
|
245
|
+
const existingDefinition = validateBuiltPlugin(
|
|
246
|
+
existingPluginRoot,
|
|
247
|
+
marker.version,
|
|
248
|
+
existingCodexInstallVersion,
|
|
249
|
+
marker.source_digest ?? "",
|
|
250
|
+
preProvenanceCandidate
|
|
251
|
+
? { allowPreProvenanceShape: true }
|
|
252
|
+
: { requireInstallationProvenance: true, allowLegacyProvenanceForMigration: true },
|
|
253
|
+
);
|
|
254
|
+
const marketplace = validateMarketplaceRoot(stableRoot, existingDefinition);
|
|
255
|
+
if (digestDirectory(existingPluginRoot) !== marker.plugin_digest) {
|
|
256
|
+
throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
|
|
257
|
+
}
|
|
258
|
+
return Object.freeze({
|
|
259
|
+
classification: preProvenanceCandidate
|
|
260
|
+
? "owned_pre_provenance_rebuild"
|
|
261
|
+
: "current_or_marker_migration",
|
|
262
|
+
definition: existingDefinition,
|
|
263
|
+
marketplace,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function invalidExistingMarketplace(error) {
|
|
268
|
+
error.existingClassification = "invalid_or_unowned";
|
|
269
|
+
return error;
|
|
270
|
+
}
|
|
271
|
+
|
|
215
272
|
function recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot) {
|
|
216
273
|
const parent = dirname(stableRoot);
|
|
217
274
|
if (existsSync(backupRoot)) {
|
|
@@ -252,25 +309,28 @@ export function prepareLocalMarketplace({
|
|
|
252
309
|
mkdirSync(parent, { recursive: true });
|
|
253
310
|
recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot);
|
|
254
311
|
|
|
255
|
-
|
|
312
|
+
let existing = null;
|
|
313
|
+
if (existsSync(stableRoot)) {
|
|
314
|
+
try {
|
|
315
|
+
existing = ownership(stableRoot);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw invalidExistingMarketplace(error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
256
320
|
const targetCodexRegistrationRevision = codexRegistrationRevision
|
|
257
321
|
?? existing?.codex_registration_revision
|
|
258
322
|
?? 0;
|
|
259
323
|
let existingMarketplace = null;
|
|
324
|
+
let existingClassification = "none";
|
|
260
325
|
if (existing) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
existingCodexInstallVersion,
|
|
267
|
-
existing.source_digest ?? "",
|
|
268
|
-
{ requireInstallationProvenance: true, allowLegacyProvenanceForMigration: true },
|
|
269
|
-
);
|
|
270
|
-
existingMarketplace = validateMarketplaceRoot(stableRoot, existingDefinition);
|
|
271
|
-
if (digestDirectory(existingPluginRoot) !== existing.plugin_digest) {
|
|
272
|
-
throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
|
|
326
|
+
let classified;
|
|
327
|
+
try {
|
|
328
|
+
classified = classifyExistingMarketplace(stableRoot, existing);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw invalidExistingMarketplace(error);
|
|
273
331
|
}
|
|
332
|
+
existingClassification = classified.classification;
|
|
333
|
+
existingMarketplace = classified.marketplace;
|
|
274
334
|
}
|
|
275
335
|
mkdirSync(stageRoot, { recursive: false });
|
|
276
336
|
try {
|
|
@@ -338,6 +398,8 @@ export function prepareLocalMarketplace({
|
|
|
338
398
|
codexInstallVersion,
|
|
339
399
|
sourceDigest,
|
|
340
400
|
digest: pluginDigest,
|
|
401
|
+
runtimeDigest: runtimeManifest.digest,
|
|
402
|
+
existingClassification,
|
|
341
403
|
changed: false,
|
|
342
404
|
commit() {},
|
|
343
405
|
rollback() {},
|
|
@@ -359,6 +421,8 @@ export function prepareLocalMarketplace({
|
|
|
359
421
|
codexInstallVersion,
|
|
360
422
|
sourceDigest,
|
|
361
423
|
digest: pluginDigest,
|
|
424
|
+
runtimeDigest: runtimeManifest.digest,
|
|
425
|
+
existingClassification,
|
|
362
426
|
changed: true,
|
|
363
427
|
commit() {
|
|
364
428
|
if (closed) return;
|
|
@@ -6,6 +6,7 @@ import process from "node:process";
|
|
|
6
6
|
import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
7
7
|
import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
|
|
8
8
|
import { resolveLocalValidator } from "../runtime/local-validator.js";
|
|
9
|
+
import { digestDirectory } from "../runtime/plugin-provenance.js";
|
|
9
10
|
import { validateLocalOpenCodePackageSource } from "./local-development.js";
|
|
10
11
|
|
|
11
12
|
const contractModules = ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"];
|
|
@@ -17,6 +18,7 @@ const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-C
|
|
|
17
18
|
const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
|
|
18
19
|
const globalOpenCodeAgentOwnershipMarker = `<!-- AGDF-GLOBAL-AGENT: ${pluginDefinition.opencode.evaluatorAgentName} -->`;
|
|
19
20
|
const globalOpenCodeValidatorPackageOwner = "create-agdf";
|
|
21
|
+
export const openCodePluginEntrypoint = `./node_modules/${pluginDefinition.opencode.npmPackage}/opencode-plugin.js`;
|
|
20
22
|
const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
|
|
21
23
|
const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
|
|
22
24
|
const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
@@ -67,10 +69,14 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
|
|
|
67
69
|
throw openCodeLifecycleError("configuration", `Refusing to update OpenCode config with non-array plugin field: ${configPath}`, { configPath });
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
const plugins =
|
|
71
|
-
|
|
72
|
+
const plugins = (config.plugin ?? []).map((entry) => (
|
|
73
|
+
entry === pluginDefinition.opencode.npmPackage || entry.startsWith(`${pluginDefinition.opencode.npmPackage}@`)
|
|
74
|
+
? openCodePluginEntrypoint
|
|
75
|
+
: entry
|
|
76
|
+
));
|
|
77
|
+
const alreadyInstalled = plugins.includes(openCodePluginEntrypoint);
|
|
72
78
|
if (!alreadyInstalled) {
|
|
73
|
-
plugins.push(
|
|
79
|
+
plugins.push(openCodePluginEntrypoint);
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
const nextConfig = {
|
|
@@ -128,6 +134,10 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
|
|
|
128
134
|
throw openCodeLifecycleError("configuration", `Failed to write OpenCode config ${configPath}: ${error.message}`, { configPath });
|
|
129
135
|
}
|
|
130
136
|
const installedPackage = resolveOpenCodePackage(configDir);
|
|
137
|
+
if (!installedPackage.loadable || !installedPackage.package_root) {
|
|
138
|
+
throw openCodeLifecycleError("verification", "Installed OpenCode AGDF package is not loadable after installation.", { configDir });
|
|
139
|
+
}
|
|
140
|
+
const installedPackageDigest = digestDirectory(installedPackage.package_root);
|
|
131
141
|
const sdkAlignment = alignOpenCodePluginSdk(configDir);
|
|
132
142
|
|
|
133
143
|
return {
|
|
@@ -135,6 +145,7 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
|
|
|
135
145
|
added: !alreadyInstalled,
|
|
136
146
|
transition: openCodePackageTransition(previousPackage, installedPackage),
|
|
137
147
|
sdk_alignment: sdkAlignment,
|
|
148
|
+
installed_package: { root: installedPackage.package_root, digest: installedPackageDigest },
|
|
138
149
|
package_source: localPackageSource
|
|
139
150
|
? { kind: "local_checkout", path: localPackageSource.tarball, digest: localPackageSource.digest, version: localPackageSource.version }
|
|
140
151
|
: { kind: "registry", path: "", digest: "", version: pluginDefinition.version },
|
|
@@ -736,7 +747,8 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
736
747
|
const configPath = join(configDir, "opencode.json");
|
|
737
748
|
const configState = readOpenCodeConfig(configPath);
|
|
738
749
|
const plugins = Array.isArray(configState.config.plugin) ? configState.config.plugin : [];
|
|
739
|
-
const globalConfigured = plugins.includes(
|
|
750
|
+
const globalConfigured = plugins.includes(openCodePluginEntrypoint);
|
|
751
|
+
const legacyPluginConfigured = plugins.some((entry) => entry === pluginDefinition.opencode.npmPackage || entry.startsWith(`${pluginDefinition.opencode.npmPackage}@`));
|
|
740
752
|
const packageState = resolveOpenCodePackage(configDir);
|
|
741
753
|
const packageVersionStatus = openCodePackageVersionStatus(packageState);
|
|
742
754
|
const globalNativeSurface = evaluateGlobalOpenCodeSurface(configDir);
|
|
@@ -754,7 +766,9 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
754
766
|
const findings = [];
|
|
755
767
|
if (!configState.exists) findings.push("OpenCode global config not found.");
|
|
756
768
|
if (configState.parseError) findings.push(`OpenCode global config is not valid JSON: ${configState.parseError}`);
|
|
757
|
-
if (!globalConfigured) findings.push(
|
|
769
|
+
if (!globalConfigured) findings.push(legacyPluginConfigured
|
|
770
|
+
? `OpenCode global config still uses the host-managed npm cache entry for ${pluginDefinition.opencode.npmPackage}; rerun installation to bind the verified local package entrypoint.`
|
|
771
|
+
: `OpenCode global config does not include ${openCodePluginEntrypoint}.`);
|
|
758
772
|
if (!packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is not loadable from the OpenCode config directory.`);
|
|
759
773
|
if (packageVersionStatus === "outdated") findings.push(`${pluginDefinition.opencode.npmPackage} version ${packageState.installed_version} is outdated; expected ${pluginDefinition.version}.`);
|
|
760
774
|
if (packageVersionStatus === "unknown" && packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is loadable but its installed version is unknown.`);
|
|
@@ -783,6 +797,8 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
783
797
|
exists: configState.exists,
|
|
784
798
|
parse_error: configState.parseError,
|
|
785
799
|
plugin_configured: globalConfigured,
|
|
800
|
+
plugin_entrypoint: openCodePluginEntrypoint,
|
|
801
|
+
legacy_plugin_configured: legacyPluginConfigured,
|
|
786
802
|
},
|
|
787
803
|
package: {
|
|
788
804
|
name: pluginDefinition.opencode.npmPackage,
|
|
@@ -27,8 +27,13 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
|
|
|
27
27
|
...(transaction.pluginRoot ? [`staged_plugin_root:${transaction.pluginRoot}`, "staged_installation_provenance:matched"] : []),
|
|
28
28
|
...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
|
|
29
29
|
...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
|
|
30
|
+
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
30
31
|
...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
|
|
31
32
|
],
|
|
33
|
+
pluginRoot: transaction.pluginRoot ?? null,
|
|
34
|
+
digest: transaction.digest ?? null,
|
|
35
|
+
runtimeDigest: transaction.runtimeDigest ?? null,
|
|
36
|
+
sourceDigest: transaction.sourceDigest ?? null,
|
|
32
37
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
33
38
|
};
|
|
34
39
|
} catch (error) {
|
|
@@ -73,8 +78,13 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
|
|
|
73
78
|
...(transaction.pluginRoot ? [`staged_plugin_root:${transaction.pluginRoot}`, "staged_installation_provenance:matched"] : []),
|
|
74
79
|
...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
|
|
75
80
|
...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
|
|
81
|
+
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
76
82
|
...(installedVersion ? [] : ["host_did_not_expose_version"]),
|
|
77
83
|
],
|
|
84
|
+
pluginRoot: transaction.pluginRoot ?? null,
|
|
85
|
+
digest: transaction.digest ?? null,
|
|
86
|
+
runtimeDigest: transaction.runtimeDigest ?? null,
|
|
87
|
+
sourceDigest: transaction.sourceDigest ?? null,
|
|
78
88
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
79
89
|
};
|
|
80
90
|
} catch (error) {
|
|
@@ -9,6 +9,9 @@ const surfaceLabels = Object.freeze({
|
|
|
9
9
|
function titleFor(report) {
|
|
10
10
|
if (report.result === "failed") return "AGDF installation failed";
|
|
11
11
|
if (report.result === "partial") return "AGDF installation partially completed";
|
|
12
|
+
if (report.result === "preview") return report.operation === "uninstall"
|
|
13
|
+
? "AGDF uninstall preview"
|
|
14
|
+
: "AGDF installation cancelled";
|
|
12
15
|
if (report.operation === "repository_setup") return "AGDF repository setup complete";
|
|
13
16
|
return "AGDF installation complete";
|
|
14
17
|
}
|
|
@@ -28,18 +31,55 @@ export function lifecycleCardLines(report) {
|
|
|
28
31
|
`Installation: ${report.installation.status}`,
|
|
29
32
|
`Activation: ${report.activation.status}`,
|
|
30
33
|
`Repository delivery: ${report.delivery.status}`,
|
|
34
|
+
`Automatic runtime checks: requested=${report.runtime_checks.requested}; effective=${report.runtime_checks.effective}; reason=${report.runtime_checks.reason}`,
|
|
31
35
|
`Verification: ${report.verification.status}`,
|
|
32
36
|
`Restart required: ${report.restart.required ? "yes" : "no"}${report.restart.reason !== "none" ? ` (${report.restart.reason})` : ""}`,
|
|
33
37
|
`Next action: ${report.next_action.text}`,
|
|
34
38
|
];
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
function friendlyRuntimeCheckState(report) {
|
|
42
|
+
if (report.runtime_checks.requested === "cancelled") return "Cancelled";
|
|
43
|
+
if (report.runtime_checks.effective === "enabled") return "On";
|
|
44
|
+
if (report.runtime_checks.effective === "manual") return "Manual";
|
|
45
|
+
if (report.runtime_checks.effective === "decision_required") return `Waiting for ${surfaceLabels[report.surface]} permission`;
|
|
46
|
+
if (report.runtime_checks.effective === "renewal_required") return "Needs your review";
|
|
47
|
+
if (["degraded", "failed", "unavailable"].includes(report.runtime_checks.effective)) return "Not available; use manual checks";
|
|
48
|
+
return report.runtime_checks.requested === "enabled" ? "Requested; verification pending" : "Not configured";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function compactVersionLine(report) {
|
|
52
|
+
if (report.version.transition === "updated" && report.version.previous && report.version.installed) {
|
|
53
|
+
return `Updated: ${report.version.previous} -> ${report.version.installed} (${report.version.status})`;
|
|
54
|
+
}
|
|
55
|
+
if (report.version.installed) return `Version: ${report.version.installed} (${report.version.status})`;
|
|
56
|
+
if (report.version.expected) return `Version: not verified (expected ${report.version.expected})`;
|
|
57
|
+
return "Version: not verified";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function compactLifecycleCardLines(report) {
|
|
61
|
+
if (report.result === "preview") return [
|
|
62
|
+
titleFor(report),
|
|
63
|
+
"No changes were made.",
|
|
64
|
+
`Next: ${report.next_action.text}`,
|
|
65
|
+
];
|
|
66
|
+
return [
|
|
67
|
+
report.result === "success"
|
|
68
|
+
? `AGDF ${report.operation === "update" ? "updated" : "installed"} for ${surfaceLabels[report.surface]}`
|
|
69
|
+
: titleFor(report),
|
|
70
|
+
compactVersionLine(report),
|
|
71
|
+
`Installation: ${report.installation.status === "healthy" ? "Ready" : report.installation.status}`,
|
|
72
|
+
`Automatic checks: ${friendlyRuntimeCheckState(report)}`,
|
|
73
|
+
`Next: ${report.next_action.text}`,
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function printLifecycleResult(report, { json = false, compact = false, io = console } = {}) {
|
|
38
78
|
if (json) {
|
|
39
79
|
io.log(JSON.stringify(report, null, 2));
|
|
40
80
|
return;
|
|
41
81
|
}
|
|
42
|
-
for (const line of lifecycleCardLines(report)) io.log(line);
|
|
82
|
+
for (const line of compact ? compactLifecycleCardLines(report) : lifecycleCardLines(report)) io.log(line);
|
|
43
83
|
if (report.failure) io.log(`Failure phase: ${report.failure.phase}: ${report.failure.message}`);
|
|
44
84
|
if (report.retained.length) {
|
|
45
85
|
io.log("Retained:");
|
|
@@ -56,5 +96,6 @@ export function printGeneralStatus(report, { json = false, io = console } = {})
|
|
|
56
96
|
io.log(`Installation: ${report.installation.status}${report.installation.version ? ` (${report.installation.version})` : ""}`);
|
|
57
97
|
io.log(`Repository: ${report.repository.status}`);
|
|
58
98
|
io.log(`Delivery: ${report.delivery.status}${report.delivery.current_gate ? ` (${report.delivery.current_gate})` : ""}`);
|
|
99
|
+
io.log(`Automatic runtime checks: requested=${report.runtime_checks.requested}; effective=${report.runtime_checks.effective}; reason=${report.runtime_checks.reason}`);
|
|
59
100
|
io.log(`Next action: ${report.next_action.text}`);
|
|
60
101
|
}
|
package/lib/lifecycle/result.js
CHANGED
|
@@ -39,6 +39,15 @@ export function createLifecycleResult(input = {}) {
|
|
|
39
39
|
delivery: {
|
|
40
40
|
status: text(input.delivery?.status) || "not_evaluated",
|
|
41
41
|
},
|
|
42
|
+
runtime_checks: {
|
|
43
|
+
requested: text(input.runtime_checks?.requested) || "unknown",
|
|
44
|
+
effective: text(input.runtime_checks?.effective) || "unknown",
|
|
45
|
+
reason: text(input.runtime_checks?.reason) || "not_evaluated",
|
|
46
|
+
capability_identity: text(input.runtime_checks?.capability_identity) || null,
|
|
47
|
+
verification: text(input.runtime_checks?.verification) || "not_evaluated",
|
|
48
|
+
mutation: text(input.runtime_checks?.mutation) || "none",
|
|
49
|
+
rollback: text(input.runtime_checks?.rollback) || "none",
|
|
50
|
+
},
|
|
42
51
|
restart: {
|
|
43
52
|
required: Boolean(input.restart?.required),
|
|
44
53
|
reason: text(input.restart?.reason) || "none",
|
package/lib/lifecycle/status.js
CHANGED
|
@@ -6,6 +6,7 @@ import { evaluateGateCheck } from "../control-evaluation/gate-check.js";
|
|
|
6
6
|
import { evaluateOpenCodeStatus } from "../installers/opencode.js";
|
|
7
7
|
import { inspectPluginSurface } from "../installers/plugin-installers.js";
|
|
8
8
|
import { inspectGeneratedRepositoryMarketplace } from "../runtime/plugin-provenance.js";
|
|
9
|
+
import { runtimeCheckStatus } from "../runtime-check-consent/service.js";
|
|
9
10
|
|
|
10
11
|
function deliveryStatus(targetDir, selection, dependencies) {
|
|
11
12
|
const controlDir = join(targetDir, ".agdf", "control");
|
|
@@ -95,6 +96,9 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
|
|
|
95
96
|
}
|
|
96
97
|
const repository = repositoryStatus(targetDir, surface);
|
|
97
98
|
const delivery = deliveryStatus(targetDir, { runId: options.runId }, deps);
|
|
99
|
+
const runtimeChecks = ["codex", "claude", "opencode"].includes(surface)
|
|
100
|
+
? runtimeCheckStatus(options.dataRoot, surface)
|
|
101
|
+
: { requested: "unknown", effective: "unavailable", reason: "unsupported_host_capability" };
|
|
98
102
|
const nextText = surface === "multiple"
|
|
99
103
|
? "Multiple AGDF host surfaces were detected; rerun status with an explicit --surface."
|
|
100
104
|
: surface === "generic"
|
|
@@ -112,5 +116,5 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
|
|
|
112
116
|
: delivery.status === "not_configured"
|
|
113
117
|
? "Start a new task; status created no run and requires no approval. Initialize durable control only when the repository needs it."
|
|
114
118
|
: "Continue with the reported delivery next step.";
|
|
115
|
-
return { schema_version: 1, installation, repository, delivery, next_action: { kind: "action", text: nextText } };
|
|
119
|
+
return { schema_version: 1, installation, repository, delivery, runtime_checks: runtimeChecks, next_action: { kind: "action", text: nextText } };
|
|
116
120
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function fixedCommand(command) {
|
|
2
|
+
if (typeof command !== "string" || !command.trim() || /[\n\r;&|*?]/.test(command)) {
|
|
3
|
+
throw new Error("AGDF_RUNTIME_CHECK_COMMAND_NOT_EXACT");
|
|
4
|
+
}
|
|
5
|
+
return command.trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function codexRuntimeCheckEvidence({ capabilityIdentity, observedIdentity, hookEnabled, reviewRequired = false }) {
|
|
9
|
+
if (hookEnabled && observedIdentity === capabilityIdentity) return { status: "enabled", capability_identity: capabilityIdentity, reason: "none" };
|
|
10
|
+
if (observedIdentity && observedIdentity !== capabilityIdentity) return { status: "renewal_required", reason: "capability_identity_changed" };
|
|
11
|
+
return { status: "decision_required", reason: reviewRequired ? "host_review_required" : "host_permission_unverified" };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function claudePermissionRule({ platform, command }) {
|
|
15
|
+
const exact = fixedCommand(command);
|
|
16
|
+
return platform === "win32" ? `PowerShell(${exact})` : `Bash(${exact})`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applyClaudeExactRule(settings, { rule, deny = [], ask = [] }) {
|
|
20
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("AGDF_CLAUDE_SETTINGS_INVALID");
|
|
21
|
+
if ([...deny, ...ask].includes(rule)) return { status: "degraded", reason: "host_permission_conflict", settings };
|
|
22
|
+
const next = structuredClone(settings);
|
|
23
|
+
next.permissions ??= {};
|
|
24
|
+
if (Array.isArray(next.permissions.allow) && next.permissions.allow.some((entry) => entry.includes("*") || entry !== entry.trim())) {
|
|
25
|
+
throw new Error("AGDF_CLAUDE_BROAD_OR_MALFORMED_RULE");
|
|
26
|
+
}
|
|
27
|
+
next.permissions.allow = [...new Set([...(next.permissions.allow ?? []), rule])];
|
|
28
|
+
return { status: "configured", reason: "none", settings: next };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function revokeClaudeExactRule(settings, rule) {
|
|
32
|
+
const next = structuredClone(settings);
|
|
33
|
+
if (Array.isArray(next.permissions?.allow)) next.permissions.allow = next.permissions.allow.filter((entry) => entry !== rule);
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function openCodeRuntimeCheckEvidence({ capabilityIdentity, observedIdentity, packageLoadable, hookObserved }) {
|
|
38
|
+
if (!packageLoadable) return { status: "unavailable", reason: "unsupported_host_capability" };
|
|
39
|
+
if (observedIdentity !== capabilityIdentity) return { status: "renewal_required", reason: "installed_identity_mismatch" };
|
|
40
|
+
if (!hookObserved) return { status: "degraded", reason: "host_permission_unverified" };
|
|
41
|
+
return { status: "enabled", capability_identity: capabilityIdentity, reason: "none" };
|
|
42
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
6
|
+
import { applyClaudeExactRule, revokeClaudeExactRule } from "./adapters.js";
|
|
7
|
+
|
|
8
|
+
export function defaultClaudeSettingsPath({ env = process.env, home = homedir() } = {}) {
|
|
9
|
+
return join(resolve(env.CLAUDE_CONFIG_DIR || join(home, ".claude")), "settings.json");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function readClaudeSettings(path = defaultClaudeSettingsPath()) {
|
|
13
|
+
if (!existsSync(path)) return { exists: false, settings: {} };
|
|
14
|
+
const stats = lstatSync(path);
|
|
15
|
+
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("AGDF_CLAUDE_SETTINGS_UNOWNED_PATH");
|
|
16
|
+
try {
|
|
17
|
+
const settings = JSON.parse(readFileSync(path, "utf8"));
|
|
18
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not an object");
|
|
19
|
+
return { exists: true, settings };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error(`AGDF_CLAUDE_SETTINGS_INVALID: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function atomicSettingsWrite(path, settings) {
|
|
26
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
27
|
+
const temp = `${path}.agdf-tmp-${process.pid}`;
|
|
28
|
+
const backup = `${path}.agdf-backup-${process.pid}`;
|
|
29
|
+
const hadPrevious = existsSync(path);
|
|
30
|
+
const mode = hadPrevious ? lstatSync(path).mode & 0o777 : 0o600;
|
|
31
|
+
writeFileSync(temp, `${JSON.stringify(settings, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode });
|
|
32
|
+
if (process.platform !== "win32") chmodSync(temp, mode);
|
|
33
|
+
try {
|
|
34
|
+
if (hadPrevious && process.platform === "win32") renameSyncWithRetry(path, backup);
|
|
35
|
+
renameSyncWithRetry(temp, path);
|
|
36
|
+
if (hadPrevious && process.platform === "win32") rmSync(backup, { force: true });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
try { rmSync(temp, { force: true }); } catch {}
|
|
39
|
+
if (hadPrevious && process.platform === "win32" && existsSync(backup) && !existsSync(path)) renameSyncWithRetry(backup, path);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function configureClaudeExactRuntimeRule({ path = defaultClaudeSettingsPath(), rule }) {
|
|
45
|
+
const before = readClaudeSettings(path);
|
|
46
|
+
const permissions = before.settings.permissions ?? {};
|
|
47
|
+
const applied = applyClaudeExactRule(before.settings, {
|
|
48
|
+
rule,
|
|
49
|
+
deny: Array.isArray(permissions.deny) ? permissions.deny : [],
|
|
50
|
+
ask: Array.isArray(permissions.ask) ? permissions.ask : [],
|
|
51
|
+
});
|
|
52
|
+
if (applied.status !== "configured") return { ...applied, path, rollback() {} };
|
|
53
|
+
atomicSettingsWrite(path, applied.settings);
|
|
54
|
+
return {
|
|
55
|
+
status: "configured",
|
|
56
|
+
reason: "host_permission_unverified",
|
|
57
|
+
path,
|
|
58
|
+
rollback() {
|
|
59
|
+
if (before.exists) atomicSettingsWrite(path, before.settings);
|
|
60
|
+
else rmSync(path, { force: true });
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function revokeClaudeRuntimeRule({ path = defaultClaudeSettingsPath(), rule }) {
|
|
66
|
+
const before = readClaudeSettings(path);
|
|
67
|
+
if (!before.exists) return { status: "manual", reason: "configuration_missing", path };
|
|
68
|
+
const next = revokeClaudeExactRule(before.settings, rule);
|
|
69
|
+
atomicSettingsWrite(path, next);
|
|
70
|
+
return { status: "manual", reason: "consent_not_provided", path };
|
|
71
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const OPERATIONS = Object.freeze([
|
|
4
|
+
"resolve-local-runtime",
|
|
5
|
+
"inspect-control-state",
|
|
6
|
+
"render-bounded-orientation",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function stable(value) {
|
|
10
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
11
|
+
if (!value || typeof value !== "object") return value;
|
|
12
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateRuntimeCheckCapability(capability) {
|
|
16
|
+
if (capability?.schemaVersion !== 1 || capability.capabilityId !== "automatic-runtime-checks") {
|
|
17
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: schema or capability id");
|
|
18
|
+
}
|
|
19
|
+
if (!Number.isInteger(capability.adapterContractVersion) || capability.adapterContractVersion < 1) {
|
|
20
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: adapter contract version");
|
|
21
|
+
}
|
|
22
|
+
if (capability.entrypoint !== "runtime/agdf-session-check.js") {
|
|
23
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: fixed entrypoint required");
|
|
24
|
+
}
|
|
25
|
+
if (JSON.stringify(capability.operations) !== JSON.stringify(OPERATIONS)) {
|
|
26
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: closed operation vocabulary required");
|
|
27
|
+
}
|
|
28
|
+
const constraints = capability.constraints ?? {};
|
|
29
|
+
if (constraints.arguments !== "forbidden" || constraints.filesystemWrites !== "forbidden"
|
|
30
|
+
|| constraints.network !== "forbidden" || constraints.gateAuthority !== "none") {
|
|
31
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: read-only constraints required");
|
|
32
|
+
}
|
|
33
|
+
const surfaces = capability.surfaces ?? {};
|
|
34
|
+
if (surfaces.codex !== "native-hook-review" || surfaces.claude !== "exact-command-rule"
|
|
35
|
+
|| surfaces.opencode !== "plugin-internal"
|
|
36
|
+
|| surfaces["portable-skills"] !== "manual-external-required") {
|
|
37
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: surface contract");
|
|
38
|
+
}
|
|
39
|
+
return capability;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function runtimeCheckCapabilityIdentity({ capability, surface, runtimeDigest, sourceDigest, command }) {
|
|
43
|
+
validateRuntimeCheckCapability(capability);
|
|
44
|
+
if (!["codex", "claude", "opencode"].includes(surface)) throw new Error("AGDF_RUNTIME_CHECK_SURFACE_INVALID");
|
|
45
|
+
for (const [name, value] of Object.entries({ runtimeDigest, sourceDigest })) {
|
|
46
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) throw new Error(`AGDF_RUNTIME_CHECK_IDENTITY_INVALID: ${name}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof command !== "string" || !command.trim()) throw new Error("AGDF_RUNTIME_CHECK_IDENTITY_INVALID: command");
|
|
49
|
+
const payload = stable({
|
|
50
|
+
schema_version: 1,
|
|
51
|
+
surface,
|
|
52
|
+
capability,
|
|
53
|
+
runtime_digest: runtimeDigest,
|
|
54
|
+
source_digest: sourceDigest,
|
|
55
|
+
command: command.trim(),
|
|
56
|
+
});
|
|
57
|
+
return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function fixedRuntimeCheckCommand(surface, pluginRoot, platform = process.platform) {
|
|
61
|
+
if (surface === "opencode") return "plugin:agdf:automatic-runtime-checks";
|
|
62
|
+
if (surface === "codex" || surface === "claude") {
|
|
63
|
+
return platform === "win32"
|
|
64
|
+
? "node \"$([Environment]::GetEnvironmentVariable('PLUGIN_ROOT') + [Environment]::GetEnvironmentVariable('CLAUDE_PLUGIN_ROOT'))\\runtime\\agdf-session-check.js\""
|
|
65
|
+
: "node \"${PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/runtime/agdf-session-check.js\"";
|
|
66
|
+
}
|
|
67
|
+
const normalizedRoot = platform === "win32" ? pluginRoot.replaceAll("/", "\\") : pluginRoot;
|
|
68
|
+
return `node \"${normalizedRoot}${platform === "win32" ? "\\" : "/"}runtime${platform === "win32" ? "\\" : "/"}agdf-session-check.js\"`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const runtimeCheckOperations = OPERATIONS;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const RUNTIME_CHECK_DECISIONS = Object.freeze(["enable", "manual", "cancel"]);
|
|
2
|
+
|
|
3
|
+
export function resolveRuntimeCheckDecision({ explicitValue, interactive = false, ask }) {
|
|
4
|
+
if (explicitValue !== undefined) {
|
|
5
|
+
if (!RUNTIME_CHECK_DECISIONS.includes(explicitValue)) throw new Error("AGDF_RUNTIME_CHECK_DECISION_INVALID");
|
|
6
|
+
return explicitValue;
|
|
7
|
+
}
|
|
8
|
+
if (!interactive || typeof ask !== "function") return "manual";
|
|
9
|
+
const answer = ask({
|
|
10
|
+
question: "Allow this AGDF installation to run narrow local read-only checks automatically?",
|
|
11
|
+
options: [...RUNTIME_CHECK_DECISIONS],
|
|
12
|
+
default: null,
|
|
13
|
+
});
|
|
14
|
+
if (!RUNTIME_CHECK_DECISIONS.includes(answer)) return "cancel";
|
|
15
|
+
return answer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function consentDisclosure(surface) {
|
|
19
|
+
const permissionOwner = surface === "codex" ? "Codex native hook trust"
|
|
20
|
+
: surface === "claude" ? "Claude Code user permission settings"
|
|
21
|
+
: "OpenCode plugin and explicit permission configuration";
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
surface,
|
|
24
|
+
installation_scope: "global user installation",
|
|
25
|
+
runs: "one argument-free local session check",
|
|
26
|
+
when: "at session start",
|
|
27
|
+
executable: "runtime/agdf-session-check.js inside the installed AGDF runtime",
|
|
28
|
+
reads: "AGDF runtime identity and .agdf/control state in the current repository",
|
|
29
|
+
writes: "one AGDF-owned intent receipt and, only where supported, one exact host permission rule",
|
|
30
|
+
network: "none",
|
|
31
|
+
permission_owner: permissionOwner,
|
|
32
|
+
renewal: "material command, runtime, source, scope or adapter identity change requires renewed consent",
|
|
33
|
+
revocation: `npx --yes @agdf/cli@latest runtime-checks manual --surface ${surface}`,
|
|
34
|
+
gate_authority: "none",
|
|
35
|
+
choices: [...RUNTIME_CHECK_DECISIONS],
|
|
36
|
+
});
|
|
37
|
+
}
|