Aoos-GPT infra conventions
This project's IaC is Pulumi (Python), not CDK or raw CloudFormation —
infra/__main__.py is the one stack entry point. Don't introduce a second
IaC tool (CDK, Terraform, a .yaml CFN template meant to be deployed
directly) without checking with the user first; port CloudFormation into
Pulumi resources instead, matching the pattern in infra/aws_config.py.
Layout
infra/__main__.py— stack entry point. Deploysbackend/mybot/api.pyto App Runner (built from GitHub source, no Docker/ECR) and callsenable_aws_config().infra/aws_config.py— a self-contained module exposing one function,enable_aws_config() -> dict, called from__main__.py. Follow this pattern for any new independent concern (new file, one entry function, imported and called from__main__.py, outputs returned as a dict andpulumi.export-ed in__main__.py) rather than growing__main__.pyinto one large file.infra/requirements.txt— pinspulumiandpulumi-awsmajors. Check the pinnedpulumi-awsmajor before using a resource/arg shape from AWS provider docs — argument classes are codegenned and can differ between majors (e.g.aws.cfg.RecorderRecordingModeArgsneeds pulumi-aws ~7+).
Conventions already in use — match these
- Config:
pulumi.Config()for the stack's own settings (githubRepoUrl,openaiApiKey, ...) in__main__.py;pulumi.Config("awsConfig")(a namespaced config) for the AWS Config module's settings. Useconfig.require(...)for values with no sane default,config.get(...) or <default>for optional ones — mirror the CloudFormation template's own defaults when porting one. - Secrets never go in plain env vars or resource args — write them to
aws.secretsmanager.Secret+SecretVersionand reference the secret's ARN (runtime_environment_secrets/ an IAM policy resource), not the value itself. - Resources that must survive
pulumi destroy(matching a CloudFormationDeletionPolicy: Retain) getpulumi.ResourceOptions(protect=True). - Explicit
depends_onis required wherever a resource only references a parent ARN and Pulumi can't infer the edge on its own (e.g. a recorder reading an S3 bucket policy that must exist first) — seeaws_config.pyfor the pattern. aws.get_caller_identity()/aws.get_partition()for account ID and partition instead of hardcodingawsor an account number, so the stack stays portable to GovCloud/China partitions.
Known gotchas
- App Runner GitHub connection:
pulumi upcreates itPENDING; it needs a one-time manual authorization in the AWS Console (App Runner → GitHub connections → complete handshake) before a secondpulumi upwill actually build the service. This can't be automated from a Claude Code session — tell the user to do it, don't treat the stack as broken. - AWS Config service-linked role is account-wide, not per-stack or
per-region. If AWS Config has already been enabled once in this AWS
account (by this stack in another region, or by something else),
aws.iam.ServiceLinkedRolewill fail on a second deploy. Setpulumi config set awsConfig:createServiceLinkedRole falsein that case — don't just retry or add error-swallowing around it. - AWS credentials are never available inside a Claude Code session here —
pulumi preview/pulumi upcan't actually be run from this sandbox. Validate changes withpython3 -m py_compile infra/*.pyand a careful read instead, and say explicitly thatpulumi previewstill needs to be run by the user with their own credentials before merging.
Adding a new AWS resource
- If it's a new, separable concern, add a new
infra/<name>.pymodule with oneenable_<name>()(or similarly-named) entry function — don't bolt it ontoaws_config.pyor inline it in__main__.py. - Import and call it from
__main__.py;pulumi.exportanything the user would want to see afterpulumi up(URLs, ARNs, bucket names). - Document any new
pulumi config setkeys this needs in the README's "Deploying the backend (Pulumi + AWS App Runner)" section, following the existingawsConfig:*keys as an example. - Run
python3 -m py_compileon the changed files before calling the change done.