mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
feat(gstack2): add unified execution result contract
This commit is contained in:
@@ -95,4 +95,23 @@ describe('gstack state external-effect CLI', () => {
|
||||
{ cwd, env, stdout: out, stderr: err },
|
||||
)).toBe(2);
|
||||
});
|
||||
|
||||
test('an empty exit-zero tool result is uncertain, never successful', async () => {
|
||||
const { cwd, env } = await fixture();
|
||||
const out = sink();
|
||||
const err = sink();
|
||||
await main(['state', 'begin', 'ship', '--run-id', 'run_empty'], { cwd, env, stdout: out, stderr: err });
|
||||
const silent = path.join(cwd, 'silent.sh');
|
||||
await fs.writeFile(silent, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
||||
const code = await main([
|
||||
'state', 'effect', 'run_empty', 'silent.tool', '--', silent,
|
||||
], { cwd, env, stdout: out, stderr: err });
|
||||
expect(code).toBe(1);
|
||||
expect(err.value()).toContain('may have occurred');
|
||||
expect(out.value()).not.toContain('"status":"success"');
|
||||
expect(await main([
|
||||
'state', 'effect', 'run_empty', 'silent.tool', '--', silent,
|
||||
], { cwd, env, stdout: out, stderr: err })).toBe(1);
|
||||
expect(err.value()).toContain('was already claimed');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
EXECUTION_RESULT_ERROR_CODES,
|
||||
executionResult,
|
||||
renderExecutionResult,
|
||||
validateExecutionResult,
|
||||
} from "../runtime/execution-result.js";
|
||||
|
||||
describe("GStack execution-result contract", () => {
|
||||
test("accepts evidenced success and preserves it in both renderings", () => {
|
||||
const result = executionResult({ status: "success", summary: "Probe completed", evidence: ["exit code 0"], data: { count: 1 } });
|
||||
expect(JSON.parse(renderExecutionResult(result, { json: true }))).toEqual(result);
|
||||
expect(renderExecutionResult(result)).toContain("SUCCESS: Probe completed");
|
||||
});
|
||||
|
||||
test("rejects empty and malformed values instead of inferring success", () => {
|
||||
expect(() => validateExecutionResult(null)).toThrow();
|
||||
expect(() => executionResult({ status: "success", summary: "Done", evidence: [], data: null }))
|
||||
.toThrow("requires evidence");
|
||||
try {
|
||||
executionResult({ status: "success", summary: "Done", evidence: [], data: null });
|
||||
} catch (error: any) {
|
||||
expect(error.code).toBe(EXECUTION_RESULT_ERROR_CODES.EMPTY);
|
||||
}
|
||||
});
|
||||
|
||||
test.each([
|
||||
["degraded", "EXECUTION_DEGRADED"],
|
||||
["unsupported", "EXECUTION_UNSUPPORTED"],
|
||||
["failed", "EXECUTION_FAILED"],
|
||||
])("keeps %s explicitly non-success", (status, code) => {
|
||||
const result = executionResult({ status, code, summary: `${status} result`, evidence: [], data: null });
|
||||
expect(renderExecutionResult(result)).toStartWith(`${status.toUpperCase()} [${code}]`);
|
||||
});
|
||||
});
|
||||
@@ -62,8 +62,11 @@ describe('GStack 2 canonical skill UX', () => {
|
||||
const runtime = fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'RUNTIME.md'), 'utf8');
|
||||
const bootstrap = fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-bootstrap.mjs'));
|
||||
const contract = JSON.parse(fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-contract.json'), 'utf8'));
|
||||
const resultContract = JSON.parse(fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'support', 'execution-result-contract.json'), 'utf8'));
|
||||
expect(bootstrap, tree).toEqual(source);
|
||||
expect(contract, tree).toEqual({ schemaVersion: 1, runtimeVersion: '2.0.0', skillApi: '2.0' });
|
||||
expect(resultContract.properties.status.enum, tree).toEqual(['success', 'degraded', 'unsupported', 'failed']);
|
||||
expect(resultContract.allOf[0].then.properties.evidence.minItems, tree).toBe(1);
|
||||
expect(runtime, tree).toContain('preview --capability <name>');
|
||||
expect(runtime, tree).toContain('It never downloads components or mutates runtime state.');
|
||||
expect(runtime, tree).toContain('install --capability <name> --yes');
|
||||
|
||||
Reference in New Issue
Block a user