diff --git a/cdk/src/constructs/bedrock-models.ts b/cdk/src/constructs/bedrock-models.ts index 9f3d3493..c1c05203 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 84524d05..dfc720b9 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 df1393a8..e6c1ecfd 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 902b9dc0..e7c91748 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', () => {