Skip to content

Commit 5eed49f

Browse files
authored
Project reviewer-safe workflow results (#1815)
1 parent 55782bf commit 5eed49f

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

.github/scripts/run-agent-task/prepare-agent-task-upload.mjs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const MAX_UPLOAD_FILE_BYTES = 4 * 1024 * 1024
99
const MAX_TRANSCRIPT_EXECUTIONS = 64
1010
const MAX_REVIEW_TEXT_BYTES = 32 * 1024
1111
const MAX_TOOL_ARGUMENT_KEYS = 32
12+
const MAX_REVIEW_COLLECTION_ENTRIES = 64
13+
const MAX_REVIEW_VALUE_DEPTH = 8
1214
const workspace = resolve(process.env.AGENT_TASK_WORKSPACE || process.cwd())
1315
const uploadPath = resolve(process.env.AGENT_TASK_UPLOAD_PATH || join(workspace, ".codebox", "agent-task-upload"))
1416
const requestPath = resolve(process.env.AGENT_TASK_REQUEST_PATH || join(workspace, ".codebox", "agent-task-request.json"))
@@ -136,7 +138,10 @@ async function stageTextFile(source, destination, options = {}) {
136138
const contents = openedMetadata.isFile() && openedMetadata.size <= MAX_UPLOAD_FILE_BYTES ? await handle.readFile() : null
137139
await handle.close()
138140
if (!contents || contents.includes(0) || !isUtf8(contents)) return false
139-
const sanitized = options.compactNativeInput ? compactNativeInput(contents.toString("utf8")) : sanitizeText(contents.toString("utf8"))
141+
const sourceText = contents.toString("utf8")
142+
const sanitized = options.projectWorkflowResult
143+
? `${JSON.stringify(projectWorkflowResult(parseJsonOrEmpty(sourceText)), null, 2)}\n`
144+
: options.compactNativeInput ? compactNativeInput(sourceText) : sanitizeText(sourceText)
140145
const text = redact(sanitizeSeedSnapshotJson(sanitized))
141146
assertNoRuntimeSourcePaths(text, privateUploadRoots, "Runtime source or workspace paths must never be persisted in artifact uploads.")
142147
assertNoSeedSnapshotPaths(text)
@@ -173,6 +178,51 @@ function boundedText(value) {
173178
return containsRuntimeSourceContent(text) ? "[redacted-source-content]" : text
174179
}
175180

181+
function projectReviewValue(value, depth = 0) {
182+
if (depth >= MAX_REVIEW_VALUE_DEPTH) return undefined
183+
if (typeof value === "string") return boundedText(value)
184+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value
185+
if (Array.isArray(value)) return value.slice(0, MAX_REVIEW_COLLECTION_ENTRIES).flatMap((entry) => {
186+
const projected = projectReviewValue(entry, depth + 1)
187+
return projected === undefined ? [] : [projected]
188+
})
189+
return Object.fromEntries(Object.entries(record(value)).slice(0, MAX_REVIEW_COLLECTION_ENTRIES).flatMap(([key, entry]) => {
190+
const projectedKey = boundedText(key)
191+
const projected = projectReviewValue(entry, depth + 1)
192+
return projectedKey === undefined || projected === undefined ? [] : [[projectedKey, projected]]
193+
}))
194+
}
195+
196+
function projectWorkflowResult(value) {
197+
const result = record(value)
198+
const execution = record(result.execution)
199+
const outputs = record(result.outputs)
200+
const reviewerEvidence = record(result.reviewer_evidence)
201+
const verification = Array.isArray(result.verification) ? result.verification.slice(0, MAX_REVIEW_COLLECTION_ENTRIES).map((value) => {
202+
const check = record(value)
203+
return projectReviewValue(Object.fromEntries(["kind", "command", "description", "success", "exit_code", "stdout_truncated", "stderr_truncated"].flatMap((key) => check[key] === undefined ? [] : [[key, check[key]]])))
204+
}) : undefined
205+
return projectReviewValue(Object.fromEntries(Object.entries({
206+
schema: result.schema,
207+
run_id: result.run_id,
208+
status: result.status,
209+
success: result.success,
210+
request_path: result.request_path,
211+
execution: Object.keys(execution).length ? { stdout_truncated: execution.stdout_truncated, stderr_truncated: execution.stderr_truncated } : undefined,
212+
reviewer_evidence: reviewerEvidence.transcript === undefined ? undefined : { transcript: reviewerEvidence.transcript },
213+
verification,
214+
publication: result.publication,
215+
transcript: result.transcript,
216+
artifacts: result.artifacts,
217+
outputs: outputs.projections === undefined ? undefined : { projections: outputs.projections },
218+
access: result.access,
219+
publication_verification: result.publication_verification,
220+
publication_error: result.publication_error,
221+
failure: result.failure,
222+
projection_error: result.projection_error,
223+
}).filter(([, entry]) => entry !== undefined)))
224+
}
225+
176226
function safeTargetPath(value) {
177227
const path = safeRelativeArtifactPath(value)
178228
return path ? `workspace/${path}` : undefined
@@ -417,7 +467,7 @@ const declaredPaths = new Set(declaredArtifactPaths(result, declarations(request
417467
await rm(uploadPath, { recursive: true, force: true })
418468
await mkdir(uploadPath, { recursive: true })
419469
await stageTextFile(requestPath, join(uploadPath, ".codebox", "agent-task-request.json"))
420-
await stageTextFile(resultSource, join(uploadPath, ".codebox", "agent-task-workflow-result.json"))
470+
await stageTextFile(resultSource, join(uploadPath, ".codebox", "agent-task-workflow-result.json"), { projectWorkflowResult: true })
421471
await stageTextFile(join(workspace, ".codebox", "native-agent-task-input.json"), join(uploadPath, ".codebox", "native-agent-task-input.json"), { compactNativeInput: true })
422472
for (const path of declaredPaths) {
423473
const source = resolve(artifactsPath, path)

tests/runtime-sources-materialization.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ await withTempDir("wp-codebox-runtime-source-upload-", async (directory) => {
159159
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), `${JSON.stringify(diagnosticRegression.result, null, 2)}\n`)
160160
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } })
161161
const stagedDiagnostic = await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8")
162-
assert.match(stagedDiagnostic, /OpenAiProvider/)
163-
assert.match(stagedDiagnostic, /WP_Agents_Registry/)
162+
assert.doesNotMatch(stagedDiagnostic, /OpenAiProvider|WP_Agents_Registry/, "raw runtime diagnostics are not part of the reviewer-safe result")
164163
await writeFile(join(artifacts, "safe.json"), "Diagnostic snippet: <?php OpenAiProvider function was unavailable in WP_Agents_Registry.")
165164
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "safe.json" } }] }))
166165
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } })
@@ -170,7 +169,8 @@ await withTempDir("wp-codebox-runtime-source-upload-", async (directory) => {
170169
await writeFile(join(artifacts, "files", "transcript.json"), transcriptSource)
171170
const transcriptDigest = createHash("sha256").update(transcriptSource).digest("hex")
172171
const reviewerEvidence = { transcript: { schema: "wp-codebox/agent-transcript/v1", kind: "codebox-transcript", path: "files/transcript.json", source_sha256: transcriptDigest, size_bytes: Buffer.byteLength(transcriptSource) } }
173-
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ reviewer_evidence: reviewerEvidence, runtime_result: { agent_task_run_result: { refs: { transcripts: [{ kind: "codebox-transcript", path: "files/transcript.json", sha256: transcriptDigest }] } } }, typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "prepared-plugins/agents-api/agents-api.php" } }] }))
172+
const rawWorkflowPayload = "raw-provider-payload-sentinel"
173+
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ schema: "wp-codebox/agent-task-workflow-result/v1", status: "succeeded", success: true, reviewer_evidence: reviewerEvidence, runtime_result: { agent_task_run_result: { refs: { transcripts: [{ kind: "codebox-transcript", path: "files/transcript.json", sha256: transcriptDigest }] }, model_output: `Target code: <?php final class Runtime_Result_Leak {} ${rawWorkflowPayload}`, tool_call: { arguments: { content: rawWorkflowPayload }, result: { body: rawWorkflowPayload }, error: { message: rawWorkflowPayload } } } }, outputs: { engine_data: { provider_response: `<?php final class Engine_Data_Leak {} ${rawWorkflowPayload}`, private_path: `${privateRoot}/source.php` }, projections: { pr_url: "https://github.com/example/repository/pull/1" } }, publication: { schema: "wp-codebox/runner-workspace-publication-result/v1", success: true, status: "published", pull_request: { number: 1, url: "https://github.com/example/repository/pull/1" } }, typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "prepared-plugins/agents-api/agents-api.php" } }] }))
174174
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot, OPENAI_API_KEY: "secret-transcript-value" } })
175175
const transcript = await readFile(join(upload, ".codebox", "agent-task-artifacts", "transcript.json"), "utf8")
176176
assert.doesNotMatch(transcript, /<\?php final class Target_Plugin/)
@@ -186,7 +186,12 @@ await withTempDir("wp-codebox-runtime-source-upload-", async (directory) => {
186186
assert.doesNotMatch(transcript, /raw-error|"args"|"provider"|"hash"/)
187187
assert.doesNotMatch(transcript, /private-runtime-source|secret-transcript-value|untrusted secret sentinel|\/Users\/example/)
188188
const stagedWorkflowResult = await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8")
189+
assert.match(stagedWorkflowResult, /"status": "succeeded"/)
190+
assert.match(stagedWorkflowResult, /"publication"/)
191+
assert.match(stagedWorkflowResult, /"pr_url": "https:\/\/github\.com\/example\/repository\/pull\/1"/)
192+
assert.match(stagedWorkflowResult, /"reviewer_evidence"/)
189193
assert.match(stagedWorkflowResult, /"projection_sha256": "[a-f0-9]{64}"/, "the staged result records the digest of the sanitized transcript projection")
194+
assert.doesNotMatch(stagedWorkflowResult, /runtime_result|engine_data|Runtime_Result_Leak|Engine_Data_Leak|raw-provider-payload-sentinel|private-runtime-source/)
190195
const transcriptExclusions = await readFile(join(upload, ".codebox", "agent-task-artifacts", "exclusions.json"), "utf8")
191196
assert.match(transcriptExclusions, /canonical_transcripts/)
192197
assert.match(transcriptExclusions, /codebox-transcript/)

0 commit comments

Comments
 (0)