From a045c2551f43a46092b7deee1970c95ddea805f4 Mon Sep 17 00:00:00 2001 From: Dukeabadoon Date: Wed, 12 Aug 2026 13:48:55 +0800 Subject: [PATCH 1/3] refactor(core): extract evidence validator (phase 3a) Move direct-evidence path checks and capability-to-kind mapping into controller/evidence-validator.ts so controller.ts stays a facade. --- packages/core/src/controller.ts | 112 +--------------- .../src/controller/evidence-validator.test.ts | 126 ++++++++++++++++++ .../core/src/controller/evidence-validator.ts | 108 +++++++++++++++ 3 files changed, 241 insertions(+), 105 deletions(-) create mode 100644 packages/core/src/controller/evidence-validator.test.ts create mode 100644 packages/core/src/controller/evidence-validator.ts diff --git a/packages/core/src/controller.ts b/packages/core/src/controller.ts index e7a86ff..1e4b547 100644 --- a/packages/core/src/controller.ts +++ b/packages/core/src/controller.ts @@ -47,6 +47,13 @@ import type { StructuredPermissionSet, TracePermissionDecision, } from "./types.js"; +import { + directEvidenceArtifactTypes, + directEvidenceTraceEventTypes, + evidenceKindsForCapability, + isUserReportedEvidencePath, + requiresDirectEvidence, +} from "./controller/evidence-validator.js"; const artifactInputsByPhase: Record> = { context_grounding: new Set([ @@ -605,67 +612,6 @@ const potentiallyMutatingPlanCapabilities = new Set([ "subagent.spawn", ]); -function requiresDirectEvidence( - path: string, - submissionType?: string, -): boolean { - return ( - (submissionType === "WorkResult" && - (/^body\.evidenceRefs\[\d+\]$/u.test(path) || - /^body\.actions\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path))) || - /\.claimEvidenceMatrix\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.completionClaims\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.observations\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.inferences\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.toolEventRefs\[\d+\]$/u.test(path) || - /\.verificationRefs\[\d+\]$/u.test(path) || - /\.diagnosisGate\.directEvidenceRefs\[\d+\]$/u.test(path) || - /\.acceptanceCoverage\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.acceptanceResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.filesChanged\[\d+\]\.diffRef$/u.test(path) || - /\.testResults\[\d+\]\.(?:commandRef|outputRef)$/u.test(path) || - /\.verificationResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || - /\.(?:ruleCompliance|regressionChecks|userFidelity)\[\d+\]\.evidenceRefs\[\d+\]$/u.test( - path, - ) || - (submissionType === "QualityReview" && - /\.hardGates\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path)) - ); -} - -const directEvidenceArtifactTypes = new Set([ - "Evidence", - "ContextDocument", - "TraceEvent", -]); - -const directEvidenceTraceEventTypes = new Set(); - -function evidenceKindsForCapability(capability: string): ReadonlySet { - switch (capability) { - case "repository.read": - return new Set(["repository", "diff", "tool_output"]); - case "repository.write": - case "repository.delete": - return new Set(["diff"]); - case "shell.inspect": - case "shell.execute": - return new Set(["tool_output", "log", "test"]); - case "runtime.inspect": - case "runtime.restart": - return new Set(["runtime", "log", "tool_output"]); - case "browser.inspect": - case "browser.mutate": - return new Set(["browser"]); - case "network.read": - case "external.write": - case "subagent.spawn": - return new Set(["tool_output"]); - default: - return new Set(); - } -} - class PermissionAuthorizationError extends Error { constructor( message: string, @@ -713,50 +659,6 @@ function terminalField(path: string): string { return /\.([A-Za-z][A-Za-z0-9]*)(?:\[\d+\])?$/u.exec(path)?.[1] ?? ""; } -function isUserReportedEvidencePath( - submission: ArtifactSubmission, - path: string, -): boolean { - if (submission.type === "ReleaseAudit") { - const match = - /^body\.claimEvidenceMatrix\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); - const matrix = - typeof submission.body === "object" && - submission.body !== null && - "claimEvidenceMatrix" in submission.body - ? (submission.body as { claimEvidenceMatrix?: unknown }) - .claimEvidenceMatrix - : null; - return ( - match !== null && - Array.isArray(matrix) && - typeof matrix[Number(match[1])] === "object" && - matrix[Number(match[1])] !== null && - (matrix[Number(match[1])] as { basis?: unknown }).basis === - "user_reported" - ); - } - if (submission.type === "UserReport") { - const match = - /^body\.completionClaims\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); - const claims = - typeof submission.body === "object" && - submission.body !== null && - "completionClaims" in submission.body - ? (submission.body as { completionClaims?: unknown }).completionClaims - : null; - return ( - match !== null && - Array.isArray(claims) && - typeof claims[Number(match[1])] === "object" && - claims[Number(match[1])] !== null && - (claims[Number(match[1])] as { status?: unknown }).status === - "user_reported" - ); - } - return false; -} - function equalStringSets( left: readonly string[], right: readonly string[], diff --git a/packages/core/src/controller/evidence-validator.test.ts b/packages/core/src/controller/evidence-validator.test.ts new file mode 100644 index 0000000..df92b32 --- /dev/null +++ b/packages/core/src/controller/evidence-validator.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from "vitest"; + +import type { ArtifactSubmission } from "../types.js"; +import { + directEvidenceArtifactTypes, + directEvidenceTraceEventTypes, + evidenceKindsForCapability, + isUserReportedEvidencePath, + requiresDirectEvidence, +} from "./evidence-validator.js"; + +describe("requiresDirectEvidence", () => { + it("matches WorkResult evidence reference paths", () => { + expect(requiresDirectEvidence("body.evidenceRefs[0]", "WorkResult")).toBe( + true, + ); + expect( + requiresDirectEvidence("body.actions[2].evidenceRefs[1]", "WorkResult"), + ).toBe(true); + expect(requiresDirectEvidence("body.observations[0].evidenceRefs[0]")).toBe( + true, + ); + expect(requiresDirectEvidence("body.filesChanged[0].diffRef")).toBe(true); + expect(requiresDirectEvidence("body.testResults[0].commandRef")).toBe(true); + }); + + it("matches QualityReview hard gate evidence paths", () => { + expect( + requiresDirectEvidence( + "body.hardGates[0].evidenceRefs[0]", + "QualityReview", + ), + ).toBe(true); + }); + + it("rejects non-evidence paths", () => { + expect(requiresDirectEvidence("body.summary")).toBe(false); + expect(requiresDirectEvidence("body.evidenceRefs[0]", "WorkPlan")).toBe( + false, + ); + }); +}); + +describe("directEvidenceArtifactTypes", () => { + it("includes expected artifact types", () => { + expect(directEvidenceArtifactTypes).toEqual( + new Set(["Evidence", "ContextDocument", "TraceEvent"]), + ); + }); +}); + +describe("directEvidenceTraceEventTypes", () => { + it("starts empty until trace event types are registered", () => { + expect(directEvidenceTraceEventTypes.size).toBe(0); + }); +}); + +describe("evidenceKindsForCapability", () => { + it("maps repository.read to repository, diff, and tool_output", () => { + expect(evidenceKindsForCapability("repository.read")).toEqual( + new Set(["repository", "diff", "tool_output"]), + ); + }); + + it("maps shell capabilities to tool_output, log, and test", () => { + expect(evidenceKindsForCapability("shell.execute")).toEqual( + new Set(["tool_output", "log", "test"]), + ); + }); + + it("returns an empty set for unknown capabilities", () => { + expect(evidenceKindsForCapability("unknown.capability")).toEqual(new Set()); + }); +}); + +describe("isUserReportedEvidencePath", () => { + it("detects user-reported ReleaseAudit claim evidence", () => { + const submission: ArtifactSubmission = { + runId: "run-1", + type: "ReleaseAudit", + body: { + claimEvidenceMatrix: [ + { basis: "user_reported", evidenceRefs: ["artifact://run-1/msg-1"] }, + ], + }, + }; + expect( + isUserReportedEvidencePath( + submission, + "body.claimEvidenceMatrix[0].evidenceRefs[0]", + ), + ).toBe(true); + }); + + it("detects user-reported UserReport completion claims", () => { + const submission: ArtifactSubmission = { + runId: "run-1", + type: "UserReport", + body: { + completionClaims: [ + { + status: "user_reported", + evidenceRefs: ["artifact://run-1/msg-1"], + }, + ], + }, + }; + expect( + isUserReportedEvidencePath( + submission, + "body.completionClaims[0].evidenceRefs[0]", + ), + ).toBe(true); + }); + + it("returns false for other artifact types and paths", () => { + const submission: ArtifactSubmission = { + runId: "run-1", + type: "WorkResult", + body: { evidenceRefs: ["artifact://run-1/ev-1"] }, + }; + expect(isUserReportedEvidencePath(submission, "body.evidenceRefs[0]")).toBe( + false, + ); + }); +}); diff --git a/packages/core/src/controller/evidence-validator.ts b/packages/core/src/controller/evidence-validator.ts new file mode 100644 index 0000000..d47e3c2 --- /dev/null +++ b/packages/core/src/controller/evidence-validator.ts @@ -0,0 +1,108 @@ +import type { ArtifactSubmission } from "../types.js"; + +export function requiresDirectEvidence( + path: string, + submissionType?: string, +): boolean { + return ( + (submissionType === "WorkResult" && + (/^body\.evidenceRefs\[\d+\]$/u.test(path) || + /^body\.actions\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path))) || + /\.claimEvidenceMatrix\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.completionClaims\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.observations\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.inferences\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.toolEventRefs\[\d+\]$/u.test(path) || + /\.verificationRefs\[\d+\]$/u.test(path) || + /\.diagnosisGate\.directEvidenceRefs\[\d+\]$/u.test(path) || + /\.acceptanceCoverage\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.acceptanceResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.filesChanged\[\d+\]\.diffRef$/u.test(path) || + /\.testResults\[\d+\]\.(?:commandRef|outputRef)$/u.test(path) || + /\.verificationResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || + /\.(?:ruleCompliance|regressionChecks|userFidelity)\[\d+\]\.evidenceRefs\[\d+\]$/u.test( + path, + ) || + (submissionType === "QualityReview" && + /\.hardGates\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path)) + ); +} + +export const directEvidenceArtifactTypes = new Set([ + "Evidence", + "ContextDocument", + "TraceEvent", +]); + +export const directEvidenceTraceEventTypes = new Set(); + +export function evidenceKindsForCapability( + capability: string, +): ReadonlySet { + switch (capability) { + case "repository.read": + return new Set(["repository", "diff", "tool_output"]); + case "repository.write": + case "repository.delete": + return new Set(["diff"]); + case "shell.inspect": + case "shell.execute": + return new Set(["tool_output", "log", "test"]); + case "runtime.inspect": + case "runtime.restart": + return new Set(["runtime", "log", "tool_output"]); + case "browser.inspect": + case "browser.mutate": + return new Set(["browser"]); + case "network.read": + case "external.write": + case "subagent.spawn": + return new Set(["tool_output"]); + default: + return new Set(); + } +} + +export function isUserReportedEvidencePath( + submission: ArtifactSubmission, + path: string, +): boolean { + if (submission.type === "ReleaseAudit") { + const match = + /^body\.claimEvidenceMatrix\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); + const matrix = + typeof submission.body === "object" && + submission.body !== null && + "claimEvidenceMatrix" in submission.body + ? (submission.body as { claimEvidenceMatrix?: unknown }) + .claimEvidenceMatrix + : null; + return ( + match !== null && + Array.isArray(matrix) && + typeof matrix[Number(match[1])] === "object" && + matrix[Number(match[1])] !== null && + (matrix[Number(match[1])] as { basis?: unknown }).basis === + "user_reported" + ); + } + if (submission.type === "UserReport") { + const match = + /^body\.completionClaims\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); + const claims = + typeof submission.body === "object" && + submission.body !== null && + "completionClaims" in submission.body + ? (submission.body as { completionClaims?: unknown }).completionClaims + : null; + return ( + match !== null && + Array.isArray(claims) && + typeof claims[Number(match[1])] === "object" && + claims[Number(match[1])] !== null && + (claims[Number(match[1])] as { status?: unknown }).status === + "user_reported" + ); + } + return false; +} From f8db02a756eb9b4473409a5c8f2bb89f7ff734e5 Mon Sep 17 00:00:00 2001 From: Dukeabadoon Date: Wed, 12 Aug 2026 13:52:13 +0800 Subject: [PATCH 2/3] fix(core): complete ArtifactSubmission stubs in evidence-validator tests --- .../src/controller/evidence-validator.test.ts | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/core/src/controller/evidence-validator.test.ts b/packages/core/src/controller/evidence-validator.test.ts index df92b32..5665c6f 100644 --- a/packages/core/src/controller/evidence-validator.test.ts +++ b/packages/core/src/controller/evidence-validator.test.ts @@ -9,6 +9,17 @@ import { requiresDirectEvidence, } from "./evidence-validator.js"; +function artifact(type: string, body: unknown = {}): ArtifactSubmission { + return { + id: `artifact-${type}`, + runId: "run-1", + type, + schemaVersion: "1.0", + producer: "test", + body, + }; +} + describe("requiresDirectEvidence", () => { it("matches WorkResult evidence reference paths", () => { expect(requiresDirectEvidence("body.evidenceRefs[0]", "WorkResult")).toBe( @@ -75,15 +86,11 @@ describe("evidenceKindsForCapability", () => { describe("isUserReportedEvidencePath", () => { it("detects user-reported ReleaseAudit claim evidence", () => { - const submission: ArtifactSubmission = { - runId: "run-1", - type: "ReleaseAudit", - body: { - claimEvidenceMatrix: [ - { basis: "user_reported", evidenceRefs: ["artifact://run-1/msg-1"] }, - ], - }, - }; + const submission = artifact("ReleaseAudit", { + claimEvidenceMatrix: [ + { basis: "user_reported", evidenceRefs: ["artifact://run-1/msg-1"] }, + ], + }); expect( isUserReportedEvidencePath( submission, @@ -93,18 +100,14 @@ describe("isUserReportedEvidencePath", () => { }); it("detects user-reported UserReport completion claims", () => { - const submission: ArtifactSubmission = { - runId: "run-1", - type: "UserReport", - body: { - completionClaims: [ - { - status: "user_reported", - evidenceRefs: ["artifact://run-1/msg-1"], - }, - ], - }, - }; + const submission = artifact("UserReport", { + completionClaims: [ + { + status: "user_reported", + evidenceRefs: ["artifact://run-1/msg-1"], + }, + ], + }); expect( isUserReportedEvidencePath( submission, @@ -114,11 +117,9 @@ describe("isUserReportedEvidencePath", () => { }); it("returns false for other artifact types and paths", () => { - const submission: ArtifactSubmission = { - runId: "run-1", - type: "WorkResult", - body: { evidenceRefs: ["artifact://run-1/ev-1"] }, - }; + const submission = artifact("WorkResult", { + evidenceRefs: ["artifact://run-1/ev-1"], + }); expect(isUserReportedEvidencePath(submission, "body.evidenceRefs[0]")).toBe( false, ); From ebb0373e751cd2d3a6cd8908f04e6aeef6b14b41 Mon Sep 17 00:00:00 2001 From: Dukeabadoon Date: Wed, 12 Aug 2026 13:59:06 +0800 Subject: [PATCH 3/3] refactor(core): extract cross-artifact validator (phase 3b) Move reference collection, producer mapping, and system-reference validation helpers into controller/cross-artifact-validator.ts. --- packages/core/src/controller.ts | 152 ++---------------- .../cross-artifact-validator.test.ts | 125 ++++++++++++++ .../controller/cross-artifact-validator.ts | 146 +++++++++++++++++ 3 files changed, 280 insertions(+), 143 deletions(-) create mode 100644 packages/core/src/controller/cross-artifact-validator.test.ts create mode 100644 packages/core/src/controller/cross-artifact-validator.ts diff --git a/packages/core/src/controller.ts b/packages/core/src/controller.ts index 1e4b547..fbae1da 100644 --- a/packages/core/src/controller.ts +++ b/packages/core/src/controller.ts @@ -54,6 +54,15 @@ import { isUserReportedEvidencePath, requiresDirectEvidence, } from "./controller/evidence-validator.js"; +import { + collectArtifactReferences, + expectedProducer, + expectedReferenceType, + isKnownSystemReference, + roleByPhase, + systemReferenceFields, + terminalField, +} from "./controller/cross-artifact-validator.js"; const artifactInputsByPhase: Record> = { context_grounding: new Set([ @@ -255,21 +264,6 @@ function explicitPermissionProjection( return effective; } -const roleByPhase: Record = - { - context_grounding: "controller", - agent_1_frame: "scenario_author", - agent_2_compile: "task_compiler", - agent_1_review: "scenario_author", - agent_2_revise: "task_compiler", - agent_3_plan: "quality_controller", - agent_4_execute: "executor", - agent_3_review: "quality_controller", - agent_3_evidence_reverify: "quality_controller", - agent_5_audit: "release_auditor", - agent_5_report: "release_auditor", - }; - export interface StartRunInput { repositoryRoot: string; originalRequest: string; @@ -388,98 +382,6 @@ function assertStartInput(input: StartRunInput): void { } } -interface LocatedReference { - ref: string; - path: string; -} - -const referenceFieldNames = new Set([ - "applicableRuleRefs", - "argumentsRef", - "changeRefs", - "clarificationRequestRef", - "commandRef", - "contextManifestRef", - "contextRefs", - "derivedRefs", - "directEvidenceRefs", - "diffRef", - "evidenceInspected", - "evidenceRefs", - "findingRefs", - "followupRequestRefs", - "inputRefs", - "instructionRef", - "originalRequestRef", - "outputRef", - "outputRefs", - "pinnedRefs", - "policyRefs", - "problemFrameRef", - "promptReadinessRubricRef", - "qualityReviewRef", - "ref", - "requestRef", - "resultRef", - "ruleRefs", - "subjectRef", - "sourceRef", - "sourceRefs", - "targetRef", - "taskContractRef", - "traceRef", - "toolEventRefs", - "userReportRef", - "verificationRefs", - "workPlanRef", - "workPlanRefs", - "workResultRefs", -]); - -function collectArtifactReferences( - value: unknown, - path = "body", - key = "", -): LocatedReference[] { - if (typeof value === "string") { - return referenceFieldNames.has(key) && - /^(?:artifact|repo|trace):\/\//u.test(value) - ? [{ ref: value, path }] - : []; - } - if (Array.isArray(value)) { - return value.flatMap((item, index) => - collectArtifactReferences(item, `${path}[${index}]`, key), - ); - } - if (typeof value !== "object" || value === null) return []; - return Object.entries(value).flatMap(([childKey, child]) => - collectArtifactReferences(child, `${path}.${childKey}`, childKey), - ); -} - -const fixedProducerByType: Readonly> = { - ContextManifest: "controller", - Evidence: "executor", - ProblemFrame: "scenario_author", - PromptReview: "scenario_author", - QualityReview: "quality_controller", - ReleaseAudit: "release_auditor", - ReceiptAudit: "controller", - RunEnvelope: "controller", - ScenarioSpec: "scenario_author", - TaskContract: "task_compiler", - TraceEvent: "controller", - UserReport: "release_auditor", - WorkPlan: "quality_controller", - WorkResult: "executor", -}; - -function expectedProducer(run: RunRecord, type: string): string { - if (type === "ClarificationRequest") return roleByPhase[run.phase]; - return fixedProducerByType[type] ?? roleByPhase[run.phase]; -} - function requireObjectBody(body: unknown): Record { if (typeof body !== "object" || body === null || Array.isArray(body)) { throw new Error("Artifact body must be an object"); @@ -623,42 +525,6 @@ class PermissionAuthorizationError extends Error { } } -function expectedReferenceType(path: string): string | null { - if (path.endsWith(".originalRequestRef")) return "UserMessage"; - if (path.endsWith(".problemFrameRef")) return "ProblemFrame"; - if (path.endsWith(".targetRef")) return "TaskContract"; - if (path.endsWith(".taskContractRef")) return "TaskContract"; - if (/\.workPlanRefs?(?:\[\d+\])?$/u.test(path)) return "WorkPlan"; - if (/\.workResultRefs\[\d+\]$/u.test(path)) return "WorkResult"; - if (path.endsWith(".qualityReviewRef")) return "QualityReview"; - if (path.endsWith(".contextManifestRef")) return "ContextManifest"; - if (path.endsWith(".clarificationRequestRef")) return "ClarificationRequest"; - return null; -} - -const systemReferenceFields = new Set([ - "applicableRuleRefs", - "instructionRef", - "policyRefs", - "promptReadinessRubricRef", - "ruleRefs", -]); - -const knownSystemReferencePatterns = [ - /^artifact:\/\/system\/roles\/(?:controller|scenario[_-]author(?:-micro)?|task[_-]compiler|quality[_-]controller(?:-spot)?|executor|release[_-]auditor)-v1$/u, - /^artifact:\/\/system\/rubrics\/contract-readiness-v1$/u, -]; - -function isKnownSystemReference(reference: string): boolean { - return knownSystemReferencePatterns.some((pattern) => - pattern.test(reference), - ); -} - -function terminalField(path: string): string { - return /\.([A-Za-z][A-Za-z0-9]*)(?:\[\d+\])?$/u.exec(path)?.[1] ?? ""; -} - function equalStringSets( left: readonly string[], right: readonly string[], diff --git a/packages/core/src/controller/cross-artifact-validator.test.ts b/packages/core/src/controller/cross-artifact-validator.test.ts new file mode 100644 index 0000000..d5d0599 --- /dev/null +++ b/packages/core/src/controller/cross-artifact-validator.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, it } from "vitest"; + +import type { RunRecord } from "../types.js"; +import { + collectArtifactReferences, + expectedProducer, + expectedReferenceType, + isKnownSystemReference, + roleByPhase, + systemReferenceFields, + terminalField, +} from "./cross-artifact-validator.js"; + +function run(overrides: Partial = {}): RunRecord { + return { + runId: "run-1", + schemaVersion: "1.0", + repositoryRoot: "/repo", + requestedMode: "analyze_and_fix", + topology: "micro", + escalationCount: 0, + priorRunId: null, + status: "running", + phase: "agent_4_execute", + resumePhase: null, + version: 1, + budgets: { + promptRevisionsRemaining: 1, + postExecutionRemediationsRemaining: 1, + }, + outcomeHint: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + ...overrides, + }; +} + +describe("collectArtifactReferences", () => { + it("collects artifact, repo, and trace refs from known fields", () => { + const refs = collectArtifactReferences({ + evidenceRefs: ["artifact://run-1/ev-1"], + contextRefs: ["repo://run-1/src"], + toolEventRefs: ["trace://run-1/event-0001"], + summary: "ignored", + }); + expect(refs).toEqual([ + { ref: "artifact://run-1/ev-1", path: "body.evidenceRefs[0]" }, + { ref: "repo://run-1/src", path: "body.contextRefs[0]" }, + { ref: "trace://run-1/event-0001", path: "body.toolEventRefs[0]" }, + ]); + }); + + it("ignores reference-shaped strings in non-reference fields", () => { + expect( + collectArtifactReferences({ + summary: "artifact://run-1/ev-1", + }), + ).toEqual([]); + }); +}); + +describe("expectedReferenceType", () => { + it("maps known reference paths to artifact types", () => { + expect(expectedReferenceType("body.problemFrameRef")).toBe("ProblemFrame"); + expect(expectedReferenceType("body.workPlanRefs[0]")).toBe("WorkPlan"); + expect(expectedReferenceType("body.workResultRefs[1]")).toBe("WorkResult"); + expect(expectedReferenceType("body.qualityReviewRef")).toBe( + "QualityReview", + ); + }); + + it("returns null for untyped reference paths", () => { + expect(expectedReferenceType("body.evidenceRefs[0]")).toBeNull(); + }); +}); + +describe("system reference helpers", () => { + it("recognizes known system artifact URIs", () => { + expect( + isKnownSystemReference("artifact://system/roles/quality-controller-v1"), + ).toBe(true); + expect(isKnownSystemReference("artifact://system/roles/unknown-v1")).toBe( + false, + ); + }); + + it("tracks fields that may hold system references", () => { + expect(systemReferenceFields.has("policyRefs")).toBe(true); + expect(systemReferenceFields.has("evidenceRefs")).toBe(false); + }); + + it("extracts terminal field names from reference paths", () => { + expect(terminalField("body.policyRefs[2]")).toBe("policyRefs"); + expect(terminalField("body.instructionRef")).toBe("instructionRef"); + }); +}); + +describe("expectedProducer", () => { + it("maps fixed artifact types to producers", () => { + expect( + expectedProducer(run({ phase: "agent_4_execute" }), "WorkResult"), + ).toBe("executor"); + expect(expectedProducer(run({ phase: "agent_3_plan" }), "WorkPlan")).toBe( + "quality_controller", + ); + }); + + it("uses phase role for ClarificationRequest and unknown types", () => { + expect( + expectedProducer( + run({ phase: "agent_2_compile" }), + "ClarificationRequest", + ), + ).toBe("task_compiler"); + expect( + expectedProducer(run({ phase: "agent_1_frame" }), "CustomArtifact"), + ).toBe("scenario_author"); + }); +}); + +describe("roleByPhase", () => { + it("maps execution phase to executor", () => { + expect(roleByPhase.agent_4_execute).toBe("executor"); + }); +}); diff --git a/packages/core/src/controller/cross-artifact-validator.ts b/packages/core/src/controller/cross-artifact-validator.ts new file mode 100644 index 0000000..2b1486f --- /dev/null +++ b/packages/core/src/controller/cross-artifact-validator.ts @@ -0,0 +1,146 @@ +import type { PhaseNextAction, RunRecord } from "../types.js"; + +export interface LocatedReference { + ref: string; + path: string; +} + +const referenceFieldNames = new Set([ + "applicableRuleRefs", + "argumentsRef", + "changeRefs", + "clarificationRequestRef", + "commandRef", + "contextManifestRef", + "contextRefs", + "derivedRefs", + "directEvidenceRefs", + "diffRef", + "evidenceInspected", + "evidenceRefs", + "findingRefs", + "followupRequestRefs", + "inputRefs", + "instructionRef", + "originalRequestRef", + "outputRef", + "outputRefs", + "pinnedRefs", + "policyRefs", + "problemFrameRef", + "promptReadinessRubricRef", + "qualityReviewRef", + "ref", + "requestRef", + "resultRef", + "ruleRefs", + "subjectRef", + "sourceRef", + "sourceRefs", + "targetRef", + "taskContractRef", + "traceRef", + "toolEventRefs", + "userReportRef", + "verificationRefs", + "workPlanRef", + "workPlanRefs", + "workResultRefs", +]); + +export function collectArtifactReferences( + value: unknown, + path = "body", + key = "", +): LocatedReference[] { + if (typeof value === "string") { + return referenceFieldNames.has(key) && + /^(?:artifact|repo|trace):\/\//u.test(value) + ? [{ ref: value, path }] + : []; + } + if (Array.isArray(value)) { + return value.flatMap((item, index) => + collectArtifactReferences(item, `${path}[${index}]`, key), + ); + } + if (typeof value !== "object" || value === null) return []; + return Object.entries(value).flatMap(([childKey, child]) => + collectArtifactReferences(child, `${path}.${childKey}`, childKey), + ); +} + +const fixedProducerByType: Readonly> = { + ContextManifest: "controller", + Evidence: "executor", + ProblemFrame: "scenario_author", + PromptReview: "scenario_author", + QualityReview: "quality_controller", + ReleaseAudit: "release_auditor", + ReceiptAudit: "controller", + RunEnvelope: "controller", + ScenarioSpec: "scenario_author", + TaskContract: "task_compiler", + TraceEvent: "controller", + UserReport: "release_auditor", + WorkPlan: "quality_controller", + WorkResult: "executor", +}; + +export const roleByPhase: Record< + RunRecord["phase"], + PhaseNextAction["logicalRole"] +> = { + context_grounding: "controller", + agent_1_frame: "scenario_author", + agent_2_compile: "task_compiler", + agent_1_review: "scenario_author", + agent_2_revise: "task_compiler", + agent_3_plan: "quality_controller", + agent_4_execute: "executor", + agent_3_review: "quality_controller", + agent_3_evidence_reverify: "quality_controller", + agent_5_audit: "release_auditor", + agent_5_report: "release_auditor", +}; + +export function expectedProducer(run: RunRecord, type: string): string { + if (type === "ClarificationRequest") return roleByPhase[run.phase]; + return fixedProducerByType[type] ?? roleByPhase[run.phase]; +} + +export function expectedReferenceType(path: string): string | null { + if (path.endsWith(".originalRequestRef")) return "UserMessage"; + if (path.endsWith(".problemFrameRef")) return "ProblemFrame"; + if (path.endsWith(".targetRef")) return "TaskContract"; + if (path.endsWith(".taskContractRef")) return "TaskContract"; + if (/\.workPlanRefs?(?:\[\d+\])?$/u.test(path)) return "WorkPlan"; + if (/\.workResultRefs\[\d+\]$/u.test(path)) return "WorkResult"; + if (path.endsWith(".qualityReviewRef")) return "QualityReview"; + if (path.endsWith(".contextManifestRef")) return "ContextManifest"; + if (path.endsWith(".clarificationRequestRef")) return "ClarificationRequest"; + return null; +} + +export const systemReferenceFields = new Set([ + "applicableRuleRefs", + "instructionRef", + "policyRefs", + "promptReadinessRubricRef", + "ruleRefs", +]); + +const knownSystemReferencePatterns = [ + /^artifact:\/\/system\/roles\/(?:controller|scenario[_-]author(?:-micro)?|task[_-]compiler|quality[_-]controller(?:-spot)?|executor|release[_-]auditor)-v1$/u, + /^artifact:\/\/system\/rubrics\/contract-readiness-v1$/u, +]; + +export function isKnownSystemReference(reference: string): boolean { + return knownSystemReferencePatterns.some((pattern) => + pattern.test(reference), + ); +} + +export function terminalField(path: string): string { + return /\.([A-Za-z][A-Za-z0-9]*)(?:\[\d+\])?$/u.exec(path)?.[1] ?? ""; +}