> ## Documentation Index
> Fetch the complete documentation index at: https://help.stralos.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Infrastructure

> Shared AWS infrastructure for Stralos

`stralos-core` owns shared AWS infrastructure for service repositories. Service stacks consume core remote state rather than recreating shared networking and database resources.

## Core environment stack

The `environment-setup` stack creates:

* VPC.
* Public and private subnets.
* Internet gateway.
* Optional NAT gateway for private subnet egress.
* Shared application services security group.
* RDS Postgres instance and subnet group.
* RDS security group.
* SSM parameters for RDS host, port, database, username, password, and connection string.

## Remote state

Service repos read the core stack through the remote state key:

```text theme={null}
environment-setup/terraform.tfstate
```

Environment backends use `stralos-terraform-state-dev`, `stralos-terraform-state-test`, `stralos-terraform-state-staging`, and `stralos-terraform-state-prod`.

Current service state keys are region-qualified, for example `au-nz/api-global/terraform.tfstate`. Their backends use S3-native locking (`use_lockfile = true`), which requires Terraform 1.10 or later and does not use a DynamoDB lock table.

## Service API infrastructure

The Activities, Documents, Estimate, Global, Rates, and Scheduler repositories define regional REST API Gateway v1 stages backed by Lambda. The stage name is `main`. The stacks include structured access logging, tracing where configured, region-qualified resource naming, and optional attachment to the environment's regional Web ACL.

## Global API Lambda Postgres pools

Each Lambda container creates up to three Postgres pools (owner, runtime, and platform). Without a cap, a burst of concurrent containers could exhaust the shared database. Each pool is bounded to:

| Setting | Value |
| - | - |
| `max` connections | 2 |
| `connectionTimeoutMillis` | 5,000 ms |

`max: 2` — one connection for the request's transaction and one spare so a helper that checks out while that transaction is open can queue behind nothing rather than deadlocking.

`connectionTimeoutMillis: 5,000` — a saturated database fails the checkout in 5 seconds. The safe error handler turns that into a `503 Service Unavailable` with `Retry-After: 5` rather than hanging until API Gateway times out at 29 seconds.

These bounds apply only to the deployed Lambda entrypoint (`src/lambda.ts`). The local development server and migration scripts keep pg's defaults (`max: 10`, no connection timeout) because they serve concurrent requests from a single process where a small pool would serialise them.

## Global API Lambda alarms

`stralos-api-global` carries CloudWatch alarm coverage for its Lambda plane. Every deployed function has an Errors alarm and a Throttles alarm, both using `treat_missing_data = "notBreaching"` so an idle period never false-alarms. The HTTP entrypoint (`api`) additionally has a guarded error-rate alarm (Errors/Invocations via metric math, only evaluated at 10 or more invocations in a 5-minute window) to catch a proportional burst even where the absolute Errors threshold is set higher to tolerate transient 5xx responses.

Scheduled workers — the lifecycle sweep (`rate(10 minutes)`), the directory-flush drain (`rate(1 minute)`) and its daily reconcile (`rate(1 day)`), the report-flush drain (`rate(1 minute)`) and its daily redrive (`rate(1 day)`), and the estate event-flush drain (`rate(1 minute)`) — each carry a dead-man's-switch alarm on `AWS/Events SuccessfulInvocationAttempts` scoped to the EventBridge rule. These alarms use `treat_missing_data = "breaching"` because absence of the metric is the failure. Window sizes match each rule's cadence to avoid false-alarms on healthy idle intervals.

The estate event-flush Lambda additionally raises an `event_outbox_backlog` alarm when any row in the `event_outbox` table has been unsent for more than 15 minutes. That condition indicates a publishing failure rather than normal latency: the inline after-commit publish sends almost everything within the request, and the flusher retries each minute. Likely causes are a missing or incorrect `EVENT_BUS_NAME`, an IAM `AccessDenied` on `events:PutEvents`, or the bus not existing. A missing `EVENT_BUS_NAME` is non-fatal — the event is still written to `event_outbox` and the create still succeeds — but no events leave the outbox until the variable is set.

The lifecycle sweep Lambda's `reserved_concurrent_executions` is excluded from Terraform management via a `lifecycle { ignore_changes = [reserved_concurrent_executions] }` block. This preserves any operator-set concurrency gate across a `terraform apply`. Set `reserved_concurrent_executions = 0` on the function to pause the sweep during a coordinated cutover (such as the project membership migration), and restore it manually once the new worker and schema are verified. Terraform will not reset the gate on the next apply.

The API Gateway stage has its own `5XXError` alarm. Fastify returns application errors as successful Lambda invocations, so the Lambda Errors alarm cannot see them. The gateway-level alarm closes that gap.

All alarms fire the shared SNS alarm topic (`var.lambda_alarm_sns_topic_arn`) created and managed by `stralos-admin-api`. A Terraform `check` compares the alarm map with the Lambda functions already live in AWS and reports a non-blocking plan warning for a gap. A function introduced by the current apply is detectable only on a later plan.

## Documents infrastructure

The Documents service adds its own S3 bucket, EventBridge/SQS routing, worker Lambdas, DLQs, provider permissions, and alarms. Its API uses the shared REST stage and optional Web ACL association.

Documents resource names are region-qualified. The bucket and EventBridge bus default to
`{project}-{environment}-{region_key}-documents`; stage queues use the corresponding
`{project}-{environment}-{region_key}-api-documents-<stage>` prefix. `region_key` (default `au-nz`)
also selects the workload AWS region through `aws_region_by_key`.

The bucket is versioned, encrypted with SSE-S3, publishes object-created events to EventBridge,
and aborts incomplete multipart uploads. Its CORS rule permits browser presigned-POST uploads
from the `cors_allow_origins` list; an empty list falls back to `http://localhost:5173`.

The bucket carries several S3 lifecycle rules:

* **`abort-incomplete-multipart`** — aborts incomplete multipart uploads after 1 day.
* **`expire-quarantine`** — expires infected objects in `quarantine/` after 30 days.
* **`expire-sync-staging`** — expires sync-staging objects and their noncurrent versions after 7 days.
* **`expire-sync-staging-delete-markers`** — reaps expired delete markers in `sync-staging/`.
* **`expire-noncurrent-versions`** (bucket-wide) — expires noncurrent versions of every object after 30 days. This is the retention half of document deletion: `DELETE /documents/:id` issues a bare `DeleteObject`, which writes a delete marker and leaves the upload as a noncurrent version; this rule reaps those bytes after the 30-day recovery window.
* **`expire-delete-markers`** (bucket-wide) — reaps expired delete markers once their last noncurrent version has been removed, preventing indefinite accumulation of one marker per deleted document.

The `expire-noncurrent-versions` rule is intentionally bucket-wide rather than prefix-scoped. A prefix list would silently exclude any prefix added later. Adding this rule can only shorten retention, never lengthen it: every prefix either already had a shorter noncurrent bound and keeps it, or had none and gains the 30-day cap.

Each primary pipeline-stage worker has an SQS event-source mapping. Terraform emits `scaling_config` only when that stage's `maximum_concurrency` is between 2 and 1,000; `-1` omits the cap. The current defaults cap `parse-jobs`, `parse-results`, `summary-jobs`, `classify-jobs`, and `variation-assessment-jobs` at 5 concurrent executions, and `area-jobs` at 10. `upload-events` and `scan-results` remain uncapped.

Primary worker Lambda functions and their corresponding IAM roles share a capped name. Terraform keeps `{name_prefix}-worker-{stage}` when it is at most 64 characters. For a longer value, it keeps the first 55 characters and appends `-` plus the first eight hexadecimal characters of the full name's SHA-1 hash, producing a stable unique name within the AWS limit.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.