mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-19 01:46:15 +02:00
Bring governed connection reviews into task history and composer approvals. Share resolution with Connections, add scoped remembered permissions, and resume agents through durable outcome receipts. Keep cards compact, collapse raw results, isolate untrusted provider output, bound continuation payloads, and reconcile missed live events. Add Storybook coverage, browser journeys, and service regression tests. Verification: all PR CI gates passed, Greptile 5/5, security scans passed, five connection-review browser journeys passed, and real native Codex approval/continuation was verified against the local MCP fixture. Co-Authored-By: Paperclip <noreply@paperclip.ing>
825 lines
31 KiB
TypeScript
825 lines
31 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import { randomUUID } from "node:crypto";
|
|
import {
|
|
activityLog,
|
|
agentWakeupRequests,
|
|
agents,
|
|
companies,
|
|
completionContracts,
|
|
createDb,
|
|
heartbeatRuns,
|
|
issueRecoveryActions,
|
|
issueThreadInteractions,
|
|
issues,
|
|
nativeRunFinalizations,
|
|
statusDecisionEffects,
|
|
statusDecisions,
|
|
workAssessments,
|
|
} from "@paperclipai/db";
|
|
import { and, eq } from "drizzle-orm";
|
|
import { startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
|
|
import {
|
|
materializeLegacyQuestionResponseWakeProjection,
|
|
materializeNativeInteractionResponses,
|
|
NativeInteractionBridgeError,
|
|
} from "../services/native-runtime/native-interaction-bridge.js";
|
|
import { PaperclipControlPlanePort } from "../services/native-runtime/paperclip-control-plane-port.js";
|
|
import { finalizeNativeRun } from "../services/native-runtime/native-run-finalizer.js";
|
|
|
|
describe("P6-19 native interaction bridge", () => {
|
|
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
|
|
let db: ReturnType<typeof createDb>;
|
|
const companyId = "78000000-0000-4000-8000-000000000001";
|
|
const agentId = "78000000-0000-4000-8000-000000000002";
|
|
const issueId = "78000000-0000-4000-8000-000000000003";
|
|
const runId = "78000000-0000-4000-8000-000000000004";
|
|
const confirmationId = "78000000-0000-4000-8000-000000000005";
|
|
const questionsId = "78000000-0000-4000-8000-000000000006";
|
|
const governedId = "78000000-0000-4000-8000-000000000007";
|
|
const selfApprovedId = "78000000-0000-4000-8000-000000000008";
|
|
const suggestedTasksId = "78000000-0000-4000-8000-000000000012";
|
|
const checkboxId = "78000000-0000-4000-8000-000000000013";
|
|
const itemVerdictsId = "78000000-0000-4000-8000-000000000014";
|
|
|
|
beforeAll(async () => {
|
|
temporary = await startEmbeddedPostgresTestDatabase("paperclip-native-interaction-");
|
|
db = createDb(temporary.connectionString);
|
|
await db.insert(companies).values({ id: companyId, name: "Native interaction", issuePrefix: "NIB" });
|
|
await db.insert(agents).values({
|
|
id: agentId,
|
|
companyId,
|
|
name: "Native interaction agent",
|
|
adapterType: "codex_local",
|
|
status: "running",
|
|
});
|
|
await db.insert(issues).values({
|
|
id: issueId,
|
|
companyId,
|
|
title: "Consume an authorized interaction response",
|
|
status: "in_progress",
|
|
assigneeAgentId: agentId,
|
|
workMode: "standard",
|
|
});
|
|
await db.insert(heartbeatRuns).values({
|
|
id: runId,
|
|
companyId,
|
|
agentId,
|
|
status: "running",
|
|
contextSnapshot: { issueId, interactionId: confirmationId },
|
|
});
|
|
await db.insert(issueThreadInteractions).values([
|
|
{
|
|
id: confirmationId,
|
|
companyId,
|
|
issueId,
|
|
kind: "request_confirmation",
|
|
status: "accepted",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date(),
|
|
payload: { version: 1, prompt: "Continue?" },
|
|
result: { version: 1, outcome: "accepted" },
|
|
},
|
|
{
|
|
id: questionsId,
|
|
companyId,
|
|
issueId,
|
|
kind: "ask_user_questions",
|
|
status: "answered",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date(),
|
|
payload: {
|
|
version: 1,
|
|
questions: [{
|
|
id: "choice",
|
|
prompt: "Which path?",
|
|
selectionMode: "single",
|
|
options: [{ id: "safe", label: "Safe" }],
|
|
}],
|
|
},
|
|
result: { version: 1, answers: [{ questionId: "choice", optionIds: ["safe"] }] },
|
|
},
|
|
{
|
|
id: governedId,
|
|
companyId,
|
|
issueId,
|
|
kind: "request_confirmation",
|
|
status: "accepted",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date(),
|
|
payload: {
|
|
version: 1,
|
|
prompt: "Execute write?",
|
|
toolAction: {
|
|
version: 1,
|
|
actionRequestId: "78000000-0000-4000-8000-000000000010",
|
|
invocationId: "78000000-0000-4000-8000-000000000011",
|
|
toolName: "write",
|
|
toolDisplayName: "Write",
|
|
connectionId: null,
|
|
applicationId: null,
|
|
appDisplayName: null,
|
|
risk: "write",
|
|
previewMarkdown: "write",
|
|
argumentsSummaryJson: "{}",
|
|
argumentsHash: "hash",
|
|
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
},
|
|
},
|
|
result: { version: 1, outcome: "accepted" },
|
|
},
|
|
{
|
|
id: selfApprovedId,
|
|
companyId,
|
|
issueId,
|
|
kind: "request_confirmation",
|
|
status: "accepted",
|
|
createdByAgentId: agentId,
|
|
resolvedByAgentId: agentId,
|
|
resolvedByRunId: runId,
|
|
resolvedAt: new Date(),
|
|
payload: { version: 1, prompt: "Self approve?" },
|
|
result: { version: 1, outcome: "accepted" },
|
|
},
|
|
{
|
|
id: suggestedTasksId,
|
|
companyId,
|
|
issueId,
|
|
kind: "suggest_tasks",
|
|
status: "accepted",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date(),
|
|
payload: {
|
|
version: 1,
|
|
tasks: [{ clientKey: "calculator", title: "Build calculator" }],
|
|
},
|
|
result: {
|
|
version: 1,
|
|
createdTasks: [{ clientKey: "calculator", issueId: "78000000-0000-4000-8000-000000000015" }],
|
|
skippedClientKeys: [],
|
|
},
|
|
},
|
|
{
|
|
id: checkboxId,
|
|
companyId,
|
|
issueId,
|
|
kind: "request_checkbox_confirmation",
|
|
status: "accepted",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date(),
|
|
payload: {
|
|
version: 1,
|
|
prompt: "Choose outputs",
|
|
options: [{ id: "guide", label: "Guide" }],
|
|
},
|
|
result: { version: 1, outcome: "accepted", selectedOptionIds: ["guide"] },
|
|
},
|
|
{
|
|
id: itemVerdictsId,
|
|
companyId,
|
|
issueId,
|
|
kind: "request_item_verdicts",
|
|
status: "pending",
|
|
payload: {
|
|
version: 1,
|
|
prompt: "Review items",
|
|
items: [
|
|
{ id: "one", label: "One" },
|
|
{ id: "two", label: "Two" },
|
|
],
|
|
},
|
|
result: {
|
|
version: 1,
|
|
outcome: "resolved",
|
|
complete: false,
|
|
items: [{
|
|
id: "one",
|
|
verdict: "approve",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: new Date().toISOString(),
|
|
}],
|
|
},
|
|
},
|
|
]);
|
|
}, 30_000);
|
|
|
|
afterAll(async () => temporary?.cleanup());
|
|
|
|
it("projects supported typed responses through the authorized interaction service", async () => {
|
|
await expect(materializeNativeInteractionResponses({
|
|
db,
|
|
companyId,
|
|
issueId,
|
|
runId,
|
|
agentId,
|
|
interactionIds: [questionsId, confirmationId, suggestedTasksId, checkboxId, itemVerdictsId],
|
|
})).resolves.toEqual([
|
|
{
|
|
interactionId: confirmationId,
|
|
kind: "request_confirmation",
|
|
response: { status: "accepted", result: { version: 1, outcome: "accepted" } },
|
|
},
|
|
{
|
|
interactionId: questionsId,
|
|
kind: "ask_user_questions",
|
|
response: {
|
|
status: "answered",
|
|
result: {
|
|
version: 1,
|
|
answers: [{ questionId: "choice", optionIds: ["safe"] }],
|
|
summaryMarkdown: [
|
|
"Resolved questions and answers:",
|
|
"- Which path?: Safe",
|
|
].join("\n"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
interactionId: suggestedTasksId,
|
|
kind: "suggest_tasks",
|
|
response: {
|
|
status: "accepted",
|
|
result: {
|
|
version: 1,
|
|
createdTasks: [{ clientKey: "calculator", issueId: "78000000-0000-4000-8000-000000000015" }],
|
|
skippedClientKeys: [],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
interactionId: checkboxId,
|
|
kind: "request_checkbox_confirmation",
|
|
response: {
|
|
status: "accepted",
|
|
result: { version: 1, outcome: "accepted", selectedOptionIds: ["guide"] },
|
|
},
|
|
},
|
|
{
|
|
interactionId: itemVerdictsId,
|
|
kind: "request_item_verdicts",
|
|
response: {
|
|
status: "pending",
|
|
result: {
|
|
version: 1,
|
|
outcome: "resolved",
|
|
complete: false,
|
|
items: [{
|
|
id: "one",
|
|
verdict: "approve",
|
|
resolvedByUserId: "board-user",
|
|
resolvedAt: expect.any(String),
|
|
}],
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("projects answered questions for legacy adapter wake prompts", async () => {
|
|
await expect(materializeLegacyQuestionResponseWakeProjection({
|
|
db,
|
|
companyId,
|
|
issueId,
|
|
runId,
|
|
agentId,
|
|
interactionId: questionsId,
|
|
})).resolves.toEqual({
|
|
interactionId: questionsId,
|
|
summaryMarkdown: [
|
|
"Resolved questions and answers:",
|
|
"- Which path?: Safe",
|
|
].join("\n"),
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
[governedId, "native_interaction_governed_request_unresolved"],
|
|
[selfApprovedId, "native_interaction_self_approval"],
|
|
])("fails closed for governed or self-approved interaction %s", async (interactionId, code) => {
|
|
const error = await materializeNativeInteractionResponses({
|
|
db,
|
|
companyId,
|
|
issueId,
|
|
runId,
|
|
agentId,
|
|
interactionIds: [interactionId],
|
|
}).catch((caught) => caught);
|
|
expect(error).toBeInstanceOf(NativeInteractionBridgeError);
|
|
expect(error).toMatchObject({ code });
|
|
});
|
|
|
|
it("routes accepted package-result attention through live issue and interaction owners", async () => {
|
|
const delegateId = "78000000-0000-4000-8000-000000000020";
|
|
const outsideCompanyId = "78000000-0000-4000-8000-000000000021";
|
|
const outsideAgentId = "78000000-0000-4000-8000-000000000022";
|
|
await db.insert(companies).values({ id: outsideCompanyId, name: "Outside attention", issuePrefix: "NIO" });
|
|
await db.insert(agents).values([
|
|
{
|
|
id: delegateId,
|
|
companyId,
|
|
name: "Native attention delegate",
|
|
adapterType: "codex_local",
|
|
status: "idle",
|
|
},
|
|
{
|
|
id: outsideAgentId,
|
|
companyId: outsideCompanyId,
|
|
name: "Outside attention delegate",
|
|
adapterType: "codex_local",
|
|
status: "idle",
|
|
},
|
|
]);
|
|
|
|
const scenarios = [
|
|
{
|
|
key: "agent",
|
|
issueId: "78000000-0000-4000-8000-000000000023",
|
|
runId: "78000000-0000-4000-8000-000000000024",
|
|
contractId: "78000000-0000-4000-8000-000000000025",
|
|
disposition: "yielded" as const,
|
|
request: {
|
|
id: "attention-agent",
|
|
requestedCapability: "domain_expertise",
|
|
summary: "Delegate a same-company native investigation",
|
|
target: { ownerClass: "agent", agentId: delegateId, companyId },
|
|
},
|
|
},
|
|
{
|
|
key: "human",
|
|
issueId: "78000000-0000-4000-8000-000000000026",
|
|
runId: "78000000-0000-4000-8000-000000000027",
|
|
contractId: "78000000-0000-4000-8000-000000000028",
|
|
disposition: "needs_review" as const,
|
|
request: {
|
|
id: "attention-human",
|
|
requestedCapability: "subjective_decision",
|
|
requiredAuthority: "board",
|
|
summary: "Choose the acceptable native result",
|
|
target: { ownerClass: "board_user", companyId },
|
|
},
|
|
},
|
|
{
|
|
key: "cross-company",
|
|
issueId: "78000000-0000-4000-8000-000000000029",
|
|
runId: "78000000-0000-4000-8000-000000000030",
|
|
contractId: "78000000-0000-4000-8000-000000000031",
|
|
disposition: "yielded" as const,
|
|
request: {
|
|
id: "attention-cross-company",
|
|
requestedCapability: "domain_expertise",
|
|
summary: "Reject an outside-company native delegate",
|
|
target: { ownerClass: "agent", agentId: outsideAgentId, companyId: outsideCompanyId },
|
|
},
|
|
},
|
|
];
|
|
|
|
for (const scenario of scenarios) {
|
|
const contractSha = `attention-contract:${scenario.key}`;
|
|
await db.insert(issues).values({
|
|
id: scenario.issueId,
|
|
companyId,
|
|
title: `Native attention ${scenario.key}`,
|
|
status: "in_progress",
|
|
assigneeAgentId: agentId,
|
|
workMode: "standard",
|
|
});
|
|
await db.insert(completionContracts).values({
|
|
id: scenario.contractId,
|
|
companyId,
|
|
issueId: scenario.issueId,
|
|
revision: 1,
|
|
schemaVersion: "paperclip.completion-contract.v1",
|
|
policyVersion: "phase6-v1",
|
|
risk: "standard",
|
|
completionAuthority: "server_arbiter",
|
|
incompleteCriteriaPolicy: "preserve_non_terminal",
|
|
contractJson: { revision: "attention-v1", objective: scenario.key, criteria: [{ id: "objective" }] },
|
|
canonicalSha256: contractSha,
|
|
createdByActorType: "system",
|
|
createdByActorId: "test",
|
|
});
|
|
await db.insert(heartbeatRuns).values({
|
|
id: scenario.runId,
|
|
companyId,
|
|
agentId,
|
|
status: "running",
|
|
runtimeMode: "native",
|
|
runtimeModeResolverVersion: "phase6-v1",
|
|
runtimeModeReason: "eligible_opt_in",
|
|
runtimeModeResolvedAt: new Date(),
|
|
nativeIssueId: scenario.issueId,
|
|
nativeSessionId: scenario.runId,
|
|
runnerInstanceId: scenario.contractId,
|
|
completionContractId: scenario.contractId,
|
|
completionContractSha256: contractSha,
|
|
contextSnapshot: { issueId: scenario.issueId },
|
|
});
|
|
const port = new PaperclipControlPlanePort(db, {
|
|
companyId,
|
|
issueId: scenario.issueId,
|
|
runId: scenario.runId,
|
|
agentId,
|
|
sessionId: scenario.runId,
|
|
completionContractId: scenario.contractId,
|
|
completionContractSha256: contractSha,
|
|
sourceInstanceId: scenario.contractId,
|
|
controlPlaneSourceInstanceId: `control:${scenario.key}`,
|
|
});
|
|
await port.completeRun({
|
|
result: {
|
|
schema: "paperclip.run_result.v1",
|
|
reportedWorkDisposition: scenario.disposition,
|
|
summary: `Native attention ${scenario.key}`,
|
|
completionClaim: {
|
|
contractRevision: "attention-v1",
|
|
objectiveSatisfied: false,
|
|
criteria: [{ criterionId: "objective", status: "unknown", evidenceRefs: [] }],
|
|
remainingWork: [{ description: "Resolve attention", blocksCompletion: true }],
|
|
},
|
|
evidence: [],
|
|
verification: [{ commandOrCheck: "attention", status: "not_run" }],
|
|
attentionRequests: [scenario.request],
|
|
artifacts: [],
|
|
...(scenario.disposition === "yielded" ? {
|
|
continuation: {
|
|
kind: "response_wake" as const,
|
|
summary: "Resume after attention",
|
|
idempotencyKey: `attention:${scenario.key}`,
|
|
},
|
|
} : {}),
|
|
},
|
|
terminal: {
|
|
schema: "paperclip.prp.terminal.v1",
|
|
turnTerminalState: "completed",
|
|
runTerminalState: "succeeded",
|
|
reportedWorkDisposition: scenario.disposition,
|
|
},
|
|
turnId: `turn:${scenario.key}`,
|
|
});
|
|
await finalizeNativeRun({
|
|
db,
|
|
runId: scenario.runId,
|
|
workspaceFinalizeStatus: "succeeded",
|
|
projectRunStatus: true,
|
|
});
|
|
}
|
|
|
|
const delegated = await db.select().from(issues).where(and(
|
|
eq(issues.parentId, scenarios[0]!.issueId),
|
|
eq(issues.companyId, companyId),
|
|
));
|
|
expect(delegated).toEqual([]);
|
|
const agentInteractions = await db.select().from(issueThreadInteractions)
|
|
.where(eq(issueThreadInteractions.issueId, scenarios[0]!.issueId));
|
|
expect(agentInteractions).toEqual([
|
|
expect.objectContaining({
|
|
kind: "request_confirmation",
|
|
status: "pending",
|
|
sourceRunId: scenarios[0]!.runId,
|
|
addresseeAgentId: delegateId,
|
|
}),
|
|
]);
|
|
const humanInteractions = await db.select().from(issueThreadInteractions)
|
|
.where(eq(issueThreadInteractions.issueId, scenarios[1]!.issueId));
|
|
expect(humanInteractions).toEqual([
|
|
expect.objectContaining({ kind: "request_confirmation", status: "pending", sourceRunId: scenarios[1]!.runId }),
|
|
]);
|
|
await expect(db.select().from(issues).where(eq(issues.id, scenarios[0]!.issueId))).resolves.toEqual([
|
|
expect.objectContaining({ status: "in_review" }),
|
|
]);
|
|
await expect(db.select().from(issues).where(eq(issues.id, scenarios[1]!.issueId))).resolves.toEqual([
|
|
expect.objectContaining({ status: "in_review" }),
|
|
]);
|
|
await expect(db.select().from(issues).where(eq(issues.parentId, scenarios[2]!.issueId))).resolves.toHaveLength(0);
|
|
await expect(db.select().from(issueRecoveryActions)
|
|
.where(eq(issueRecoveryActions.sourceIssueId, scenarios[2]!.issueId))).resolves.toEqual([]);
|
|
await expect(db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, scenarios[2]!.runId))).resolves.toEqual([
|
|
expect.objectContaining({ status: "succeeded", nativePhase: "committed" }),
|
|
]);
|
|
|
|
for (const scenario of scenarios) {
|
|
const assessments = await db.select().from(workAssessments)
|
|
.where(eq(workAssessments.issueId, scenario.issueId));
|
|
expect(assessments.map((row) => row.triggerKind)).toEqual(["native_result"]);
|
|
const decisions = await db.select().from(statusDecisions)
|
|
.where(eq(statusDecisions.issueId, scenario.issueId));
|
|
expect(decisions).toHaveLength(1);
|
|
const effects = await db.select().from(statusDecisionEffects)
|
|
.where(eq(statusDecisionEffects.decisionId, decisions[0]!.id));
|
|
expect(effects.length).toBeGreaterThan(0);
|
|
await expect(db.select().from(activityLog).where(and(
|
|
eq(activityLog.entityId, scenario.issueId),
|
|
eq(activityLog.runId, scenario.runId),
|
|
))).resolves.toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ actorId: "native-status-committer" }),
|
|
]));
|
|
await expect(db.select().from(nativeRunFinalizations)
|
|
.where(eq(nativeRunFinalizations.runId, scenario.runId))).resolves.toEqual([
|
|
expect.objectContaining({ assessmentId: decisions[0]!.assessmentId, decisionId: decisions[0]!.id }),
|
|
]);
|
|
}
|
|
}, 30_000);
|
|
|
|
it("commits duplicate and stale attention as replay-stable zero-decision audit outcomes", async () => {
|
|
const seedAuditRun = async (input: {
|
|
label: string;
|
|
requests: Record<string, unknown>[];
|
|
interactions: Array<typeof issueThreadInteractions.$inferInsert>;
|
|
}) => {
|
|
const localIssueId = randomUUID();
|
|
const localRunId = randomUUID();
|
|
const localContractId = randomUUID();
|
|
const contractSha = `audit-contract:${input.label}`;
|
|
await db.insert(issues).values({
|
|
id: localIssueId,
|
|
companyId,
|
|
title: `Audit-only native attention ${input.label}`,
|
|
status: "in_progress",
|
|
assigneeAgentId: agentId,
|
|
workMode: "standard",
|
|
});
|
|
await db.insert(completionContracts).values({
|
|
id: localContractId,
|
|
companyId,
|
|
issueId: localIssueId,
|
|
revision: 1,
|
|
schemaVersion: "paperclip.completion-contract.v1",
|
|
policyVersion: "phase6-v1",
|
|
risk: "standard",
|
|
completionAuthority: "server_arbiter",
|
|
incompleteCriteriaPolicy: "preserve_non_terminal",
|
|
contractJson: {
|
|
revision: "audit-v1",
|
|
objective: "Retain superseded attention for audit",
|
|
criteria: [{ id: "objective", requirement: "Preserve the authoritative issue state" }],
|
|
},
|
|
canonicalSha256: contractSha,
|
|
createdByActorType: "system",
|
|
createdByActorId: "test",
|
|
});
|
|
await db.insert(heartbeatRuns).values({
|
|
id: localRunId,
|
|
companyId,
|
|
agentId,
|
|
status: "running",
|
|
runtimeMode: "native",
|
|
runtimeModeResolverVersion: "phase6-v1",
|
|
runtimeModeReason: "eligible_opt_in",
|
|
runtimeModeResolvedAt: new Date(),
|
|
nativeIssueId: localIssueId,
|
|
nativeSessionId: localRunId,
|
|
runnerInstanceId: localContractId,
|
|
completionContractId: localContractId,
|
|
completionContractSha256: contractSha,
|
|
contextSnapshot: { issueId: localIssueId },
|
|
});
|
|
if (input.interactions.length > 0) {
|
|
await db.insert(issueThreadInteractions).values(input.interactions.map((interaction) => ({
|
|
...interaction,
|
|
companyId: interaction.companyId ?? companyId,
|
|
issueId: interaction.issueId ?? localIssueId,
|
|
})));
|
|
}
|
|
const port = new PaperclipControlPlanePort(db, {
|
|
companyId,
|
|
issueId: localIssueId,
|
|
runId: localRunId,
|
|
agentId,
|
|
sessionId: localRunId,
|
|
completionContractId: localContractId,
|
|
completionContractSha256: contractSha,
|
|
sourceInstanceId: localContractId,
|
|
controlPlaneSourceInstanceId: `audit-control:${input.label}`,
|
|
});
|
|
await port.completeRun({
|
|
result: {
|
|
schema: "paperclip.run_result.v1",
|
|
reportedWorkDisposition: "yielded",
|
|
summary: `Audit-only native attention ${input.label}`,
|
|
completionClaim: {
|
|
contractRevision: "audit-v1",
|
|
objectiveSatisfied: false,
|
|
criteria: [{ criterionId: "objective", status: "unknown", evidenceRefs: [] }],
|
|
remainingWork: [{ description: "The canonical request remains authoritative", blocksCompletion: true }],
|
|
},
|
|
evidence: [],
|
|
verification: [{ commandOrCheck: "audit-only attention", status: "not_run" }],
|
|
attentionRequests: input.requests,
|
|
artifacts: [],
|
|
continuation: {
|
|
kind: "response_wake",
|
|
summary: "Wait for the canonical request",
|
|
idempotencyKey: `audit-only:${input.label}`,
|
|
},
|
|
},
|
|
terminal: {
|
|
schema: "paperclip.prp.terminal.v1",
|
|
turnTerminalState: "completed",
|
|
runTerminalState: "succeeded",
|
|
reportedWorkDisposition: "yielded",
|
|
},
|
|
turnId: `audit-turn:${input.label}`,
|
|
});
|
|
return { issueId: localIssueId, runId: localRunId };
|
|
};
|
|
|
|
const cases = ["duplicate", "stale", "mixed"] as const;
|
|
for (const label of cases) {
|
|
const canonicalId = randomUUID();
|
|
const duplicateId = randomUUID();
|
|
const staleId = randomUUID();
|
|
const duplicateRequest = {
|
|
id: `audit:${label}:duplicate`,
|
|
requestedCapability: "duplicate",
|
|
requiredAuthority: "agent",
|
|
target: { ownerClass: "current_agent", companyId },
|
|
summary: `Duplicate attention ${label}`,
|
|
responseState: "none",
|
|
targetInteractionId: duplicateId,
|
|
canonicalRequestId: canonicalId,
|
|
};
|
|
const staleRequest = {
|
|
id: `audit:${label}:stale`,
|
|
requestedCapability: "context_lookup",
|
|
requiredAuthority: "agent",
|
|
target: { ownerClass: "current_agent", companyId },
|
|
summary: `Stale attention ${label}`,
|
|
responseState: "stale",
|
|
targetInteractionId: staleId,
|
|
};
|
|
const requests = label === "duplicate"
|
|
? [duplicateRequest]
|
|
: label === "stale" ? [staleRequest] : [duplicateRequest, staleRequest];
|
|
const interactions: Array<typeof issueThreadInteractions.$inferInsert> = [];
|
|
if (label !== "stale") {
|
|
interactions.push(
|
|
{
|
|
id: canonicalId,
|
|
kind: "request_confirmation",
|
|
status: "pending",
|
|
payload: { version: 1, prompt: `Canonical ${label}` },
|
|
},
|
|
{
|
|
id: duplicateId,
|
|
kind: "request_confirmation",
|
|
status: "expired",
|
|
resolvedAt: new Date(),
|
|
payload: { version: 1, prompt: `Duplicate ${label}` },
|
|
result: { version: 1, outcome: "superseded_by_newer_request", supersededByInteractionId: canonicalId },
|
|
},
|
|
);
|
|
}
|
|
if (label !== "duplicate") {
|
|
interactions.push({
|
|
id: staleId,
|
|
kind: "request_confirmation",
|
|
status: "expired",
|
|
resolvedAt: new Date(),
|
|
payload: { version: 1, prompt: `Stale ${label}` },
|
|
result: { version: 1, outcome: "superseded_by_comment", commentId: randomUUID() },
|
|
});
|
|
}
|
|
const seeded = await seedAuditRun({ label, requests, interactions });
|
|
const finalized = await finalizeNativeRun({
|
|
db,
|
|
runId: seeded.runId,
|
|
workspaceFinalizeStatus: "succeeded",
|
|
projectRunStatus: true,
|
|
});
|
|
expect(finalized).toMatchObject({ phase: "committed", decisionId: expect.any(String) });
|
|
const finalDecisionId = finalized.decisionId;
|
|
|
|
await expect(db.select().from(issues).where(eq(issues.id, seeded.issueId))).resolves.toEqual([
|
|
expect.objectContaining({
|
|
status: label === "stale" ? "in_progress" : "in_review",
|
|
statusVersion: 1,
|
|
lastStatusDecisionId: expect.any(String),
|
|
}),
|
|
]);
|
|
await expect(db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, seeded.runId))).resolves.toEqual([
|
|
expect.objectContaining({
|
|
status: "succeeded",
|
|
nativePhase: "committed",
|
|
resultJson: expect.objectContaining({
|
|
finalizationPhase: "committed",
|
|
decisionId: expect.any(String),
|
|
authoritativeDecision: label === "stale" ? "in_progress" : "in_review",
|
|
ignoredAttentionRequests: expect.any(Array),
|
|
}),
|
|
}),
|
|
]);
|
|
await expect(db.select().from(nativeRunFinalizations)
|
|
.where(eq(nativeRunFinalizations.runId, seeded.runId))).resolves.toEqual([
|
|
expect.objectContaining({ phase: "committed", decisionId: expect.any(String), failureCode: null }),
|
|
]);
|
|
await expect(db.select().from(statusDecisions).where(eq(statusDecisions.issueId, seeded.issueId))).resolves.toHaveLength(1);
|
|
await expect(db.select().from(statusDecisionEffects).where(eq(statusDecisionEffects.issueId, seeded.issueId)))
|
|
.resolves.not.toHaveLength(0);
|
|
const issueWakeups = (await db.select().from(agentWakeupRequests)
|
|
.where(eq(agentWakeupRequests.companyId, companyId)))
|
|
.filter((request) => (request.payload as Record<string, unknown> | null)?.issueId === seeded.issueId);
|
|
if (label === "stale") {
|
|
expect(issueWakeups).toEqual([
|
|
expect.objectContaining({ payload: expect.objectContaining({ issueId: seeded.issueId }) }),
|
|
]);
|
|
} else {
|
|
// Duplicate actionable attention is owned by the review interaction;
|
|
// it must not wake the agent before that human decision is resolved.
|
|
expect(issueWakeups).toEqual([]);
|
|
}
|
|
|
|
const beforeReplay = await db.select({
|
|
id: issueThreadInteractions.id,
|
|
summary: issueThreadInteractions.summary,
|
|
updatedAt: issueThreadInteractions.updatedAt,
|
|
}).from(issueThreadInteractions).where(and(
|
|
eq(issueThreadInteractions.issueId, seeded.issueId),
|
|
));
|
|
const assessmentCount = await db.select().from(workAssessments)
|
|
.where(eq(workAssessments.runId, seeded.runId)).then((rows) => rows.length);
|
|
await expect(finalizeNativeRun({
|
|
db,
|
|
runId: seeded.runId,
|
|
workspaceFinalizeStatus: "succeeded",
|
|
projectRunStatus: true,
|
|
})).resolves.toMatchObject({ phase: "committed", decisionId: finalDecisionId });
|
|
const afterReplay = await db.select({
|
|
id: issueThreadInteractions.id,
|
|
summary: issueThreadInteractions.summary,
|
|
updatedAt: issueThreadInteractions.updatedAt,
|
|
}).from(issueThreadInteractions).where(eq(issueThreadInteractions.issueId, seeded.issueId));
|
|
expect(afterReplay).toEqual(beforeReplay);
|
|
await expect(db.select().from(workAssessments)
|
|
.where(eq(workAssessments.runId, seeded.runId)).then((rows) => rows.length)).resolves.toBe(assessmentCount);
|
|
const coordinatorBeforeCommittedReplay = await db.select({ attempt: nativeRunFinalizations.attempt })
|
|
.from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, seeded.runId))
|
|
.then((rows) => rows[0] ?? null);
|
|
await expect(finalizeNativeRun({
|
|
db,
|
|
runId: seeded.runId,
|
|
workspaceFinalizeStatus: "succeeded",
|
|
projectRunStatus: true,
|
|
})).resolves.toMatchObject({ phase: "committed", decisionId: finalDecisionId });
|
|
await expect(db.select({ attempt: nativeRunFinalizations.attempt })
|
|
.from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, seeded.runId))
|
|
.then((rows) => rows[0] ?? null)).resolves.toEqual(coordinatorBeforeCommittedReplay);
|
|
}
|
|
|
|
const outsideCompanyId = randomUUID();
|
|
const outsideIssueId = randomUUID();
|
|
const outsideInteractionId = randomUUID();
|
|
await db.insert(companies).values({ id: outsideCompanyId, name: "Outside audit target", issuePrefix: "OAT" });
|
|
await db.insert(issues).values({
|
|
id: outsideIssueId,
|
|
companyId: outsideCompanyId,
|
|
title: "Outside audit target",
|
|
status: "in_progress",
|
|
workMode: "standard",
|
|
});
|
|
await db.insert(issueThreadInteractions).values({
|
|
id: outsideInteractionId,
|
|
companyId: outsideCompanyId,
|
|
issueId: outsideIssueId,
|
|
kind: "request_confirmation",
|
|
status: "expired",
|
|
resolvedAt: new Date(),
|
|
payload: { version: 1, prompt: "Outside stale response" },
|
|
result: { version: 1, outcome: "superseded_by_comment", commentId: randomUUID() },
|
|
});
|
|
for (const [label, targetInteractionId] of [
|
|
["missing", randomUUID()],
|
|
["cross-company", outsideInteractionId],
|
|
] as const) {
|
|
const seeded = await seedAuditRun({
|
|
label: `invalid-${label}`,
|
|
requests: [{
|
|
id: `audit:invalid:${label}`,
|
|
requestedCapability: "context_lookup",
|
|
requiredAuthority: "agent",
|
|
target: { ownerClass: "current_agent", companyId },
|
|
summary: `Invalid audit target ${label}`,
|
|
responseState: "stale",
|
|
targetInteractionId,
|
|
}],
|
|
interactions: [],
|
|
});
|
|
await expect(finalizeNativeRun({
|
|
db,
|
|
runId: seeded.runId,
|
|
workspaceFinalizeStatus: "succeeded",
|
|
projectRunStatus: true,
|
|
})).resolves.toMatchObject({ phase: "committed", failureCode: null });
|
|
await expect(db.select().from(issues).where(eq(issues.id, seeded.issueId))).resolves.toEqual([
|
|
expect.objectContaining({ status: "in_progress", statusVersion: 1, lastStatusDecisionId: expect.any(String) }),
|
|
]);
|
|
await expect(db.select().from(statusDecisions).where(eq(statusDecisions.issueId, seeded.issueId))).resolves.toHaveLength(1);
|
|
await expect(db.select({ resultJson: heartbeatRuns.resultJson }).from(heartbeatRuns)
|
|
.where(eq(heartbeatRuns.id, seeded.runId)).then((rows) => rows[0]?.resultJson)).resolves.toEqual(
|
|
expect.objectContaining({ ignoredAttentionRequests: expect.any(Array) }),
|
|
);
|
|
}
|
|
await expect(db.select({ summary: issueThreadInteractions.summary })
|
|
.from(issueThreadInteractions)
|
|
.where(eq(issueThreadInteractions.id, outsideInteractionId))).resolves.toEqual([{ summary: null }]);
|
|
}, 30_000);
|
|
});
|