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
23 changes: 13 additions & 10 deletions bin/aas.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,25 @@ export function parseJsonOutput(text, label) {
if (!trimmed) {
throw new Error(`${label} produced empty output.`);
}
let parseError;
try {
return JSON.parse(trimmed);
} catch (error) {
parseError = error;
}
const lines = trimmed.split(/\r?\n/).filter((line) => line.trim().length > 0);
for (let index = lines.length - 1; index >= 0; index -= 1) {
const line = lines[index].trim();
if (line.startsWith("{") || line.startsWith("[")) {
for (let end = lines.length; end > 0; end -= 1) {
if (!/[}\]]$/.test(lines[end - 1].trim())) continue;
for (let start = 0; start < end; start += 1) {
if (!/^[\[{]/.test(lines[start].trim())) continue;
try {
return JSON.parse(line);
return JSON.parse(lines.slice(start, end).join("\n"));
Comment on lines +124 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Truncated evaluations can authorize actions

When outer JSON is truncated, parseJsonOutput accepts a complete nested object as the entire response. A nested passed: true can permit a refund despite an unreadable policy evaluation.

Prompt for agents
Update parseJsonOutput in bin/aas.mjs so logged output extraction recognizes complete top-level JSON documents rather than trying every line-delimited substring. It must reject a valid nested object or array when its enclosing document is truncated, while still supporting pretty-printed top-level objects and arrays, leading or trailing logs, and multiple complete values with the last value selected. Add regression tests for truncated outer objects and arrays that contain valid nested values, including a nested object with passed: true.
Devin Review

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

} catch {
// Continue until the complete output is tried.
// Keep looking for the last complete JSON range.
}
}
}
try {
return JSON.parse(trimmed);
} catch (error) {
throw new Error(`${label} did not return JSON: ${error.message}`);
}
throw new Error(`${label} did not return JSON: ${parseError.message}`);
}

function pythonCandidates() {
Expand Down
15 changes: 15 additions & 0 deletions test/stack.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import test from "node:test";
import { fileURLToPath } from "node:url";
import { loadComponentLock, inspectDependencyDirectory, npmInvocation } from "../scripts/bootstrap.mjs";
import {
parseJsonOutput,
persistRunBundle,
resolveComponentProvenance,
runAct,
Expand Down Expand Up @@ -139,6 +140,20 @@ test("act rejects a zero-exit payload without a valid outcome", () => {
);
});

test("child output parser accepts logged pretty-printed JSON", () => {
assert.deepEqual(
parseJsonOutput('starting child\n{\n "ok": true,\n "result": { "count": 2 }\n}\n', "fixture"),
{ ok: true, result: { count: 2 } },
);
});

test("child output parser accepts JSON before a trailing log", () => {
assert.deepEqual(
parseJsonOutput('{"ok":true}\nchild complete\n', "fixture"),
{ ok: true },
);
});

test("pass bundle contains stage status, provenance, and only current artifacts", async () => {
const outputRoot = tempRoot();
const result = await runDemo(["--response", "pass"], stubOptions(outputRoot, { runId: "pass-run" }));
Expand Down
Loading