fix(runner): repair direct live provider bootstrap

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-06 10:29:14 -05:00
parent 54d14c63b3
commit ef53c45b29
16 changed files with 88 additions and 27 deletions

View File

@ -268,11 +268,13 @@ jobs:
--pack-destination "$RUNNER_TEMP/runner-protocol-build/package"
package="$(find "$RUNNER_TEMP/runner-protocol-build/package" -maxdepth 1 -type f -name '*.tgz' -print -quit)"
test -f "$package"
npm install --prefix "$RUNNER_TEMP/runner-protocol-build/portable" --omit=dev "$package"
pnpm --filter @paperclipai/paperclip-runner deploy --prod \
"$RUNNER_TEMP/runner-protocol-build/portable"
cp "$package" "$RUNNER_TEMP/runner-protocol-build/paperclip-runner.tgz"
cp packages/paperclip-runner/runner/target/debug/paperclip-runnerd "$RUNNER_TEMP/runner-protocol-build/paperclip-runnerd"
cp -R packages/paperclip-runner/dist-issue-thread "$RUNNER_TEMP/runner-protocol-build/dist-issue-thread"
test -f "$RUNNER_TEMP/runner-protocol-build/portable/node_modules/@paperclipai/paperclip-runner/dist/cli/eval-session.js"
test -f "$RUNNER_TEMP/runner-protocol-build/portable/dist/cli/eval-session.js"
test -d "$RUNNER_TEMP/runner-protocol-build/portable/node_modules/.pnpm"
test -x "$RUNNER_TEMP/runner-protocol-build/paperclip-runnerd"
tar --create --gzip --file runner-protocol-build.tar.gz -C "$RUNNER_TEMP/runner-protocol-build" .
sha256sum runner-protocol-build.tar.gz > runner-protocol-build.tar.gz.sha256
@ -433,7 +435,7 @@ jobs:
python3 .paperclip-evals/evals/paperclip-runner/tools/run_live_roster.py run \
--roster ".paperclip-evals/evals/paperclip-runner/rosters/$ROSTER_FILE" \
--case "$CASE_ID" \
--runner-cli runner-protocol-build/extracted/portable/node_modules/@paperclipai/paperclip-runner/dist/cli/eval-session.js \
--runner-cli runner-protocol-build/extracted/portable/dist/cli/eval-session.js \
--runner-package runner-protocol-build/extracted/paperclip-runner.tgz \
--runnerd runner-protocol-build/extracted/paperclip-runnerd \
--runs-root cell-output/runs \

View File

@ -43,6 +43,13 @@ the native daemon, provider dependencies, and the attempt viewer are built
once and reused by every cell. Because the complete suite requires two matrix
shards, this workflow accepts a shared concurrency ceiling from 2 through 100.
The reused provider runtime is created with `pnpm deploy --prod` from the
frozen workspace lock. That preserves the repository's qualified dependency
versions and patched ACP server bytes. Do not replace this step with a fresh
`npm install` of the packed Runner tarball: npm cannot apply the workspace's
`patchedDependencies`, so the resulting ACPX executables no longer match their
qualified digests.
`all` is intentionally literal. A disabled driver, missing remote profile, or
unavailable provider is retained as an infrastructure result; it is not
silently omitted. In particular, the ACPX Pi roster remains visible while Pi
@ -70,7 +77,9 @@ Claude Managed also requires the four nonsecret
nonsecret `PAPERCLIP_AWS_AGENTCORE_*` profile variables, including
`PAPERCLIP_AWS_AGENTCORE_EXECUTION_ROLE_ARN` and the immutable
`PAPERCLIP_AWS_AGENTCORE_QUALIFICATION_REVISION`; the eval fails closed when
that deployed revision differs from the pinned roster config. The workflow
that deployed revision differs from the pinned roster config. The currently
qualified context-aware harness revision is
`aws-agentcore-harness-context-v2`. The workflow
writes the GitHub OIDC token to a mode-`0600` file and never forwards long-lived
AWS access keys.
Provision the AgentCore stack with `--github-oidc-provider-arn` so that scoped

View File

@ -2211,7 +2211,7 @@ mod tests {
context_bucket: "paperclip-context-test".to_owned(),
context_prefix: "companies/company-test/profiles/profile-test".to_owned(),
context_kms_key_arn: "arn:aws:kms:us-east-1:123456789012:key/test".to_owned(),
qualification_revision: "aws-agentcore-harness-v1".to_owned(),
qualification_revision: "aws-agentcore-harness-context-v2".to_owned(),
event_expiry_days: 90,
max_estimated_session_cost_usd: 1.0,
max_iterations: 8,

View File

@ -43,7 +43,7 @@ const MAX_INSTRUCTIONS_BYTES: usize = 1024 * 1024;
const QUALIFIED_CLAUDE_MODEL: &str = "claude-sonnet-5";
const QUALIFIED_CLAUDE_BETA: &str = "managed-agents-2026-04-01";
const QUALIFIED_AGENTCORE_MODEL: &str = "global.anthropic.claude-sonnet-4-6";
const QUALIFIED_AGENTCORE_REVISION: &str = "aws-agentcore-harness-v1";
const QUALIFIED_AGENTCORE_REVISION: &str = "aws-agentcore-harness-context-v2";
fn initial_event_sequence() -> u64 {
1

View File

@ -283,7 +283,7 @@ fn managed_prepare_payload(kind: &str) -> Value {
"contextBucket": "context-bucket",
"contextPrefix": "companies/company/profiles/profile",
"contextKmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/test",
"qualificationRevision": "aws-agentcore-harness-v1",
"qualificationRevision": "aws-agentcore-harness-context-v2",
"eventExpiryDays": 90,
"maxEstimatedSessionCostUsd": 1.0,
"maxIterations": 8,

View File

@ -105,6 +105,15 @@ test("resolves both repositories immutably and bounds total matrix concurrency",
}
assert.match(workflow, /matrix_0/u);
assert.match(workflow, /matrix_1/u);
assert.match(
workflow,
/pnpm --filter @paperclipai\/paperclip-runner deploy --prod/u,
);
assert.match(
workflow,
/--runner-cli runner-protocol-build\/extracted\/portable\/dist\/cli\/eval-session\.js/u,
);
assert.doesNotMatch(workflow, /npm install --prefix/u);
});
test("publishes only the separately sanitized Evalbook through trusted OIDC code", async () => {

View File

@ -209,7 +209,7 @@ function managedExecution(
contextBucket: "context-bucket",
contextPrefix: "companies/company/profiles/profile",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/test",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
eventExpiryDays: 90,
},
},
@ -388,7 +388,7 @@ describe("native backend factory", () => {
[
"aws_agentcore" as const,
"aws_agentcore_harness_api",
"aws-agentcore-harness-v1",
"aws-agentcore-harness-context-v2",
],
])("routes %s through runnerd", async (kind, name, version) => {
const backend = createNativeSessionBackend(managedExecution(kind), {

View File

@ -235,7 +235,7 @@ describe("NativeExecutionInputV1", () => {
contextBucket: "paperclip-agentcore-context",
contextPrefix: "paperclip/runtime",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/test",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
eventExpiryDays: 90,
},
maxEstimatedSessionCostUsd: 1,

View File

@ -634,6 +634,34 @@ it("derives the ACPX package authority only from the verified dist/cli layout",
).toThrow("ACPX sidecar must use the provider package dist/cli layout");
});
it("keeps a deployed ACPX package inside its pnpm-owned dependency root", async () => {
const deployedPackageRoot = await mkdtemp(
join(tmpdir(), "paperclip-deployed-provider-package-"),
);
await mkdir(join(deployedPackageRoot, "dist", "cli"), { recursive: true });
await mkdir(join(deployedPackageRoot, "node_modules", ".pnpm"), {
recursive: true,
});
try {
expect(
runnerdLaunchProfileInternals.acpxProviderPackageAuthority(
join(
deployedPackageRoot,
"dist",
"cli",
"acpx-runtime-sidecar.cjs",
),
deployedPackageRoot,
),
).toEqual({
root: deployedPackageRoot,
manifest: join(deployedPackageRoot, "package.json"),
});
} finally {
await rm(deployedPackageRoot, { recursive: true, force: true });
}
});
it("requires a provider-pack authority for remote ACPX artifact hashes", () => {
expect(() =>
runnerdLaunchProfileInternals.acpxRunnerLaunchProfile(

View File

@ -1549,7 +1549,10 @@ function resolveBuildOwnedCliArtifact(
);
}
function acpxProviderPackageAuthority(sidecarScript: string): {
function acpxProviderPackageAuthority(
sidecarScript: string,
ownerPackageRoot = packageRoot,
): {
root: string;
manifest: string;
} {
@ -1566,10 +1569,19 @@ function acpxProviderPackageAuthority(sidecarScript: string): {
const sidecarPackageRoot = resolve(cliDirectory, "../..");
// A local source build consumes pnpm's workspace-owned node_modules tree.
// A deployed provider pack owns a closed node_modules tree at its own root.
return sidecarPackageRoot === packageRoot
// Source builds resolve provider dependencies from the monorepo root. A
// `pnpm deploy`, however, owns a complete virtual store below the deployed
// package root and must not escape to its caller's filesystem. This marker
// is generated by pnpm itself and keeps the two layouts unambiguous.
const localDependencyRoot = existsSync(
resolve(ownerPackageRoot, "node_modules", ".pnpm"),
)
? ownerPackageRoot
: resolve(ownerPackageRoot, "../..");
return sidecarPackageRoot === ownerPackageRoot
? {
root: resolve(packageRoot, "../.."),
manifest: resolve(packageRoot, "package.json"),
root: localDependencyRoot,
manifest: resolve(ownerPackageRoot, "package.json"),
}
: {
root: sidecarPackageRoot,

View File

@ -123,7 +123,7 @@ describe("managed provider profile route authorization", () => {
contextBucket: "paperclip-runner-context",
contextPrefix: "profiles/example",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/example",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
defaultModel: "global.anthropic.claude-sonnet-4-6",
eventExpiryDays: 90,
},

View File

@ -4626,7 +4626,7 @@ registry.registerPath({
configuration: z.record(z.string(), z.unknown()),
enabled: z.boolean().optional(),
retentionAcknowledged: z.boolean().optional(),
qualification: z.object({ suite: z.literal("aws-agentcore-harness-v1") }).strict().optional(),
qualification: z.object({ suite: z.literal("aws-agentcore-harness-context-v2") }).strict().optional(),
})),
},
responses: {

View File

@ -87,7 +87,7 @@ describe("Paperclip Runner native provider configuration", () => {
contextBucket: "paperclip-context",
contextPrefix: "runner/",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
eventExpiryDays: 90,
};
const stored = {
@ -224,7 +224,7 @@ describe("Paperclip Runner native provider configuration", () => {
contextBucket: "paperclip-context",
contextPrefix: "runner/",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
defaultModel: "global.anthropic.claude-sonnet-4-6",
eventExpiryDays: 90,
defaultMaxEstimatedSessionCostUsd: 1.25,
@ -278,7 +278,7 @@ describe("Paperclip Runner native provider configuration", () => {
contextBucket: "paperclip-context",
contextPrefix: "runner/",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
defaultModel: "global.anthropic.claude-sonnet-4-6",
eventExpiryDays: 90,
defaultMaxEstimatedSessionCostUsd: 1.25,
@ -352,7 +352,7 @@ describe("Paperclip Runner native provider configuration", () => {
contextBucket: "paperclip-context",
contextPrefix: "runner/",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
defaultModel: "global.anthropic.claude-sonnet-4-6",
eventExpiryDays: 90,
defaultMaxEstimatedSessionCostUsd: 1.25,

View File

@ -301,7 +301,7 @@ describe("buildNativeExecutionInput wake projection", () => {
contextBucket: "paperclip-context",
contextPrefix: "runner/",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
eventExpiryDays: 90,
},
maxEstimatedSessionCostUsd: 1.25,

View File

@ -11,7 +11,8 @@ export const CLAUDE_MANAGED_QUALIFICATION = {
} as const;
export const CLAUDE_MANAGED_QUALIFIED_MODEL = "claude-sonnet-5" as const;
export const AGENTCORE_QUALIFICATION_SUITE = "aws-agentcore-harness-v1" as const;
export const AGENTCORE_QUALIFICATION_SUITE =
"aws-agentcore-harness-context-v2" as const;
export const AGENTCORE_QUALIFIED_MODEL = "global.anthropic.claude-sonnet-4-6" as const;
const REVISION_PREFIX = "sha256:";

View File

@ -28,7 +28,7 @@ const AWS_CONFIGURATION = {
contextBucket: "paperclip-runner-context",
contextPrefix: "profiles/example",
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/example",
qualificationRevision: "aws-agentcore-harness-v1",
qualificationRevision: "aws-agentcore-harness-context-v2",
defaultModel: "global.anthropic.claude-sonnet-4-6",
eventExpiryDays: 90,
defaultMaxEstimatedSessionCostUsd: 1,
@ -44,7 +44,7 @@ function remoteInput(
configuration: { ...AWS_CONFIGURATION },
enabled: false,
retentionAcknowledged: false,
qualification: { suite: "aws-agentcore-harness-v1" },
qualification: { suite: "aws-agentcore-harness-context-v2" },
...overrides,
};
}
@ -362,7 +362,7 @@ describe("remote agent profile metadata validation", () => {
});
it("does not allow a qualified AgentCore profile key to be repointed", async () => {
const qualification = { suite: "aws-agentcore-harness-v1" };
const qualification = { suite: "aws-agentcore-harness-context-v2" };
const existing = {
id: "30000000-0000-4000-8000-000000000003",
companyId: COMPANY_ID,
@ -506,7 +506,7 @@ describe("remote agent profile metadata validation", () => {
qualifiedRevision: existingManaged.qualifiedRevision,
});
const awsQualification = { suite: "aws-agentcore-harness-v1" };
const awsQualification = { suite: "aws-agentcore-harness-context-v2" };
const existingRemote = {
id: "30000000-0000-4000-8000-000000000005",
companyId: COMPANY_ID,
@ -547,7 +547,7 @@ describe("remote agent profile metadata validation", () => {
});
it("rejects runtime use when stored identity drifts from the qualified revision", async () => {
const qualification = { suite: "aws-agentcore-harness-v1" };
const qualification = { suite: "aws-agentcore-harness-context-v2" };
const qualifiedRevision = computeRemoteAgentProfileRevision({
service: "aws_bedrock_agentcore_harness",
configuration: AWS_CONFIGURATION,