mirror of
https://github.com/Sea-Haven-Industries/engineering-handbook.git
synced 2026-09-30 02:13:15 +00:00
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.
This commit is contained in:
parent
4b5d39fb91
commit
651dff5dd3
6 changed files with 290 additions and 1 deletions
|
|
@ -5,6 +5,7 @@ Engineering conventions and best practices for Sea Haven Industries.
|
|||
## Contents
|
||||
|
||||
- [Naming Conventions](naming-conventions.md) -- kebab-case everywhere, no exceptions
|
||||
- [Development Environment](dev-environment.md) -- workstation directory layout, pyenv, Node, launchd/TCC
|
||||
- [Git Workflow](git-workflow.md) -- feature branches, incremental commits, deploy-then-merge
|
||||
- [Commit Messages](commit-messages.md) -- imperative mood, 50/72 rule, explain "why"
|
||||
- [Pull Requests](pull-requests.md) -- scope, title, description format, merge strategy
|
||||
|
|
@ -12,8 +13,10 @@ Engineering conventions and best practices for Sea Haven Industries.
|
|||
- [GitHub Standards](github-standards.md) -- branch defaults, repo hygiene, Dependabot
|
||||
- [AWS Infrastructure](aws-infrastructure.md) -- SAM vs CDK, Lambda defaults, CloudFormation
|
||||
- [SAM Project Layout](sam-project-layout.md) -- standard directory structure for serverless projects
|
||||
- [Lambda Starter Template](lambda-template.md) -- minimal SAM scaffold for a new Python Lambda
|
||||
- [Secrets and Configuration](secrets-and-config.md) -- Secrets Manager vs SSM Parameter Store
|
||||
- [CI/CD Pipelines](cicd.md) -- every deployable repo gets a pipeline, no manual deploys
|
||||
- [Bedrock](bedrock.md) -- cross-region inference profiles, alias pinning, KB Docker requirement
|
||||
- [Git Hooks](hooks/) -- recommended pre-push and pre-commit hooks
|
||||
- [Scripts](scripts/) -- repo provisioning, automation tooling
|
||||
- [CDK Constructs](constructs/) -- shared VPN EC2 instance construct and other reusable patterns
|
||||
|
|
|
|||
59
bedrock.md
Normal file
59
bedrock.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# AWS Bedrock
|
||||
|
||||
Conventions and gotchas for using AWS Bedrock — Agents, Knowledge Bases, and model invocations.
|
||||
|
||||
## Foundation Model IDs
|
||||
|
||||
For **Claude 4.x models on Bedrock Agents**, always use the cross-region inference profile ID, not the direct model ID.
|
||||
|
||||
| Use | Example |
|
||||
|---|---|
|
||||
| Direct invocation (`bedrock-runtime`) | `anthropic.claude-sonnet-4-5-20250929-v1:0` |
|
||||
| Bedrock Agent `foundationModel` | `us.anthropic.claude-sonnet-4-5-20250929-v1:0` |
|
||||
|
||||
The direct model ID is only valid for `bedrock-runtime` invocations. Using it on `CfnAgent.foundationModel` makes the agent prepare successfully but throws `ResourceNotFoundException` at invocation time.
|
||||
|
||||
## IAM Permissions
|
||||
|
||||
Bedrock Agents need both the underlying foundation model and the cross-region inference profile in their IAM policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "bedrock:InvokeModel",
|
||||
"Resource": [
|
||||
"arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"arn:aws:bedrock:us-east-1:<account>:inference-profile/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The foundation model ARN uses the wildcard region (`*`) since cross-region inference can route to any of the profile's regions.
|
||||
|
||||
## Alias Version Pinning
|
||||
|
||||
When updating an agent's `foundationModel` or `instruction` via CDK or CloudFormation, the alias does **not** automatically re-point to the new version.
|
||||
|
||||
CloudFormation only updates the `DRAFT` version when `CfnAgent` changes. The alias stays pinned to the previous version unless the alias resource itself changes.
|
||||
|
||||
Force version rotation by bumping the alias `description` in the same change set:
|
||||
|
||||
```typescript
|
||||
new bedrock.CfnAgentAlias(this, 'Alias', {
|
||||
agentAliasName: 'live',
|
||||
agentId: agent.attrAgentId,
|
||||
description: `bumped 2026-05-14 — model update`, // change this to force a new version
|
||||
});
|
||||
```
|
||||
|
||||
## Knowledge Bases
|
||||
|
||||
The `@cdklabs/generative-ai-cdk-constructs` `VectorKnowledgeBase` L2 construct requires Docker Desktop on the deploying machine — it uses `Code.fromDockerBuild` for a custom resource Lambda. CI runners need Docker available.
|
||||
|
||||
For Knowledge Base S3 buckets, follow the standard tagging convention from [AWS Infrastructure](aws-infrastructure.md#s3): `Purpose` and `ManagedBy` tags.
|
||||
|
||||
## Action Groups
|
||||
|
||||
Action group Lambdas receive a Bedrock-specific event shape — they are not invoked as plain Lambda function URLs. The `apiPath` and `httpMethod` fields in the event identify which OpenAPI operation triggered the call. Validate them before dispatching.
|
||||
|
||||
Document the action group's OpenAPI schema in the same repo as the Lambda code; CloudFormation stores it inline.
|
||||
55
dev-environment.md
Normal file
55
dev-environment.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Development Environment
|
||||
|
||||
Local workstation conventions for Sea Haven engineering work.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
~/
|
||||
├── Documents/
|
||||
│ ├── repositories/ # Git repos only — no loose files, scripts, or data
|
||||
│ ├── working-docs/ # Cross-cutting docs, audits, drafts, reference material
|
||||
│ └── ...
|
||||
├── Desktop/ # Temporary workspace — nothing permanent
|
||||
├── Downloads/ # Transient — delete installers after use
|
||||
├── .ssh/ # All keys, certs, PEMs, credential files (chmod 600)
|
||||
└── (standard macOS dirs)
|
||||
```
|
||||
|
||||
- `repositories/` contains only git-tracked project directories. No loose scripts, CSVs, or data files.
|
||||
- `working-docs/` is for documentation that spans multiple repos — not a git repo.
|
||||
- Never store secrets in `repositories/` or `Downloads/`.
|
||||
- `.env` files are gitignored and local-only.
|
||||
- No credentials in Notion, Confluence, Slack, or other plaintext docs.
|
||||
|
||||
## Python
|
||||
|
||||
Use **pyenv**, not Homebrew Python. Homebrew auto-upgrades Python on minor releases (3.13 → 3.14) which breaks compiled tools like SAM CLI and pip packages with C extensions.
|
||||
|
||||
- Global default: Python 3.12 (matches Lambda runtime — see [AWS Infrastructure](aws-infrastructure.md#lambda-defaults))
|
||||
- SAM CLI installed via pip under pyenv, not via Homebrew
|
||||
- `pyenv init` belongs in `~/.zshrc`
|
||||
|
||||
When troubleshooting Python issues, check that pyenv is active before anything else. `which python` should point inside `~/.pyenv/`.
|
||||
|
||||
## Node.js
|
||||
|
||||
- Local: Node 24 / npm 11 (generates `lockfileVersion: 3`)
|
||||
- Lambda: Node 22.x or 24.x (set explicitly in IaC)
|
||||
- Reusable workflows: always pass `node-version: "24"` (the default is 22 / npm 10, which can fail `npm ci` on npm 11 lockfiles)
|
||||
|
||||
## macOS launchd and the TCC Sandbox
|
||||
|
||||
macOS TCC blocks launchd from reading `~/Documents/`, `~/Desktop/`, `~/Downloads/`, and iCloud folders. A script in `~/Documents/repositories/...` fails silently when launched by launchd even when `chmod +x` and manual invocation both succeed. The failure surfaces as `LastExitStatus = 32256` (exit 126) in `launchctl list`.
|
||||
|
||||
Any launchd agent or scheduled automation must reference a script outside TCC-protected directories. Keep the source in the repo for version control, and install the runnable copy to `~/.local/bin/` or `~/Library/Application Support/<name>/`. Do not grant Full Disk Access to `/bin/bash`.
|
||||
|
||||
If you edit the repo source, re-copy to the installed location — launchd reads the installed copy.
|
||||
|
||||
## Cleanup Cadence
|
||||
|
||||
| Cadence | Tasks |
|
||||
|---|---|
|
||||
| Weekly | Clear Desktop of anything older than 2 weeks |
|
||||
| Monthly | Check Downloads for stale installers; check `repositories/` for loose files; prune old VS Code extension versions |
|
||||
| Quarterly | Audit Docker (`docker system df`), npm/yarn caches; review GitHub repos for archival candidates |
|
||||
113
lambda-template.md
Normal file
113
lambda-template.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# 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](aws-infrastructure.md#lambda-defaults).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
my-stack/
|
||||
├── template.yaml
|
||||
├── samconfig.toml.example
|
||||
├── src/
|
||||
│ └── handler/
|
||||
│ ├── app.py
|
||||
│ └── requirements.txt
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
See [sam-project-layout.md](sam-project-layout.md) for the full directory convention.
|
||||
|
||||
## template.yaml
|
||||
|
||||
```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
|
||||
|
||||
```python
|
||||
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](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](naming-conventions.md).
|
||||
|
|
@ -37,3 +37,12 @@ new MyStack(app, 'MyStack', {
|
|||
| `expenseApprovalBot` | `expense-approval-bot` | camelCase |
|
||||
| `Expense-Approval-Bot` | `expense-approval-bot` | Mixed case |
|
||||
| `feature/AddParser` | `feature/add-parser` | PascalCase in branch |
|
||||
|
||||
## Legacy Stacks
|
||||
|
||||
A handful of stacks predate this convention and remain PascalCase because renaming would require stack replacement (data loss, deploy windows). Examples in active use:
|
||||
|
||||
- `SeaHavenDoorUnlockStack`
|
||||
- `WorkorderIngestStack`
|
||||
|
||||
Do not rename these solely to enforce kebab-case if doing so means tearing down and recreating production resources. New stacks must follow the convention; legacy stacks may keep their names until a planned migration brings them in line.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,57 @@ One directory per Lambda function. Each contains its own handler (`app.py`) and
|
|||
|
||||
### `src/shared/`
|
||||
|
||||
Optional. Used for code shared across multiple functions, typically deployed as a Lambda layer.
|
||||
Optional. Used for code shared across multiple functions, typically deployed as a Lambda layer. See the next section for layout details.
|
||||
|
||||
## Lambda Layers
|
||||
|
||||
When sharing code across functions via a layer, the source layout matters because SAM transforms `ContentUri` depending on whether `BuildMethod` is set.
|
||||
|
||||
### Correct layout (with `BuildMethod`)
|
||||
|
||||
```
|
||||
src/shared/
|
||||
├── shared/ # Package goes here directly — SAM wraps in python/ at build time
|
||||
│ ├── __init__.py
|
||||
│ └── utils.py
|
||||
└── requirements.txt # Layer-level pip dependencies
|
||||
```
|
||||
|
||||
Template:
|
||||
|
||||
```yaml
|
||||
SharedLayer:
|
||||
Type: AWS::Serverless::LayerVersion
|
||||
Properties:
|
||||
LayerName: my-stack-shared
|
||||
ContentUri: src/shared/
|
||||
CompatibleRuntimes:
|
||||
- python3.12
|
||||
CompatibleArchitectures:
|
||||
- arm64
|
||||
Metadata:
|
||||
BuildMethod: python3.12
|
||||
BuildArchitecture: arm64
|
||||
```
|
||||
|
||||
### BuildMethod nesting gotcha
|
||||
|
||||
With `BuildMethod: python3.12`, SAM copies `ContentUri` into a `python/` subdirectory during build, then pip-installs `requirements.txt` deps into that same `python/` directory.
|
||||
|
||||
Do **not** include a `python/` wrapper in your source — SAM adds it. The wrong layout:
|
||||
|
||||
```
|
||||
src/shared/
|
||||
├── python/ # SAM wraps this again → python/python/shared/ — module unreachable
|
||||
│ └── shared/
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
A real production incident (~22 hours of outage) traced to this exact pattern when a refactor moved layer code under an extra `python/` directory.
|
||||
|
||||
### Without BuildMethod (raw zip)
|
||||
|
||||
If the layer has no pip dependencies and you omit `BuildMethod`, SAM zips `ContentUri` as-is — you DO need the `python/` wrapper. Reserve raw zip for layers that ship only Python source.
|
||||
|
||||
## Standard .gitignore
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue