Connect personal Muse through secure native execution and Vault OAuth (#16448)

Add company-scoped Muse native control, OAuth, wake receiver and recovery.

Recheck current authority after waits and before effects or receipt replay. Preserve task-read scope, assignment expiry, independent holds, nonblocking reassignment and truthful cleanup provenance.

Retain source trust on progress comments and verify current Muse V8 authority through real signed-JWT HTTP requests. Preserve the upstream trimmed ordinary authentication lookup.

Refs #16448.

Preserve captured canonical queue recovery and concurrent environment preparation while retaining the scoped Muse V8 exclusion. Normal migration 0354 and existing Muse SQL remain unchanged. Fresh current-head Greptile review is 5/5 with the project-write finding addressed. Required CI and independent technical review gate the merge; maintainer override is reserved for formal approval rules.

Recheck current Muse project-write authority after all receipt/task/run/admission waits, before creation or replay. Real PostgreSQL regressions reproduce four late effects/replays on the old route; the repaired three-file treatment passes 354 cases with no skips, and the scoped server typecheck passes. Ordinary/Dot behavior is preserved.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-17 22:44:36 -05:00
1 parent 63fbb714e6
commit e6dcc6b095
72 files changed
+9775 -1021

No files matched your search

@@ -12,6 +12,7 @@ export function createLocalNativeQuestionBridge(input: {
resolve: (input: {
runId: string; requestId: string; turnId: string;
resolution: HarnessRuntimeRequestResolution;
commandId?: string;
authorizeBeforeDispatch: () => Promise<void>;
}) => Promise<{ commandId: string }>;
}) {
@@ -23,13 +24,13 @@ export function createLocalNativeQuestionBridge(input: {
close();
release = registerNativeQuestionCommandTarget({
binding: input.binding,
queueCommand: async (type, payload) => {
queueCommand: async (type, payload, commandId) => {
if (type !== "request.resolve" || typeof payload?.requestId !== "string") throw new Error("native_question_command_invalid");
const requestId = payload.requestId;
const pending = await readPendingNativeRuntimeRequest(input.db, { ...input.binding, requestId });
if (!pending || pending.requestKind !== "runtime") throw new Error("native_question_not_pending");
const result = await input.resolve({
runId: input.binding.runId, requestId, turnId: pending.turnId,
runId: input.binding.runId, requestId, turnId: pending.turnId, commandId,
resolution: { action: "submit", response: payload.response as never },
authorizeBeforeDispatch: async () => {
const current = await readPendingNativeRuntimeRequest(input.db, { ...input.binding, requestId });
@@ -0,0 +1,87 @@
import { randomBytes, randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { agents, agentWakeupRequests, authUsers, companies, companyMemberships, createDb,
heartbeatRuns, issueComments, issues, museAgentBindings, museMailboxItems,
museRunnerAssignments, museRunnerOperations } from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js";
import { heartbeatService } from "../heartbeat.js";
import { instanceSettingsService } from "../instance-settings.js";
import { consumeMuseHistoryReceipt } from "../muse-assignment-follow-up.js";
import { queuedCommentIdsFromWakePayload } from "../issue-queued-comment-queue.js";
describe("Muse native comment follow-up", () => {
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let db: ReturnType<typeof createDb>;
beforeAll(async () => {
temporary = await startEmbeddedPostgresTestDatabase("muse-native-follow-up-");
db = createDb(temporary.connectionString);
vi.stubEnv("PAPERCLIP_SECRETS_MASTER_KEY", randomBytes(32).toString("base64"));
await instanceSettingsService(db).updateExperimental({ enableNativeRunner: true, enableMuse: true });
}, 30000);
afterAll(async () => { await temporary?.cleanup(); vi.unstubAllEnvs(); });
async function fixture() {
const companyId = randomUUID(), agentId = randomUUID(), userId = randomUUID(), runId = randomUUID(), bindingId = randomUUID();
await db.insert(authUsers).values({ id: userId, name: "Muse operator", email: `${userId}@example.test`, createdAt: new Date(), updatedAt: new Date() });
await db.insert(companies).values({ id: companyId, name: "Muse follow-up", issuePrefix: `MF${companyId.slice(0, 6)}`, defaultResponsibleUserId: userId });
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "owner" });
await db.insert(agents).values({ id: agentId, companyId, name: "Muse", status: "active", adapterType: "paperclip_runner",
adapterConfig: { provider: "muse", museBindingId: bindingId, allowUnmeteredProvider: true, lifecycleMode: "per_turn" } });
await db.insert(museAgentBindings).values({ id: bindingId, companyId, agentId, operatorId: userId,
status: "ready", pairedAt: new Date(), receiverContactAt: new Date(), verifiedReplyAt: new Date() });
const [task] = await db.insert(issues).values({ companyId, title: "Active Muse task", status: "in_progress",
assigneeAgentId: agentId, responsibleUserId: userId }).returning();
const sessionId = randomUUID();
const [run] = await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, status: "running", invocationSource: "assignment",
runtimeMode: "native", nativeIssueId: task!.id, issueId: task!.id, nativeSessionId: sessionId,
responsibleUserId: userId, contextSnapshot: { issueId: task!.id } }).returning();
await db.update(issues).set({ executionRunId: runId, checkoutRunId: runId }).where(eq(issues.id, task!.id));
const [assignment] = await db.insert(museRunnerAssignments).values({ companyId, bindingId, bindingGeneration: 1, runId, agentId,
normalizedSessionId: sessionId, turnId: randomUUID(), controllerGeneration: 1, catalogDigest: "sha256:fixture",
status: "accepted", projection: {}, acceptBy: new Date(Date.now() + 60000), expiresAt: new Date(Date.now() + 60000),
claimedAt: new Date(), nativeAcceptedAt: new Date() }).returning();
const comments = await db.insert(issueComments).values([1, 2].map(index => ({ companyId, issueId: task!.id,
authorUserId: userId, body: `Follow-up ${index}` }))).returning();
const wake = (commentId: string) => heartbeatService(db).wakeup(agentId, { source: "assignment", triggerDetail: "system",
reason: "issue_commented", payload: { issueId: task!.id, commentId }, requestedByActorType: "user", requestedByActorId: userId,
contextSnapshot: { issueId: task!.id, wakeCommentId: commentId, wakeReason: "issue_commented" } });
async function history(commentIds: string[], failed = false) {
const [receipt] = await db.insert(museRunnerOperations).values({ companyId, assignmentId: assignment!.id,
requestId: randomUUID(), digest: "sha256:history", status: "settled",
command: { action: "tool", input: { name: "get_task_history", arguments: {} } },
outcome: { status: "completed", isError: failed, result: { comments: commentIds.map(id => ({ id })) } } }).returning();
return receipt!;
}
return { companyId, agentId, run: run!, assignment: assignment!, comments, wake, history };
}
it("queues unread comments on the accepted turn, and consumes only an exact successful history receipt", async () => {
const f = await fixture();
for (const comment of [f.comments[0]!, f.comments[0]!, f.comments[1]!]) expect((await f.wake(comment.id))?.id).toBe(f.run.id);
expect(await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.agentId, f.agentId))).toHaveLength(1);
let wakes = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, f.agentId));
expect(wakes).toHaveLength(1);
expect(wakes[0]).toMatchObject({ status: "deferred_issue_execution", runId: null, finishedAt: null });
expect(queuedCommentIdsFromWakePayload(wakes[0]!.payload)).toEqual(f.comments.map(comment => comment.id));
expect(await db.select().from(museMailboxItems).where(and(eq(museMailboxItems.assignmentId, f.assignment.id), eq(museMailboxItems.kind, "follow_up")))).toHaveLength(2);
// Reading references is not consumption; the deferred wake remains durable.
wakes = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, f.agentId));
expect(wakes[0]?.status).toBe("deferred_issue_execution");
const receipt = await f.history(f.comments.map(comment => comment.id));
await consumeMuseHistoryReceipt(db, receipt);
await consumeMuseHistoryReceipt(db, receipt);
expect((await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, wakes[0]!.id)))[0]).toMatchObject({ status: "coalesced", runId: f.run.id });
});
it.each(["partial", "failed", "read before admission", "terminal"] as const)("preserves unread input at the %s boundary", async scenario => {
const f = await fixture(), receipt = await f.history([f.comments[0]!.id], scenario === "failed");
if (scenario === "read before admission") await consumeMuseHistoryReceipt(db, receipt);
for (const comment of f.comments) await f.wake(comment.id);
if (scenario === "terminal") await db.update(heartbeatRuns).set({ status: "cancelled", finishedAt: new Date() }).where(eq(heartbeatRuns.id, f.run.id));
await consumeMuseHistoryReceipt(db, receipt);
const wakes = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, f.agentId));
const pending = wakes.find(wake => wake.status === "deferred_issue_execution");
expect(pending).toBeDefined();
expect(queuedCommentIdsFromWakePayload(pending!.payload)).toEqual(["failed", "terminal"].includes(scenario)
? f.comments.map(comment => comment.id) : [f.comments[1]!.id]);
if (scenario === "read before admission") expect(wakes.find(wake => wake.payload?.commentId === f.comments[0]!.id)?.status).toBe("coalesced");
});
});
@@ -0,0 +1,457 @@
import { randomUUID } from "node:crypto";
import { LOW_TRUST_REVIEW_PRESET, DEFAULT_TRUST_PRESET } from "@paperclipai/shared";
import { and, desc, eq } from "drizzle-orm";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { agents, approvals, authUsers, companies, companyMemberships, createDb, completionContracts, nativeRunResults, workAssessments, statusDecisions, issueApprovals, documents, documentRevisions, externalAgentHolds, heartbeatRuns, instanceUserRoles, issueAccessGrants, issues, issueDocuments, issueThreadInteractions, principalPermissionGrants, projectAccessMembers, projects } from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js";
import { approvalReadSqlCondition, authorizationService, canActorReadIssuePrivacy, issueReadSqlCondition, projectReadSqlCondition, type AuthorizationActor } from "../authorization.js";
import { claimQueuedNativeReviewRun } from "./native-review-dispatch.js";
import { PaperclipRunnerToolAuthority } from "./paperclip-runner-tool-authority.js";
import { documentService } from "../documents.js";
import { buildLowTrustSourceTrust, buildPromotedSourceTrust, LOW_TRUST_QUARANTINED_BODY } from "../source-trust.js";
describe("Muse native visibility intersection", () => {
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let db: ReturnType<typeof createDb>;
beforeAll(async () => { temporary = await startEmbeddedPostgresTestDatabase("muse-native-visibility-"); db = createDb(temporary.connectionString); }, 30000);
afterAll(async () => { await temporary?.cleanup(); });
afterEach(() => vi.unstubAllEnvs());
async function fixture() {
const companyId = randomUUID(), agentId = randomUUID(), responsible = randomUUID(), authorizer = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Muse visibility", issuePrefix: `V${companyId.slice(0, 6)}` });
await db.insert(authUsers).values([responsible, authorizer].map(id => ({ id, name: id, email: `${id}@example.test`, createdAt: new Date(), updatedAt: new Date() })));
await db.insert(companyMemberships).values([responsible, authorizer].map(principalId => ({ companyId, principalType: "user", principalId, status: "active", membershipRole: "operator" })));
await db.insert(agents).values({ id: agentId, companyId, name: "Muse", role: "engineer", status: "idle", adapterType: "paperclip_runner", adapterConfig: { provider: "muse" }, runtimeConfig: {}, permissions: {} });
const [issue] = await db.insert(issues).values({ companyId, title: "Private task", visibility: "private", assigneeAgentId: agentId, responsibleUserId: responsible }).returning();
const actor: AuthorizationActor = { type: "agent", agentId, companyId, source: "agent_jwt", onBehalfOfUserId: responsible, authorizingUserId: authorizer };
return { companyId, agentId, responsible, authorizer, issue, actor };
}
async function documentFixture(lowTrustReader = false) {
const f = await fixture(), runId = randomUUID();
const executionPolicy = lowTrustReader ? { authorizationPolicy: { trustBoundary: {
mode: LOW_TRUST_REVIEW_PRESET, companyId: f.companyId, rootIssueId: f.issue.id,
} } } : null;
await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId,
nativeIssueId: f.issue.id, responsibleUserId: f.responsible, status: "running", runtimeMode: "native",
invocationSource: "assignment", contextSnapshot: { issueId: f.issue.id, executionPolicy } });
await db.update(issues).set({ status: "in_progress", executionRunId: runId, executionPolicy: null }).where(eq(issues.id, f.issue.id));
await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" });
const authority = new PaperclipRunnerToolAuthority(db, { companyId: f.companyId, agentId: f.agentId,
issueId: f.issue.id, runId, museRuntime: true, externalAuthorizingUserId: f.authorizer,
assertBridgeAuthority: async () => {} });
return { ...f, runId, authority };
}
async function taskReadBoundaryFixture(restricted:boolean) {
const f=await documentFixture();
const [allowedProject,outsideProject]=await db.insert(projects).values([
{companyId:f.companyId,name:"Native allowed project"},{companyId:f.companyId,name:"Native outside project"},
]).returning();
await db.update(issues).set({projectId:allowedProject.id}).where(eq(issues.id,f.issue.id));
if(restricted)await db.update(heartbeatRuns).set({contextSnapshot:{issueId:f.issue.id,executionPolicy:{authorizationPolicy:{trustPreset:LOW_TRUST_REVIEW_PRESET,trustBoundary:{mode:LOW_TRUST_REVIEW_PRESET,companyId:f.companyId,projectIds:[allowedProject.id]}}}}}).where(eq(heartbeatRuns.id,f.runId));
const [allowed,outside]=await db.insert(issues).values([
{companyId:f.companyId,parentId:f.issue.id,projectId:allowedProject.id,title:"MUSE_READ_SCOPE_ALLOWED",description:"MUSE_READ_ALLOWED_BODY",visibility:"open",status:"done"},
{companyId:f.companyId,parentId:f.issue.id,projectId:outsideProject.id,title:"MUSE_READ_SCOPE_OUTSIDE",description:"MUSE_READ_OUTSIDE_BODY",visibility:"open",status:"done"},
]).returning();
const actor={...f.actor,runId:f.runId};
expect(await canActorReadIssuePrivacy(db,actor,outside)).toBe(true);
const decisions=await authorizationService(db).decideReads(actor,[allowed,outside].map(issue=>({type:"issue" as const,companyId:f.companyId,issueId:issue.id})));
expect(decisions.map(decision=>decision.allowed)).toEqual([true,!restricted]);
return {...f,allowed,outside};
}
async function recordedChildReview(f:Awaited<ReturnType<typeof taskReadBoundaryFixture>>,issueId:string) {
const contractId=randomUUID(),sourceRunId=randomUUID(),resultId=randomUUID(),assessmentId=randomUUID(),decisionId=randomUUID(),interactionId=randomUUID();
await db.insert(completionContracts).values({id:contractId,companyId:f.companyId,issueId,revision:1,schemaVersion:"paperclip.completion-contract.v1",policyVersion:"native-read-fixture",risk:"standard",completionAuthority:"server_arbiter",incompleteCriteriaPolicy:"preserve_non_terminal",contractJson:{objective:"Review child"},canonicalSha256:contractId,createdByActorType:"system",createdByActorId:"test"});
await db.insert(heartbeatRuns).values({id:sourceRunId,companyId:f.companyId,agentId:f.agentId,nativeIssueId:issueId,status:"succeeded",runtimeMode:"native",completionContractId:contractId});
await db.insert(nativeRunResults).values({id:resultId,companyId:f.companyId,issueId,runId:sourceRunId,completionContractId:contractId,serverFingerprint:resultId,schemaStatus:"accepted",resultJson:{},canonicalSha256:resultId});
await db.insert(workAssessments).values({id:assessmentId,companyId:f.companyId,issueId,runId:sourceRunId,contractId,resultId,triggerKind:"native_result",triggerActorCompanyId:f.companyId,priorIssueStatus:"in_progress",priorStatusVersion:0,policyVersion:"native-read-fixture",assessmentJson:{},inputDigest:assessmentId});
await db.insert(statusDecisions).values({id:decisionId,companyId:f.companyId,issueId,runId:sourceRunId,assessmentId,decisionVersion:1,policyVersion:"native-read-fixture",fromStatus:"in_progress",toStatus:"done",reasonCode:"explicit_review",decisionJson:{},decisionDigest:decisionId,applicationState:"applied"});
await db.insert(issueThreadInteractions).values({id:interactionId,companyId:f.companyId,issueId,kind:"request_confirmation",status:"accepted",sourceRunId,createdByAgentId:f.agentId,resolvedByUserId:f.authorizer,resolvedAt:new Date(),payload:{version:1,prompt:"Review child",target:{type:"custom",key:"native_completion_review",revisionId:decisionId}}});
return interactionId;
}
it.each([true,false].flatMap(restricted=>["search_tasks","child_tasks","child_review_outcomes","approval_tasks"].map(shape=>({restricted,shape}))))("applies ordinary per-task decisions to native Muse result collections ($shape; restricted: $restricted)",async({restricted,shape})=>{
const f=await taskReadBoundaryFixture(restricted);
let approvalId:string|undefined;
if(shape==="child_review_outcomes"){
await recordedChildReview(f,f.allowed.id);await recordedChildReview(f,f.outside.id);
}
if(shape==="approval_tasks"){
const [approval]=await db.insert(approvals).values({companyId:f.companyId,type:"hire_agent",status:"pending",payload:{issueId:f.issue.id}}).returning();approvalId=approval.id;
await db.insert(issueApprovals).values([f.allowed,f.outside].map(issue=>({companyId:f.companyId,issueId:issue.id,approvalId:approval.id,linkedByAgentId:f.agentId})));
}
const result=await f.authority.execute({tool:shape==="search_tasks"?"search_tasks":shape==="approval_tasks"?"get_approval_context":"get_task_context",callId:"per-task-"+shape,arguments:shape==="search_tasks"?{query:"MUSE_READ_SCOPE",limit:10}:shape==="approval_tasks"?{approvalId}:{}});
const ids=shape==="child_review_outcomes"
?(result as {childReviewOutcomes:Array<{result:{childTaskId:string}}>}).childReviewOutcomes.map(row=>row.result.childTaskId)
:shape==="child_tasks"?(result as {childTasks:Array<{id:string}>}).childTasks.map(row=>row.id)
:(result as {tasks:Array<{id:string}>}).tasks.map(row=>row.id);
expect(ids).toContain(f.allowed.id);
if(restricted){expect(ids).not.toContain(f.outside.id);for(const value of [f.outside.id,f.outside.title,f.outside.description])expect(JSON.stringify(result)).not.toContain(value);}
else expect(ids).toContain(f.outside.id);
if(shape==="search_tasks"){
const outsideOnly=await f.authority.execute({tool:"search_tasks",callId:"outside-only",arguments:{query:"MUSE_READ_SCOPE_OUTSIDE",limit:1}});
expect(outsideOnly).toMatchObject({tasks:restricted?[]:[{id:f.outside.id}]});
}
});
it("retains the child search fallback when the authorized child follows a capped denied prefix",async()=>{
const f=await taskReadBoundaryFixture(true),older=new Date("2026-01-01T00:00:00.000Z"),newer=new Date("2026-01-01T01:00:00.000Z");
await db.update(issues).set({createdAt:older}).where(eq(issues.id,f.allowed.id));
await db.update(issues).set({createdAt:newer}).where(eq(issues.id,f.outside.id));
const denied=await db.insert(issues).values(Array.from({length:100},(_,index)=>({
id:f.companyId.slice(0,24)+index.toString(16).padStart(12,"0"),companyId:f.companyId,parentId:f.issue.id,
projectId:f.outside.projectId,title:"MUSE_DENIED_PREFIX_"+index,description:"MUSE_DENIED_PREFIX_BODY_"+index,
visibility:"open",status:"done",createdAt:newer,
}))).returning();
const actor={...f.actor,runId:f.runId};
const candidates=await db.select({id:issues.id}).from(issues).where(and(eq(issues.companyId,f.companyId),eq(issues.parentId,f.issue.id),await issueReadSqlCondition(db,actor)))
.orderBy(desc(issues.createdAt),desc(issues.id)).limit(101);
expect(candidates.map(row=>row.id)).toEqual([f.outside.id,...denied.map(row=>row.id)].sort().reverse());
expect(candidates.map(row=>row.id)).not.toContain(f.allowed.id);
const result=await f.authority.execute({tool:"get_task_context",callId:"capped-denied-prefix",arguments:{}}) as {childTasksTruncated:boolean;childTasks:Array<{id:string}>};
expect(result.childTasksTruncated).toBe(true);
expect(result.childTasks).toEqual([]);
for(const row of [f.outside,...denied])for(const value of [row.id,row.title,row.description])expect(JSON.stringify(result)).not.toContain(value);
expect(await f.authority.execute({tool:"search_tasks",callId:"older-authorized-search",arguments:{query:"MUSE_READ_SCOPE_ALLOWED",limit:1}}))
.toMatchObject({tasks:[{id:f.allowed.id,title:f.allowed.title,description:f.allowed.description}]});
});
it("preserves ordinary unrestricted native task search results",async()=>{
const f=await taskReadBoundaryFixture(false);
await db.update(agents).set({adapterConfig:{provider:"codex"}}).where(eq(agents.id,f.agentId));
const authority=new PaperclipRunnerToolAuthority(db,{companyId:f.companyId,agentId:f.agentId,issueId:f.issue.id,runId:f.runId});
expect(await authority.execute({tool:"search_tasks",callId:"ordinary-search",arguments:{query:"MUSE_READ_SCOPE",limit:10}})).toMatchObject({tasks:expect.arrayContaining([{...f.allowed},{...f.outside}].map(task=>expect.objectContaining({id:task.id,title:task.title,description:task.description})))});
});
it("tags actual Muse native document writes with the retained low-trust run before storing or replaying them", async () => {
const f = await documentFixture(true);
const sourceTrust = buildLowTrustSourceTrust({ issueId: f.issue.id, runId: f.runId, agentId: f.agentId });
const call = { tool: "write_document", callId: "low-trust-write", arguments: {
idempotencyKey: "low-trust-write", key: "report", title: "Report", body: "LOW_TRUST_NATIVE_WRITE_CANARY", baseRevisionId: null,
} };
const written = await f.authority.execute(call);
expect(written).toMatchObject({ disposition: "applied", document: { body: "LOW_TRUST_NATIVE_WRITE_CANARY", sourceTrust } });
expect(await documentService(db).getIssueDocumentByKey(f.issue.id, "report"))
.toMatchObject({ body: "LOW_TRUST_NATIVE_WRITE_CANARY", sourceTrust });
expect(await f.authority.execute({ ...call, callId: "low-trust-write-replay" })).toEqual(written);
const current = await documentService(db).getIssueDocumentByKey(f.issue.id, "report");
expect(await f.authority.execute({ ...call, callId: "low-trust-update", arguments: { ...call.arguments,
idempotencyKey: "low-trust-update", body: "LOW_TRUST_NATIVE_UPDATE_CANARY", baseRevisionId: current!.latestRevisionId,
} })).toMatchObject({ disposition: "applied", document: { body: "LOW_TRUST_NATIVE_UPDATE_CANARY", sourceTrust } });
expect(await documentService(db).getIssueDocumentByKey(f.issue.id, "report"))
.toMatchObject({ body: "LOW_TRUST_NATIVE_UPDATE_CANARY", sourceTrust });
expect(await documentService(db).listIssueDocumentRevisions(f.issue.id, "report")).toHaveLength(2);
});
it.each([false, true])("projects Muse current documents, old revisions and exact accepted plans for the named reader (low trust: %s)", async lowTrustReader => {
const f = await documentFixture(lowTrustReader), writerRunId = randomUUID();
const lowPolicy = { authorizationPolicy: { trustBoundary: {
mode: LOW_TRUST_REVIEW_PRESET, companyId: f.companyId, rootIssueId: f.issue.id,
} } };
await db.insert(heartbeatRuns).values({ id: writerRunId, companyId: f.companyId, agentId: f.agentId,
nativeIssueId: f.issue.id, status: "succeeded", contextSnapshot: { issueId: f.issue.id, executionPolicy: lowPolicy } });
const quarantine = buildLowTrustSourceTrust({ issueId: f.issue.id, runId: writerRunId, agentId: f.agentId });
const service = documentService(db);
const first = await service.upsertIssueDocument({ issueId: f.issue.id, key: "plan", title: "Plan", format: "markdown",
body: "LOW_TRUST_REVISION_CANARY", changeSummary: "LOW_TRUST_SUMMARY_CANARY", createdByAgentId: f.agentId,
createdByRunId: writerRunId, sourceTrust: quarantine });
const currentBody = lowTrustReader ? "LOW_TRUST_REVISION_CANARY" : LOW_TRUST_QUARANTINED_BODY;
expect(await f.authority.execute({ tool: "read_document", callId: "read-quarantined", arguments: { key: "plan" } }))
.toMatchObject({ document: { id: first.document.id, body: currentBody, sourceTrust: quarantine } });
expect(await f.authority.execute({ tool: "list_documents", callId: "list-quarantined", arguments: {} }))
.toMatchObject({ documents: [{ id: first.document.id, body: currentBody, sourceTrust: quarantine }] });
const promotedFirst = buildPromotedSourceTrust({ sourceIssueId: f.issue.id, sourceArtifactKind: "document",
sourceArtifactId: first.document.id, promotedByActorType: "user", promotedByActorId: f.authorizer });
await db.update(documents).set({ sourceTrust: promotedFirst }).where(eq(documents.id, first.document.id));
expect(await f.authority.execute({ tool: "read_document", callId: "read-explicitly-promoted", arguments: { key: "plan" } }))
.toMatchObject({ document: { body: "LOW_TRUST_REVISION_CANARY", sourceTrust: promotedFirst } });
const second = await service.upsertIssueDocument({ issueId: f.issue.id, key: "plan", title: "Plan", format: "markdown",
body: "Trusted Board replacement", baseRevisionId: first.document.latestRevisionId, createdByUserId: f.authorizer });
const promotion = buildPromotedSourceTrust({ sourceIssueId: f.issue.id, sourceArtifactKind: "document",
sourceArtifactId: second.document.id, promotedByActorType: "user", promotedByActorId: f.authorizer });
await db.update(documents).set({ sourceTrust: promotion }).where(eq(documents.id, first.document.id));
await db.update(heartbeatRuns).set({ contextSnapshot: {
issueId: f.issue.id, ...(lowTrustReader ? { executionPolicy: lowPolicy } : {}),
planReviewInteraction: { acceptedTargetRevision: { issueId: f.issue.id, documentId: first.document.id,
key: "plan", revisionId: first.document.latestRevisionId, revisionNumber: first.document.latestRevisionNumber } },
} }).where(eq(heartbeatRuns.id, f.runId));
const storedBefore = await db.select().from(documentRevisions).where(eq(documentRevisions.documentId, first.document.id));
const history = await f.authority.execute({ tool: "list_document_revisions", callId: "history-after-rewrite", arguments: { key: "plan" } });
expect(history).toMatchObject({ revisions: [
{ id: second.document.latestRevisionId, body: "Trusted Board replacement" },
{ id: first.document.latestRevisionId, body: currentBody,
changeSummary: lowTrustReader ? "LOW_TRUST_SUMMARY_CANARY" : null },
] });
const context = await f.authority.execute({ tool: "get_task_context", callId: "accepted-old-revision", arguments: {} });
expect(context).toMatchObject({ acceptedPlan: { documentId: first.document.id, revisionId: first.document.latestRevisionId,
revisionNumber: first.document.latestRevisionNumber, markdown: currentBody } });
expect(await f.authority.execute({ tool: "read_document", callId: "read-promoted-current", arguments: { key: "plan" } }))
.toMatchObject({ document: { id: first.document.id, body: "Trusted Board replacement", sourceTrust: promotion } });
if (!lowTrustReader) {
expect(JSON.stringify(history)).not.toContain("LOW_TRUST_REVISION_CANARY");
expect(JSON.stringify(history)).not.toContain("LOW_TRUST_SUMMARY_CANARY");
expect(JSON.stringify(context)).not.toContain("LOW_TRUST_REVISION_CANARY");
}
expect(await db.select().from(documentRevisions).where(eq(documentRevisions.documentId, first.document.id))).toEqual(storedBefore);
expect(await service.getIssueDocumentByKey(f.issue.id, "plan"))
.toMatchObject({ body: "Trusted Board replacement", sourceTrust: promotion });
});
it("quarantines legacy untagged current Muse output using its original retained run without mutable writer permissions", async () => {
const f = await documentFixture(), writerRunId = randomUUID();
await db.insert(heartbeatRuns).values({ id: writerRunId, companyId: f.companyId, agentId: f.agentId,
nativeIssueId: f.issue.id, status: "succeeded", contextSnapshot: { issueId: f.issue.id, executionPolicy: {
trustPreset: LOW_TRUST_REVIEW_PRESET, authorizationPolicy: { trustBoundary: {
mode: LOW_TRUST_REVIEW_PRESET, companyId: f.companyId, rootIssueId: f.issue.id,
} },
} } });
const written = await documentService(db).upsertIssueDocument({ issueId: f.issue.id, key: "legacy", title: "Legacy",
format: "markdown", body: "UNTAGGED_LOW_TRUST_CANARY", createdByAgentId: f.agentId, createdByRunId: writerRunId });
expect(written.document.sourceTrust).toBeNull();
expect(await f.authority.execute({ tool: "read_document", callId: "legacy-current", arguments: { key: "legacy" } }))
.toMatchObject({ document: { body: LOW_TRUST_QUARANTINED_BODY, sourceTrust: null } });
expect(await f.authority.execute({ tool: "list_documents", callId: "legacy-list", arguments: {} }))
.toMatchObject({ documents: [{ body: LOW_TRUST_QUARANTINED_BODY }] });
expect(await documentService(db).getIssueDocumentByKey(f.issue.id, "legacy"))
.toMatchObject({ body: "UNTAGGED_LOW_TRUST_CANARY", sourceTrust: null });
});
it.each(["missing_run", "foreign_company", "wrong_writer", "wrong_task", "missing_policy", "missing_user"])(
"withholds unknown Muse revision provenance without claiming quarantine (%s)", async kind => {
const f = await documentFixture(), writerRunId = randomUUID();
let writerCompanyId = f.companyId, writerAgentId = f.agentId;
if (kind === "foreign_company") {
writerCompanyId = randomUUID();
await db.insert(companies).values({ id: writerCompanyId, name: "Other writer", issuePrefix: `F${writerCompanyId.slice(0, 6)}` });
}
if (kind === "wrong_writer" || kind === "foreign_company") {
writerAgentId = randomUUID();
await db.insert(agents).values({ id: writerAgentId, companyId: writerCompanyId, name: "Other writer", adapterType: "paperclip_runner" });
}
if (kind !== "missing_run" && kind !== "missing_user") await db.insert(heartbeatRuns).values({ id: writerRunId,
companyId: writerCompanyId, agentId: writerAgentId, nativeIssueId: kind === "wrong_task" ? null : f.issue.id,
status: "succeeded", contextSnapshot: { issueId: kind === "wrong_task" ? randomUUID() : f.issue.id,
...(kind === "missing_policy" ? {} : { executionPolicy: { trustPreset: DEFAULT_TRUST_PRESET } }) } });
const service = documentService(db);
const first = await service.upsertIssueDocument({ issueId: f.issue.id, key: "plan", title: "Plan", format: "markdown",
body: "UNKNOWN_REVISION_CANARY", changeSummary: "UNKNOWN_SUMMARY_CANARY",
...(kind === "missing_user" ? { createdByUserId: "not-an-authenticated-user" }
: { createdByAgentId: f.agentId, createdByRunId: kind === "missing_run" ? null : writerRunId }) });
const second = await service.upsertIssueDocument({ issueId: f.issue.id, key: "plan", title: "Plan", format: "markdown",
body: "Trusted current Board text", baseRevisionId: first.document.latestRevisionId, createdByUserId: f.authorizer });
const storedBefore = await db.select().from(documentRevisions).where(eq(documentRevisions.documentId, first.document.id));
await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issue.id, planReviewInteraction: {
acceptedTargetRevision: { key: "plan", revisionId: first.document.latestRevisionId },
} } }).where(eq(heartbeatRuns.id, f.runId));
const unknownBody = "[Document revision content omitted: historical source trust could not be established.]";
const history = await f.authority.execute({ tool: "list_document_revisions", callId: "unknown-history", arguments: { key: "plan" } });
expect(history).toMatchObject({ revisions: [
{ id: second.document.latestRevisionId, body: "Trusted current Board text" },
{ id: first.document.latestRevisionId, body: unknownBody, changeSummary: null },
] });
expect(JSON.stringify(history)).not.toContain("UNKNOWN_REVISION_CANARY");
expect(JSON.stringify(history)).not.toContain("UNKNOWN_SUMMARY_CANARY");
expect(JSON.stringify(history)).not.toContain(LOW_TRUST_QUARANTINED_BODY);
expect(await f.authority.execute({ tool: "get_task_context", callId: "unknown-accepted", arguments: {} }))
.toMatchObject({ acceptedPlan: { revisionId: first.document.latestRevisionId, markdown: unknownBody } });
expect(await f.authority.execute({ tool: "read_document", callId: "known-current", arguments: { key: "plan" } }))
.toMatchObject({ document: { body: "Trusted current Board text" } });
expect(await db.select().from(documentRevisions).where(eq(documentRevisions.documentId, first.document.id))).toEqual(storedBefore);
},
);
it("preserves verified standard-run and authenticated Board revision bodies for higher-trust Muse readers", async () => {
const f = await documentFixture(), writerRunId = randomUUID(), service = documentService(db);
await db.insert(heartbeatRuns).values({ id: writerRunId, companyId: f.companyId, agentId: f.agentId,
nativeIssueId: f.issue.id, status: "succeeded", contextSnapshot: { issueId: f.issue.id,
executionPolicy: { trustPreset: DEFAULT_TRUST_PRESET } } });
const first = await service.upsertIssueDocument({ issueId: f.issue.id, key: "report", title: "Report", format: "markdown",
body: "Trusted standard-run revision", createdByAgentId: f.agentId, createdByRunId: writerRunId });
const second = await service.upsertIssueDocument({ issueId: f.issue.id, key: "report", title: "Report", format: "markdown",
body: "Trusted Board revision", baseRevisionId: first.document.latestRevisionId, createdByUserId: f.authorizer });
await service.upsertIssueDocument({ issueId: f.issue.id, key: "report", title: "Report", format: "markdown",
body: "Trusted current native revision", baseRevisionId: second.document.latestRevisionId,
createdByAgentId: f.agentId, createdByRunId: f.runId });
expect(await f.authority.execute({ tool: "list_document_revisions", callId: "trusted-history", arguments: { key: "report" } }))
.toMatchObject({ revisions: [ { body: "Trusted current native revision" }, { body: "Trusted Board revision" },
{ body: "Trusted standard-run revision" } ] });
expect(await f.authority.execute({ tool: "read_document", callId: "trusted-current", arguments: { key: "report" } }))
.toMatchObject({ document: { body: "Trusted current native revision", sourceTrust: null } });
});
it("projects the exact accepted source-conversation plan before exposing it to a Muse child task", async () => {
const f = await documentFixture(), sourceRunId = randomUUID();
const [source] = await db.insert(issues).values({ companyId: f.companyId, title: "Source conversation", visibility: "open",
conversationAgentId: f.agentId, assigneeAgentId: f.agentId, conversationUserId: f.responsible, conversationState: "active" }).returning();
await db.insert(heartbeatRuns).values({ id: sourceRunId, companyId: f.companyId, agentId: f.agentId, nativeIssueId: source.id,
status: "succeeded", contextSnapshot: { issueId: source.id, executionPolicy: { authorizationPolicy: { trustBoundary: {
mode: LOW_TRUST_REVIEW_PRESET, companyId: f.companyId, rootIssueId: source.id,
} } } } });
const service = documentService(db);
const first = await service.upsertIssueDocument({ issueId: source.id, key: "plan", title: "Plan", format: "markdown",
body: "LOW_TRUST_SOURCE_PLAN_CANARY", createdByAgentId: f.agentId, createdByRunId: sourceRunId,
sourceTrust: buildLowTrustSourceTrust({ issueId: source.id, runId: sourceRunId, agentId: f.agentId }) });
await service.upsertIssueDocument({ issueId: source.id, key: "plan", title: "Plan", format: "markdown",
body: "Trusted newer source text", baseRevisionId: first.document.latestRevisionId, createdByUserId: f.authorizer });
const [accepted] = await db.insert(issueThreadInteractions).values({ companyId: f.companyId, issueId: source.id,
kind: "request_confirmation", status: "accepted", resolvedAt: new Date(f.issue.createdAt.getTime() - 1),
payload: { version: 1, prompt: "Approve plan", target: { type: "issue_document", issueId: source.id,
key: "plan", revisionId: first.document.latestRevisionId } } }).returning();
await db.update(issues).set({ originRunId: sourceRunId }).where(eq(issues.id, f.issue.id));
const context = await f.authority.execute({ tool: "get_task_context", callId: "source-plan-context", arguments: {} });
expect(context).toMatchObject({ sourcePlanApproval: { sourceIssueId: source.id, interactionId: accepted.id,
revisionId: first.document.latestRevisionId, markdown: LOW_TRUST_QUARANTINED_BODY } });
expect(JSON.stringify(context)).not.toContain("LOW_TRUST_SOURCE_PLAN_CANARY");
expect((await service.listIssueDocumentRevisions(source.id, "plan"))[1])
.toMatchObject({ id: first.document.latestRevisionId, body: "LOW_TRUST_SOURCE_PLAN_CANARY" });
});
it("rechecks current Muse authorizer and company authority before all native document projections", async () => {
const f = await documentFixture();
await documentService(db).upsertIssueDocument({ issueId: f.issue.id, key: "report", title: "Report", format: "markdown",
body: "Trusted scoped document", createdByUserId: f.authorizer });
await db.update(companyMemberships).set({ status: "inactive" })
.where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
for (const tool of ["read_document", "list_documents", "list_document_revisions", "get_task_context"]) {
await expect(f.authority.execute({ tool, callId: tool, arguments: { key: "report" } })).rejects.toThrow("no longer available");
}
const foreign = new PaperclipRunnerToolAuthority(db, { companyId: randomUUID(), agentId: f.agentId,
issueId: f.issue.id, runId: f.runId, museRuntime: true, externalAuthorizingUserId: f.authorizer,
assertBridgeAuthority: async () => {} });
await expect(foreign.execute({ tool: "read_document", callId: "foreign", arguments: { key: "report" } }))
.rejects.toThrow("binding_not_authorized");
});
it.each(["enforce", "shadow", "off"])("filters direct and paginated reads before data leaves the DB in %s mode", async mode => {
vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", mode);
const f = await fixture();
await db.insert(instanceUserRoles).values({ userId: f.authorizer, role: "instance_admin" });
expect(await canActorReadIssuePrivacy(db, f.actor, f.issue)).toBe(false);
expect(await db.select({ id: issues.id }).from(issues).where(and(eq(issues.companyId, f.companyId), await issueReadSqlCondition(db, f.actor))).limit(1)).toEqual([]);
expect((await authorizationService(db).decide({ actor: f.actor, action: "issue:read", resource: { type: "issue", companyId: f.companyId, issueId: f.issue.id } })).allowed).toBe(false);
const [grant] = await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" }).returning();
expect(await canActorReadIssuePrivacy(db, f.actor, f.issue)).toBe(true);
await db.update(issueAccessGrants).set({ revokedAt: new Date() }).where(eq(issueAccessGrants.id, grant.id));
expect(await canActorReadIssuePrivacy(db, f.actor, f.issue)).toBe(false);
});
it("intersects project and approval reads with current authorizer membership", async () => {
const f = await fixture();
const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private project", visibility: "private" }).returning();
await db.insert(projectAccessMembers).values([{ subjectType: "agent", subjectId: f.agentId }, { subjectType: "user", subjectId: f.responsible }].map(subject => ({ ...subject, companyId: f.companyId, projectId: project.id })));
expect(await db.select({ id: projects.id }).from(projects).where(and(eq(projects.companyId, f.companyId), await projectReadSqlCondition(db, f.actor)))).toEqual([]);
await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.authorizer });
expect(await db.select({ id: projects.id }).from(projects).where(and(eq(projects.id, project.id), await projectReadSqlCondition(db, f.actor)))).toHaveLength(1);
const [approval] = await db.insert(approvals).values({ companyId: f.companyId, type: "hire_agent", status: "pending", payload: { issueId: f.issue.id } }).returning();
expect(await db.select({ id: approvals.id }).from(approvals).where(and(eq(approvals.id, approval.id), await approvalReadSqlCondition(db, f.actor)))).toEqual([]);
await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" });
expect(await db.select({ id: approvals.id }).from(approvals).where(and(eq(approvals.id, approval.id), await approvalReadSqlCondition(db, f.actor)))).toHaveLength(1);
await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
expect(await canActorReadIssuePrivacy(db, f.actor, f.issue)).toBe(false);
});
it("filters native task search with the same authorizer and responsible-user predicates", async () => {
const f = await fixture(), runId = randomUUID();
await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, nativeIssueId: f.issue.id,
responsibleUserId: f.responsible, status: "running", runtimeMode: "native", invocationSource: "assignment" });
await db.update(issues).set({ executionRunId: runId }).where(eq(issues.id, f.issue.id));
await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" });
const [hidden] = await db.insert(issues).values({ companyId: f.companyId, title: "Another private task", visibility: "private",
assigneeAgentId: f.agentId, responsibleUserId: f.responsible }).returning();
const authority = new PaperclipRunnerToolAuthority(db, { companyId: f.companyId, agentId: f.agentId, issueId: f.issue.id, runId,
museRuntime: true, externalAuthorizingUserId: f.authorizer, assertBridgeAuthority: async () => {} });
const result = await authority.execute({ tool: "search_tasks", callId: "search", arguments: { limit: 10 } });
expect(result).toMatchObject({ tasks: [{ id: f.issue.id }] });
expect(JSON.stringify(result)).not.toContain(hidden.id);
await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
await expect(authority.execute({ tool: "search_tasks", callId: "search-again", arguments: {} })).rejects.toThrow("no longer available");
});
it.each(["create_task", "reassign_task"])("intersects native %s with current authorizer assignment scope before writes or wakes", async tool => {
vi.stubEnv("PAPERCLIP_RESPONSIBLE_USER_AUTHZ_MODE", "shadow");
const f = await fixture(), runId = randomUUID();
const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Assignment scope" }).returning();
const [protectedAgent] = await db.insert(agents).values({ companyId: f.companyId, name: "Protected target", status: "active", role: "engineer",
adapterType: "process", adapterConfig: {}, permissions: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning();
const [target] = await db.insert(issues).values({ companyId: f.companyId, projectId: project.id, title: "Reassignment target", status: "todo" }).returning();
await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, nativeIssueId: f.issue.id,
responsibleUserId: f.responsible, status: "running", runtimeMode: "native", invocationSource: "assignment" });
await db.update(issues).set({ status: "in_progress", projectId: project.id, executionRunId: runId }).where(eq(issues.id, f.issue.id));
await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "agent", principalId: f.agentId, status: "active", membershipRole: "member" });
await db.insert(principalPermissionGrants).values([
{ companyId: f.companyId, principalType: "agent", principalId: f.agentId, permissionKey: "tasks:assign" },
{ companyId: f.companyId, principalType: "user", principalId: f.responsible, permissionKey: "tasks:assign" },
]);
await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" });
// Instance-wide elevation must not become permission granted to Muse.
await db.insert(instanceUserRoles).values({ userId: f.authorizer, role: "instance_admin" });
const enqueueWakeup = vi.fn(async () => null);
const authority = new PaperclipRunnerToolAuthority(db, { companyId: f.companyId, agentId: f.agentId, issueId: f.issue.id, runId,
museRuntime: true, externalAuthorizingUserId: f.authorizer, assertBridgeAuthority: async () => {}, enqueueWakeup });
const call = { tool, callId: "authorizer-scope", arguments: tool === "create_task"
? { idempotencyKey: "scope", title: "Protected child", assigneeActorId: protectedAgent.id }
: { idempotencyKey: "scope", taskId: target.id, assigneeActorId: protectedAgent.id,
expectedAssigneeActorId: null, expectedStatusVersion: target.statusVersion, reason: "Scoped reassignment" } };
const assertUnchanged = async () => {
expect(await db.select({ id: issues.id }).from(issues).where(eq(issues.parentId, f.issue.id))).toEqual([]);
expect(await db.select({ owner: issues.assigneeAgentId, version: issues.statusVersion }).from(issues).where(eq(issues.id, target.id)))
.toEqual([{ owner: null, version: target.statusVersion }]);
expect(enqueueWakeup).not.toHaveBeenCalled();
};
await expect(authority.execute(call)).rejects.toThrow("Authorizing user is not authorized for tasks:assign");
await assertUnchanged();
const [grant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user", principalId: f.authorizer,
permissionKey: "tasks:assign_scope", scope: { agentIds: [protectedAgent.id], projectIds: [randomUUID()] } }).returning();
await expect(authority.execute(call)).rejects.toThrow("Authorizing user is not authorized for tasks:assign");
await assertUnchanged();
await db.update(principalPermissionGrants).set({ scope: { agentIds: [randomUUID()], projectIds: [project.id] } }).where(eq(principalPermissionGrants.id, grant.id));
await expect(authority.execute(call)).rejects.toThrow("Authorizing user is not authorized for tasks:assign");
await assertUnchanged();
await db.update(principalPermissionGrants).set({ scope: { agentIds: [protectedAgent.id], projectIds: [project.id] } }).where(eq(principalPermissionGrants.id, grant.id));
await expect(authority.execute(call)).resolves.toMatchObject({ disposition: "applied" });
expect(enqueueWakeup).toHaveBeenCalledTimes(1);
await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id));
await expect(authority.execute(call)).rejects.toThrow("Authorizing user is not authorized for tasks:assign");
expect(enqueueWakeup).toHaveBeenCalledTimes(1);
});
it.each(["enforce", "shadow"])("denies native document writes for a viewer authorizer in responsible-user %s mode", async mode => {
vi.stubEnv("PAPERCLIP_RESPONSIBLE_USER_AUTHZ_MODE", mode);
const f = await fixture(), runId = randomUUID();
await db.update(companyMemberships).set({ membershipRole: "viewer" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
await db.insert(issueAccessGrants).values({ issueId: f.issue.id, subjectType: "user", subjectId: f.authorizer, source: "explicit" });
await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, nativeIssueId: f.issue.id,
responsibleUserId: f.responsible, status: "running", runtimeMode: "native", invocationSource: "assignment" });
await db.update(issues).set({ executionRunId: runId }).where(eq(issues.id, f.issue.id));
const authority = new PaperclipRunnerToolAuthority(db, { companyId: f.companyId, agentId: f.agentId, issueId: f.issue.id, runId,
museRuntime: true, externalAuthorizingUserId: f.authorizer, assertBridgeAuthority: async () => {} });
const call = { tool: "write_document", callId: "viewer-write", arguments: { key: "report", title: "Report", body: "Must not be written", baseRevisionId: null, idempotencyKey: "viewer-write" } };
await expect(authority.execute(call)).rejects.toThrow("Authorizing user is not authorized for issue:mutate");
expect(await db.select({ id: issueDocuments.id }).from(issueDocuments).where(eq(issueDocuments.issueId, f.issue.id))).toEqual([]);
await db.update(companyMemberships).set({ membershipRole: "operator" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
await expect(authority.execute(call)).resolves.toMatchObject({ disposition: "applied" });
await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.authorizer)));
await expect(authorizationService(db).decide({ actor: f.actor, action: "tasks:assign",
resource: { type: "issue", companyId: f.companyId, assigneeAgentId: f.agentId }, scope: { assigneeAgentId: f.agentId } }))
.resolves.toMatchObject({ allowed: false, reason: "deny_missing_membership" });
});
it("applies external admission before a native reviewer changes the issue execution owner", async () => {
const f = await fixture(), runId = randomUUID();
const [run] = await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, status: "queued", runtimeMode: "native",
invocationSource: "assignment", contextSnapshot: { issueId: f.issue.id, nativeReviewInteractionId: randomUUID(), nativeReviewDecisionId: randomUUID() } }).returning();
await db.insert(externalAgentHolds).values({ companyId: f.companyId, agentId: f.agentId, provider: "muse", assignmentId: randomUUID(), bindingId: randomUUID(),
bindingGeneration: 1, runId: randomUUID(), workerUnknown: false, nativeEffectsUnknown: true });
await expect(claimQueuedNativeReviewRun(db, { run, claimedAt: new Date(), agentNameKey: "muse", claimValues: { status: "running" } }))
.rejects.toMatchObject({ status: 409, details: { code: "external_agent_overlap" } });
expect(await db.select({ status: heartbeatRuns.status }).from(heartbeatRuns).where(eq(heartbeatRuns.id, runId))).toEqual([{ status: "queued" }]);
expect(await db.select({ owner: issues.executionRunId }).from(issues).where(eq(issues.id, f.issue.id))).toEqual([{ owner: null }]);
});
it("advertises only first-party tools for Muse and rejects missing authorizer authority", async () => {
const f = await fixture();
const authority = new PaperclipRunnerToolAuthority(db, { companyId: f.companyId, agentId: f.agentId, issueId: f.issue.id, runId: randomUUID(), museRuntime: true, apiToolsEnabled: true, workspaceBridge: true });
const names = authority.definitions().map(tool => tool.name);
expect(names).toContain("get_identity"); expect(names).toContain("get_task_context"); expect(names).toContain("write_document");
for (const excluded of ["search_api", "call_api", "request_human_input", "connections_search", "workspace_command", "read_workspace_file", "register_deliverable", "get_workspace", "list_workspaces", "select_workspace", "prepare_repository"]) expect(names).not.toContain(excluded);
for (const tool of ["get_workspace", "list_workspaces", "select_workspace", "prepare_repository"]) {
await expect(authority.execute({ tool, callId: tool, arguments: {} })).rejects.toThrow("first-party runtime profile");
}
await expect(authority.execute({ tool: "get_task_context", callId: "test", arguments: {} })).rejects.toThrow("authorizer");
});
});
@@ -0,0 +1,26 @@
import { heartbeatRuns, type Db } from "@paperclipai/db";
import { museRunnerBroker } from "../muse-runner-broker.js";
import { logger } from "../../middleware/logger.js";
function record(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown> : {};
}
/** Call only after run projection commits and outside issue/run transactions.
* Reconcile the original binding, including after a provider configuration change.
* The broker retains failed-worker and unsettled-native-effect uncertainty.
*/
export async function reconcileMuseNativeTerminalRun(db: Db, run: Pick<typeof heartbeatRuns.$inferSelect,
"id" | "companyId" | "agentId" | "runtimeMode" | "status" | "runnerProfileJson">): Promise<void> {
if (run.runtimeMode !== "native" || !["succeeded", "failed", "cancelled", "timed_out", "interrupted"].includes(run.status)) return;
const execution = record(record(run.runnerProfileJson).nativeExecutionInput);
const provider = record(execution.provider);
const binding = record(provider.binding);
if (execution.schema !== "paperclip.native-execution-input.v8" || provider.kind !== "muse"
|| binding.companyId !== run.companyId || binding.agentId !== run.agentId
|| typeof binding.bindingId !== "string" || !binding.bindingId) return;
await museRunnerBroker(db).reconcileTerminalAssignments(run.companyId, run.agentId, binding.bindingId).catch(err => {
logger.warn({ err, runId: run.id, bindingId: binding.bindingId }, "Muse terminal assignment reconciliation deferred to maintenance");
});
}
@@ -331,7 +331,7 @@ describeEmbeddedPostgres("native question bridge", () => {
);
if (answered.kind !== "ask_user_questions") throw new Error("wrong question kind");
expect(await deliverNativeQuestionResponse(db, answered)).toBe("queued");
expect(resolve).toHaveBeenCalledWith(expect.objectContaining({ runId, requestId: "request-1", turnId: "turn-1",
expect(resolve).toHaveBeenCalledWith(expect.objectContaining({ runId, requestId: "request-1", turnId: "turn-1", commandId: `question_${cards[0]!.id}`,
resolution: { action: "submit", response: { schema: "paperclip.question_response.v1", answers: { color: { selectedOptionIds: ["green"] } } } },
}));
await db.update(heartbeatRuns).set({ status: "cancelled" }).where(eq(heartbeatRuns.id, runId));
@@ -1,3 +1,4 @@
import { withExternalAdmissionGuard, assertNoExternalOverlap } from "../../modules/external-agents/index.js";
import { agentWakeupRequests, heartbeatRuns, issues, type Db } from "@paperclipai/db";
import { and, eq } from "drizzle-orm";
import type { PgUpdateSetSource } from "drizzle-orm/pg-core";
@@ -33,6 +34,7 @@ export async function claimQueuedNativeReviewRun(db: Db, input: {
)).for("update");
if (!run || run.status !== "queued" || run.wakeupRequestId !== input.run.wakeupRequestId) return null;
if ((run.contextSnapshot as Record<string, unknown> | null)?.issueId !== issueId) return null;
await withExternalAdmissionGuard(tx, run.companyId, run.agentId, () => assertNoExternalOverlap(tx, run.companyId, run.agentId));
const locked = await claimNativeReviewExecutionLock(tx as unknown as Db, {
companyId: run.companyId, issueId, agentId: run.agentId, runId: run.id,
contextSnapshot: context, agentNameKey: input.agentNameKey, claimedAt: input.claimedAt,
@@ -1,3 +1,4 @@
import { reconcileMuseNativeTerminalRun } from "./muse-terminal-reconciliation.js";
import { eligibleIssueMonitorWait } from "../issue-monitors.js";
import { isNativePlanWaitResult, readNativePlanWait } from "./native-plan-wait.js";
import { activeIssueInteractionCondition } from "../issue-question-context.js";
@@ -1184,6 +1185,10 @@ export async function finalizeNativeRun(input: {
});
}
await resolveCommittedFinalizationRecovery(input.db, run, coordinator.issueId);
if (input.projectRunStatus) {
const [projectedRun] = await input.db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, run.id));
if (projectedRun) await reconcileMuseNativeTerminalRun(input.db, projectedRun);
}
return coordinator;
}
const [resultRow, contractRow] = await Promise.all([
@@ -1590,6 +1595,7 @@ export async function finalizeNativeRun(input: {
});
}
if (finalizationPhase === "committed") await resolveCommittedFinalizationRecovery(input.db, run, coordinator.issueId);
if (input.projectRunStatus && updatedRun) await reconcileMuseNativeTerminalRun(input.db, updatedRun);
return {
...coordinator,
phase: finalizationPhase,
@@ -2690,6 +2690,18 @@ describe("remote runner build metadata", () => {
prpTransportModes: ["dial_ws_loopback", "dial_wss", "listen_ws"],
};
it("accepts the current contract with the required transport", () => {
expect(() =>
assertRemoteRunnerBuildMetadata(current, "listen_ws"),
).not.toThrow();
});
it("requires the versioned Muse artifact capability and does not accept Dot capability as proof", () => {
expect(() => assertRemoteRunnerBuildMetadata(current, "listen_ws", "muse_external_v1")).toThrow("runner_remote_muse_capability_missing");
expect(() => assertRemoteRunnerBuildMetadata({ ...current, externalProviderCapabilities: ["openai_dot_mcp"] }, "listen_ws", "muse_external_v1")).toThrow("runner_remote_muse_capability_missing");
expect(() => assertRemoteRunnerBuildMetadata({ ...current, externalProviderCapabilities: ["openai_dot_mcp", "muse_external_v1"] }, "listen_ws", "muse_external_v1")).not.toThrow();
});
it("requires an explicit Dot bridge capability only for remote Dot execution", () => {
expect(() => assertRemoteRunnerBuildMetadata(current, "listen_ws", "openai_dot_mcp"))
.toThrow("runner_remote_dot_capability_missing");
@@ -1,3 +1,4 @@
import { museRunnerBroker } from "../muse-runner-broker.js";
import { stageRemoteLaunchAssets } from "./remote-launch-assets.js";
import { createRunPreparationTiming } from "../execution-preparation-timing.js";
import { configuredEnvironment } from "../../vendor/paperclip-runner/index.js";
@@ -1658,6 +1659,12 @@ const initializingSessionToolAuthorities = new Set<string>();
const executingRunnerdSessionScopes = new Map<string, string>();
const executingNativeOwnerScopes = new Map<string, symbol>();
async function assertExternalNativeRunAuthority(db: Db, execution: NativeExecutionInput, authorityDatabase: Db = db): Promise<void> {
if (execution.schema === "paperclip.native-execution-input.v6") return dotRunnerBroker(db).assertRunAuthority(execution);
if (execution.schema === "paperclip.native-execution-input.v8") return museRunnerBroker(db).assertRunAuthority(execution, authorityDatabase);
throw new Error("external_native_execution_required");
}
function nativeSessionKey(execution: NativeExecutionInput): string {
return (
execution.session.normalizedSessionId ??
@@ -6566,6 +6573,7 @@ export async function resolveNativeRuntimeRequest(input: {
requestId: string;
turnId: string;
resolution: HarnessRuntimeRequestResolution;
commandId?: string;
/**
* Revalidate the caller's durable lifecycle and authorization immediately
* before the provider mutation. Capability and snapshot reads above this
@@ -6616,7 +6624,7 @@ export async function resolveNativeRuntimeRequest(input: {
return { commandId: prior.commandId };
}
const commandId = `native-runtime-response:${randomUUID()}`;
const commandId = input.commandId ?? `native-runtime-response:${randomUUID()}`;
// Reserve the request key before yielding to authorization or provider I/O.
// This makes duplicate retries join one dispatch and makes a conflicting
// response fail closed even while the first authorization check is pending.
@@ -6632,6 +6640,7 @@ export async function resolveNativeRuntimeRequest(input: {
requestId: input.requestId,
turnId: input.turnId,
resolution: input.resolution,
commandId,
});
});
const resolution: NativeRuntimeRequestResolution = {
@@ -7569,10 +7578,10 @@ export async function executePaperclipNativeSession(input: {
await retireSupersededWarmNativeSessions(
ownerScope, nativeSessionScopeKey(input.execution),
);
if (input.execution.schema === "paperclip.native-execution-input.v6" && isCloudManagedInstance()
if ((input.execution.schema === "paperclip.native-execution-input.v6" || input.execution.schema === "paperclip.native-execution-input.v8") && isCloudManagedInstance()
&& (!input.useRunnerd || input.runnerExecutionTarget?.kind !== "remote"
|| input.runnerExecutionTarget.transport !== "sandbox")) {
throw new Error("dot_cloud_requires_managed_runner");
throw new Error(input.execution.provider.kind === "muse" ? "muse_cloud_requires_managed_runner" : "dot_cloud_requires_managed_runner");
}
if (!input.useRunnerd) {
return await executePaperclipNativeSessionWithinScope(input);
@@ -7594,7 +7603,7 @@ export async function executePaperclipNativeSession(input: {
// a later sweep from closing an owner this turn is about to acquire.
await closingWarmNativeSessions.get(sessionScopeId);
if (input.execution.schema === "paperclip.native-execution-input.v6") {
if (input.execution.schema === "paperclip.native-execution-input.v6" || input.execution.schema === "paperclip.native-execution-input.v8") {
return await executePaperclipNativeSessionWithinScope(input);
}
const workspaceRoot = input.execution.workspace.cwd;
@@ -7710,7 +7719,8 @@ async function executePaperclipNativeSessionWithinScope(
input.execution.provider.kind !== "claude_managed" &&
input.execution.provider.kind !== "aws_agentcore" &&
input.execution.provider.kind !== "acpx" &&
input.execution.schema !== "paperclip.native-execution-input.v6"
input.execution.schema !== "paperclip.native-execution-input.v6" &&
input.execution.schema !== "paperclip.native-execution-input.v8"
) {
throw new Error("paperclip_runner_provider_unsupported");
}
@@ -8681,7 +8691,7 @@ async function executePaperclipNativeSessionWithinScope(
});
try {
const prepareRunnerEnvironment = async () => {
if (input.managedGitHub && input.execution.schema !== "paperclip.native-execution-input.v6") {
if (input.managedGitHub && input.execution.schema !== "paperclip.native-execution-input.v6" && input.execution.schema !== "paperclip.native-execution-input.v8") {
if (!input.execution.workspace.cwd) throw new Error("native_workspace_required");
githubAccess ??= await timeController("github_access", () => createNativeGitHubAccess({
scope: input.execution.binding,
@@ -9712,7 +9722,7 @@ async function executePaperclipNativeSessionWithinScope(
resultJson: {
nativeResult: native.result as unknown as Record<string, unknown>,
nativeTerminal: native.terminal as unknown as Record<string, unknown>,
...(input.execution.schema === "paperclip.native-execution-input.v6" ? { providerAccounting: { usage: null, cost: null, externallyBilled: true }, externalStopConfirmed: false } : {}),
...((input.execution.schema === "paperclip.native-execution-input.v6" || input.execution.schema === "paperclip.native-execution-input.v8") ? { providerAccounting: { usage: null, cost: null, externallyBilled: true }, externalStopConfirmed: false } : {}),
...(providerFailure ? { nativeProviderFailure: providerFailure.diagnostic } : {}),
...(unclassifiedTurnFailure ? { nativeTurnFailure: unclassifiedTurnFailure } : {}),
...(native.goalRolloverRequired ? { goalRolloverRequired: true } : {}),
@@ -9724,7 +9734,7 @@ async function executePaperclipNativeSessionWithinScope(
...resolveNativeBilling(input.execution.provider, input.runnerEnvironment, input.billingIdentity),
model: input.execution.provider.model,
usage: normalizeNativeUsage(native.usage, { inputIncludesCacheReads: input.execution.provider.kind === "codex" }),
costUsd: input.execution.schema !== "paperclip.native-execution-input.v6" && normalizeNativeUsage(native.usage, { inputIncludesCacheReads: input.execution.provider.kind === "codex" })
costUsd: input.execution.schema !== "paperclip.native-execution-input.v6" && input.execution.schema !== "paperclip.native-execution-input.v8" && normalizeNativeUsage(native.usage, { inputIncludesCacheReads: input.execution.provider.kind === "codex" })
? nativeUsageCostUsd(native.usage, input.execution.provider) ?? null : null,
costUsdExact: typeof native.usage?.accountingCostUsdExact === "string" ? native.usage.accountingCostUsdExact : undefined,
costStatus: nativeAccountingComplete && native.usage?.accountingCostIncomplete !== true
@@ -10263,7 +10273,7 @@ function readRemoteProviderPackIdentity(packRoot: string, verifyControllerFiles:
export function assertRemoteRunnerBuildMetadata(
value: unknown,
requiredMode: "dial_wss" | "listen_ws",
requiredExternalProvider?: "openai_dot_mcp" | "grok_bot_mcp",
requiredExternalProvider?: "openai_dot_mcp" | "grok_bot_mcp" | "muse_external_v1",
): void {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("runner_remote_artifact_metadata_invalid");
@@ -10282,7 +10292,7 @@ export function assertRemoteRunnerBuildMetadata(
// Reject them before launch so preparation stages the bundled runner instead.
if (requiredExternalProvider && (!Array.isArray(metadata.externalProviderCapabilities)
|| !metadata.externalProviderCapabilities.includes(requiredExternalProvider))) {
throw new Error("runner_remote_dot_capability_missing: install the current Paperclip Runner artifact");
throw new Error(requiredExternalProvider === "muse_external_v1" ? "runner_remote_muse_capability_missing: install the current Paperclip Runner artifact" : "runner_remote_dot_capability_missing: install the current Paperclip Runner artifact");
}
const sessionCapabilities = Array.isArray(metadata.durableSessionCapabilities)
? metadata.durableSessionCapabilities
@@ -11342,13 +11352,16 @@ export async function createRunnerdBackend(input: {
/** Dot uses the same managed process launcher and authenticated PRP transports.
* Its execution contract still exposes no workspace, shell, or provider secrets. */
async function prepareRemoteDotRunner({ input, target, runner, root, identity }: {
async function prepareRemoteExternalRunner({ input, target, runner, root, identity }: {
input: Parameters<typeof createRunnerdBackend>[0];
target: Extract<AdapterExecutionTarget, { kind: "remote" }>;
runner: CommandManagedRuntimeRunner;
root: string;
identity: NonNullable<NonNullable<Parameters<typeof createNativeSessionBackend>[1]>["dotRunnerOptions"]>["identity"];
}) {
const muse = input.execution.provider.kind === "muse";
const checkpointFile = muse ? "muse-provider-state.json" : "dot-provider-state.json";
const checkpointSchema = muse ? "paperclip.runner.muse-provider-state.v1" : "paperclip.runner.dot-provider-state.v1";
const runnerBinary = input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary();
const remoteRoot = posix.join(target.remoteCwd, ".paperclip-runtime", "paperclip-runner");
const remoteBinary = posix.join(remoteRoot, "bin", "paperclip-runnerd");
@@ -11370,7 +11383,7 @@ async function prepareRemoteDotRunner({ input, target, runner, root, identity }:
}
const metadata = await runner.execute({ command: remoteBinary, args: ["--build-metadata"], bypassSession: true, timeoutMs: 30000 });
if (metadata.exitCode !== 0 || metadata.timedOut) throw new Error("runner_remote_artifact_verification_failed");
assertRemoteRunnerBuildMetadata(JSON.parse(metadata.stdout), requiredMode, input.execution.provider.kind === "grok_bot" ? "grok_bot_mcp" : "openai_dot_mcp");
assertRemoteRunnerBuildMetadata(JSON.parse(metadata.stdout), requiredMode, muse ? "muse_external_v1" : input.execution.provider.kind === "grok_bot" ? "grok_bot_mcp" : "openai_dot_mcp");
const checksum = await runner.execute({ command: "sh", args: ["-c",
'if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1"; else shasum -a 256 "$1"; fi', "paperclip-dot-artifact", remoteBinary],
bypassSession: true, timeoutMs: 10000 });
@@ -11381,16 +11394,16 @@ async function prepareRemoteDotRunner({ input, target, runner, root, identity }:
const readProviderState = async () => {
const result = await runner.execute({ command: "sh", args: ["-c",
'set -eu; if test ! -e "$1" && test ! -L "$1"; then exit 3; fi; test -f "$1" && test ! -L "$1"; test "$(wc -c < "$1")" -le 33554432; base64 < "$1"',
"paperclip-dot-checkpoint", posix.join(runnerStateDirectory, "dot-provider-state.json")], bypassSession: true, timeoutMs: 10000 });
"paperclip-dot-checkpoint", posix.join(runnerStateDirectory, checkpointFile)], bypassSession: true, timeoutMs: 10000 });
if (result.exitCode === 3 && !result.timedOut) return null;
if (result.exitCode !== 0 || result.timedOut) throw new Error("dot_provider_checkpoint_unavailable");
const state = record(JSON.parse(Buffer.from(result.stdout.replace(/\s+/g, ""), "base64").toString("utf8")));
if (state.schema !== "paperclip.runner.dot-provider-state.v1" || state.runId !== identity.runId
if (state.schema !== checkpointSchema || state.runId !== identity.runId
|| state.sessionId !== identity.normalizedSessionId || state.turnId !== identity.turnId) throw new Error("dot_runner_checkpoint_authority_mismatch");
// Retain inspectable recovery evidence on the controller without changing
// which execution target is authoritative for replay.
mkdirSync(join(root, "runner"), { recursive: true, mode: 0o700 });
const saved = join(root, "runner", "dot-provider-state.json");
const saved = join(root, "runner", checkpointFile);
const temporary = `${saved}.${randomUUID()}.tmp`;
writeFileSync(temporary, JSON.stringify(state), { mode: 0o600 });
renameSync(temporary, saved);
@@ -11444,9 +11457,6 @@ async function createRunnerdBackendWithinSessionClaim(
retainedTransition?: VerifiedWarmTransitionBinding,
): Promise<NativeSessionBackend & { bindManagedSession(session: NativeSession): NativeSession }> {
const timeController = createRunPreparationTiming(input.execution.binding.runId, "native.controller");
if (input.execution.schema === "paperclip.native-execution-input.v8") {
throw new Error("paperclip_runner_provider_unsupported");
}
let recoveryPending = retainedTransition !== undefined;
const target = input.runnerExecutionTarget ?? { kind: "local" as const };
const remoteTarget = target.kind === "remote" ? target : null;
@@ -11468,7 +11478,7 @@ async function createRunnerdBackendWithinSessionClaim(
input.execution.binding,
));
const pinnedSkills = new Set("runtimeContext" in input.execution ? input.execution.runtimeContext.skills.map((skill) => skill.key) : []);
const connectorAssignments = connectorToolDefinitionsForSkills(pinnedSkills);
const connectorAssignments = input.execution.provider.kind !== "muse" ? connectorToolDefinitionsForSkills(pinnedSkills) : [];
const reviewRun = await input.db.select({ contextSnapshot: heartbeatRuns.contextSnapshot })
.from(heartbeatRuns).where(and(
eq(heartbeatRuns.id, input.execution.binding.runId),
@@ -11533,7 +11543,10 @@ async function createRunnerdBackendWithinSessionClaim(
taskAttachmentRead: dotAttachmentActor?.config.dotAttachmentAccess === true,
dotRuntime: input.execution.schema === "paperclip.native-execution-input.v6",
firstPartyOnly: input.execution.provider.kind === "grok_bot",
assertBridgeAuthority: input.execution.schema === "paperclip.native-execution-input.v6" ? () => dotRunnerBroker(input.db).assertRunAuthority(input.execution as import("../../vendor/paperclip-runner/index.js").NativeExecutionInputV6) : undefined,
museRuntime: input.execution.provider.kind === "muse",
externalAuthorizingUserId: input.execution.provider.kind === "muse"
? await museRunnerBroker(input.db).authorizingUserIdForBinding(input.execution.provider.binding) : undefined,
assertBridgeAuthority: input.execution.schema === "paperclip.native-execution-input.v6" || input.execution.schema === "paperclip.native-execution-input.v8" ? (database = input.db) => assertExternalNativeRunAuthority(input.db, input.execution, database) : undefined,
workspaceRoot: input.dotWorkspaceRoot ?? remoteTarget?.remoteCwd ?? input.execution.workspace.cwd ?? undefined,
executionTargetKind: target.kind,
readRemoteWorkspaceFile: remoteTarget && remoteCommandRunner
@@ -11573,34 +11586,42 @@ async function createRunnerdBackendWithinSessionClaim(
input.durableEnvironmentLeaseId ??
input.execution.binding.executionWorkspaceId;
mkdirSync(root, { recursive: true, mode: 0o700 });
if (input.execution.schema === "paperclip.native-execution-input.v6") {
if (input.execution.schema === "paperclip.native-execution-input.v6" || input.execution.schema === "paperclip.native-execution-input.v8") {
if (target.kind === "remote" && input.dotWorkspaceRoot) throw new Error("dot_remote_workspace_access_unavailable");
const remoteDot = target.kind === "remote"
? await prepareRemoteDotRunner({ input, target, runner: remoteCommandRunner!, root,
? await prepareRemoteExternalRunner({ input, target, runner: remoteCommandRunner!, root,
identity: { runnerInstanceId: effectiveRunnerInstanceId, environmentLeaseId: effectiveEnvironmentLeaseId,
runId: input.execution.binding.runId, normalizedSessionId: nativeSessionKey(input.execution),
turnId: `turn-${input.execution.binding.runId}`, itemId: `item-${input.execution.binding.runId}` } })
: null;
const recoveredProcess = input.restartRecovery?.kind === "reattach_existing_runner" ? input.restartRecovery.process : null;
const backend = createNativeSessionBackend(input.execution, {
onSpawn: input.onSpawn, dynamicTools,
dynamicToolHandler: async call => { await dotRunnerBroker(input.db).assertRunAuthority(input.execution as import("../../vendor/paperclip-runner/index.js").NativeExecutionInputV6); return authorityEpoch.execute(call); },
completionFeedback: async result => { await dotRunnerBroker(input.db).assertRunAuthority(input.execution as import("../../vendor/paperclip-runner/index.js").NativeExecutionInputV6); await timeController("tool_authority", () => authorityEpoch.definitions()); return nativeCompletionFeedback(input.db, input.execution.binding.runId, result); },
dotRunnerOptions: {
const externalExecution = input.execution;
const externalOptions = {
stateDirectory: root, runnerBinary: remoteDot?.runnerBinary ?? resolvePaperclipRunnerBinary(),
...(remoteDot ?? {}),
identity: { runnerInstanceId: effectiveRunnerInstanceId, environmentLeaseId: effectiveEnvironmentLeaseId,
runId: input.execution.binding.runId, normalizedSessionId: nativeSessionKey(input.execution),
turnId: `turn-${input.execution.binding.runId}`, itemId: `item-${input.execution.binding.runId}` },
port: dotRunnerBroker(input.db).port(input.execution),
controlPlaneRegistration: remoteDot?.controlPlaneRegistration ?? createNativeRunnerExecutionDriver({
target, binding: input.execution.binding, trace: input.trace, onLog: input.onLog,
}).controlPlaneRegistration,
adoptExistingRunner: remoteDot?.adoptExistingRunner ?? (recoveredProcess ? { ...recoveredProcess,
isAlive: () => verifiedRecoveryProcessIsAlive(recoveredProcess),
signal: signal => signalVerifiedRecoveryProcess(recoveredProcess, signal) } : undefined),
signal: (signal: NodeJS.Signals = "SIGTERM") => signalVerifiedRecoveryProcess(recoveredProcess, signal) } : undefined),
};
const backendOptions = {
onSpawn: input.onSpawn, dynamicTools,
dynamicToolHandler: async (call: Parameters<SessionToolAuthorityEpoch["execute"]>[0]) => {
await assertExternalNativeRunAuthority(input.db, externalExecution); return authorityEpoch.execute(call);
},
});
completionFeedback: async (result: PrpStructuredRunResult) => {
await assertExternalNativeRunAuthority(input.db, externalExecution); await timeController("tool_authority", () => authorityEpoch.definitions());
return nativeCompletionFeedback(input.db, externalExecution.binding.runId, result);
},
};
const backend = externalExecution.schema === "paperclip.native-execution-input.v8"
? createNativeSessionBackend(externalExecution, { ...backendOptions, museRunnerOptions: { ...externalOptions, port: museRunnerBroker(input.db).port(externalExecution) } })
: createNativeSessionBackend(externalExecution, { ...backendOptions, dotRunnerOptions: { ...externalOptions, port: dotRunnerBroker(input.db).port(externalExecution) } });
const prior = sessionToolAuthorityEpochs.get(sessionScopeId); if (prior) prior.revoke();
sessionToolAuthorityEpochs.set(sessionScopeId, authorityEpoch);
return { ...backend, descriptor: () => backend.descriptor(), openSession: input => backend.openSession(input),
@@ -1,5 +1,6 @@
import { DELIVERY_QUEUES, notifyDeliveryWork } from "../delivery-work-notifications.js";
import { skillAccessService } from "../skill-access.js";
import { withExternalExecutionAdmission, type ExternalAdmissionTransaction } from "../../modules/external-agents/index.js";
import { callTaskWorkspaceTool } from "../task-workspace-tools.js";
import { TaskAttachmentReadCache, TASK_ATTACHMENT_DEFINITIONS, TASK_ATTACHMENT_SCHEMAS, listTaskAttachments, readTaskAttachment } from "./runner-task-attachments.js";
import { RUNNER_BRIDGE_SCHEMAS, runnerBridgeDefinitions, readAssignedSkill, executeWorkspaceTool, readWorkspaceUploadFile, settleRunnerBridgeRead, assertRunnerWorkspacePathAllowed } from "./runner-bridge-tools.js";
@@ -22,7 +23,7 @@ import { assertAssignableAgent } from "../agent-assignability.js";
import { approvalReadSqlCondition, canActorReadApproval, authorizationService, issueReadSqlCondition, type AuthorizationActor } from "../authorization.js";
import { resolveCoreTrustPreset } from "../trust-preset-resolver.js";
import { normalizeIssueExecutionPolicy, redactIssueMonitorExternalRef } from "../issue-execution-policy.js";
import { buildLowTrustSourceTrust } from "../source-trust.js";
import { buildLowTrustSourceTrust, isLowTrustQuarantined, LOW_TRUST_QUARANTINED_BODY, resolveActorSourceTrustForIssue, sanitizeQuarantinedCommentForHigherTrust } from "../source-trust.js";
import { handoffPlanContext } from "./handoff-plan-context.js";
import { callCreateSkillTool, callUpdateSkillTool } from "../skill-tools.js";
import { callProjectTool } from "../project-tools.js";
@@ -52,7 +53,7 @@ import { badRequest, forbidden, notFound, HttpError } from "../../errors.js";
import { runnerApiCatalog, searchRunnerApi } from "./runner-api-catalog.js";
import { executeRunnerApi, validateRunnerApiCall, RUNNER_API_MAX_BYTES, type RunnerApiFile } from "./runner-api-client.js";
import { acquireRunnerApiResponseSlot, runnerApiCompanyCaptureMaxBytes, RUNNER_API_RESPONSE_MAX_BYTES, RUNNER_API_RESPONSE_RUN_MAX_BYTES, RunnerApiResponseLimitError } from "./runner-api-response-limits.js";
import { and, desc, eq, isNull, notInArray, sql } from "drizzle-orm";
import { and, desc, eq, inArray, isNull, notInArray, sql } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import {
authUsers,
@@ -123,10 +124,22 @@ const IMPLEMENTED_OPERATIONS = new Set([
"list_agents", "get_agent", "list_approvals", "get_approval", "get_approval_context",
]);
/** Closed first-party native Muse profile. No connector, generic API, credential or workspace grants. */
export const MUSE_NATIVE_OPERATIONS = new Set([
"get_identity", "list_people",
"submit_complaint", "submit_suggestion", "get_task_context", "get_task_history", "search_tasks", "report_progress", "set_task_title", "set_task_monitor",
"create_task", "reassign_task", "set_dependencies", "create_project", "list_projects", "list_documents", "read_document", "list_document_revisions", "write_document",
"list_agents", "get_agent", "list_approvals", "get_approval", "get_approval_context", "read_current_wake_comments", "resolve_review",
]);
const NATIVE_REVIEW_READ_TOOLS = new Set([
"get_task_context", "get_task_history", "list_documents", "read_document", "list_document_revisions",
]);
type NativeIssueDocument = NonNullable<Awaited<ReturnType<ReturnType<typeof documentService>["getIssueDocumentByKey"]>>>;
type NativeDocumentContent = { id: string | null; documentId: string; body: string; changeSummary?: string | null };
const UNKNOWN_DOCUMENT_REVISION_BODY = "[Document revision content omitted: historical source trust could not be established.]";
type Binding = {
/** Server-derived scope for one addressed native completion review. */
nativeReview?: NativeReviewAssignmentContext;
@@ -148,12 +161,14 @@ type Binding = {
workspaceBridge?: boolean;
/** Pinned provider identity; configuration edits cannot remove API file guards. */
dotRuntime?: boolean;
museRuntime?: boolean;
externalAuthorizingUserId?: string;
/** Grok retains its provider apps; this release exposes first-party Paperclip tools only. */
firstPartyOnly?: boolean;
/** Operator-approved Dot attachment access, pinned when the turn opens. */
taskAttachmentRead?: boolean;
runtimeContext?: NativeRuntimeContextSnapshot;
assertBridgeAuthority?: () => Promise<void>;
assertBridgeAuthority?: (database?: Db) => Promise<void>;
executionTargetKind?: "local" | "remote";
readRemoteWorkspaceFile?: RemoteWorkspaceFileReader;
currentWakeComments?: CurrentWakeCommentsBinding;
@@ -299,7 +314,8 @@ export class PaperclipRunnerToolAuthority {
...(this.binding.connectorAssignments ?? []).flatMap((assignment) => assignment.tools)];
const assignedTools = this.binding.firstPartyOnly ? [] : this.binding.assignedMcpTools?.definitions((tools) =>
runnerCodexDynamicToolsFit([...connectionTools, ...tools, ...definitions])) ?? [];
return [...connectionTools, ...assignedTools, ...definitions];
const all = [...connectionTools, ...assignedTools, ...definitions];
return this.binding.museRuntime ? all.filter(definition => MUSE_NATIVE_OPERATIONS.has(String(definition.name))) : all;
}
async execute(call: {
@@ -307,6 +323,10 @@ export class PaperclipRunnerToolAuthority {
callId: string;
arguments: unknown;
}): Promise<unknown> {
if (this.binding.museRuntime) {
await this.binding.assertBridgeAuthority?.();
if (!MUSE_NATIVE_OPERATIONS.has(call.tool)) throw forbidden("This operation is outside the Muse first-party runtime profile");
}
if (this.binding.firstPartyOnly && (EXTERNAL_WORKSPACE_OPERATIONS.has(call.tool) || call.tool.startsWith("workspace_"))) throw forbidden("Paperclip workspace access is unavailable to this Grok Bot session.");
if (this.binding.nativeReview) {
if (call.tool === "resolve_review") return this.#resolveReview(call.arguments);
@@ -537,7 +557,7 @@ export class PaperclipRunnerToolAuthority {
case "select_workspace":
case "prepare_repository": {
const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
const token = this.#apiToken(context);
if (!apiUrl || !token) throw new Error("Workspace tool authentication is unavailable");
return callTaskWorkspaceTool({ name: call.tool, arguments: input, apiUrl, token, companyId: this.binding.companyId, issueId: this.binding.issueId, workspaceRoot: this.binding.workspaceRoot });
}
@@ -593,6 +613,9 @@ export class PaperclipRunnerToolAuthority {
))
.orderBy(desc(issues.createdAt), desc(issues.id))
.limit(101);
const readableChildren = this.binding.museRuntime
? await this.#readableMuseTaskIds(context.run, childTasks.map(task => task.id)) : null;
const visibleChildTasks = readableChildren ? childTasks.filter(task => readableChildren.has(task.id)) : childTasks;
return {
company: { id: this.binding.companyId },
actor: redactedActor(context.actor),
@@ -600,7 +623,7 @@ export class PaperclipRunnerToolAuthority {
...this.binding, conversationMode: isConversation(context.issue),
}),
activeTask: redactedTask(context.issue),
childTasks: childTasks.slice(0, 100).map(redactedTask),
childTasks: visibleChildTasks.slice(0, 100).map(redactedTask),
childTasksTruncated: childTasks.length > 100,
delegationGuidance: "Reuse existing child tasks for delegated work. Inspect a completed child's comments and documents before creating another task for the same deliverable. Use search_tasks if the child task list is truncated.",
run: {
@@ -609,9 +632,9 @@ export class PaperclipRunnerToolAuthority {
invocationSource: context.run.invocationSource,
},
connectionGuidance: CONNECTION_INTENT_AGENT_GUIDANCE,
acceptedPlan: await this.#acceptedPlan(context.run.contextSnapshot),
sourcePlanApproval: await handoffPlanContext(this.db, context.issue, this.#privacyActor(context.run)),
childReviewOutcomes: await childReviewOutcomes(this.db, this.binding.companyId, this.binding.issueId, this.#privacyActor(context.run)),
acceptedPlan: await this.#acceptedPlan(context.run.contextSnapshot, context.issue),
sourcePlanApproval: await this.#sourcePlanApproval(context),
childReviewOutcomes: await this.#readableChildReviewOutcomes(context.run),
...(this.binding.nativeReview ? {
assignedReview: (await getNativeReviewAssignment(this.db, {
...this.binding, contextSnapshot: this.binding.nativeReview,
@@ -621,11 +644,14 @@ export class PaperclipRunnerToolAuthority {
};
}
case "get_task_history": {
const readerSourceTrust = await resolveActorSourceTrustForIssue({ db: this.db, issue: context.issue,
actor: { actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId } });
const limit = boundedLimit(input.limit);
const comments = await this.db.select({
id: issueComments.id,
body: issueComments.body,
origin: issueComments.origin,
sourceTrust: issueComments.sourceTrust,
authorAgentId: issueComments.authorAgentId,
authorUserId: issueComments.authorUserId,
createdAt: issueComments.createdAt,
@@ -637,26 +663,40 @@ export class PaperclipRunnerToolAuthority {
))
.orderBy(desc(issueComments.createdAt))
.limit(limit);
return { comments: comments.reverse().map(comment => ({ ...comment, body: fastResponseHistoryBody(comment) })) };
return { comments: comments.reverse().map(comment => {
const visible = isLowTrustQuarantined(readerSourceTrust) ? comment : sanitizeQuarantinedCommentForHigherTrust(comment);
return { ...visible, body: fastResponseHistoryBody(visible) };
}) };
}
case "search_tasks": {
const tasks = await issueService(this.db).list(this.binding.companyId, { readCondition: await issueReadSqlCondition(this.db, this.#privacyActor(context.run)) });
const query = typeof input.query === "string" ? input.query.toLowerCase() : "";
const statuses = Array.isArray(input.statuses) ? new Set(input.statuses.filter((value): value is string => typeof value === "string")) : null;
return { tasks: tasks.filter((task) =>
const matching = tasks.filter((task) =>
(!query || `${task.identifier} ${task.title} ${task.description ?? ""}`.toLowerCase().includes(query))
&& (!statuses || statuses.size === 0 || statuses.has(task.status))
).slice(0, boundedLimit(input.limit)).map(redactedTask) };
);
const readable = this.binding.museRuntime ? await this.#readableMuseTaskIds(context.run, matching.map(task => task.id)) : null;
return { tasks: matching.filter(task => !readable || readable.has(task.id))
.slice(0, boundedLimit(input.limit)).map(redactedTask) };
}
case "list_documents":
return { documents: await documentService(this.db).listIssueDocuments(this.binding.issueId) };
return { documents: await this.#visibleMuseDocuments(context.issue,
await documentService(this.db).listIssueDocuments(this.binding.issueId)) };
case "read_document": {
const document = await documentService(this.db).getIssueDocumentByKey(this.binding.issueId, requiredString(input.key));
if (!document) throw new Error("paperclip_runner_document_not_found");
return { document };
const [visible] = await this.#visibleMuseDocuments(context.issue, [document]);
return { document: visible };
}
case "list_document_revisions": {
const key = requiredString(input.key);
const service = documentService(this.db);
const revisions = await service.listIssueDocumentRevisions(this.binding.issueId, key);
if (!this.binding.museRuntime || revisions.length === 0) return { revisions };
const document = await service.getIssueDocumentByKey(this.binding.issueId, key);
return { revisions: await this.#projectMuseDocumentContent(context.issue, revisions, document ? [document] : []) };
}
case "list_document_revisions":
return { revisions: await documentService(this.db).listIssueDocumentRevisions(this.binding.issueId, requiredString(input.key)) };
case "write_document": return this.#writeDocument(input);
case "list_agents":
return { actors: (await agentService(this.db).list(this.binding.companyId)).map(redactedActor) };
@@ -681,7 +721,8 @@ export class PaperclipRunnerToolAuthority {
eq(issues.companyId, this.binding.companyId),
await issueReadSqlCondition(this.db, this.#privacyActor(context.run)),
));
return { approval, tasks: tasks.map((row) => row.issue) };
const readable = this.binding.museRuntime ? await this.#readableMuseTaskIds(context.run, tasks.map(row => row.issue.id)) : null;
return { approval, tasks: tasks.filter(row => !readable || readable.has(row.issue.id)).map(row => row.issue) };
}
case "report_progress": return this.#reportProgress(input);
case "request_human_input": return this.#requestHumanInput(input,
@@ -745,7 +786,7 @@ export class PaperclipRunnerToolAuthority {
run: { responsibleUserId: string | null; contextSnapshot: unknown };
}) {
return createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType,
this.binding.runId, context.run.responsibleUserId, this.#apiScope(context));
this.binding.runId, context.run.responsibleUserId, this.#apiScope(context), this.binding.externalAuthorizingUserId);
}
async #callApi(callId: string, value: unknown): Promise<unknown> {
@@ -1019,12 +1060,32 @@ export class PaperclipRunnerToolAuthority {
return approval;
}
async #readableMuseTaskIds(run: { responsibleUserId: string | null }, taskIds: string[]): Promise<Set<string>> {
const ids = [...new Set(taskIds)];
if (!ids.length) return new Set();
const decisions = await authorizationService(this.db).decideReads(this.#privacyActor(run),
ids.map(issueId => ({ type: "issue", companyId: this.binding.companyId, issueId })));
return new Set(ids.filter((_id, index) => decisions[index]?.allowed));
}
async #readableChildReviewOutcomes(run: { responsibleUserId: string | null }) {
const outcomes = await childReviewOutcomes(this.db, this.binding.companyId, this.binding.issueId, this.#privacyActor(run));
if (!this.binding.museRuntime) return outcomes;
const taskId = (outcome: (typeof outcomes)[number]) => record(outcome.result).childTaskId;
const readable = await this.#readableMuseTaskIds(run, outcomes.flatMap(outcome => {
const id = taskId(outcome); return typeof id === "string" ? [id] : [];
}));
return outcomes.filter(outcome => { const id = taskId(outcome); return typeof id === "string" && readable.has(id); });
}
#privacyActor(run: { responsibleUserId: string | null }): AuthorizationActor {
return { type: "agent", agentId: this.binding.agentId, companyId: this.binding.companyId,
runId: this.binding.runId, source: "agent_jwt", onBehalfOfUserId: run.responsibleUserId };
runId: this.binding.runId, source: "agent_jwt", onBehalfOfUserId: run.responsibleUserId,
...(this.binding.museRuntime ? { authorizingUserId: this.binding.externalAuthorizingUserId } : {}) };
}
async #boundContext() {
if (this.binding.museRuntime && !this.binding.externalAuthorizingUserId) throw forbidden("Muse authorizer is unavailable");
const [row] = await this.db.select({ issue: issues, actor: agents, run: heartbeatRuns })
.from(heartbeatRuns)
.innerJoin(issues, eq(issues.id, this.binding.issueId))
@@ -1116,11 +1177,13 @@ export class PaperclipRunnerToolAuthority {
idempotencyKey,
input,
async (tx, context) => {
const sourceTrust = this.binding.museRuntime ? await resolveActorSourceTrustForIssue({ db: tx, issue: context.issue,
actor: { actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId } }) : undefined;
const comment = await issueService(tx).addComment(
this.binding.issueId,
body,
{ agentId: this.binding.agentId, runId: this.binding.runId },
{ authorizationReason: "paperclip_runner_protocol" },
{ authorizationReason: "paperclip_runner_protocol", sourceTrust },
tx,
);
const result = { commentId: comment.id, issueId: this.binding.issueId, disposition: "applied" };
@@ -1154,7 +1217,9 @@ export class PaperclipRunnerToolAuthority {
async #writeDocument(input: Record<string, unknown>): Promise<unknown> {
const idempotencyKey = requiredString(input.idempotencyKey);
let publication: Awaited<ReturnType<typeof persistActivity>>["publication"] | null = null;
const result = await this.#withMutationReceipt("write_document", idempotencyKey, input, async (tx) => {
const result = await this.#withMutationReceipt("write_document", idempotencyKey, input, async (tx, context) => {
const sourceTrust = this.binding.museRuntime ? await resolveActorSourceTrustForIssue({ db: tx, issue: context.issue,
actor: { actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId } }) : undefined;
const write = await documentService(tx).upsertIssueDocument({
issueId: this.binding.issueId,
key: requiredString(input.key),
@@ -1167,6 +1232,7 @@ export class PaperclipRunnerToolAuthority {
: requiredString(input.changeSummary),
createdByAgentId: this.binding.agentId,
createdByRunId: this.binding.runId,
sourceTrust,
});
const activity = await persistActivity(tx, {
companyId: this.binding.companyId,
@@ -1240,10 +1306,14 @@ export class PaperclipRunnerToolAuthority {
}) => {
const parentIssueId = context.issue.conversationAgentId ? null : context.issue.id;
const projectId = nullableProviderId(input.projectId) ?? (parentIssueId ? context.issue.projectId : null);
if (this.binding.museRuntime && projectId) {
const access = await authorizationService(tx).decide({ actor: this.#privacyActor(context.run), action: "project:read",
resource: { type: "project", companyId: this.binding.companyId, projectId } });
if (!access.allowed) throw forbidden("Project is unavailable to this Muse run");
}
const scope = { projectId, parentIssueId, assigneeAgentId, assigneeUserId };
const decision = await authorizationService(tx).decide({
actor: { type: "agent", source: "agent_jwt", companyId: this.binding.companyId,
agentId: this.binding.agentId, runId: this.binding.runId, onBehalfOfUserId: context.run.responsibleUserId },
actor: this.#privacyActor(context.run),
action: "tasks:assign",
resource: { type: "issue", companyId: this.binding.companyId, ...scope },
scope,
@@ -1436,16 +1506,24 @@ export class PaperclipRunnerToolAuthority {
return record(prior.details?.receipt);
};
const authorize = async (tx: Db, run: typeof heartbeatRuns.$inferSelect) => {
const [target] = await tx.select().from(issues).where(and(eq(issues.id, taskId), eq(issues.companyId, this.binding.companyId))).for("update");
// Muse idle comments lock this target before admission. Never wait for
// their target while holding the caller's admission lock.
const [target] = await tx.select().from(issues).where(and(
eq(issues.id, taskId), eq(issues.companyId, this.binding.companyId),
)).for("update", this.binding.museRuntime ? { noWait: true } : undefined).catch((error: unknown) => {
if (this.binding.museRuntime && (record(error).code === "55P03" || record(record(error).cause).code === "55P03")) {
throw new HttpError(409, "paperclip_runner_reassignment_conflict: target is busy; retry after current work settles");
}
throw error;
});
if (!target) throw new Error("paperclip_runner_task_not_found");
const actor = { type: "agent" as const, source: "agent_jwt" as const, companyId: this.binding.companyId,
agentId: this.binding.agentId, runId: this.binding.runId, onBehalfOfUserId: run.responsibleUserId };
const actor = this.#privacyActor(run);
for (const action of ["issue:read", "tasks:assign"] as const) {
const decision = await authorizationService(tx).decide({ actor, action, resource: {
type: "issue", companyId: this.binding.companyId, issueId: taskId, projectId: target.projectId,
parentIssueId: target.parentId, assigneeAgentId: action === "tasks:assign" ? assigneeAgentId : target.assigneeAgentId,
assigneeUserId: action === "tasks:assign" ? assigneeUserId : target.assigneeUserId, status: target.status,
} });
}, scope: { issueId: taskId, projectId: target.projectId, parentIssueId: target.parentId, assigneeAgentId, assigneeUserId } });
if (!decision.allowed) throw forbidden(decision.explanation);
}
if (assigneeAgentId) await assertAssignableAgent(tx, this.binding.companyId, assigneeAgentId, { kind: "work" });
@@ -1681,7 +1759,12 @@ export class PaperclipRunnerToolAuthority {
throw new Error("paperclip_runner_tool_input_invalid");
}
const blockedByIssueIds = input.blockedByTaskIds.map(requiredString);
return this.#withMutationReceipt("set_dependencies", idempotencyKey, input, async (tx) => {
return this.#withMutationReceipt("set_dependencies", idempotencyKey, input, async (tx, context) => {
if (this.binding.museRuntime) for (const issueId of blockedByIssueIds) {
const decision = await authorizationService(tx).decide({ actor: this.#privacyActor(context.run), action: "issue:read",
resource: { type: "issue", companyId: this.binding.companyId, issueId } });
if (!decision.allowed) throw forbidden("Dependency task is unavailable");
}
const updated = await issueService(tx).update(this.binding.issueId, {
blockedByIssueIds,
actorAgentId: this.binding.agentId,
@@ -1874,7 +1957,80 @@ export class PaperclipRunnerToolAuthority {
return result;
}
async #acceptedPlan(contextSnapshot: unknown): Promise<{
async #visibleMuseDocuments(issue: typeof issues.$inferSelect, documents: NativeIssueDocument[]) {
if (!this.binding.museRuntime) return documents;
const visible = await this.#projectMuseDocumentContent(issue, documents.map(document => ({
id: document.latestRevisionId, documentId: document.id, body: document.body ?? "",
})), documents);
return documents.map((document, index) => ({ ...document, body: visible[index]!.body }));
}
async #projectMuseDocumentContent<T extends NativeDocumentContent>(
readerIssue: typeof issues.$inferSelect,
entries: T[],
currentDocuments: NativeIssueDocument[],
sourceIssueId = this.binding.issueId,
): Promise<T[]> {
if (!this.binding.museRuntime || entries.length === 0) return entries;
const readerSourceTrust = await resolveActorSourceTrustForIssue({ db: this.db, issue: readerIssue,
actor: { actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId } });
if (isLowTrustQuarantined(readerSourceTrust)) return entries;
const ids = entries.flatMap(entry => entry.id ? [entry.id] : []);
const provenance = ids.length === 0 ? [] : await this.db.select({
id: documentRevisions.id, documentId: documentRevisions.documentId,
createdByAgentId: documentRevisions.createdByAgentId, createdByUserId: documentRevisions.createdByUserId,
createdByRunId: documentRevisions.createdByRunId, userId: authUsers.id, writerCompanyId: agents.companyId,
runId: heartbeatRuns.id, runCompanyId: heartbeatRuns.companyId, runAgentId: heartbeatRuns.agentId,
runIssueId: heartbeatRuns.nativeIssueId, runExecutionPolicy: sql<unknown>`${heartbeatRuns.contextSnapshot}->'executionPolicy'`,
}).from(documentRevisions)
.leftJoin(authUsers, eq(authUsers.id, documentRevisions.createdByUserId))
.leftJoin(agents, and(eq(agents.id, documentRevisions.createdByAgentId), eq(agents.companyId, this.binding.companyId)))
.leftJoin(heartbeatRuns, and(eq(heartbeatRuns.id, documentRevisions.createdByRunId), eq(heartbeatRuns.companyId, this.binding.companyId)))
.where(and(eq(documentRevisions.companyId, this.binding.companyId), inArray(documentRevisions.id, ids)));
const byRevision = new Map(provenance.map(row => [row.id, row]));
const byDocument = new Map(currentDocuments.filter(document => document.companyId === this.binding.companyId
&& document.issueId === sourceIssueId).map(document => [document.id, document]));
const withhold = (entry: T, body: string): T => ({ ...entry, body,
...("changeSummary" in entry ? { changeSummary: null } : {}) });
return entries.map(entry => {
const document = byDocument.get(entry.documentId);
const original = entry.id ? byRevision.get(entry.id) : undefined;
if (!document || !original || original.documentId !== document.id) return withhold(entry, UNKNOWN_DOCUMENT_REVISION_BODY);
const current = document.latestRevisionId === entry.id;
if (current && isLowTrustQuarantined(document.sourceTrust)) return withhold(entry, LOW_TRUST_QUARANTINED_BODY);
if (current && document.sourceTrust?.disposition === "promoted"
&& document.sourceTrust.promotedFrom?.artifactKind === "document"
&& document.sourceTrust.promotedFrom.artifactId === document.id) return entry;
if (original.createdByUserId && original.userId === original.createdByUserId
&& !original.createdByAgentId && !original.createdByRunId) return entry;
if (!original.createdByAgentId || !original.createdByRunId || original.runId !== original.createdByRunId
|| original.writerCompanyId !== this.binding.companyId || original.runCompanyId !== this.binding.companyId
|| original.runAgentId !== original.createdByAgentId
|| original.runIssueId !== sourceIssueId) return withhold(entry, UNKNOWN_DOCUMENT_REVISION_BODY);
const policy = original.runExecutionPolicy;
if (policy !== null && policy !== undefined && (typeof policy !== "object" || Array.isArray(policy))) {
return withhold(entry, UNKNOWN_DOCUMENT_REVISION_BODY);
}
const trust = resolveCoreTrustPreset({ companyId: this.binding.companyId,
run: { companyId: original.runCompanyId, executionPolicy: policy } });
if (trust.kind === "low_trust_review") return withhold(entry, LOW_TRUST_QUARANTINED_BODY);
if (trust.kind === "standard" && ((current && document.sourceTrust === null)
|| trust.sourcePresets.run === trust.preset)) return entry;
return withhold(entry, UNKNOWN_DOCUMENT_REVISION_BODY);
});
}
async #sourcePlanApproval(context: { issue: typeof issues.$inferSelect; run: typeof heartbeatRuns.$inferSelect }) {
const plan = await handoffPlanContext(this.db, context.issue, this.#privacyActor(context.run));
if (!plan || !this.binding.museRuntime) return plan;
const document = await documentService(this.db).getIssueDocumentByKey(plan.sourceIssueId, "plan");
const [visible] = await this.#projectMuseDocumentContent(context.issue, [{
id: plan.revisionId, documentId: document?.id ?? "", body: plan.markdown,
}], document ? [document] : [], plan.sourceIssueId);
return { ...plan, markdown: visible!.body };
}
async #acceptedPlan(contextSnapshot: unknown, issue: typeof issues.$inferSelect): Promise<{
documentId: string;
revisionId: string;
revisionNumber: number;
@@ -1907,7 +2063,12 @@ export class PaperclipRunnerToolAuthority {
eq(documentRevisions.companyId, this.binding.companyId),
))
.limit(1);
return revision ?? null;
if (!revision || !this.binding.museRuntime) return revision ?? null;
const document = await documentService(this.db).getIssueDocumentByKey(this.binding.issueId, "plan");
const [visible] = await this.#projectMuseDocumentContent(issue, [{
id: revision.revisionId, documentId: revision.documentId, body: revision.markdown,
}], document ? [document] : []);
return { ...revision, markdown: visible!.body };
}
async #latestAcceptedPlanRevisionId(): Promise<string | null> {
@@ -2060,7 +2221,7 @@ export class PaperclipRunnerToolAuthority {
) => Promise<void>;
} = {},
): Promise<unknown> {
return this.db.transaction(async (tx) => {
const mutate = async (tx: ExternalAdmissionTransaction) => {
try {
const context = await this.#lockAuthorizedMutationContext(
tx as unknown as Db,
@@ -2151,6 +2312,12 @@ export class PaperclipRunnerToolAuthority {
await options.onDefinitePreCommitFailure?.().catch(() => undefined);
throw error;
}
};
return this.db.transaction(async tx => {
if (operationId === "create_task") {
await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`issue-privacy-tree:${this.binding.companyId}`}, 0))`);
}
return mutate(tx);
});
}
@@ -2159,6 +2326,10 @@ export class PaperclipRunnerToolAuthority {
issue: typeof issues.$inferSelect;
actor: typeof agents.$inferSelect;
}> {
if (this.binding.museRuntime) {
await withExternalExecutionAdmission(tx as unknown as ExternalAdmissionTransaction,
this.binding.companyId, this.binding.agentId, this.binding.runId, async () => {});
}
// Match identity activation and queue mutations before locking the run.
await tx.select({ id: issues.id }).from(issues).where(and(
eq(issues.id, this.binding.issueId), eq(issues.companyId, this.binding.companyId),
@@ -2200,6 +2371,16 @@ export class PaperclipRunnerToolAuthority {
) {
throw new Error("paperclip_runner_tool_binding_not_authorized");
}
if (this.binding.museRuntime) {
const decision = await authorizationService(tx).decide({
actor: this.#privacyActor(context.run), action: "issue:mutate",
resource: { type: "issue", companyId: this.binding.companyId, issueId: context.issue.id,
projectId: context.issue.projectId, parentIssueId: context.issue.parentId,
status: context.issue.status, assigneeAgentId: context.issue.assigneeAgentId, assigneeUserId: context.issue.assigneeUserId },
});
if (!decision.allowed) throw forbidden(decision.explanation);
await this.binding.assertBridgeAuthority?.(tx);
}
return context;
}
@@ -38,6 +38,7 @@ export type QualifiedPaperclipRunnerAcpxAgent =
type AdmittedPaperclipRunnerAcpxAgent = QualifiedPaperclipRunnerAcpxAgent | AcpxQualificationCandidate;
export type PaperclipRunnerProviderProfile =
| { provider: "muse"; backend: "muse_external"; model: null; museBindingId: string }
| { provider: "grok_bot"; backend: "grok_bot_mcp"; model: null; externalBindingId: string }
| { provider: "openai_dot"; backend: "openai_dot_mcp"; model: null; dotBindingId: string }
| {
@@ -72,6 +73,7 @@ export type PaperclipRunnerProviderProfile =
};
export type PaperclipRunnerNativeProviderInput =
| { provider: "muse"; model: null; museBinding: import("../../vendor/paperclip-runner/index.js").MuseBindingSnapshot }
| { provider: "grok_bot"; model: null; dotBinding: import("../../vendor/paperclip-runner/index.js").DotBindingSnapshot }
| { provider: "openai_dot"; model: null; dotBinding: import("../../vendor/paperclip-runner/index.js").DotBindingSnapshot }
| {
@@ -169,6 +171,17 @@ export function validatePaperclipRunnerDotConfig(config: Record<string, unknown>
return bindingId;
}
/** Closed personal runtime configuration; model and workspace grants are excluded. */
export function validatePaperclipRunnerMuseConfig(config: Record<string, unknown>, requireBinding = true): string | null {
const bindingId = optionalString(config.museBindingId);
if ((requireBinding && !bindingId) || (bindingId && !/^[0-9a-f-]{36}$/i.test(bindingId))
|| config.allowUnmeteredProvider !== true || (config.lifecycleMode && config.lifecycleMode !== "per_turn")
|| optionalString(config.model) || config.dotWorkspaceAccess === true || config.dotAttachmentAccess === true) {
throw new PaperclipRunnerProviderProfileError("paperclip_runner_muse_config_invalid", "Muse requires a paired personal-agent binding, per-turn lifecycle and unavailable-usage acknowledgement.");
}
return bindingId;
}
export function validatePaperclipRunnerGrokConfig(config: Record<string, unknown>, requireBinding = true): string | null {
const bindingId = optionalString(config.externalBindingId);
if ((requireBinding && !bindingId) || (bindingId && !/^[0-9a-f-]{36}$/i.test(bindingId))
@@ -385,6 +398,10 @@ export function resolvePaperclipRunnerProviderProfile(
);
}
if (candidate === "muse") {
const bindingId = validatePaperclipRunnerMuseConfig(config)!;
return { provider: "muse", backend: "muse_external", model: null, museBindingId: bindingId };
}
if (candidate === "grok_bot") {
return { provider: "grok_bot", backend: "grok_bot_mcp", model: null, externalBindingId: validatePaperclipRunnerGrokConfig(config)! };
}
@@ -547,6 +564,7 @@ export function resolvePaperclipRunnerNativeProviderInput(input: {
adapterConfig: unknown;
/** Verified image CLI version for a fresh remote Codex run only. */
codexCliVersion?: string | null;
museBinding?: import("../../vendor/paperclip-runner/index.js").MuseBindingSnapshot;
dotBinding?: import("../../vendor/paperclip-runner/index.js").DotBindingSnapshot;
managedProfile?: {
id: string;
@@ -585,6 +603,10 @@ export function resolvePaperclipRunnerNativeProviderInput(input: {
"Paperclip Runner provider changed after this run selected its native backend.",
);
}
if (profile.provider === "muse") {
if (!input.museBinding || input.museBinding.bindingId !== profile.museBindingId) throw new PaperclipRunnerProviderProfileError("paperclip_runner_muse_binding_unavailable", "Muse binding must be resolved by the server after normal admission.");
return { provider: "muse", model: null, museBinding: input.museBinding };
}
if (profile.provider === "grok_bot") {
if (!input.dotBinding || input.dotBinding.bindingId !== profile.externalBindingId) throw new PaperclipRunnerProviderProfileError("paperclip_runner_grok_binding_unavailable", "The Grok binding must be resolved by the server after normal admission.");
return { provider: "grok_bot", model: null, dotBinding: input.dotBinding };
@@ -502,3 +502,18 @@ describe("resolveHeartbeatRuntimeMode compatibility", () => {
})).toThrow(NativeRunnerSelectionError);
});
});
describe("Muse native selection", () => {
const muse = { ...eligible, museEnabled: true, adapterConfig: { provider: "muse", museBindingId: "00000000-0000-4000-8000-000000000001", allowUnmeteredProvider: true, lifecycleMode: "per_turn" } };
it("requires both Runner and Muse rollout gates for fresh work", () => {
expect(() => resolveNativeRuntimeMode({ ...muse, museEnabled: false })).toThrow("Enable Muse");
expect(() => resolveNativeRuntimeMode({ ...muse, enabled: false })).toThrow("disabled");
expect(resolveNativeRuntimeMode(muse)).toMatchObject({ kind: "native", profile: { backend: "muse_external" } });
});
it("rejects model, workspace and inherited Dot attachment grants", () => {
for (const extra of [{ model: "local-model" }, { dotWorkspaceAccess: true }, { dotAttachmentAccess: true }, { lifecycleMode: "warm" }]) {
expect(() => resolveNativeRuntimeMode({ ...muse, adapterConfig: { ...muse.adapterConfig, ...extra } })).toThrow("Muse requires");
}
});
});
@@ -51,7 +51,9 @@ export type NativeRuntimeResolution =
| "claude_managed_agents_api"
| "aws_agentcore_harness_api"
| "acpx_runtime"
| "openai_dot_mcp" | "grok_bot_mcp";
| "openai_dot_mcp"
| "grok_bot_mcp"
| "muse_external";
protocolVersion: 1;
};
authorityDecision: NativeStatusDecision;
@@ -97,6 +99,7 @@ function ineligible(
export function resolveNativeRuntimeMode(input: {
enabled: boolean;
museEnabled?: boolean;
dotEnabled?: boolean;
grokEnabled?: boolean;
runtimeConfig: unknown;
@@ -127,7 +130,11 @@ export function resolveNativeRuntimeMode(input: {
}
throw error;
}
if (runnerProfile.provider === "openai_dot") {
// Dot has its own rollout; enabling it does not opt in other Runner providers.
if (runnerProfile.provider === "muse") {
if (!input.enabled) throw ineligible("paperclip_runner_rollout_disabled", "Paperclip Runner is experimental and disabled on this instance.");
if (input.museEnabled !== true) throw ineligible("paperclip_runner_muse_disabled", "Enable Muse personal-agent connections in experimental settings before assigning new work.");
} else if (runnerProfile.provider === "openai_dot") {
if (input.dotEnabled !== true) throw ineligible("paperclip_runner_dot_disabled", "Enable OpenAI Dot and Assistant connections (MCP) in experimental settings before assigning new work.");
} else if (runnerProfile.provider === "grok_bot" && (input.grokEnabled !== true || !input.enabled)) {
throw ineligible("paperclip_runner_grok_disabled", "Enable Grok Bot, Paperclip Runner, and Assistant connections (MCP) before assigning new work.");
@@ -188,6 +195,7 @@ export function resolveHeartbeatRuntimeMode(input: {
runtimeModeResolvedAt: Date | null;
};
enabled: boolean;
museEnabled?: boolean;
dotEnabled?: boolean;
grokEnabled?: boolean;
adapterType: string | null;
@@ -216,6 +224,7 @@ export function resolveHeartbeatRuntimeMode(input: {
try {
resolution = resolveNativeRuntimeMode({
enabled: input.enabled,
museEnabled: input.museEnabled,
dotEnabled: input.dotEnabled,
grokEnabled: input.grokEnabled,
runtimeConfig: {},
@@ -253,6 +262,7 @@ export function resolveHeartbeatRuntimeMode(input: {
? "claude_managed"
: resolution.profile.backend === "aws_agentcore_harness_api"
? "aws_agentcore"
: resolution.profile.backend === "muse_external" ? "muse"
: resolution.profile.backend === "grok_bot_mcp" ? "grok_bot"
: resolution.profile.backend === "openai_dot_mcp" ? "openai_dot"
: resolution.profile.backend === "acpx_runtime"
@@ -274,6 +284,7 @@ export function resolveHeartbeatNativeRuntimeMode(input: {
driverKind?: string | null;
};
enabled: boolean;
museEnabled?: boolean;
dotEnabled?: boolean;
grokEnabled?: boolean;
runtimeConfig: unknown;
@@ -307,6 +318,7 @@ export function resolveHeartbeatNativeRuntimeMode(input: {
? "claude_managed_agents_api"
: driverKind === "aws_agentcore_harness_api"
? "aws_agentcore_harness_api"
: driverKind === "muse_external" ? "muse_external"
: driverKind === "grok_bot_mcp" ? "grok_bot_mcp"
: driverKind === "openai_dot_mcp" ? "openai_dot_mcp"
: driverKind === "acpx_runtime"