fix(native-runner): materialize phase 6 live effects

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-09 06:05:21 +00:00
1 parent 33d9e8ef66
commit 3bed55f3f7
14 files changed
+1920 -478

No files matched your search

@@ -1,11 +1,14 @@
import type { Db } from "@paperclipai/db";
import type { NativeInteractionResponseEnvelope } from "@paperclipai/paperclip-runner";
import { and, eq, inArray } from "drizzle-orm";
import { heartbeatRuns, issues, issueThreadInteractions, nativeRunFinalizations } from "@paperclipai/db";
import { issueThreadInteractionService } from "../issue-thread-interactions.js";
import {
arbitrateNativeStatusScenario,
NATIVE_STATUS_ARBITER_POLICY_VERSION,
type NativeAuthoritativeIssueStatus,
type NativeGovernanceGate,
type NativeStatusAuthorityScenario,
type NativeStatusDecision,
type NativeStatusEffect,
} from "./status-arbiter.js";
export class NativeInteractionBridgeError extends Error {
@@ -15,6 +18,108 @@ export class NativeInteractionBridgeError extends Error {
}
}
function record(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
export type NativeAttentionMaterializedTarget = {
effectKind: string;
targetType: "issue_thread_interaction" | "native_run_finalization";
targetId: string;
};
/**
* Applies audit-only attention outcomes that intentionally create no status
* decision. The durable interaction/coordinator mutation is the proof; a
* synthetic delivered effect row is neither created nor accepted.
*/
export async function applyNativeAttentionStatusDecision(input: {
db: Db;
companyId: string;
issueId: string;
runId: string;
decision: NativeStatusDecision;
targetInteractionId?: string;
canonicalRequestId?: string;
}): Promise<NativeAttentionMaterializedTarget[]> {
if (input.decision.statusAction !== "preserve") {
throw new NativeInteractionBridgeError("native_attention_status_authority_invalid", "Audit-only attention cannot change issue status");
}
return input.db.transaction(async (tx) => {
const targets: NativeAttentionMaterializedTarget[] = [];
for (const effect of input.decision.effects) {
if (effect.kind === "link_canonical_request") {
const boundIds = [input.canonicalRequestId, input.targetInteractionId]
.filter((id): id is string => typeof id === "string");
const interactions = await tx.select({ id: issueThreadInteractions.id })
.from(issueThreadInteractions).where(and(
eq(issueThreadInteractions.companyId, input.companyId),
eq(issueThreadInteractions.issueId, input.issueId),
...(boundIds.length > 0 ? [inArray(issueThreadInteractions.id, boundIds)] : []),
)).limit(2);
const canonical = input.canonicalRequestId
? interactions.find((interaction) => interaction.id === input.canonicalRequestId)
: interactions[0];
const duplicate = input.targetInteractionId
? interactions.find((interaction) => interaction.id === input.targetInteractionId)
: interactions.find((interaction) => interaction.id !== canonical?.id);
if (!canonical || !duplicate || canonical.id === duplicate.id) {
throw new NativeInteractionBridgeError("native_attention_canonical_pair_missing", "Canonical and duplicate requests are required");
}
await tx.update(issueThreadInteractions).set({
summary: `Canonical native attention request: ${canonical.id}`,
updatedAt: new Date(),
}).where(eq(issueThreadInteractions.id, duplicate.id));
targets.push({ effectKind: effect.kind, targetType: "issue_thread_interaction", targetId: duplicate.id });
continue;
}
if (effect.kind === "record_stale_response") {
const interaction = await tx.select({ id: issueThreadInteractions.id })
.from(issueThreadInteractions).where(and(
eq(issueThreadInteractions.companyId, input.companyId),
eq(issueThreadInteractions.issueId, input.issueId),
...(input.targetInteractionId ? [eq(issueThreadInteractions.id, input.targetInteractionId)] : []),
)).limit(1).then((rows) => rows[0] ?? null);
if (!interaction) throw new NativeInteractionBridgeError("native_stale_response_missing", "Stale response is not durable");
await tx.update(issueThreadInteractions).set({
summary: "Response retained for audit after native supersession.",
updatedAt: new Date(),
}).where(eq(issueThreadInteractions.id, interaction.id));
targets.push({ effectKind: effect.kind, targetType: "issue_thread_interaction", targetId: interaction.id });
continue;
}
if (effect.kind === "record_finalization_error") {
const [coordinator] = await tx.update(nativeRunFinalizations).set({
phase: "terminal_failure",
failureCode: effect.cause,
failureDetail: { nextAction: effect.nextAction, recoveryOwner: { kind: "agent", agentId: effect.agentId } },
nextAttemptAt: null,
updatedAt: new Date(),
}).where(and(
eq(nativeRunFinalizations.runId, input.runId),
eq(nativeRunFinalizations.companyId, input.companyId),
eq(nativeRunFinalizations.issueId, input.issueId),
)).returning({ runId: nativeRunFinalizations.runId });
if (!coordinator) throw new NativeInteractionBridgeError("native_finalization_coordinator_missing", "Native coordinator is required");
await tx.update(heartbeatRuns).set({
status: "failed",
finishedAt: new Date(),
nativePhase: "terminal_failure",
nativePhaseUpdatedAt: new Date(),
errorCode: effect.cause,
updatedAt: new Date(),
}).where(and(eq(heartbeatRuns.id, input.runId), eq(heartbeatRuns.companyId, input.companyId)));
targets.push({ effectKind: effect.kind, targetType: "native_run_finalization", targetId: coordinator.runId });
continue;
}
throw new NativeInteractionBridgeError("native_attention_effect_unimplemented", effect.kind);
}
return targets;
});
}
/**
* Materialize only interactions that authorized this run's wake. Resolution
* remains owned by the existing interaction service; this bridge is a
@@ -30,7 +135,14 @@ export async function materializeNativeInteractionResponses(input: {
}): Promise<NativeInteractionResponseEnvelope[]> {
const requestedIds = new Set(input.interactionIds.filter(Boolean));
if (requestedIds.size === 0) return [];
const interactions = await issueThreadInteractionService(input.db).listForIssue(input.issueId);
const [interactions, issue] = await Promise.all([
issueThreadInteractionService(input.db).listForIssue(input.issueId),
input.db.select({ status: issues.status }).from(issues).where(and(
eq(issues.id, input.issueId),
eq(issues.companyId, input.companyId),
)).limit(1).then((rows) => rows[0] ?? null),
]);
if (!issue) throw new NativeInteractionBridgeError("native_interaction_binding_mismatch", "Issue binding not found");
const responses: NativeInteractionResponseEnvelope[] = [];
for (const interaction of interactions) {
@@ -41,6 +153,41 @@ export async function materializeNativeInteractionResponses(input: {
`Interaction ${interaction.id} is not bound to the native company and issue`,
);
}
const interactionResult = record(interaction.result);
const supersessionOutcome = interaction.status === "expired"
&& ["superseded_by_newer_request", "superseded_by_comment", "stale_target"].includes(String(interactionResult.outcome));
if (supersessionOutcome) {
const duplicate = interactionResult.outcome === "superseded_by_newer_request";
const decision = resolveNativeAttentionStatus({
facts: duplicate
? {
companyScopeValid: true,
responseState: "none",
route: "duplicate",
summary: interaction.summary ?? interaction.title ?? "Duplicate native attention request",
}
: {
companyScopeValid: true,
responseState: "stale",
route: "context",
summary: interaction.summary ?? interaction.title ?? "Stale native attention response",
},
priorIssueStatus: issue.status as NativeAuthoritativeIssueStatus,
agentId: input.agentId,
});
await applyNativeAttentionStatusDecision({
db: input.db,
companyId: input.companyId,
issueId: input.issueId,
runId: input.runId,
decision,
targetInteractionId: interaction.id,
canonicalRequestId: typeof interactionResult.supersededByInteractionId === "string"
? interactionResult.supersededByInteractionId
: undefined,
});
continue;
}
if (
interaction.resolvedByRunId === input.runId
|| (
@@ -83,6 +230,19 @@ export async function materializeNativeInteractionResponses(input: {
`Confirmation ${interaction.id} is not authoritatively resolved`,
);
}
const resolution = resolveNativeAttentionStatus({
facts: {
companyScopeValid: true,
responseState: "resolved",
route: "context",
summary: interaction.summary ?? interaction.title ?? "Resolved native interaction",
},
priorIssueStatus: issue.status as NativeAuthoritativeIssueStatus,
agentId: input.agentId,
});
if (resolution.reasonCode !== "attention_resolved_from_context") {
throw new NativeInteractionBridgeError("native_attention_resolution_invalid", "Resolved interaction has no continuation policy");
}
responses.push({
interactionId: interaction.id,
kind: interaction.kind,
@@ -101,6 +261,19 @@ export async function materializeNativeInteractionResponses(input: {
`Question interaction ${interaction.id} is not authoritatively answered`,
);
}
const resolution = resolveNativeAttentionStatus({
facts: {
companyScopeValid: true,
responseState: "resolved",
route: "context",
summary: interaction.summary ?? interaction.title ?? "Answered native interaction",
},
priorIssueStatus: issue.status as NativeAuthoritativeIssueStatus,
agentId: input.agentId,
});
if (resolution.reasonCode !== "attention_resolved_from_context") {
throw new NativeInteractionBridgeError("native_attention_resolution_invalid", "Answered interaction has no continuation policy");
}
responses.push({
interactionId: interaction.id,
kind: interaction.kind,
@@ -139,23 +312,110 @@ export function rejectUnsupportedNativeRuntimeRequest(requestKind: string) {
};
}
const ATTENTION_STATUS_SCENARIOS = new Set<NativeStatusAuthorityScenario>([
"alternate_track_runnable", "context_answer_current", "ordinary_domain_expertise",
"intentional_human_judgment", "equivalent_attention_family", "resolver_budget_exhausted",
"transient_retry_then_success", "cross_company_target", "response_after_supersession",
"interaction_expired", "governed_gate_pending",
]);
export type NativeAttentionFacts = {
companyScopeValid: boolean;
responseState: "none" | "resolved" | "stale" | "expired";
route: "alternate_track" | "context" | "retry" | "agent" | "human" | "duplicate" | "recovery";
summary: string;
budgetExhausted?: boolean;
governanceGate?: NativeGovernanceGate | null;
};
/** Server-owned attention resolution; candidates never author status/effects. */
/** Server-owned attention resolution from canonical routing facts. */
export function resolveNativeAttentionStatus(input: {
scenario: NativeStatusAuthorityScenario;
facts: NativeAttentionFacts;
priorIssueStatus: NativeAuthoritativeIssueStatus;
agentId: string;
governanceGate?: NativeGovernanceGate | null;
blockerAction?: string;
}) {
if (!ATTENTION_STATUS_SCENARIOS.has(input.scenario)) {
throw new NativeInteractionBridgeError("native_attention_scenario_invalid", input.scenario);
}): NativeStatusDecision {
const preserve = (reasonCode: string, effects: NativeStatusEffect[]): NativeStatusDecision => ({
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "preserve",
toStatus: input.priorIssueStatus,
reasonCode,
unblockDescriptor: null,
effects,
});
const continuation = (reasonCode: string): NativeStatusDecision => ({
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "in_progress",
toStatus: "in_progress",
reasonCode,
unblockDescriptor: null,
effects: [{
kind: "enqueue_continuation",
continuationKind: input.facts.route === "agent" ? "response_wake" : "same_agent",
summary: input.facts.summary,
idempotencyKey: `native-attention:${input.facts.route}:${input.facts.summary}`,
agentId: input.agentId,
}],
});
if (!input.facts.companyScopeValid) {
return preserve("result_schema_rejected", [{
kind: "record_finalization_error",
cause: "result_schema_rejected",
nextAction: "Remove the cross-company attention target.",
agentId: input.agentId,
}]);
}
return arbitrateNativeStatusScenario(input);
if (input.facts.responseState === "stale") {
return preserve("attention_duplicate_suppressed", [{ kind: "record_stale_response" }]);
}
if (input.facts.responseState === "expired") {
return preserve("attention_budget_exhausted", [{ kind: "record_expiry" }]);
}
if (input.facts.budgetExhausted || input.facts.route === "recovery") {
return preserve("attention_budget_exhausted", [{
kind: "record_finalization_error",
cause: "attention_budget_exhausted",
nextAction: "Assign a named recovery owner.",
agentId: input.agentId,
}]);
}
if (input.facts.route === "duplicate") {
return preserve("attention_duplicate_suppressed", [{ kind: "link_canonical_request" }]);
}
if (input.facts.governanceGate) {
return {
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "in_review",
toStatus: "in_review",
reasonCode: "governed_gate_pending",
unblockDescriptor: null,
effects: [
{ kind: "create_interaction", gate: input.facts.governanceGate },
{ kind: "notify_owner", agentId: input.agentId, reason: "governed_gate_pending" },
],
};
}
if (input.facts.route === "alternate_track") {
return continuation("turn_waiting_other_track_live");
}
if (input.facts.route === "context" || input.facts.route === "retry") {
return continuation("attention_resolved_from_context");
}
if (input.facts.route === "agent") {
const decision = continuation("attention_routed_to_agent");
return {
...decision,
effects: [
{ kind: "create_delegated_issue", agentId: input.agentId, summary: input.facts.summary },
...decision.effects,
],
};
}
if (input.facts.route === "human") {
return {
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "in_review",
toStatus: "in_review",
reasonCode: "attention_requires_human_authority",
unblockDescriptor: null,
effects: [
{ kind: "create_interaction", prompt: input.facts.summary },
{ kind: "notify_owner", agentId: input.agentId, reason: "attention_requires_human_authority" },
],
};
}
throw new NativeInteractionBridgeError("native_attention_facts_invalid", "Attention facts do not select a route");
}