mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 02:26:12 +02:00
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:
1 parent
63fbb714e6
commit
e6dcc6b095
72 files changed
+9775
-1021
No files matched your search
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user