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] ?? ""; +}