create-agdf 0.13.7 → 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 +2 -0
- package/lib/installers/opencode.js +21 -5
- package/lib/installers/plugin-installers.js +8 -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
|
@@ -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,
|
|
@@ -30,6 +30,10 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
|
|
|
30
30
|
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
31
31
|
...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
|
|
32
32
|
],
|
|
33
|
+
pluginRoot: transaction.pluginRoot ?? null,
|
|
34
|
+
digest: transaction.digest ?? null,
|
|
35
|
+
runtimeDigest: transaction.runtimeDigest ?? null,
|
|
36
|
+
sourceDigest: transaction.sourceDigest ?? null,
|
|
33
37
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
34
38
|
};
|
|
35
39
|
} catch (error) {
|
|
@@ -77,6 +81,10 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
|
|
|
77
81
|
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
78
82
|
...(installedVersion ? [] : ["host_did_not_expose_version"]),
|
|
79
83
|
],
|
|
84
|
+
pluginRoot: transaction.pluginRoot ?? null,
|
|
85
|
+
digest: transaction.digest ?? null,
|
|
86
|
+
runtimeDigest: transaction.runtimeDigest ?? null,
|
|
87
|
+
sourceDigest: transaction.sourceDigest ?? null,
|
|
80
88
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
81
89
|
};
|
|
82
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
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
7
|
+
import { defaultOpenCodeConfigDir, resolveOpenCodeInstalledPackage } from "../installers/opencode.js";
|
|
8
|
+
import { defaultAgdfDataRoot } from "../installers/local-marketplace.js";
|
|
9
|
+
import { digestNormalizedPluginSource } from "../runtime/plugin-provenance.js";
|
|
10
|
+
import { fixedRuntimeCheckCommand, runtimeCheckCapabilityIdentity } from "./contract.js";
|
|
11
|
+
import { consentDisclosure, resolveRuntimeCheckDecision } from "./coordinator.js";
|
|
12
|
+
import { claudePermissionRule } from "./adapters.js";
|
|
13
|
+
import { configureClaudeExactRuntimeRule, defaultClaudeSettingsPath, revokeClaudeRuntimeRule } from "./claude-settings.js";
|
|
14
|
+
import { createRuntimeCheckReceipt, readRuntimeCheckReceipt, writeRuntimeCheckReceipt } from "./state.js";
|
|
15
|
+
|
|
16
|
+
export { fixedRuntimeCheckCommand } from "./contract.js";
|
|
17
|
+
|
|
18
|
+
export function prepareInstallConsent(surface, options = {}, adapters = {}) {
|
|
19
|
+
const explicit = options.runtimeChecksDecision;
|
|
20
|
+
const interactive = explicit === undefined && Boolean(adapters.interactive);
|
|
21
|
+
const answer = interactive ? adapters.ask?.(consentDisclosure(surface)) : undefined;
|
|
22
|
+
const decision = resolveRuntimeCheckDecision({ explicitValue: explicit ?? answer, interactive: false });
|
|
23
|
+
return { decision, persist: explicit !== undefined || interactive, disclosure: consentDisclosure(surface) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function prospectiveRuntimeCheckIdentity(surface, platform = process.platform) {
|
|
27
|
+
if (surface === "opencode") {
|
|
28
|
+
const installed = resolveOpenCodeInstalledPackage(defaultOpenCodeConfigDir(), pluginDefinition.opencode.npmPackage);
|
|
29
|
+
if (!installed.loadable || !installed.digest) return null;
|
|
30
|
+
return runtimeCheckCapabilityIdentity({
|
|
31
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
32
|
+
surface,
|
|
33
|
+
runtimeDigest: installed.digest,
|
|
34
|
+
sourceDigest: installed.digest,
|
|
35
|
+
command: fixedRuntimeCheckCommand(surface, installed.root, platform),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!["codex", "claude"].includes(surface)) return null;
|
|
39
|
+
const pluginRoot = join(generatedRoot, "plugins", "agdf");
|
|
40
|
+
let runtimeManifest;
|
|
41
|
+
try { runtimeManifest = JSON.parse(readFileSync(join(pluginRoot, "runtime", "runtime-manifest.json"), "utf8")); } catch { return null; }
|
|
42
|
+
if (!/^[a-f0-9]{64}$/.test(runtimeManifest?.digest ?? "")) return null;
|
|
43
|
+
const sourceDigest = digestNormalizedPluginSource(pluginRoot, pluginDefinition.version);
|
|
44
|
+
const command = fixedRuntimeCheckCommand(surface, pluginRoot, platform);
|
|
45
|
+
return runtimeCheckCapabilityIdentity({
|
|
46
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
47
|
+
surface,
|
|
48
|
+
runtimeDigest: runtimeManifest.digest,
|
|
49
|
+
sourceDigest,
|
|
50
|
+
command,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function retainCurrentInstallConsent(surface, dataRoot = defaultAgdfDataRoot(), platform = process.platform) {
|
|
55
|
+
const capabilityIdentity = prospectiveRuntimeCheckIdentity(surface, platform);
|
|
56
|
+
if (!capabilityIdentity) return null;
|
|
57
|
+
const current = readRuntimeCheckReceipt(dataRoot, surface);
|
|
58
|
+
if (current.status !== "valid" || current.receipt.capability_identity !== capabilityIdentity) return null;
|
|
59
|
+
return {
|
|
60
|
+
decision: current.receipt.requested_state === "enabled" ? "enable" : "manual",
|
|
61
|
+
persist: false,
|
|
62
|
+
retained: true,
|
|
63
|
+
disclosure: consentDisclosure(surface),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function persistInstallConsent({ surface, decision, installed, dataRoot, platform, claudeSettingsPath }) {
|
|
68
|
+
if (decision === "cancel") return { requested: "cancelled", effective: "cancelled", reason: "consent_not_provided", capability_identity: null, verification: "not_attempted", mutation: "none", rollback: "none" };
|
|
69
|
+
const runtimeDigest = installed?.runtimeDigest ?? installed?.digest;
|
|
70
|
+
if (!installed?.pluginRoot || !runtimeDigest || !installed?.sourceDigest) {
|
|
71
|
+
return { requested: decision === "enable" ? "enabled" : "manual", effective: "manual", reason: "host_permission_unverified", capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none" };
|
|
72
|
+
}
|
|
73
|
+
const command = fixedRuntimeCheckCommand(surface, installed.pluginRoot, platform);
|
|
74
|
+
const capabilityIdentity = runtimeCheckCapabilityIdentity({
|
|
75
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
76
|
+
surface,
|
|
77
|
+
runtimeDigest,
|
|
78
|
+
sourceDigest: installed.sourceDigest,
|
|
79
|
+
command,
|
|
80
|
+
});
|
|
81
|
+
const receipt = createRuntimeCheckReceipt({ surface, decision, capabilityIdentity, command });
|
|
82
|
+
let configured = null;
|
|
83
|
+
let configurationReason = null;
|
|
84
|
+
if (surface === "claude" && decision === "enable") {
|
|
85
|
+
configured = configureClaudeExactRuntimeRule({
|
|
86
|
+
path: claudeSettingsPath ?? defaultClaudeSettingsPath(),
|
|
87
|
+
rule: claudePermissionRule({ platform: platform ?? process.platform, command }),
|
|
88
|
+
});
|
|
89
|
+
if (configured.status !== "configured") {
|
|
90
|
+
configurationReason = configured.reason;
|
|
91
|
+
configured = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
writeRuntimeCheckReceipt(dataRoot ?? defaultAgdfDataRoot(), receipt);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
configured?.rollback();
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (configurationReason) return { requested: "enabled", effective: "degraded", reason: configurationReason, capability_identity: capabilityIdentity, verification: "conflict", mutation: "receipt_written", rollback: "none" };
|
|
101
|
+
return {
|
|
102
|
+
requested: receipt.requested_state,
|
|
103
|
+
effective: receipt.requested_state === "manual" ? "manual" : "decision_required",
|
|
104
|
+
reason: receipt.requested_state === "manual" ? "consent_not_provided" : "host_permission_unverified",
|
|
105
|
+
capability_identity: capabilityIdentity,
|
|
106
|
+
verification: receipt.requested_state === "manual" ? "not_required" : "host_unverified",
|
|
107
|
+
mutation: surface === "claude" && decision === "enable" ? "exact_rule_and_receipt_written" : "receipt_written",
|
|
108
|
+
rollback: "none",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function setRuntimeChecksManual({ dataRoot = defaultAgdfDataRoot(), surface, platform = process.platform, claudeSettingsPath }) {
|
|
113
|
+
const result = readRuntimeCheckReceipt(dataRoot, surface);
|
|
114
|
+
if (result.status === "receipt_missing") return { requested: "manual", effective: "manual", reason: "consent_not_provided", capability_identity: null, verification: "not_required", mutation: "none", rollback: "none", path: result.path };
|
|
115
|
+
if (result.status !== "valid") return { requested: "manual", effective: result.status, reason: result.status, capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none", path: result.path };
|
|
116
|
+
if (surface === "claude" && result.receipt.requested_state === "enabled") {
|
|
117
|
+
revokeClaudeRuntimeRule({
|
|
118
|
+
path: claudeSettingsPath ?? defaultClaudeSettingsPath(),
|
|
119
|
+
rule: claudePermissionRule({ platform, command: result.receipt.command }),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const receipt = createRuntimeCheckReceipt({
|
|
123
|
+
surface,
|
|
124
|
+
decision: "manual",
|
|
125
|
+
capabilityIdentity: result.receipt.capability_identity,
|
|
126
|
+
command: result.receipt.command,
|
|
127
|
+
});
|
|
128
|
+
writeRuntimeCheckReceipt(dataRoot, receipt);
|
|
129
|
+
return {
|
|
130
|
+
requested: "manual",
|
|
131
|
+
effective: "manual",
|
|
132
|
+
reason: "consent_not_provided",
|
|
133
|
+
capability_identity: receipt.capability_identity,
|
|
134
|
+
verification: "not_required",
|
|
135
|
+
mutation: surface === "claude" ? "exact_owned_rule_removed" : "receipt_updated",
|
|
136
|
+
rollback: "none",
|
|
137
|
+
path: result.path,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function runtimeCheckStatus(dataRoot = defaultAgdfDataRoot(), surface, platform = process.platform) {
|
|
142
|
+
const result = readRuntimeCheckReceipt(dataRoot, surface);
|
|
143
|
+
if (result.status !== "valid") return { requested: "unknown", effective: result.status, reason: result.status, capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none", path: result.path };
|
|
144
|
+
const currentIdentity = prospectiveRuntimeCheckIdentity(surface, platform);
|
|
145
|
+
if (currentIdentity && currentIdentity !== result.receipt.capability_identity) {
|
|
146
|
+
return { requested: result.receipt.requested_state, effective: "renewal_required", reason: "capability_identity_changed", capability_identity: result.receipt.capability_identity, verification: "identity_mismatch", mutation: "none", rollback: "none", path: result.path };
|
|
147
|
+
}
|
|
148
|
+
return { requested: result.receipt.requested_state, effective: result.receipt.requested_state === "manual" ? "manual" : "decision_required", reason: result.receipt.requested_state === "manual" ? "consent_not_provided" : "host_permission_unverified", capability_identity: result.receipt.capability_identity, verification: result.receipt.requested_state === "manual" ? "not_required" : "host_unverified", mutation: "none", rollback: "none", path: result.path };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function executeOpenCodeAutomaticRuntimeCheck({
|
|
152
|
+
directory = process.cwd(),
|
|
153
|
+
dataRoot = defaultAgdfDataRoot(),
|
|
154
|
+
packageRoot = fileURLToPath(new URL("../../", import.meta.url)),
|
|
155
|
+
run = spawnSync,
|
|
156
|
+
executable = process.platform === "win32" ? "node.exe" : "node",
|
|
157
|
+
statusResolver = runtimeCheckStatus,
|
|
158
|
+
entrypointExists = existsSync,
|
|
159
|
+
} = {}) {
|
|
160
|
+
const status = statusResolver(dataRoot, "opencode");
|
|
161
|
+
if (status.requested !== "enabled" || status.effective !== "decision_required") {
|
|
162
|
+
return { ...status, ran: false, output: "" };
|
|
163
|
+
}
|
|
164
|
+
const entrypoint = join(packageRoot, "generated", "plugins", "agdf", "runtime", "agdf-session-check.js");
|
|
165
|
+
if (!entrypointExists(entrypoint)) {
|
|
166
|
+
return { ...status, effective: "unavailable", reason: "unsupported_host_capability", verification: "entrypoint_missing", ran: false, output: "" };
|
|
167
|
+
}
|
|
168
|
+
const child = run(executable, [entrypoint], {
|
|
169
|
+
cwd: directory,
|
|
170
|
+
env: { ...process.env, AGDF_SURFACE: "opencode" },
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
173
|
+
timeout: 10000,
|
|
174
|
+
maxBuffer: 1024 * 1024,
|
|
175
|
+
});
|
|
176
|
+
const output = String(child.stdout || "").trim().slice(0, 20000);
|
|
177
|
+
if (child.status !== 0 || !output.includes("AGDF automatic runtime check:")) {
|
|
178
|
+
return { ...status, effective: "degraded", reason: "host_permission_unverified", verification: "execution_failed", ran: true, output: "" };
|
|
179
|
+
}
|
|
180
|
+
return { ...status, effective: "enabled", reason: "none", verification: "host_observed", ran: true, output };
|
|
181
|
+
}
|