Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion bin/aas-gui.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ function requestBoundaryFailure(request) {
const hostHeaders = request.rawHeaders.filter((value, index) => index % 2 === 0 && value.toLowerCase() === "host");
if (hostHeaders.length !== 1 || request.headers.host !== expectedHost) return "host";
const origin = request.headers.origin;
// Distinguish a missing-Origin POST (caller forgot to identify itself) from
// a wrong-Origin POST (caller is some other origin). The first is a 400
// ("you forgot to send Origin"), the second is a 403 ("Origin does not
// match this server"). Both still return 403 today; the missing-Origin
// case is the surprising one for programmatic local clients.
if (request.method === "POST" && origin === undefined) return "missing-origin";
if (request.method === "POST" && origin !== `http://${expectedHost}`) return "origin";
if (origin !== undefined && origin !== `http://${expectedHost}`) return "origin";
return null;
Expand Down Expand Up @@ -90,7 +96,12 @@ export function createGuiServer({
return createServer(async (request, response) => {
const url = new URL(request.url ?? "/", "http://127.0.0.1");
try {
if (requestBoundaryFailure(request) !== null) {
const boundary = requestBoundaryFailure(request);
if (boundary === "missing-origin") {
sendJson(response, 400, { error: "Origin header required for POST" });
return;
}
if (boundary !== null) {
sendJson(response, 403, { error: "Forbidden" });
return;
}
Expand Down
9 changes: 8 additions & 1 deletion bin/aas.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,15 @@ export function runAct(
const result = runner(process.execPath, [crctl, ...args], {
cwd: join(depsDir, "consequence-rail"),
});
if (result.error || result.status !== 0) throw childProcessError("act", result);
if (result.error) throw childProcessError("act", result);
const payload = parseStageJson("act", result);
if (result.status !== 0) {
// A nonzero exit with parseable JSON is an unsuccessful act (the CLI
// surfaces structured errors as JSON on stdout), not a child-process
// error. Mirror runProve so persistRunBundle and printHuman see the
// structured failure and the GUI can render the stage artifact.
return { ok: false, raw: payload, status: result.status, ...failedStderr(result) };
Comment on lines +525 to +530

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Failed actions become successful runs

When the action child exits nonzero with JSON, runAct returns ok: false, but the orchestrator ignores it. The run records success and can exit zero.

Prompt for agents
Update bin/aas.mjs so runDemo handles a false runAct result as a failed action, preserving raw output and stderr in the stage artifact and report, setting exitCode to 1, and preventing a later successful proof from converting the overall run to success. Add stack tests using the real runAct path with a nonzero child result containing parseable JSON, and assert failed action status, nonzero run exit, preserved artifact, and appropriate proof handling.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
if (!payload || typeof payload !== "object" || Array.isArray(payload)
|| ![null, "settled", "compensated", "disputed"].includes(payload.outcome)) {
throw attachChildDiagnostics(new Error("act did not return a valid outcome"), {
Expand Down
3 changes: 2 additions & 1 deletion test/gui.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ test("GUI rejects rebinding requests, cross-origin runs, unsafe options, and uns
});
assert.equal(foreignOrigin.status, 403);
const missingOrigin = await requestServer(server, "/api/run", { method: "POST" });
assert.equal(missingOrigin.status, 403);
assert.equal(missingOrigin.status, 400);
assert.equal(JSON.parse(missingOrigin.body).error, "Origin header required for POST");
const getRun = await requestServer(server, "/api/run?response=pass&fault=none");
assert.equal(getRun.status, 404);
const unsafeOption = await requestServer(server, "/api/run?response=..%2Fsecret&fault=none", {
Expand Down
Loading