diff --git a/bedrock-haiku.tf b/bedrock-haiku.tf index 9dafadf1..6328607b 100644 --- a/bedrock-haiku.tf +++ b/bedrock-haiku.tf @@ -10,6 +10,8 @@ # (BedrockRuntimeInvoke in dev-instance-common.tf); this file leaves them alone. Not here: the GPU boxes # run with no instance profile at all (gpu-box.tf), the jumpboxes are administrators, and runners and # the AMI builder are CI machines, not boxes. +# - the fcvm fleet coordinator's task role, in the fleet account, for the viewer word check. Its grant is in +# fcvm-fleet.tf (CheckViewerWordsWithClaudeHaiku55) with that account's id, using the model locals below. # # The model, from AWS's model card (docs.aws.amazon.com/bedrock/latest/userguide/ # model-card-anthropic-claude-haiku-5-5.html, checked 2026-10-10; launched on Bedrock 2026-10-07): diff --git a/fcvm-fleet.tf b/fcvm-fleet.tf index 871ca79a..f6eb05f4 100644 --- a/fcvm-fleet.tf +++ b/fcvm-fleet.tf @@ -140,6 +140,9 @@ locals { ["--dns-subzone", local.fcvm_fleet_names_zone, "--dns-subzone-ns", join(",", local.fcvm_fleet_names_ns), "--dns-listen", "0.0.0.0:${local.fcvm_fleet_dns_port}"], # The EC2 half's aggregator beside the coordinator: its layout on loopback, browsers on 443 of the task's own addresses. local.fcvm_fleet_aggregated ? ["--ec2-aggregate-listen", "127.0.0.1:${local.fcvm_fleet_aggregate_port}", "--ec2-aggregate-tls", "--ec2-aggregate-tls-dir", "/run/fcvm-fleet/tls"] : [], + # Every viewer word is checked by Claude Haiku 5.5 on Bedrock before it enters the scene (the task role's + # CheckViewerWordsWithClaudeHaiku55). Without a checker the coordinator refuses every word. + ["--text-check", "bedrock", "--text-check-model", local.bedrock_haiku_profile, "--text-check-region", local.bedrock_haiku_region], ) fcvm_fleet_coordinator_running = local.fcvm_fleet_coordinator_enabled && local.fcvm_fleet_coordinator_tag != "" @@ -722,6 +725,33 @@ resource "aws_iam_role_policy" "fcvm_fleet_coordinator_task" { Action = "secretsmanager:PutSecretValue" Resource = local.fcvm_fleet_secret_arn["tls-wildcard"] }, + { + # The viewer word check (--text-check bedrock): Claude Haiku 5.5 through the US inference profile, which routes + # from us-west-2 to us-east-1, us-east-2 and us-west-2 (bedrock-haiku.tf), all of them fleet regions the SCP admits. + # A routed call needs the profile in this account and the foundation model in the region it lands in. + Sid = "CheckViewerWordsWithClaudeHaiku55" + Effect = "Allow" + Action = "bedrock:InvokeModel" + Resource = [ + "arn:aws:bedrock:*:${local.fcvm_fleet_account_id}:inference-profile/${local.bedrock_haiku_profile}", + "arn:aws:bedrock:*::foundation-model/${local.bedrock_haiku_model}", + ] + }, + { + # The account's first Haiku 5.5 call subscribes it to the model's Marketplace product with the caller's + # permissions (as for the box roles in bedrock-haiku.tf); limited to that product, no Unsubscribe. + Sid = "SubscribeClaudeHaiku55OnFirstUse" + Effect = "Allow" + Action = "aws-marketplace:Subscribe" + Resource = "*" + Condition = { "ForAnyValue:StringEquals" = { "aws-marketplace:ProductId" = ["prod-6cyn7tgqazjhu"] } } + }, + { + Sid = "ViewMarketplaceSubscriptions" + Effect = "Allow" + Action = "aws-marketplace:ViewSubscriptions" + Resource = "*" + }, { # Created resources: only through a tile template, in the fleet regions. Sid = "LaunchTilesFromTheirTemplates" diff --git a/scripts/test-fcvm-fleet.py b/scripts/test-fcvm-fleet.py index 9f4a08b2..b76d76a3 100644 --- a/scripts/test-fcvm-fleet.py +++ b/scripts/test-fcvm-fleet.py @@ -459,6 +459,19 @@ def test_the_coordinator_sleeps_and_runs_the_ec2_side(self): task = block('resource "aws_ecs_task_definition" "fcvm_fleet_coordinator"') self.assertIn("command = local.fcvm_fleet_coordinator_running ? local.fcvm_fleet_coordinator_command : null", task) + def test_the_coordinator_checks_viewer_words_with_haiku_it_may_invoke(self): + # The default, --text-check none, refuses every viewer word. The model the command names is the one the + # task role may invoke, through the profile in the fleet account and the foundation model it routes to. + cmd = TF.split("fcvm_fleet_coordinator_command = concat(", 1)[1].split("\n )\n", 1)[0] + self.assertIn('["--text-check", "bedrock", "--text-check-model", local.bedrock_haiku_profile, ' + '"--text-check-region", local.bedrock_haiku_region]', cmd) + pol = block('resource "aws_iam_role_policy" "fcvm_fleet_coordinator_task"') + grant = pol.split('Sid = "CheckViewerWordsWithClaudeHaiku55"', 1)[1].split("\n },", 1)[0] + self.assertIn('Action = "bedrock:InvokeModel"', grant) + self.assertIn('"arn:aws:bedrock:*:${local.fcvm_fleet_account_id}:inference-profile/${local.bedrock_haiku_profile}"', grant) + self.assertIn('"arn:aws:bedrock:*::foundation-model/${local.bedrock_haiku_model}"', grant) + self.assertIn('"aws-marketplace:ProductId" = ["prod-6cyn7tgqazjhu"]', pol) + class TileSubnetTests(unittest.TestCase): def test_both_tile_templates_name_a_fleet_subnet(self):