engineering-handbook/lambda-template.md
Adam Moussa 651dff5dd3
Add Bedrock, dev-env, and Lambda template pages (#7)
Adds three handbook pages covering conventions that were previously
scattered across feedback memories or rederived from scratch each
time:

- bedrock.md captures the cross-region inference profile requirement
  for Claude 4.x Bedrock Agents and the alias-version pinning gotcha,
  plus the IAM resource pattern and the KB Docker requirement.
- dev-environment.md documents the workstation directory layout,
  pyenv/Node conventions, the macOS launchd/TCC sandbox gotcha, and
  cleanup cadence.
- lambda-template.md provides a minimal SAM scaffold that follows the
  Lambda defaults already in aws-infrastructure.md (Python 3.12,
  arm64, explicit 60-day log retention, scoped Secrets Manager
  access, module-level secret cache).

Also extends two existing pages:

- sam-project-layout.md gains a Lambda Layers section with the
  BuildMethod nesting pattern that caused a ~22-hour production
  outage when violated.
- naming-conventions.md adds a Legacy Stacks note acknowledging that
  pre-convention PascalCase stacks (SeaHavenDoorUnlockStack,
  WorkorderIngestStack) stay as-is rather than risk stack
  replacement.
2026-05-14 19:50:37 -04:00

3.2 KiB

Lambda Starter Template

Minimal SAM scaffold for a new Python Lambda. Drop into template.yaml and adjust names. Follows the defaults in aws-infrastructure.md.

Project Structure

my-stack/
├── template.yaml
├── samconfig.toml.example
├── src/
│   └── handler/
│       ├── app.py
│       └── requirements.txt
└── .gitignore

See sam-project-layout.md for the full directory convention.

template.yaml

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: my-stack — one-line purpose

Globals:
  Function:
    Runtime: python3.12
    Architecture: arm64
    Timeout: 30
    MemorySize: 256
    LoggingConfig:
      LogFormat: JSON

Resources:
  HandlerFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: my-stack-handler
      CodeUri: src/handler/
      Handler: app.handler
      Environment:
        Variables:
          CONFIG_SECRET: my-stack/config
      Policies:
        - AWSLambdaBasicExecutionRole
        - Statement:
            - Effect: Allow
              Action: secretsmanager:GetSecretValue
              Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:my-stack/*

  HandlerLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HandlerFunction}
      RetentionInDays: 60

Outputs:
  HandlerArn:
    Description: Handler Lambda ARN
    Value: !GetAtt HandlerFunction.Arn

Key points:

  • Globals.Function sets runtime, architecture, and JSON logging once for the whole template.
  • The LogGroup is declared explicitly with RetentionInDays: 60. Omit it and CloudWatch creates the log group on first invocation with no retention — logs accumulate forever.
  • IAM scopes secretsmanager:GetSecretValue to the stack's secret prefix only. Add specific permissions as needed; never use AdministratorAccess.

src/handler/app.py

import json
import os

import boto3

_secrets_client = boto3.client("secretsmanager")
_config = None


def _get_config():
    global _config
    if _config is None:
        resp = _secrets_client.get_secret_value(SecretId=os.environ["CONFIG_SECRET"])
        _config = json.loads(resp["SecretString"])
    return _config


def handler(event, context):
    config = _get_config()
    # ... your logic ...
    return {"statusCode": 200, "body": json.dumps({"ok": True})}

The module-level _config global caches the secret across warm invocations. The first call per cold start hits Secrets Manager; subsequent calls reuse the cached value. See secrets-and-config.md for the rationale.

src/handler/requirements.txt

Keep this file in every function directory even when empty — SAM looks for it during sam build.

# Per-function dependencies. Leave empty if the function uses only boto3 and stdlib.

Naming Reminders

  • FunctionName must be kebab-case and start with the stack name (my-stack-handler).
  • Secret IDs use stack-name/secret-name.
  • Stack name itself is set in samconfig.toml, not the template — match the repo name.

See naming-conventions.md.