Provide verified outcomes to task graph reviewers

Materialize audience-approved text artifacts and evidence summaries with bounded, redacted content so isolated reviews can inspect their supporting outcomes.

Checks: task graph and human gate recovery tests; workspace reviewer integration test.
This commit is contained in:
Ryder Sun committed 2026-11-07 18:40:55 +08:00
1 parent 2cb640ca99
commit 4a9fa31812
2 files changed
+58

No files matched your search

@@ -167,6 +167,35 @@ const taskGraphMethods = {
].join('\n')
},
taskGraphReviewOutcomeText(entries) {
const lines = []
let remaining = 24000
for (const entry of entries) {
if (!['artifact-ref', 'evidence-ref'].includes(entry.entryType)) continue
const id = entry.refs?.[0]
let label
let raw
if (entry.entryType === 'artifact-ref') {
const identity = this.v4ArtifactIdentity(id)
label = `Artifact ${id}`
raw = identity.artifact.contentRef?.mediaType === 'text/plain'
? identity.content : '[Non-text artifact; content unavailable in this handoff.]'
} else {
const evidence = this.v4EvidenceRecord(id)
label = `Evidence ${id} (${evidence.kind}, ${evidence.level})`
raw = evidence.summary
}
const limit = Math.max(0, Math.min(6000, remaining))
if (!limit) { lines.push('[Additional outcome content truncated.]'); break }
const content = publicCollaborationText(raw || '', limit + 1)
const truncated = content.length > limit
const shown = content.slice(0, limit)
lines.push(`${label}: ${shown}${truncated ? ' [Content truncated.]' : ''}`)
remaining -= shown.length
}
return lines.length ? `Verified outcome content:\n${lines.join('\n')}\nDo not assume truncated or unavailable content was reviewed.` : ''
},
checkpointTaskGraph(group, controller, cursorInput, options = {}) {
let taskGraph = parseTaskGraphCursor(cursorInput)
taskGraph = parseTaskGraphCursor({
@@ -453,6 +482,10 @@ const taskGraphMethods = {
throw new Error('LOCAL_WORKFLOW_INPUT_UNAVAILABLE')
}
const isolated = ['reviewer', 'arbiter'].includes(node.role)
if (isolated) {
const outcomes = this.taskGraphReviewOutcomeText(collaborationPackage.entries)
if (outcomes) collaborationPackage.text += `\n${outcomes}`
}
const attachments = node.role === 'primary'
&& !controller.orchestration.attachmentRecipients.includes(node.agentKind)
? context.rootAttachments.map(attachment => attachment.path)
@@ -342,6 +342,29 @@ test('task graph includes outcome references for both sides of a conflict', () =
for (const ref of refs) assert.ok(handoff.text.includes(ref))
})
test('task graph review materializes bounded verified outcome content without exposing secrets', () => {
const runner = { ...taskGraphMethods,
v4ArtifactIdentity: id => {
assert.equal(id, 'artifact-one')
return { artifact: { contentRef: { mediaType: 'text/plain' } }, content: `Result sk-test-12345678901234567890 ${'x'.repeat(7000)}` }
},
v4EvidenceRecord: id => {
assert.equal(id, 'evidence-one')
return { kind: 'test-result', level: 'observed', summary: 'Verification succeeded' }
},
}
const text = runner.taskGraphReviewOutcomeText([
{ entryType: 'artifact-ref', refs: ['artifact-one'] },
{ entryType: 'evidence-ref', refs: ['evidence-one'] },
])
assert.match(text, /Result/)
assert.match(text, /Verification succeeded/)
assert.match(text, /truncated/i)
assert.doesNotMatch(text, /sk-test-12345678901234567890/)
assert.ok(text.length < 10000)
assert.throws(() => runner.taskGraphReviewOutcomeText([{ entryType: 'artifact-ref', refs: ['missing'] }]))
})
test('task graph cannot accept an empty arbitration while conflicts remain', () => {
const node = graphNode({ role: 'arbiter', agentKind: 'codex', terminal: true,
acceptance: { requireConclusion: false, minArtifactRefs: 0, minEvidenceRefs: 0 } })
@@ -1381,6 +1404,8 @@ test('automatic Primary Reviewer Arbiter flow uses selective typed collaboration
assert.match(calls[0].prompt, /Role: primary/)
assert.match(calls[0].prompt, /Selected blackboard entries: \(none\)/)
assert.match(calls[1].prompt, /Role: reviewer/)
assert.match(calls[1].prompt, /Verified outcome content:/)
assert.match(calls[1].prompt, /Evidence .*\(.*\)/)
assert.match(calls[1].prompt, /The release is ready after tests\./)
assert.match(calls[2].prompt, /Role: arbiter/)
assert.match(calls[2].prompt, /\[conflict\]/)