fix(P01): add Zod BackendResult validation and fix opencode silent success
---ci---
project: ci
phase: 1
milestone: v0.8
status: in_progress
decisions:
- id: D-022
decision: Validate BackendResult at boundary with Zod schema
rationale: External backend output is untrusted; runtime validation prevents corrupt commit streams
confidence: 0.92
- id: D-023
decision: opencode parseResult returns success:false on malformed JSON
rationale: Silent success:true on parse failure masks backend errors; fail loudly instead
confidence: 0.95
requirements:
covered: [FIX-02, FIX-03]
---/ci---
FIX-02: Add Zod BackendResultSchema and validateBackendResult() in
backends/types.ts. backendResultToAgentResult() in base.ts now validates
before passing through. Invalid results produce success:false with error
detail. Path traversal protection: artifact paths with '..' or leading '/'
are rejected.
FIX-03: opencode.ts parseResult() no longer defaults to success:true when
JSON parsing fails entirely. Both the inner parse error and the no-JSON
match case now return emptyBackendResult() with descriptive error messages.
This commit is contained in:
+12
-14
@@ -117,8 +117,14 @@ export class OpencodeBackend implements IntelligenceBackend {
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonMatch[0]);
|
||||
if (typeof parsed.success !== "boolean") {
|
||||
return emptyBackendResult(`Backend returned non-boolean success field: ${typeof parsed.success}`);
|
||||
}
|
||||
if (parsed.success === false && !parsed.error && !parsed.output) {
|
||||
return emptyBackendResult("Backend returned failure with no error or output");
|
||||
}
|
||||
return {
|
||||
success: parsed.success ?? true,
|
||||
success: parsed.success,
|
||||
output: parsed.output || output,
|
||||
artifacts: Array.isArray(parsed.artifacts)
|
||||
? parsed.artifacts.filter((a: unknown) => !!a).map((a: Record<string, unknown>) => ({
|
||||
@@ -156,7 +162,7 @@ export class OpencodeBackend implements IntelligenceBackend {
|
||||
options: Array.isArray(e.options) ? e.options : [],
|
||||
default_option_id: String(e.default_option_id || ""),
|
||||
resolution: (e.resolution as "approved" | "rejected" | "modified" | "pending" | "timeout_auto_proceed") || "pending",
|
||||
audit_file: String(e.audit_file || ""),
|
||||
commit_hash: String(e.commit_hash || ""),
|
||||
}))
|
||||
: [],
|
||||
usage: parsed.usage || {
|
||||
@@ -164,19 +170,11 @@ export class OpencodeBackend implements IntelligenceBackend {
|
||||
total_tokens: Math.ceil(output.length / 4),
|
||||
},
|
||||
};
|
||||
} catch {}
|
||||
} catch {
|
||||
return emptyBackendResult(`Backend output contained JSON-like structure but failed to parse: ${output.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output,
|
||||
artifacts: [],
|
||||
decisions: [],
|
||||
escalations: [],
|
||||
usage: {
|
||||
...emptyTokenUsage(),
|
||||
total_tokens: Math.ceil(output.length / 4),
|
||||
},
|
||||
};
|
||||
return emptyBackendResult(`Backend output did not contain valid JSON result: ${output.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user