mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 12:20:48 +02:00
fix(gstack2): harden integrated runtime verification
This commit is contained in:
+66
-6
@@ -28,7 +28,6 @@ import {
|
||||
import {
|
||||
CAPABILITY_READINESS_CAPABILITIES,
|
||||
capabilityReadiness,
|
||||
formatCapabilityReadiness,
|
||||
runDoctor,
|
||||
formatDoctor,
|
||||
} from "./doctor.js";
|
||||
@@ -99,6 +98,11 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
||||
throw cliError(`Unknown command: ${command}`, "USAGE");
|
||||
}
|
||||
} catch (error) {
|
||||
const structuredResult = error?.executionResult ?? error?.cause?.executionResult;
|
||||
if (structuredResult) {
|
||||
write(stderr, renderExecutionResult(structuredResult, { json: true }));
|
||||
return 1;
|
||||
}
|
||||
const json = args.includes("--json");
|
||||
const safeMessage = redactSensitiveText(error?.message ?? String(error));
|
||||
if (json) {
|
||||
@@ -171,12 +175,47 @@ async function doctorCommand({ args, home, cwd, stdout }) {
|
||||
}
|
||||
const report = await runDoctor({ home, cwd, expectedSkillApi: parsed.values.get("--skill-api") });
|
||||
const result = capability ? capabilityReadiness(report, capability) : report;
|
||||
if (capability) {
|
||||
const envelope = capabilityExecutionResult(result);
|
||||
write(stdout, renderExecutionResult(envelope, { json: parsed.flags.has("--json") }));
|
||||
return envelope.status === "success" ? 0 : 1;
|
||||
}
|
||||
write(stdout, parsed.flags.has("--json")
|
||||
? `${JSON.stringify(result, null, 2)}\n`
|
||||
: capability ? formatCapabilityReadiness(result) : formatDoctor(result));
|
||||
: formatDoctor(result));
|
||||
return result.ok ? 0 : 1;
|
||||
}
|
||||
|
||||
function capabilityExecutionResult(result) {
|
||||
const statusByReadiness = {
|
||||
ready: "success",
|
||||
degraded: "degraded",
|
||||
unavailable: "degraded",
|
||||
unsupported: "unsupported",
|
||||
failed: "failed",
|
||||
};
|
||||
const codeByReadiness = {
|
||||
ready: null,
|
||||
degraded: EXECUTION_RESULT_ERROR_CODES.DEGRADED,
|
||||
unavailable: EXECUTION_RESULT_ERROR_CODES.CAPABILITY_UNAVAILABLE,
|
||||
unsupported: EXECUTION_RESULT_ERROR_CODES.CAPABILITY_UNSUPPORTED,
|
||||
failed: EXECUTION_RESULT_ERROR_CODES.CAPABILITY_FAILED,
|
||||
};
|
||||
const readiness = result.readiness.status;
|
||||
const evidence = result.readiness.evidence?.map((item) =>
|
||||
`${item.id}: ${item.status} — ${item.message}`) ?? [
|
||||
`platform: ${result.platform.status}`,
|
||||
`judgment: ${result.judgment.status}`,
|
||||
];
|
||||
return executionResult({
|
||||
status: statusByReadiness[readiness],
|
||||
code: codeByReadiness[readiness],
|
||||
summary: result.readiness.message,
|
||||
evidence,
|
||||
data: result,
|
||||
});
|
||||
}
|
||||
|
||||
async function configCommand({ args, home, cwd, stdout }) {
|
||||
const [action, ...tail] = args;
|
||||
if (action === "get") {
|
||||
@@ -387,9 +426,12 @@ async function runExternalCommand(command, { cwd, env, stdout, stderr }) {
|
||||
child.once("close", (code, signal) => {
|
||||
if (code === 0) {
|
||||
if (captured.truncated) {
|
||||
reject(cliError(
|
||||
reject(executionResultError(
|
||||
`External command ${path.basename(executable)} exceeded the ${maxCapturedBytes}-byte result limit; verify its side effect before reconciling it as applied.`,
|
||||
EXECUTION_RESULT_ERROR_CODES.DEGRADED,
|
||||
"degraded",
|
||||
["output exceeded 1048576-byte capture limit"],
|
||||
{ executable: path.basename(executable), exitCode: 0, truncated: true },
|
||||
));
|
||||
return;
|
||||
}
|
||||
@@ -398,9 +440,12 @@ async function runExternalCommand(command, { cwd, env, stdout, stderr }) {
|
||||
if (captured.stderr.trim()) evidence.push("non-empty stderr");
|
||||
const name = path.basename(executable);
|
||||
if (evidence.length === 0) {
|
||||
reject(cliError(
|
||||
reject(executionResultError(
|
||||
`External command ${name} exited 0 but produced no output; verify its side effect before reconciling it as applied.`,
|
||||
EXECUTION_RESULT_ERROR_CODES.EMPTY,
|
||||
"degraded",
|
||||
["exit code 0", "stdout and stderr were empty"],
|
||||
{ executable: name, exitCode: 0, stdout: "", stderr: "" },
|
||||
));
|
||||
return;
|
||||
}
|
||||
@@ -417,15 +462,30 @@ async function runExternalCommand(command, { cwd, env, stdout, stderr }) {
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const error = cliError(
|
||||
const error = executionResultError(
|
||||
`External command ${path.basename(executable)} ${signal ? `ended by ${signal}` : `exited ${code}`}`,
|
||||
"EXTERNAL_COMMAND_FAILED",
|
||||
EXECUTION_RESULT_ERROR_CODES.FAILED,
|
||||
"failed",
|
||||
[signal ? `terminated by signal ${signal}` : `exit code ${code}`],
|
||||
{
|
||||
executable: path.basename(executable),
|
||||
exitCode: code,
|
||||
signal: signal ?? null,
|
||||
stdout: captured.stdout,
|
||||
stderr: captured.stderr,
|
||||
},
|
||||
);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function executionResultError(summary, code, status, evidence, data) {
|
||||
const error = cliError(summary, code);
|
||||
error.executionResult = executionResult({ status, code, summary, evidence, data });
|
||||
return error;
|
||||
}
|
||||
|
||||
async function withOwnedRuntimeMutation(home, callback) {
|
||||
return withRuntimeLifecycleLock(home, async () => {
|
||||
await assertManagedHome(home);
|
||||
|
||||
@@ -14,6 +14,9 @@ export const EXECUTION_RESULT_ERROR_CODES = Object.freeze({
|
||||
DEGRADED: "EXECUTION_DEGRADED",
|
||||
UNSUPPORTED: "EXECUTION_UNSUPPORTED",
|
||||
FAILED: "EXECUTION_FAILED",
|
||||
CAPABILITY_UNAVAILABLE: "CAPABILITY_UNAVAILABLE",
|
||||
CAPABILITY_UNSUPPORTED: "CAPABILITY_UNSUPPORTED",
|
||||
CAPABILITY_FAILED: "CAPABILITY_FAILED",
|
||||
});
|
||||
|
||||
export const EXECUTION_RESULT_SCHEMA = Object.freeze({
|
||||
|
||||
@@ -281,6 +281,9 @@ export const DEFAULT_RUNTIME_BUNDLE = Object.freeze([
|
||||
entry("node_modules/detect-libc"),
|
||||
entry("node_modules/semver"),
|
||||
entry("node_modules/@anthropic-ai/sdk"),
|
||||
entry("node_modules/standardwebhooks"),
|
||||
entry("node_modules/@stablelib/base64"),
|
||||
entry("node_modules/fast-sha256"),
|
||||
entry(platformBinary("design/dist/design"), "core", true),
|
||||
entry("design/dist/.version", "core"),
|
||||
entry(platformBinary("make-pdf/dist/pdf"), "core", true),
|
||||
|
||||
Reference in New Issue
Block a user