Skip to content

Commit 44e38d5

Browse files
authored
Distinguish diagnostics from runtime source (#1778)
1 parent acd794e commit 44e38d5

5 files changed

Lines changed: 58 additions & 3 deletions

File tree

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@ const runtimeSourcePrefix = process.env.WP_CODEBOX_RUNTIME_SOURCE_PREFIX ? resol
1515
const runtimeSourceRoots = [runtimeSourceRoot, runtimeSourcePrefix].filter(Boolean)
1616
const SOURCE_TREE = /(^|\/)(prepared-plugins|prepared-source-packages|source-package[^/]*)(\/|$)/i
1717
const SOURCE_FILE = /\.(?:php|phtml|js|mjs|cjs|jsx|ts|tsx)$/i
18-
const RUNTIME_SOURCE_CONTENT = /(?:Plugin Name:|WP_Agents_Registry|OpenAiProvider)/
18+
const PHP_OPENING_TAG = /<\?(?:php|=)(?:\s|$)/i
19+
const PHP_DECLARATION = /\b(?:namespace\s+\\?[A-Za-z_]\w*(?:\\[A-Za-z_]\w*)*|(?:abstract\s+|final\s+|readonly\s+)*(?:class|interface|trait|enum)\s+[A-Za-z_]\w*|function\s+&?\s*[A-Za-z_]\w*\s*\()/i
20+
const WORDPRESS_PLUGIN_HEADER = /\/\*[\s\S]{0,200}?\bPlugin Name\s*:/i
21+
22+
// Diagnostics commonly name runtime classes. Reject only PHP-shaped source, even
23+
// when a source file has been disguised with a reviewer-safe extension.
24+
function containsRuntimeSourceContent(text) {
25+
const hasPhpTag = PHP_OPENING_TAG.test(text)
26+
const hasDeclaration = PHP_DECLARATION.test(text)
27+
return (hasPhpTag && hasDeclaration) || (WORDPRESS_PLUGIN_HEADER.test(text) && (hasPhpTag || hasDeclaration))
28+
}
1929

2030
function redact(value) {
2131
return secretValues.reduce((output, secret) => output.split(secret).join("[REDACTED]"), value)
@@ -74,7 +84,7 @@ async function stageTextFile(source, destination, options = {}) {
7484
if (!contents || contents.includes(0) || !isUtf8(contents)) return false
7585
const text = redact(options.compactNativeInput ? compactNativeInput(contents.toString("utf8")) : sanitizeText(contents.toString("utf8")))
7686
assertNoRuntimeSourcePaths(text, runtimeSourceRoots, "Runtime source paths must never be persisted in artifact uploads.")
77-
if (RUNTIME_SOURCE_CONTENT.test(text)) throw new Error("Prepared runtime plugin source contents must never be staged for artifact upload.")
87+
if (containsRuntimeSourceContent(text)) throw new Error("Prepared runtime plugin source contents must never be staged for artifact upload.")
7888
await mkdir(resolve(destination, ".."), { recursive: true })
7989
await writeFile(destination, text)
8090
return true
@@ -154,7 +164,7 @@ async function finalScan(directory) {
154164
const bytes = await readFile(path)
155165
const text = isUtf8(bytes) ? bytes.toString("utf8") : ""
156166
assertNoRuntimeSourcePaths(text, runtimeSourceRoots, "Runtime source paths must never be persisted in artifact uploads.")
157-
if (RUNTIME_SOURCE_CONTENT.test(text)) throw new Error("Prepared runtime plugin source contents must never be persisted in artifact uploads.")
167+
if (containsRuntimeSourceContent(text)) throw new Error("Prepared runtime plugin source contents must never be persisted in artifact uploads.")
158168
} else throw new Error("Only regular files may be persisted in artifact uploads.")
159169
}
160170
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"run_id": "29307978522",
3+
"workflow": "Build With WordPress Skills Agent",
4+
"result": {
5+
"schema": "wp-codebox/agent-task-workflow-result/v1",
6+
"status": "failed",
7+
"success": false,
8+
"diagnostics": [
9+
{
10+
"message": "Unable to resolve WordPress\\OpenAiAiProvider\\Provider\\OpenAiProvider for the selected model.",
11+
"stack": "Error: provider resolution failed\n at WP_Agents_Registry::get_instance (runtime.php:42)\n at executeAgentTask (runner.php:87)"
12+
}
13+
]
14+
}
15+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
namespace Runtime\Provider;
3+
4+
final class OpenAiProvider {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
/*
3+
* Plugin Name: Disguised Runtime Provider
4+
*/
5+
function runtime_provider_bootstrap() {}

tests/runtime-sources-materialization.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ const uploadLayoutRegression = JSON.parse(await readFile(new URL("../fixtures/ag
1717
assert.equal(uploadLayoutRegression.run_id, "29306539573")
1818
assert.equal(uploadLayoutRegression.observed.upload_preparation, "failed-on-runtime-source")
1919
assert.match(uploadLayoutRegression.raw_layout.runtime_source, /prepared-plugins\/agents-api\/agents-api\.php$/)
20+
const diagnosticRegression = JSON.parse(await readFile(new URL("../fixtures/agent-task-upload-run-29307978522.json", import.meta.url), "utf8"))
21+
assert.equal(diagnosticRegression.run_id, "29307978522")
22+
assert.match(diagnosticRegression.result.diagnostics[0].message, /OpenAiProvider/)
23+
assert.match(diagnosticRegression.result.diagnostics[0].stack, /WP_Agents_Registry/)
2024
const hostedPathRegression = JSON.parse(await readFile(new URL("../fixtures/agent-task-runtime-paths-run-29305012941.json", import.meta.url), "utf8"))
2125
for (const result of [hostedPathRegression.success, hostedPathRegression.failure]) {
2226
const sanitized = sanitizeRuntimeSourceValue(result, hostedPathRegression.runtime_root)
@@ -151,6 +155,15 @@ await withTempDir("wp-codebox-runtime-source-upload-", async (directory) => {
151155
assert.doesNotMatch(stagedInput, /component_contracts|source_package_root/)
152156
const stagedResult = await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8")
153157
assert.doesNotMatch(stagedResult, /private-runtime-source/)
158+
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), `${JSON.stringify(diagnosticRegression.result, null, 2)}\n`)
159+
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } })
160+
const stagedDiagnostic = await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8")
161+
assert.match(stagedDiagnostic, /OpenAiProvider/)
162+
assert.match(stagedDiagnostic, /WP_Agents_Registry/)
163+
await writeFile(join(artifacts, "safe.json"), "Diagnostic snippet: <?php OpenAiProvider function was unavailable in WP_Agents_Registry.")
164+
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "safe.json" } }] }))
165+
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } })
166+
assert.match(await readFile(join(upload, ".codebox", "agent-task-artifacts", "safe.json"), "utf8"), /OpenAiProvider/)
154167
await writeFile(join(artifacts, "leak.json"), `runtime log ${privateRoot}/source.php`)
155168
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ status: "failed", success: false, typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "leak.json" } }] }))
156169
await execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } })
@@ -167,7 +180,15 @@ await withTempDir("wp-codebox-runtime-source-upload-", async (directory) => {
167180
assert.doesNotMatch(exclusionManifest, /prepared-plugins|agents-api|private-runtime-source/)
168181
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "prepared-plugins/agents-api/agents-api.php" } }] }))
169182
await assert.rejects(execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } }), /Declared reviewer artifacts/)
183+
assert.ok(await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8"), "Declared artifact rejection preserves the normalized control result")
170184
await rm(join(artifacts, "prepared-plugins"), { recursive: true, force: true })
185+
for (const path of ["runtime-source-disguised.json", "runtime-source-disguised.txt"]) {
186+
await writeFile(join(artifacts, path), await readFile(new URL(`../fixtures/agent-task-upload/${path}`, import.meta.url), "utf8"))
187+
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path } }] }))
188+
await assert.rejects(execFileAsync(process.execPath, [script.pathname], { env: { ...process.env, AGENT_TASK_WORKSPACE: workspace, AGENT_TASK_UPLOAD_PATH: upload, WP_CODEBOX_RUNTIME_SOURCE_ROOT: privateRoot } }), /source contents/)
189+
assert.ok(await readFile(join(upload, ".codebox", "agent-task-workflow-result.json"), "utf8"), "Disguised source rejection preserves the normalized control result")
190+
await rm(join(artifacts, path))
191+
}
171192
await mkdir(suffixedPrivateRoot, { recursive: true })
172193
await writeFile(join(artifacts, "suffixed-root-leak.json"), suffixedPrivateRoot)
173194
await writeFile(join(workspace, ".codebox", "agent-task-workflow-result.json"), JSON.stringify({ typed_artifacts: [{ name: "reviewer-report", type: "report", artifact: { path: "suffixed-root-leak.json" } }] }))

0 commit comments

Comments
 (0)