From 9626887ed728b0a3bc8f33022194c1c5f1758f9c Mon Sep 17 00:00:00 2001 From: Conan Scott Date: Thu, 2 Jul 2026 19:05:26 +1000 Subject: [PATCH] feat(api): /tokenize now runs the full round trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repoint public POST /tokenize at the orchestrator (tokenize -> agent -> restore) so a single path returns {final, deidentified_prompt, agent_tokenized, session_id} — matching what Fusion mirrors from the front-end path, and what openapi.yaml already documents. The granular detector moves to internal /tokenize-raw (x-api-key protected; the orchestrator calls it). /demo kept as an alias so the UI keeps working. Co-Authored-By: Claude Opus 4.8 --- terraform/gateway.tf | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/terraform/gateway.tf b/terraform/gateway.tf index cf51f5d..7881804 100644 --- a/terraform/gateway.tf +++ b/terraform/gateway.tf @@ -107,9 +107,11 @@ resource "aws_apigatewayv2_integration" "tokenize" { payload_format_version = "2.0" } +# Granular detector is now INTERNAL at /tokenize-raw (x-api-key protected); the +# orchestrator calls it. Public POST /tokenize does the full round trip (below). resource "aws_apigatewayv2_route" "tokenize" { api_id = aws_apigatewayv2_api.gateway.id - route_key = "POST /tokenize" + route_key = "POST /tokenize-raw" target = "integrations/${aws_apigatewayv2_integration.tokenize.id}" } @@ -117,7 +119,29 @@ resource "aws_apigatewayv2_stage" "default" { api_id = aws_apigatewayv2_api.gateway.id name = "$default" auto_deploy = true - tags = local.onprem_tag + # Access logs capture the RAW method + path AWS received -- for debugging what + # Fusion actually sends (404 route-no-match never reaches a Lambda log). + access_log_settings { + destination_arn = aws_cloudwatch_log_group.apigw_access.arn + format = jsonencode({ + requestId = "$context.requestId" + ip = "$context.identity.sourceIp" + method = "$context.httpMethod" + path = "$context.path" + routeKey = "$context.routeKey" + status = "$context.status" + protocol = "$context.protocol" + userAgent = "$context.identity.userAgent" + integrationError = "$context.integrationErrorMessage" + }) + } + tags = local.onprem_tag +} + +resource "aws_cloudwatch_log_group" "apigw_access" { + name = "/apigw/${local.name}-gateway-access" + retention_in_days = 7 + tags = local.onprem_tag } resource "aws_lambda_permission" "tokenize_apigw" { @@ -261,7 +285,7 @@ resource "aws_lambda_function" "orchestrator" { environment { variables = { REGION = var.region - TOKENIZE_URL = "${trimsuffix(aws_apigatewayv2_stage.default.invoke_url, "/")}/tokenize" + TOKENIZE_URL = "${trimsuffix(aws_apigatewayv2_stage.default.invoke_url, "/")}/tokenize-raw" RESTORE_URL = "${trimsuffix(aws_apigatewayv2_stage.default.invoke_url, "/")}/restore" TOKENIZE_API_KEY = var.tokenize_api_key AGENT_RUNTIME_ARN = var.agent_runtime_arn @@ -277,12 +301,21 @@ resource "aws_apigatewayv2_integration" "orchestrator" { payload_format_version = "2.0" } +# Full round trip (tokenize -> agent -> restore). Public entrypoint is /tokenize +# (what Fusion mirrors); /demo kept as an alias so the existing UI keeps working. resource "aws_apigatewayv2_route" "orchestrator" { api_id = aws_apigatewayv2_api.gateway.id route_key = "POST /demo" target = "integrations/${aws_apigatewayv2_integration.orchestrator.id}" } +resource "aws_apigatewayv2_route" "orchestrator_tokenize" { + api_id = aws_apigatewayv2_api.gateway.id + route_key = "POST /tokenize" + target = "integrations/${aws_apigatewayv2_integration.orchestrator.id}" + depends_on = [aws_apigatewayv2_route.tokenize] # free up "POST /tokenize" first +} + resource "aws_lambda_permission" "orchestrator_apigw" { statement_id = "AllowApiGatewayInvoke" action = "lambda:InvokeFunction"