From 2691e98f1be51c688c0f6dd490a247c52eee7113 Mon Sep 17 00:00:00 2001 From: scottschreckengaust <345885+scottschreckengaust@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:40:38 +0000 Subject: [PATCH] feat(cdk): grant Claude Opus 5 for Bedrock invocation Add anthropic.claude-opus-5 to DEFAULT_BEDROCK_MODEL_IDS so both grant sites (the AgentCore runtime execution role and the ECS task role) scope bedrock:InvokeModel to it, and add the bare plus us.-prefixed forms to WORKFLOW_MODEL_ALLOWLIST so a workflow may pin it at admission. Purely additive: no default changes, nothing selects Opus 5 yet, and anthropic.claude-opus-4-8 is retained because blueprints may pin it per repo. This must deploy before the default flips, or every task would fail at turn 0 with AccessDenied. Closes #744 Co-Authored-By: Claude --- cdk/src/constructs/bedrock-models.ts | 9 +++++++++ cdk/src/handlers/shared/workflows.ts | 8 ++++++++ cdk/test/constructs/ecs-agent-cluster.test.ts | 8 ++++++++ cdk/test/stacks/agent.test.ts | 15 +++++++++++++-- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/cdk/src/constructs/bedrock-models.ts b/cdk/src/constructs/bedrock-models.ts index 9f3d34932..c1c05203a 100644 --- a/cdk/src/constructs/bedrock-models.ts +++ b/cdk/src/constructs/bedrock-models.ts @@ -41,6 +41,15 @@ export const DEFAULT_BEDROCK_MODEL_IDS: readonly string[] = [ // this entry and that default in the same change — a fallback the role cannot // invoke fails every task on the stack, not just an edge case. 'anthropic.claude-opus-4-8', + // Claude Opus 5 — granted ahead of anything selecting it (#744). The grant + // must be DEPLOYED before the platform default flips, or every task fails at + // turn 0 with AccessDenied. Bare ID by contract: Bedrock refuses the bare ID + // for on-demand invocation ("ValidationException: … isn't supported. Retry + // your request with the ID or ARN of an inference profile"), and both grant + // sites derive the `us.`-prefixed inference-profile ARN — the invocable one — + // from this entry. Opus 4.8 above stays granted: blueprints may pin it + // per-repo, so removing it would fail those repos at turn 0. + 'anthropic.claude-opus-5', 'anthropic.claude-haiku-4-5-20251001-v1:0', ]; diff --git a/cdk/src/handlers/shared/workflows.ts b/cdk/src/handlers/shared/workflows.ts index 84524d059..dfc720b9d 100644 --- a/cdk/src/handlers/shared/workflows.ts +++ b/cdk/src/handlers/shared/workflows.ts @@ -94,6 +94,14 @@ export const WORKFLOW_MODEL_ALLOWLIST: readonly string[] = [ // `bedrockModels` context) in the same change. 'anthropic.claude-opus-4-8', 'us.anthropic.claude-opus-4-8', + // Claude Opus 5 (#744) — kept in step with the IAM grant added to + // DEFAULT_BEDROCK_MODEL_IDS in the same change, per the note above. Only the + // bare and `us.`-prefixed forms: `global.anthropic.claude-opus-5` is a live + // profile but is deliberately withheld until the grant sites derive the + // `global.` ARN (#747) — admitting it here first would pass admission and then + // fail at turn 0 with AccessDenied, exactly the drift this comment warns about. + 'anthropic.claude-opus-5', + 'us.anthropic.claude-opus-5', 'anthropic.claude-haiku-4-5-20251001-v1:0', 'us.anthropic.claude-haiku-4-5-20251001-v1:0', ]; diff --git a/cdk/test/constructs/ecs-agent-cluster.test.ts b/cdk/test/constructs/ecs-agent-cluster.test.ts index df1393a8f..e6c1ecfdf 100644 --- a/cdk/test/constructs/ecs-agent-cluster.test.ts +++ b/cdk/test/constructs/ecs-agent-cluster.test.ts @@ -416,6 +416,14 @@ describe('EcsAgentCluster construct', () => { expect(serialized).toContain('inference-profile/us.anthropic.claude-sonnet-4-6'); expect(serialized).toContain('anthropic.claude-opus-4-20250514-v1:0'); expect(serialized).toContain('anthropic.claude-haiku-4-5-20251001-v1:0'); + // Claude Opus 5 (#744) — AgentCore/ECS parity. Both ARNs: the bare id isn't + // on-demand invocable, so the `us.` profile is the one actually called. + expect(serialized).toContain('foundation-model/anthropic.claude-opus-5'); + expect(serialized).toContain('inference-profile/us.anthropic.claude-opus-5'); + // REGRESSION (#744): Opus 4.8 stays granted alongside Opus 5 — blueprints may + // pin it per-repo, so dropping it would fail those repos at turn 0. + expect(serialized).toContain('foundation-model/anthropic.claude-opus-4-8'); + expect(serialized).toContain('inference-profile/us.anthropic.claude-opus-4-8'); }); test('task role can DescribeAvailabilityZones so a CDK target repo can `cdk synth` on a fresh clone (ECS-parity)', () => { diff --git a/cdk/test/stacks/agent.test.ts b/cdk/test/stacks/agent.test.ts index 902b9dc00..e7c917486 100644 --- a/cdk/test/stacks/agent.test.ts +++ b/cdk/test/stacks/agent.test.ts @@ -259,13 +259,24 @@ describe('AgentStack', () => { test('runtime is granted the default Bedrock model set', () => { // Default (no bedrockModels context): the runtime execution role must hold - // bedrock:InvokeModel on the three default foundation models + their US - // inference profiles, scoped (never Resource: '*'). + // bedrock:InvokeModel on every default foundation model + its US + // inference profile, scoped (never Resource: '*'). const serialized = JSON.stringify(template.findResources('AWS::IAM::Policy')); expect(serialized).toContain('foundation-model/anthropic.claude-sonnet-4-6'); expect(serialized).toContain('inference-profile/us.anthropic.claude-sonnet-4-6'); expect(serialized).toContain('anthropic.claude-opus-4-20250514-v1:0'); expect(serialized).toContain('anthropic.claude-haiku-4-5-20251001-v1:0'); + // Claude Opus 5 (#744). Granted ahead of any default flip: the bare id is + // not on-demand invocable (Bedrock returns ValidationException), so the + // `us.`-prefixed inference profile is the one actually called — both ARNs + // must be present or the agent gets AccessDenied at turn 0. + expect(serialized).toContain('foundation-model/anthropic.claude-opus-5'); + expect(serialized).toContain('inference-profile/us.anthropic.claude-opus-5'); + // REGRESSION (#744): Opus 4.8 stays granted alongside Opus 5. Blueprints may + // pin 4.8 per-repo; dropping it would fail those repos at turn 0. Retiring + // 4.8 is a separate, announced change — not a side effect of adding 5. + expect(serialized).toContain('foundation-model/anthropic.claude-opus-4-8'); + expect(serialized).toContain('inference-profile/us.anthropic.claude-opus-4-8'); }); test('bedrockModels context override propagates to the runtime execution role', () => {