Infrastructure

Physical AWS resources, as provisioned by deploy/ in this repo. The DynamoDB table itself is provisioned in the infrastructure repo (aws/underwriting/); this stack only reads it as a data source. The continuous environments are test in the old shared account (us-west-2), test in its own account (us-east-2) and staging (us-east-2, its own account), all deployed on push to main; prod (us-east-2) deploys on release publish. The SageMaker models only exist in the old test account and prod (sagemaker_enabled).

Deployment

on_pr.yaml, on_main.yaml and on_release.yaml each call deploy-env.yaml once per target, passing the account, region, state bucket, artifact bucket and sagemaker_enabled as inputs. A pull request builds once and plans every continuous target from that build with the read-only site-github-services-plan role; a push to main applies the same build to each with site-github-services-deploy; a published release applies prod from the release’s tarball. Authentication is GitHub OIDC with no static credentials. Both roles, and the site-github-services-boundary permissions boundary fmtf-module-lambda attaches to every function role, are owned by the infrastructure repo. The Terraform providers use the workflow’s credentials directly, with no further role assumption. State lives under terraform/underwriting-api/ in floatme-site-media for old test and prod and in floatme-site-services-terraform-state for the new test account and staging, locked by the terraform-locking table in each account. Lambda packages upload to the bucket named by artifact_bucket: floatme-site-media except in the new test account, where it is floatme-test-services-artifacts.

The seven peer service APIs are resolved by name at plan time and addressed by convention (https://{id}.execute-api.{region}.amazonaws.com). A peer that has not yet deployed to an environment yields an empty URL and an account-wide execute-api:Invoke grant until the next deploy picks up its id, so a fresh environment comes up in two passes rather than failing the plan.

Lambda functions

All five are built from cmd/<name>/main.go, named site-underwriting-<name>, and deployed via the shared fmtf-module-lambda module. Memory is 256 MB in prod, 128 MB elsewhere.

Function Timeout VPC Trigger Notable configuration

api

300s

yes

API Gateway (ANY /{proxy+})

URLs/regions for all seven peer services; PAYBACK_PREDICTOR_ENABLED plus SageMaker endpoint + feature-store names; SM_GROWTHBOOK_NAME

rule-runner

108s

no

rule-runner-sqs-tap (batch 1); direct invoke from api (instant check)

Peer service URLs; RULEBOOK_CACHE_EXPIRE_MINUTES; RULE_OUTCOME_TTL; SAGEMAKER_ENABLED plus the rule-ML SageMaker endpoint; SM_SEGMENT_NAME, SM_GROWTHBOOK_NAME

result-runner

108s

no

result-runner-sqs-tap (batch 1); direct invoke from api (instant check)

RULE_OUTCOME_TTL (also sets evaluation TTL); SM_SEGMENT_NAME, SM_GROWTHBOOK_NAME, SM_ITERABLE_NAME

float-created-handler

108s

no

float-created-sqs-tap (batch 1)

Dynamo + transactions/user service + EventBridge region; SM_SEGMENT_NAME

profile-handler

108s

no

profile-handler-sqs-tap (batch 1)

Dynamo only (DYNAMO_REGION, DYNAMO_TABLE)

Source: deploy/lambda.tf, deploy/main.tf

IAM per function

Function Permissions

api

DynamoDB (BatchGet/BatchWrite/Get/Put/Query) on table+indexes; events:PutEvents; execute-api:Invoke on payments, txn, float, loc, user, subscriptions, insight; secretsmanager:GetSecretValue (growthbook); lambda:InvokeFunction on rule-runner and result-runner; when SageMaker is enabled, sagemaker:InvokeEndpoint (payback predictor v2) and sagemaker:GetRecord + kms:Decrypt (feature store)

rule-runner

DynamoDB (adds UpdateItem); SQS receive on its tap; events:PutEvents; execute-api:Invoke on all seven services; secrets: segment, growthbook; when SageMaker is enabled, sagemaker:InvokeEndpoint (rule-ML endpoint)

result-runner

DynamoDB (incl. UpdateItem); SQS receive on its tap; events:PutEvents; execute-api:Invoke on payments only; secrets: segment, growthbook, iterable

float-created-handler

DynamoDB (incl. UpdateItem); SQS receive on its tap; events:PutEvents; execute-api:Invoke on txn and user; secrets: segment

profile-handler

DynamoDB (BatchGet/Get/Put/Query); SQS receive on its tap

Source: deploy/lambda.tf

API Gateway

HTTP API (v2) named site-underwriting, single route ANY /{proxy+} with AWS_IAM authorization, AWS_PROXY integration to the api Lambda (payload format 1.0, 30s timeout). The public /v1 prefix is mapped outside this repo. Access logs go to API-Gateway-Execution-Logs_underwriting/site with 7-day retention.

The gateway is also reachable as underwriting.site.svc.floatme.io (deploy/domain.tf): an alias record in the infrastructure repo’s per-environment site.svc.floatme.io zone, using its wildcard certificate. The domain, mapping, and record are standalone resources rather than module inputs because the module disables the execute-api endpoint for HTTP APIs when it creates a domain; that endpoint stays enabled for existing callers.

Source: deploy/lambda.tf, deploy/domain.tf

SQS queues

Four tap queues, each fed by one EventBridge rule (patterns in Events) and consumed by one Lambda. All main queues: visibility timeout 120s, maxReceiveCount 1 → DLQ. Retention is the AWS default (4 days) on both mains and DLQs.

Queue DLQ

site-underwriting-rule-runner-sqs-tap

…-rule-runner-sqs-tap-dlq

site-underwriting-result-runner-sqs-tap

…-result-runner-sqs-tap-dlq

site-underwriting-float-created-sqs-tap

…-float-created-sqs-tap-dlq

site-underwriting-profile-handler-sqs-tap

…-profile-handler-sqs-tap-dlq

Source: deploy/sqs.tf, deploy/main.tf

DynamoDB table

site-underwriting — single table, keys PK/SK, TTL attribute ttl, GSI1 and GSI2 (both KEYS_ONLY projection). Prod is provisioned capacity with autoscaling; other environments are on-demand. Point-in-time recovery and deletion protection are enabled. Key design and access patterns: DynamoDB.

Source: infrastructure repo, aws/underwriting/dynamo.tf

Secrets

Secret Env var Used by

site/growthbook

SM_GROWTHBOOK_NAME

api, rule-runner, result-runner (feature flags)

site/segment

SM_SEGMENT_NAME

rule-runner, result-runner, float-created-handler (analytics events)

site/iterable

SM_ITERABLE_NAME

result-runner (early-advance notification)

site/datadog/terraform

—

Terraform Datadog provider only

Source: deploy/secrets.tf, deploy/lambda.tf

SageMaker

The sagemaker_enabled Terraform variable (default true; the staging deploy sets it false) says whether the machine-learning repo’s models exist in the environment. When false, the feature-store KMS alias is not looked up, the SageMaker and KMS IAM statements are left off the api and rule-runner roles, and both functions are told not to call the models: SAGEMAKER_ENABLED=false makes the rule runner skip the rule-ML call (Rules Reference) and PAYBACK_PREDICTOR_ENABLED=false makes the API run without the payback predictor (Payback Predictor V2). The endpoint and feature group names are still set but unused.

Source: deploy/variables.tf, deploy/lambda.tf, deploy/main.tf, cmd/api/main.go

Monitoring

Datadog monitors and SLOs exist in prod only (local.create_datadog_monitors in deploy/datadog.tf). Alerts outside prod go unread, and the old and new test accounts both deploy as test, so Datadog would reject the second account’s identical monitors as duplicates.

Datadog monitors are created inside the shared Lambda module; this stack aggregates them into three SLOs (errors, throughput, latency), each targeting 99.9% over 7d/30d and covering api, rule-runner, result-runner, and profile-handler (float-created-handler is not in any SLO). A Datadog service definition registers the service as underwriting, tier 1.

Source: deploy/datadog.tf

Referenced (not provisioned here)

EventBridge default bus; the seven peer service API Gateways (txn, floats, insight, user-service, subscription-service, payments, loc-service); and, where SageMaker is enabled, the endpoints underwriting-inference-endpoint-site (rule ML) and underwriting-v2-inference-endpoint-site (payback predictor v2) and the feature group site-machine-learning-underwriting-v2-user-features with its KMS key (owned by the machine-learning repo).