From 3070bc1deb98192ca32737395e3dead074c46f32 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 20:48:06 -0400 Subject: [PATCH 01/11] =?UTF-8?q?docs(spec):=20AWS=20Bedrock=20setup=20ins?= =?UTF-8?q?tructions=20=E2=80=94=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the docs gap between what the tutorial currently tells a reader about getting Bedrock working and what they actually need to know: - Account/region reachability check - Deployer CLI principal IAM permissions (currently unstated) - cdk bootstrap as a first-time prerequisite (currently unstated) - AWS Marketplace subscription, with explicit disambiguation from the vendor's direct site - Per-family EULA caveat (Anthropic only) - First-deploy smoke + optional real-Bedrock-call trigger - Tail reference: regions, model switching, cost monitoring, troubleshooting Design only; implementation plan and reader-facing doc follow in subsequent commits. Co-Authored-By: Claude --- ...08-10-bedrock-setup-instructions-design.md | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md diff --git a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md new file mode 100644 index 0000000..c6bba70 --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md @@ -0,0 +1,247 @@ +# AWS Bedrock Setup Instructions — Design + +**Date:** 2026-08-10 +**Status:** Approved +**Scope:** Documentation-only change. Closes the gap between what the tutorial currently tells a reader about getting Bedrock working and what they actually need to know. No code changes. + +--- + +## 1. Purpose and constraints + +The tutorial currently documents Bedrock setup in a single 20-line section (`docs/02-rehydration.md` §Bedrock setup, lines 100–119) that lives at the bottom of a file whose title is "Rehydration." That placement was the original mistake this spec corrects: rehydration and AWS account setup are different topics and serve different readers at different moments. + +The new doc is end-to-end setup — what a reader needs to know *before* `cdk deploy` produces a working bot. It is the only place in the repo that should describe Bedrock prerequisites. Everything in the current `02-rehydration.md` §Bedrock setup section moves into it, plus three currently-unstated prerequisites that a brand-new AWS account will trip on: + +- **Bedrock may not be reachable in every account/region.** Verify Bedrock is accessible from the deploying account in `us-east-1` before attempting the procedure. Some account types (e.g. accounts that have only ever used Lightsail, or accounts in restricted Regions) do not have Bedrock in their service catalog. The doc's Step 1 covers the verification. +- **The deploying CLI principal needs permission to create the stack.** A brand-new IAM user cannot run `cdk deploy` without granting a wide set of IAM/Lambda/S3/Events/ECR/CFN permissions, none of which is currently mentioned in the repo. +- **`cdk bootstrap` is a first-time prerequisite.** Not currently mentioned anywhere in `README.md` or `docs/`. + +**Constraints that shape every decision below:** + +- **Docs only.** No changes to `infra/stack.ts`, `src/`, or `tests/`. The runtime IAM policy CDK generates is correct; the gap is that the deployer's *own* IAM principal isn't documented as a prerequisite. +- **Match the existing `docs/06-discord-webhook-setup.md` style.** Same step-numbered walkthrough + tail reference pattern, same prose density, same code-fence conventions. The setup docs should look like a series, not a grab-bag. +- **Public tutorial quality.** Every non-obvious decision explained. Same bar the rest of the repo holds itself to. +- **Region pinned to `us-east-1` for the procedure.** Reference material addresses what changes if a reader moves. +- **No content duplication with `docs/bedrock-model-comparison.md` or `docs/07-budget-protection.md`.** The new doc points to both rather than restating their content. + +--- + +## 2. Audience + +**Primary.** A reader who has the tutorial repo, an AWS account they've just opened or rarely used, and no prior Bedrock experience. This is the unblock path — they should be able to complete a deploy top-to-bottom without asking a question. + +**Secondary.** A reader who's already deployed this tutorial once and is now switching the default model (ZAI → Anthropic), switching regions, or troubleshooting a `cdk deploy` AccessDenied. + +**What this doc is not.** It is not a Bedrock reference manual, not a model-comparison doc, and not a cost-optimization guide. Those are `docs/bedrock-model-comparison.md` and `docs/07-budget-protection.md`'s jobs; the new doc links to both. + +--- + +## 3. New doc — `docs/11-aws-bedrock-setup.md` + +### 3.1 Structure + +Mirrors `docs/06-discord-webhook-setup.md`: numbered "Step N: …" sections for the procedure, then tail reference material. Target length 250–350 lines. + +``` +# AWS Bedrock setup + 2–3 sentence opener: what this doc covers, why it lives separately + from 02-rehydration.md. + +## Step 1: Confirm Bedrock is reachable in your account and region + - Open the AWS console in us-east-1 and navigate to Bedrock. + - If the service is unavailable in your account (some accounts + that have only used Lightsail, or accounts in restricted + Regions, may not see it): the tutorial will not deploy. Stop + here and resolve before continuing — typical resolutions are + switching to a standard AWS account or to us-east-1. + - Standard accounts (including Free Tier) in commercial Regions + (us-east-1 in particular): proceed. + +## Step 2: Grant the deploying CLI principal permission to create the stack + - Minimum IAM actions the principal needs: + iam:CreateRole, iam:AttachRolePolicy, iam:PassRole + lambda:CreateFunction, lambda:UpdateFunctionCode + s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging + events:CreateRule, events:PutTargets + ecr:CreateRepository, ecr:GetAuthorizationToken + cloudformation:CreateStack / UpdateStack / DeleteStack + pass-role + ssm:GetParameter (CDK bootstrap resources) + - Honest trade-off note: AdministratorAccess for the first deploy, + then scope down. A tighter policy is feasible but enumerating one + is out of scope for this doc. + - Explicit call-out: this is about the *deployer's* IAM principal, + NOT the runtime Lambda role (which CDK generates automatically — + see infra/stack.ts:103–107). + +## Step 3: Run cdk bootstrap (first-time only) + - What it does: provisions the CDKToolkit CloudFormation stack, + an S3 staging bucket, and an ECR repo in the deploying account + the first time CDK is used in a region. + - The command: npx cdk bootstrap aws:///us-east-1 + - Why this is unstated in the current README: it was an oversight. + +## Step 4: Subscribe to the model in AWS Marketplace + - Walk through the Marketplace subscription UI for + zai.glm-4.7-flash. + - Subscribing is free; you pay per token used through AWS billing. + - Explicit disambiguation: zai.com (the vendor's own site, which + has different plans with minimums) is *not* the right place. + This tutorial uses the AWS Marketplace listing. + +## Step 5: Confirm model access in the Bedrock console + - Navigate to Bedrock → Model access in us-east-1. + - For zai.* models: nothing to click. The Marketplace subscription + from Step 4 is the gate; Bedrock enables foundation-model access + by default in commercial Regions once the subscription is in + place. + - For anthropic.claude-* models: must accept the per-model EULA on + this page before the first InvokeModel call succeeds. This is + the one remaining reason to open the Model access screen for + those families. + +## Step 6: Deploy + - Source .env.discord (set -a; . ./.env.discord; set +a). + - npm run deploy. + - What success looks like: stack outputs include the agent + Function URL and the smoke status URL; no AccessDenied in + CloudFormation events. + +## Step 7: Verify with npm run smoke + - Probes the status Function URL with SigV4 and asserts the URL + actually requires it. + - Does NOT invoke Bedrock; that's a separate check. + - Read-only and safe to run any time. + +## Step 8 (optional): Trigger a real fetch to invoke Bedrock + - The on-demand trigger path, gated by FETCH_TRIGGER_TOKEN. + - Confirms the full pipeline end-to-end: Lambda → S3 hydrate → + external source fetch → Bedrock Converse call → Discord post. + - The first Bedrock call here is where Marketplace-subscription + mistakes (Step 4) and EULA mistakes (Step 5, for Anthropic) + surface as AccessDeniedException. + +## Reference + + ### Region availability + - The procedure assumes us-east-1. What changes if a reader + moves: AWS_REGION + BEDROCK_REGION env vars; some ZAI models + are not available in every Region; Marketplace subscription + is per-Region. + + ### Switching models + - zai.glm-4.7-flash → anthropic.claude-*: requires EULA + acceptance (Step 5). Resource ARN pattern changes too — + bare id vs global.* prefix is decided per-family by + src/format/families.ts. Don't hand-edit the id. + - Point to docs/bedrock-model-comparison.md for picking a + model; don't duplicate the comparison table here. + + ### Cost monitoring + - Brief pointer to docs/07-budget-protection.md. + - One-paragraph "what to expect at the default cadence": + ~$0.02–$0.04/day at the 5-minute loop cadence from + README.md §Cost. + + ### Troubleshooting + - AccessDenied on cdk deploy → deployer IAM (Step 2). + - AccessDeniedException at first fetch → Marketplace + subscription missing (Step 4) or Region mismatch. + - ValidationException at first fetch → bad model id format + (most often: hand-added a prefix that the family rejects). + - ResourceNotFoundException → bare model id got a prefix by + mistake (e.g. global.zai.glm-4.7-flash — zai.* family + accepts only bare id per src/format/families.ts). +``` + +### 3.2 What the new doc deliberately does NOT cover + +- Model selection rationale or pricing comparison → `docs/bedrock-model-comparison.md`. +- Budget alarms, auto-revoke IAM, or detailed cost protection → `docs/07-budget-protection.md`. +- A least-privilege deployer IAM policy. The minimum-viable list is given; tightening it is the reader's job, not the tutorial's. +- VPC setup, custom domains, or any production hardening. The doc is for first-deploy, not prod. + +--- + +## 4. Changes to existing files + +Three small edits so the new doc slots in cleanly. + +### 4.1 `docs/02-rehydration.md` — delete §Bedrock setup + +Lines 100–119 (the `## Bedrock setup` heading and its body) are removed. The file returns to being a single-topic doc about the four rehydration mechanisms + the `/tmp` ceiling. No other edits to this file. + +### 4.2 `README.md` — update one link and one table row + +- **Lines 47–50:** currently link to `docs/02-rehydration.md#bedrock-setup`. Change to `docs/11-aws-bedrock-setup.md`. The phrasing stays — the warning that the deploy needs the Marketplace subscription before the first fetch is still true, just the link moves. +- **Line 186 docs table:** add a new row next to the existing `bedrock-model-comparison.md` entry: + ``` + | [docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) | Account type, deployer IAM, Marketplace subscription, Region, EULA, first-deploy smoke | + ``` + +### 4.3 Cross-reference search + +Before declaring done: search the entire repo for `02-rehydration.md#bedrock-setup` and any other inbound link to the deleted section. Update each to point to the new doc. The README is the only known caller; verify and surface any others. + +### 4.4 `docs/09-lesson-script.md` — add new doc to the Prerequisite line + +This is the RAG-extension lesson script. It assumes the student has already completed the base tutorial's Bedrock setup end-to-end, but currently names only `[08-rag-vector-search.md](08-rag-vector-search.md)` and `[01-architecture.md](01-architecture.md)` as prerequisites — neither of which is the canonical Bedrock-setup reference. Update line 5 to make the prereq explicit: + +``` +**Prerequisite:** the student has completed the base tutorial end-to-end, +including the Bedrock setup documented in +[11-aws-bedrock-setup.md](11-aws-bedrock-setup.md). The student has read +[08-rag-vector-search.md](08-rag-vector-search.md) and the base tutorial's +[01-architecture.md](01-architecture.md). Familiarity with the +writer/reader asymmetry in [01-architecture.md](01-architecture.md) and +the per-source error isolation in [03-schema.md](03-schema.md) is +assumed. +``` + +The wording changes from "has read" to "has completed … end-to-end" for the base tutorial, because by the time the student reaches the RAG lesson the base tutorial should be deployed and working — Bedrock setup is a setup step, not a reading step. `11-aws-bedrock-setup.md` is the pointer they can revisit if their base deploy is broken. + +No other edits to `09-lesson-script.md`. The lesson bodies assume Bedrock is working but never reference the setup doc, so updating only the Prerequisite line is sufficient. + +### 4.5 No-op list (explicitly) + +The following are NOT changed by this spec: +- `infra/stack.ts` — runtime IAM is already correct. +- `src/format/families.ts` — model-id family resolution is already correct. +- Any source file under `src/`. +- Any test file under `tests/`. +- `docs/01-architecture.md`, `docs/03-schema.md`, `docs/04-extending.md`, `docs/05-from-tutorial-to-prod.md`, `docs/06-discord-webhook-setup.md`, `docs/07-budget-protection.md`, `docs/08-rag-vector-search.md`, `docs/10-concurrency.md`, `docs/bedrock-model-comparison.md`. +- `package.json`, `tsconfig*.json`, `vitest.config.ts`, `Dockerfile`. + +--- + +## 5. Success criteria + +### 5.1 Unblock test (primary audience) + +A reader following the new doc top-to-bottom on a brand-new AWS account reaches a working first Discord post without asking a question and without consulting the now-deleted `02-rehydration.md` Bedrock section. + +### 5.2 Quality test (secondary audience) + +- All non-obvious decisions are explained, not just stated. +- The doc reads like `docs/06-discord-webhook-setup.md` and `docs/02-rehydration.md`: same prose density, same code-fence conventions, same heading depth, same tone. +- The Marketplace subscription step is unambiguous about which site to subscribe on (AWS Marketplace, not zai.com). +- The EULA caveat is clearly conditional on the model family (Anthropic only), not presented as a blanket requirement. +- Every cross-link from and to the new doc resolves. + +### 5.3 Integration test (the project itself) + +- `docs/02-rehydration.md` is shorter by the deleted section and reads cleanly as a single-topic doc. +- `docs/09-lesson-script.md` Prerequisite line points to the new doc and reads cleanly as a single prerequisite statement. +- README's Quick start still makes sense end-to-end. +- No broken inbound links anywhere in the repo (`grep -r '02-rehydration.md#bedrock-setup' .` returns nothing). +- The new doc table row in the README links to a file that exists. + +### 5.4 What we will NOT do + +- No automated tests for doc content. Verification is the read-through checklist above plus the inbound-link grep. +- No live deploy against a real AWS account. The remote collaborator's actual attempt is the integration test. + +--- + +## 6. Open items + +None. The remote collaborator's three blockers — `.env` not being read, "no pay-as-you-go" confusion, and the unstated deployer-IAM prerequisite — are all addressed by this spec. The `.env` issue is a usage error rather than a docs gap (the README's `set -a; . ./.env; set +a` pattern is already documented at line 23), so it does not need a doc change. The Marketplace-vs-direct-vendor confusion is addressed by Step 4 of the new doc. From bf2e6a15a5442b743af2ac7d68a50058329b7314 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:37:19 -0400 Subject: [PATCH 02/11] docs(spec): CLI-first verification, Docker + Titan prerequisites --- .../plans/2026-08-10-aws-bedrock-setup-doc.md | 847 ++++++++++++++++++ ...08-10-bedrock-setup-instructions-design.md | 194 ++-- 2 files changed, 986 insertions(+), 55 deletions(-) create mode 100644 docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md diff --git a/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md b/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md new file mode 100644 index 0000000..7b9c4f2 --- /dev/null +++ b/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md @@ -0,0 +1,847 @@ +# AWS Bedrock Setup Doc Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Create `docs/11-aws-bedrock-setup.md` — a CLI-first, end-to-end AWS setup walkthrough that gets a brand-new AWS account from zero to a working Discord post — and rewire the three existing docs that should point at it. + +**Architecture:** Documentation only. The new doc mirrors `docs/06-discord-webhook-setup.md`'s numbered-step structure: Steps 0–9 as the procedure, then a `Reference` tail (Region, model switching, cost, troubleshooting). Every step carries an `aws` CLI command that proves it succeeded, so a reader can hand the work to an AI assistant with CLI access; Step 4 (AWS Marketplace subscription) is the one console-only detour and is labelled as such. Three existing files change: `docs/02-rehydration.md` loses its `## Bedrock setup` section, `README.md` gets a re-pointed link plus a docs-table row, and `docs/09-lesson-script.md` gets an updated Prerequisite line. + +**Tech Stack:** Markdown. AWS CLI v2 (`bedrock`, `bedrock-runtime`, `cloudformation`, `sts`) for the commands quoted in the doc. No changes to `src/`, `infra/`, `tests/`, or any config file. + +**Spec:** `docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md` + +--- + +## Context the implementing engineer needs + +You have not seen this repo. Read these before Task 1 — they are short: + +- `docs/06-discord-webhook-setup.md` (99 lines) — **the style template.** Match it: sentence-case `## Step N: …` headings, prose wrapped at ~90 columns, `> **Bold callout.**` blockquotes for warnings, fenced `bash` blocks with a one-line `#` comment explaining the block's purpose. +- `docs/02-rehydration.md` lines 100–119 — the section being deleted. Its content is absorbed into the new doc's Steps 4–5. +- `README.md` lines 46–51 (the Bedrock warning), line 186 (docs table), lines 86–144 (the on-demand trigger section the new Step 9 links to). + +Facts verified against the code — do not restate them differently: + +| Fact | Source | +|---|---| +| The Lambda is a **container image** function, so `cdk deploy` needs a running Docker daemon | `infra/stack.ts:84` — `lambda.DockerImageFunction` / `DockerImageCode.fromImageAsset` | +| `npm run deploy` **already runs `cdk bootstrap`** — the reader never types it | `scripts/deploy.sh:8-11` | +| Stack name is `SqliteS3AgentTutorial` | `scripts/deploy.sh:6` | +| Deploy Region comes from `AWS_REGION` (default `us-east-1`), profile from `AWS_PROFILE` (default `default`) | `scripts/deploy.sh:4-5` | +| Stack outputs: `SnapshotBucketName`, `AgentFunctionName`, `AgentFunctionUrl`, `LoopRuleName` | `infra/stack.ts:152-155` | +| Two Bedrock models are invoked per tick: the configurable chat model and the **fixed** `amazon.titan-embed-text-v2:0` | `src/config.ts:114`, `src/embed/titan.ts:16`, `infra/stack.ts:103-113` | +| Default chat model is `zai.glm-4.7-flash` | `src/config.ts:114` | +| Deployed `BEDROCK_REGION` is hardcoded to the stack Region — not an independent knob | `infra/stack.ts:74`; the env var only matters locally, `src/config.ts:135` | +| `zai.*` accepts the **bare** model id only; `anthropic.claude-*` defaults to the `global.` prefix; a prefix on the configured id throws at startup | `src/format/families.ts` | +| `FETCH_TRIGGER_TOKEN` is read at **synth** time — setting it after deploying requires a redeploy | `README.md:94-95` | + +CLI syntax was verified against aws-cli/2.36.11 while writing this plan. `bedrock-runtime converse` takes `--model-id` and `--messages` (a JSON document — shorthand is not supported). `bedrock get-foundation-model` takes `--model-identifier`, not `--model-id`. `bedrock-runtime invoke-model` takes `--body`, `--content-type`, `--model-id`, and a **positional outfile**, and needs `--cli-binary-format raw-in-base64-out` for a plain-JSON body on CLI v2. + +--- + +## File Structure + +- **Create** `docs/11-aws-bedrock-setup.md` — the only place in the repo describing Bedrock prerequisites. Built up over Tasks 1–4, one commit per section group, so each commit leaves a readable document. +- **Modify** `docs/02-rehydration.md` — delete lines 100–119 (`## Bedrock setup` + body). Task 5. +- **Modify** `README.md` — re-point the line-50 link, add a docs-table row. Task 6. +- **Modify** `docs/09-lesson-script.md` — rewrite line 5 (Prerequisite). Task 7. + +Not touched (spec §4.5): `infra/`, `src/`, `tests/`, `package.json`, `tsconfig*.json`, `vitest.config.ts`, `Dockerfile`, and every other file in `docs/`. Historical records under `docs/superpowers/plans/` and `docs/superpowers/specs/` are **not** rewritten even though they contain stale links. + +--- + +## Task 0: Branch + +**Files:** none + +- [ ] **Step 1: Create the working branch** + +```bash +git checkout -b docs/aws-bedrock-setup +git status --porcelain +``` + +Expected: branch created; the only modified file is the spec (already revised in the design session). If the spec shows as modified, commit it first: + +```bash +git add docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md +git commit -m "docs(spec): CLI-first verification, Docker + Titan prerequisites" +``` + +--- + +## Task 1: New doc — opener and Steps 0–3 + +**Files:** +- Create: `docs/11-aws-bedrock-setup.md` + +- [ ] **Step 1: Write the opener and Steps 0–3** + +Create `docs/11-aws-bedrock-setup.md` with exactly this content: + +````markdown +# AWS Bedrock setup + +This is the AWS half of getting the tutorial running: everything a brand-new account +needs before `npm run deploy` produces a working bot. The Discord half is +[06-discord-webhook-setup.md](06-discord-webhook-setup.md). This used to live at the +bottom of [02-rehydration.md](02-rehydration.md), which was the wrong home — account +setup and rehydration are different topics, read at different moments. + +Every step below except Step 4 can be verified from the AWS CLI, which means you can +hand most of this to an AI assistant that has CLI access rather than clicking through +consoles. A prompt that works: + +``` +I'm setting up the sqlite-s3-agent-tutorial on a fresh AWS account. Walk me +through docs/11-aws-bedrock-setup.md using the AWS CLI in us-east-1: run each +step's verification command, tell me what the output means, and stop at the +first failure rather than continuing. Show me any command that creates, +subscribes to, or grants something before you run it. +``` + +> **Step 4 is the exception.** Subscribing to a model in AWS Marketplace has no +> CLI equivalent you can rely on — it is a console flow with a legal agreement. +> Expect to do that one yourself. + +## Step 0: Local prerequisites + +Four things must be true on your machine and account before anything else matters. + +**1. The AWS CLI is installed and authenticated.** + +```bash +# Should print your account id, user id, and principal ARN. +aws sts get-caller-identity +``` + +If this errors, install the AWS CLI and run `aws configure` (or `aws sso login`) +before continuing. Every other command in this doc assumes it works. + +**2. A container runtime is running.** + +```bash +# Should print server info, not "Cannot connect to the Docker daemon". +docker info +``` + +The tutorial's Lambda is a **container image** function (`infra/stack.ts:84` uses +`lambda.DockerImageFunction` with `DockerImageCode.fromImageAsset`), so `cdk deploy` +builds the image locally and pushes it to ECR. Without a running daemon the deploy +fails with a Docker error that mentions neither Bedrock nor this tutorial, which is a +confusing place to lose an hour. Docker Desktop, Finch, and Podman all work. + +**3. Dependencies are installed.** `npm install` and `npm test` from the README's +Quick start should both pass. Nothing here depends on AWS yet. + +**4. The account has a valid payment method.** Bedrock is not a Free Tier service. +An account without a payment method on file shows no pay-as-you-go option and will +refuse model subscriptions in Step 4 — if that is what you are seeing, this is why, +and it is fixed in **Billing and Cost Management → Payment preferences**, not in +Bedrock. + +## Step 1: Confirm Bedrock is reachable in your account and Region + +This whole tutorial pins `us-east-1`. Check that Bedrock answers there and that the +default model exists: + +```bash +aws bedrock list-foundation-models --region us-east-1 \ + --query "modelSummaries[?contains(modelId, 'glm')].modelId" --output text +``` + +Read the result: + +| Output | Meaning | What to do | +|---|---|---| +| One or more `zai.glm-…` ids | Bedrock is reachable and the model exists in the Region | Continue to Step 2 | +| Empty output, command succeeds | Bedrock answered, but this model family isn't offered to your account/Region | Try `--region us-east-1` explicitly; if still empty, see [bedrock-model-comparison.md](bedrock-model-comparison.md) and pick a model your account does list | +| `AccessDeniedException` | Your principal lacks `bedrock:ListFoundationModels` | Step 2 — this is an IAM problem, not a Bedrock one | +| `Could not connect to the endpoint URL` | Bedrock is not available in the Region you asked for | Use `us-east-1` | + +Some account types — accounts that have only ever used Lightsail, accounts in +restricted Regions, some organisation-managed accounts with service control policies — +do not have Bedrock at all. If that is your account, stop here: the tutorial will not +deploy. The fix is a standard AWS account in a commercial Region, not a workaround. + +## Step 2: Give the deploying principal permission to create the stack + +This step is about **your** IAM principal — the identity `aws sts get-caller-identity` +printed in Step 0. It is *not* about the Lambda's runtime role, which CDK generates for +you (`infra/stack.ts:103-113` grants exactly the two Bedrock models the code invokes, +and nothing else). A brand-new IAM user typically has none of what a CDK deploy needs. + +The deploy touches, at minimum: + +``` +iam:CreateRole, iam:AttachRolePolicy, iam:PassRole +lambda:CreateFunction, lambda:UpdateFunctionCode, lambda:CreateFunctionUrlConfig +s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging +events:PutRule, events:PutTargets +ecr:CreateRepository, ecr:GetAuthorizationToken, + ecr:InitiateLayerUpload, ecr:UploadLayerPart, + ecr:CompleteLayerUpload, ecr:PutImage +cloudformation:CreateStack, cloudformation:UpdateStack, cloudformation:DeleteStack +sts:AssumeRole (the CDK bootstrap deploy roles — see Step 3) +ssm:GetParameter (CDK bootstrap version lookup) +``` + +> **Treat that list as representative, not exhaustive.** CDK's exact call set shifts +> between versions, and an incomplete list presented as "the minimum" is worse than no +> list — it sends you hunting one denied action at a time. The pragmatic path is +> `AdministratorAccess` on a dedicated deploy principal for the first deploy, then +> scoping down once you can see in CloudTrail what was actually called. Enumerating a +> least-privilege deploy policy is out of scope for this tutorial. + +## Step 3: Know what the first deploy bootstraps + +You do not run `cdk bootstrap` by hand — `scripts/deploy.sh:8-11` already runs it as +the first thing `npm run deploy` does. This step exists so that the first run's extra +activity isn't a surprise. + +The first time CDK is used in an account/Region pair, it provisions a `CDKToolkit` +CloudFormation stack: an S3 staging bucket for assets, an ECR repository for container +images, an SSM parameter recording the bootstrap version, and a set of IAM roles CDK +assumes to do the deploy. Check whether that already happened: + +```bash +aws cloudformation describe-stacks --stack-name CDKToolkit --region us-east-1 \ + --query "Stacks[0].StackStatus" --output text +``` + +`CREATE_COMPLETE` or `UPDATE_COMPLETE` means bootstrap is done and the first deploy +will skip straight to your stack. `ValidationError … does not exist` means the first +`npm run deploy` will bootstrap first — expect it to take a few extra minutes and to +need the CloudFormation, S3, ECR, SSM, and `iam:CreateRole` permissions from Step 2. +Most first-run `AccessDenied` failures land here rather than on the tutorial's own +resources. +```` + +- [ ] **Step 2: Verify the file renders and no link is broken** + +```bash +# Every relative link target must exist. +grep -o '](0[0-9]-[a-z0-9-]*\.md\|](bedrock-model-comparison\.md' docs/11-aws-bedrock-setup.md \ + | sed 's/](//' | sort -u | while read -r f; do + test -f "docs/$f" && echo "OK $f" || echo "MISS $f" + done +``` + +Expected: `OK 06-discord-webhook-setup.md`, `OK 02-rehydration.md`, `OK bedrock-model-comparison.md`. No `MISS` lines. + +- [ ] **Step 3: Commit** + +```bash +git add docs/11-aws-bedrock-setup.md +git commit -m "docs: add AWS Bedrock setup doc — prerequisites through bootstrap" +``` + +--- + +## Task 2: New doc — Steps 4–6 (Marketplace, model access, pre-deploy probe) + +**Files:** +- Modify: `docs/11-aws-bedrock-setup.md` (append) + +- [ ] **Step 1: Append Steps 4–6** + +Append exactly this to the end of `docs/11-aws-bedrock-setup.md`: + +````markdown +## Step 4: Subscribe to the model in AWS Marketplace + +This is the console-only step, and it is the one people get wrong. + +1. Open the [AWS Marketplace](https://aws.amazon.com/marketplace) console in the same + account you authenticated in Step 0, and search for the model — `GLM-4.7-Flash` for + this tutorial's default. +2. Open the listing and click **View purchase options** / **Subscribe**. +3. Accept the terms. **Subscribing costs nothing.** There is no minimum, no monthly + fee, and no commitment — you pay per token consumed, billed through your normal AWS + invoice alongside Lambda and S3. +4. Wait for the subscription status to show as active. It is usually immediate but can + take a few minutes. + +> **Subscribe on AWS Marketplace, not on the vendor's own site.** The model vendor +> (for the default model, `zai.com`) sells its own API plans, some with minimum spends. +> Those are a different product with different credentials, and they will not make +> `bedrock:InvokeModel` work. What this tutorial needs is the AWS Marketplace listing, +> reached from the AWS console, billed to your AWS account. + +The subscription is **per Region**. Subscribing while your console is in `us-east-1` is +what the rest of this doc assumes. + +## Step 5: Confirm access to both models this tutorial invokes + +There are two, and readers regularly arrange access for only the first: + +| Model | Configurable? | Used for | +|---|---|---| +| `zai.glm-4.7-flash` (default) | Yes — `BEDROCK_MODEL_ID` (`src/config.ts:114`) | The friendly message + haiku, via Converse | +| `amazon.titan-embed-text-v2:0` | **No** — hardcoded at `src/embed/titan.ts:16` | Embedding each tick's message for RAG search | + +Every tick calls both. Verify each is visible to your account: + +```bash +# The chat model — Marketplace subscription from Step 4 is what gates this one. +aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier zai.glm-4.7-flash \ + --query "modelDetails.modelId" --output text + +# The embedding model — Amazon-family access, a separate gate. +aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier amazon.titan-embed-text-v2:0 \ + --query "modelDetails.modelId" --output text +``` + +Both should echo the id back. What to do per family if one doesn't: + +- **`zai.*`** — nothing to click. The Marketplace subscription from Step 4 is the gate; + Bedrock enables foundation-model access by default in commercial Regions once the + subscription is in place, and the legacy manual *Bedrock → Model access* console flow + is no longer the gating step for this model. A failure here means Step 4 didn't take, + or took in a different Region. +- **`amazon.titan-*`** — Amazon-family models are enabled by default in most commercial + Region accounts, but not universally. If the probe fails, open **Bedrock → Model + access** in `us-east-1` and enable Titan Text Embeddings V2 there. +- **`anthropic.claude-*`** — only relevant if you switch the default. Anthropic models + require a per-model first-time-use EULA acceptance on the **Model access** page before + the first `InvokeModel` succeeds. That is a manual console action and is the one + remaining reason to open that screen. It does **not** apply to the default model. + +## Step 6: Prove Bedrock works before you deploy + +One real model call, costing a fraction of a cent, tells you whether Steps 1, 4, and 5 +actually landed: + +```bash +# Should return JSON containing an assistant message. +aws bedrock-runtime converse \ + --region us-east-1 \ + --model-id zai.glm-4.7-flash \ + --messages '[{"role":"user","content":[{"text":"Reply with the single word: ok"}]}]' \ + --query "output.message.content[0].text" --output text +``` + +And the embedding model: + +```bash +# Should write a JSON body containing a 1024-float "embedding" array. +aws bedrock-runtime invoke-model \ + --region us-east-1 \ + --model-id amazon.titan-embed-text-v2:0 \ + --content-type application/json \ + --cli-binary-format raw-in-base64-out \ + --body '{"inputText":"hello"}' \ + /tmp/titan-probe.json && head -c 80 /tmp/titan-probe.json && echo +``` + +**Why this step exists.** Without it, the first real Bedrock call happens inside a +deployed Lambda at Step 9, which means every subscription, EULA, Region, or model-id +mistake costs a full bootstrap-and-deploy cycle to discover and a CloudWatch log dive to +diagnose. Here the same mistakes surface in two seconds with the error text on your own +terminal. Map what you get to [Troubleshooting](#troubleshooting) below before moving on +— a failure at this step will not fix itself during deployment. + +> **Note on the model id.** Pass the **bare** id, exactly as above. Bedrock inference +> profile prefixes (`global.`, `us.`) are decided per model family by +> `src/format/families.ts` — the `zai.*` family accepts the bare id only, while +> `anthropic.claude-*` defaults to `global.`. The code adds the right prefix itself and +> throws at startup if you configure an id that already carries one. Never hand-edit a +> prefix onto `BEDROCK_MODEL_ID`. +```` + +- [ ] **Step 2: Verify the CLI syntax quoted in the doc is real** + +These must all print usage without an "Unknown options" error. This validates flag +names only — it does not call AWS: + +```bash +aws bedrock get-foundation-model --model-identifier zai.glm-4.7-flash --region us-east-1 --generate-cli-skeleton output >/dev/null && echo "get-foundation-model OK" +aws bedrock-runtime converse --model-id zai.glm-4.7-flash --messages '[{"role":"user","content":[{"text":"x"}]}]' --generate-cli-skeleton output >/dev/null && echo "converse OK" +aws bedrock-runtime invoke-model --model-id amazon.titan-embed-text-v2:0 --body '{"inputText":"x"}' --content-type application/json --cli-binary-format raw-in-base64-out --generate-cli-skeleton output >/dev/null && echo "invoke-model OK" +``` + +Expected: three `OK` lines. `--generate-cli-skeleton output` validates the inputs and +returns a sample response without sending a request, so this costs nothing. + +- [ ] **Step 3: Commit** + +```bash +git add docs/11-aws-bedrock-setup.md +git commit -m "docs: Bedrock setup — Marketplace, both model gates, pre-deploy probe" +``` + +--- + +## Task 3: New doc — Steps 7–9 (deploy, smoke, end-to-end) + +**Files:** +- Modify: `docs/11-aws-bedrock-setup.md` (append) + +- [ ] **Step 1: Append Steps 7–9** + +Append exactly this: + +````markdown +## Step 7: Deploy + +Two environment variables are read at **synth** time, which means they must be set +before `npm run deploy`, not after: + +- `DISCORD_WEBHOOK_URL` — required. `infra/stack.ts` throws without it. +- `FETCH_TRIGGER_TOKEN` — optional, and only needed if you want the on-demand trigger + in Step 9. **Set it now if you want it at all** — adding it later means another + deploy. + +```bash +export AWS_PROFILE=your-profile # defaults to `default` +export AWS_REGION=us-east-1 # scripts/deploy.sh reads this + +# Source secrets from an untracked file rather than echoing them inline. +set -a; . ./.env.discord; set +a # .env.discord is gitignored +export FETCH_TRIGGER_TOKEN="$(openssl rand -hex 24)" # optional — see Step 9 + +npm run deploy +``` + +`npm run deploy` bootstraps (Step 3), builds the TypeScript, builds and pushes the +container image, and deploys the `SqliteS3AgentTutorial` stack. Success looks like four +stack outputs: + +``` +SqliteS3AgentTutorial.AgentFunctionName = ... +SqliteS3AgentTutorial.AgentFunctionUrl = https://....lambda-url.us-east-1.on.aws/ +SqliteS3AgentTutorial.LoopRuleName = ... +SqliteS3AgentTutorial.SnapshotBucketName = ... +``` + +If it fails, the CloudFormation event log names the exact denied action or failed +resource: + +```bash +aws cloudformation describe-stack-events \ + --stack-name SqliteS3AgentTutorial --region us-east-1 --max-items 20 \ + --query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \ + --output table +``` + +> **The schedule starts immediately.** The stack declares its EventBridge rule +> `enabled: true`, so the bot begins ticking every 5 minutes as soon as the deploy +> finishes. Run `npm run loop-stop` when you're done experimenting — see the README's +> Loop mode section and [07-budget-protection.md](07-budget-protection.md). + +## Step 8: Verify the deployment + +```bash +npm run smoke +``` + +This probes the status Function URL twice: once unsigned (expecting `403`, proving the +URL really does require IAM) and once SigV4-signed (expecting the status payload). It is +read-only and safe to run at any time, including mid-tick. + +It deliberately does **not** call Bedrock, post to Discord, or write to S3 — so a +passing smoke run proves the deployment and its IAM are sound, and proves nothing at all +about your model access. Step 6 was that check; Step 9 is the end-to-end one. + +## Step 9 (optional): Trigger a real fetch end-to-end + +The scheduled run does this every 5 minutes on its own — but if you'd rather not wait, +and you set `FETCH_TRIGGER_TOKEN` before deploying in Step 7, you can trigger one over +HTTP. The request needs **both** a valid token and SigV4 signing with a same-account +principal; the full signed `curl` is in the README's *Triggering a fetch on demand* +section and is not repeated here. + +A successful trigger exercises the entire pipeline: Lambda cold start → hydrate the +SQLite snapshot from S3 → fetch weather and crypto → Bedrock Converse for the message +and haiku → Titan embedding → conditional write back to S3 → Discord post. Within a few +seconds you should see a message in your Discord channel. + +If you skipped `FETCH_TRIGGER_TOKEN` at deploy time, either wait up to 5 minutes for the +scheduled tick or redeploy with the variable set. If the trigger returns `403`, that is +the token or the signature — not Bedrock. Bedrock problems show up as a tick that runs +and posts nothing; check the Lambda's CloudWatch logs and match the exception against +[Troubleshooting](#troubleshooting). +```` + +- [ ] **Step 2: Verify the referenced npm scripts and stack outputs are real** + +```bash +grep -n '"deploy"\|"smoke"\|"loop-stop"' package.json +grep -n "CfnOutput" infra/stack.ts +``` + +Expected: `deploy`, `smoke`, and `loop-stop` all exist in `package.json`; the four +`CfnOutput` names in `infra/stack.ts:152-155` match the four quoted in Step 7. + +- [ ] **Step 3: Commit** + +```bash +git add docs/11-aws-bedrock-setup.md +git commit -m "docs: Bedrock setup — deploy, smoke verification, end-to-end trigger" +``` + +--- + +## Task 4: New doc — Reference tail + +**Files:** +- Modify: `docs/11-aws-bedrock-setup.md` (append) + +- [ ] **Step 1: Append the Reference section** + +Append exactly this: + +````markdown +--- + +# Reference + +Everything above is the procedure. What follows is the material you come back for. + +## Region availability + +The procedure pins `us-east-1`. If you move: + +- **Set `AWS_REGION`.** `scripts/deploy.sh:5` reads it, and it becomes the CDK stack's + Region. That single variable is the whole change for a deploy. +- **`BEDROCK_REGION` is not a deploy knob.** `infra/stack.ts:74` hardcodes the deployed + Lambda's `BEDROCK_REGION` to the stack's own Region, so setting it in your shell before + a deploy does nothing. It only matters for local runs, where `src/config.ts:135` lets + it override `AWS_REGION` — useful if you want to run the writer locally against a + Bedrock Region different from the rest of your setup. +- **Model availability is per Region.** Not every model in + [bedrock-model-comparison.md](bedrock-model-comparison.md) exists everywhere; re-run + Step 1's `list-foundation-models` in the new Region before assuming. +- **The Marketplace subscription is per Region.** Moving Regions means subscribing again + (Step 4). This catches people who move from `us-east-1` after a working deploy. +- **Bootstrap is per account *and* Region.** The first deploy into a new Region + bootstraps again (Step 3). + +## Switching models + +`BEDROCK_MODEL_ID` selects the chat model at synth time (`infra/app.ts`, +`src/config.ts:114`). The embedding model is not configurable. To switch: + +1. Confirm the new model is listed and subscribed in your Region — Steps 1, 4, and 5. +2. For `anthropic.claude-*`, accept the per-model EULA on **Bedrock → Model access** + first. This is the one family where the console step is still mandatory. +3. Set the **bare** base id and redeploy: + +```bash +export BEDROCK_MODEL_ID=anthropic.claude-... # bare id, no global./us. prefix +npm run deploy +``` + +The inference-profile prefix is supplied by the model's family in +`src/format/families.ts`, not by you: `zai.*` takes the bare id only, `amazon.nova-*` +accepts bare or `us.`, and `anthropic.claude-*` defaults to `global.`. Configuring an id +that already carries a prefix throws at startup by design, and the generated IAM policy +is derived from the same family resolution — so hand-editing a prefix breaks the policy +and the call together. + +For which model to pick and what each costs, see +[bedrock-model-comparison.md](bedrock-model-comparison.md). That comparison is not +repeated here. + +## Cost + +At the default 5-minute cadence — 288 ticks/day, each one Converse call plus one Titan +embedding — `zai.glm-4.7-flash` runs roughly **$0.02–$0.04/day**. Lambda, S3, and +EventBridge at this volume are rounding errors next to that. + +The two things that actually change the number are cadence and a leaked +`FETCH_TRIGGER_TOKEN`, since an authorized caller with the token can drive Bedrock calls +as fast as they can sign requests. Set up a budget alert before leaving the loop running +unattended: [07-budget-protection.md](07-budget-protection.md) has the full breakdown +and the alarm setup. + +## Troubleshooting + +| Symptom | Cause | Go to | +|---|---|---| +| `Cannot connect to the Docker daemon` during `npm run deploy` | No container runtime running; the Lambda is a container image | Step 0 | +| `AccessDeniedException` on `list-foundation-models` | Deploying principal lacks Bedrock read permissions | Step 2 | +| `Could not connect to the endpoint URL` for a `bedrock` call | Bedrock not available in that Region | Step 1 | +| `AccessDenied` / `CREATE_FAILED` during `npm run deploy` | Deployer IAM, most often a bootstrap-only permission on the first run | Steps 2, 3 | +| `AccessDeniedException` naming the **chat** model | Marketplace subscription missing, or made in another Region | Steps 4, 1 | +| `AccessDeniedException` naming `amazon.titan-embed-text-v2:0` | Amazon-family model access not enabled — a *separate* gate from the chat model | Step 5 | +| `AccessDeniedException` on an `anthropic.claude-*` model | Per-model EULA not accepted | Step 5 | +| `ValidationException` on the first call | Malformed model id — usually a hand-added inference-profile prefix the family rejects | Step 6 note, `src/format/families.ts` | +| `ResourceNotFoundException` | A bare-id family got a prefix, e.g. `global.zai.glm-4.7-flash` | Step 6 note | +| Startup error: *"already carries the inference-profile prefix"* | `BEDROCK_MODEL_ID` was set to a prefixed id | Switching models, above | +| `403` from the on-demand trigger | `FETCH_TRIGGER_TOKEN` unset at deploy time, or the request isn't SigV4-signed | Steps 7, 9 | +| Deploy succeeds, ticks run, nothing posts to Discord | Bedrock or Discord failure inside the tick — `agent_runs.error` records it per source | CloudWatch logs, [06-discord-webhook-setup.md](06-discord-webhook-setup.md) | + +The fastest general diagnostic is Step 6's `converse` probe. If it passes and the +deployed bot still fails, the problem is not model access. +```` + +- [ ] **Step 2: Verify anchors, links, and length** + +```bash +wc -l docs/11-aws-bedrock-setup.md +grep -c '^## ' docs/11-aws-bedrock-setup.md +grep -n 'Troubleshooting' docs/11-aws-bedrock-setup.md +``` + +Expected: length in the 300–400 range (spec §3.1); the `#troubleshooting` anchor +referenced from Steps 6 and 9 matches the `## Troubleshooting` heading in Reference. + +- [ ] **Step 3: Read the whole doc top to bottom** + +Read `docs/11-aws-bedrock-setup.md` in full against `docs/06-discord-webhook-setup.md` +and confirm: sentence-case headings, ~90-column prose, no step that says "configure +appropriately" without showing the command, and every `aws` invocation carries +`--region us-east-1` or an explanation of why it doesn't. + +- [ ] **Step 4: Commit** + +```bash +git add docs/11-aws-bedrock-setup.md +git commit -m "docs: Bedrock setup reference — Region, model switching, cost, troubleshooting" +``` + +--- + +## Task 5: Delete the Bedrock section from 02-rehydration.md + +**Files:** +- Modify: `docs/02-rehydration.md:100-119` + +- [ ] **Step 1: Confirm the exact boundaries before deleting** + +```bash +sed -n '96,119p' docs/02-rehydration.md +``` + +Expected: line 100 is `## Bedrock setup`, lines 102–117 are its two paragraphs, and +line 119 is the trailing `For Discord webhook setup, see …` pointer. Line 98 ends the +preceding `/tmp`-ceiling paragraph. + +- [ ] **Step 2: Delete lines 100–119** + +Remove the `## Bedrock setup` heading, both paragraphs, **and** the trailing Discord +pointer line — the file's remaining content is entirely about rehydration, and a lone +Discord link at the end of it is a leftover from the section being removed. The file +should now end with the ephemeral-storage paragraph that currently ends at line 98. + +Verify: + +```bash +tail -5 docs/02-rehydration.md +grep -c 'Bedrock' docs/02-rehydration.md +``` + +Expected: the file ends on the `agent_runs` / RAG-corpus sentence; `grep -c` returns +`0`. Trailing blank lines: exactly one newline at end of file, no blank line before it. + +- [ ] **Step 3: Commit** + +```bash +git add docs/02-rehydration.md +git commit -m "docs(rehydration): move Bedrock setup out to 11-aws-bedrock-setup.md" +``` + +--- + +## Task 6: Re-point README + +**Files:** +- Modify: `README.md:46-51`, `README.md:186` + +- [ ] **Step 1: Re-point the pre-deploy warning** + +Replace `README.md` lines 46–51 with: + +```markdown +Before your first deploy, ensure your AWS account in `us-east-1` has an active AWS +Marketplace subscription for `zai.glm-4.7-flash` (Bedrock enables foundation-model access +by default in commercial Regions once the Marketplace subscription is in place; the legacy +manual *Bedrock → Model access* console flow is no longer the gate for this model) — see +[docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) for the full setup, what else +is required, and what breaks if you skip it. +``` + +The only substantive change is the link target and the tail phrasing; the Marketplace +warning itself is still accurate and stays. + +- [ ] **Step 2: Add the docs-table row** + +In the `## What's here` table, insert this row immediately **before** the +`bedrock-model-comparison.md` row (so numbered docs stay in order and the two Bedrock +docs sit adjacent): + +```markdown +| [docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) | Account type, deployer IAM, Marketplace subscription, Region, EULA, first-deploy smoke | +``` + +- [ ] **Step 3: Verify** + +```bash +grep -n '02-rehydration.md#bedrock-setup' README.md || echo "no stale anchor — good" +grep -n '11-aws-bedrock-setup' README.md +test -f docs/11-aws-bedrock-setup.md && echo "target exists" +``` + +Expected: no stale anchor; two `11-aws-bedrock-setup` hits (the warning and the table +row); target exists. + +- [ ] **Step 4: Commit** + +```bash +git add README.md +git commit -m "docs(readme): point Bedrock setup at 11-aws-bedrock-setup.md" +``` + +--- + +## Task 7: Update the lesson-script prerequisite + +**Files:** +- Modify: `docs/09-lesson-script.md:5` + +- [ ] **Step 1: Replace line 5** + +Current line 5 reads: + +```markdown +**Prerequisite:** the student has read [08-rag-vector-search.md](08-rag-vector-search.md) and the base tutorial's [01-architecture.md](01-architecture.md). Familiarity with the writer/reader asymmetry in [01-architecture.md](01-architecture.md) and the per-source error isolation in [03-schema.md](03-schema.md) is assumed. +``` + +Replace it with (single line — the file's other prose is unwrapped, match that): + +```markdown +**Prerequisite:** the student has completed the base tutorial end-to-end, including the AWS setup in [11-aws-bedrock-setup.md](11-aws-bedrock-setup.md) — the RAG lesson assumes a deployed, working bot, and that doc is where to go if the base deploy is broken. The student has read [08-rag-vector-search.md](08-rag-vector-search.md) and the base tutorial's [01-architecture.md](01-architecture.md). Familiarity with the writer/reader asymmetry in [01-architecture.md](01-architecture.md) and the per-source error isolation in [03-schema.md](03-schema.md) is assumed. +``` + +"has read" becomes "has completed … end-to-end" for the base tutorial because by this +point the student needs a *deployed* system, not just a read one — Titan embeddings are +being invoked live throughout the lesson. + +- [ ] **Step 2: Verify no other edits to the file** + +```bash +git diff --stat docs/09-lesson-script.md +``` + +Expected: `1 file changed, 1 insertion(+), 1 deletion(-)`. + +- [ ] **Step 3: Commit** + +```bash +git add docs/09-lesson-script.md +git commit -m "docs(lesson-script): require completed base tutorial incl. Bedrock setup" +``` + +--- + +## Task 8: Final verification + +**Files:** none modified + +- [ ] **Step 1: No stale inbound links in live docs** + +```bash +grep -rn '02-rehydration.md#bedrock-setup' README.md docs/*.md src infra scripts 2>/dev/null \ + || echo "clean" +``` + +Expected: `clean`. Scoped deliberately — `docs/superpowers/plans/` and +`docs/superpowers/specs/` are historical records and are **not** rewritten, so a repo-wide +grep will still show hits there. That is correct. + +- [ ] **Step 2: Every relative markdown link in the touched files resolves** + +```bash +for f in README.md docs/02-rehydration.md docs/09-lesson-script.md docs/11-aws-bedrock-setup.md; do + d=$(dirname "$f") + grep -o '](\([^)#h][^)]*\.md\)' "$f" | sed 's/](//' | sort -u | while read -r t; do + test -f "$d/$t" || echo "BROKEN in $f -> $t" + done +done +echo "link check done" +``` + +Expected: no `BROKEN` lines. + +- [ ] **Step 3: Repo still green** + +```bash +npm test && npm run typecheck +``` + +Expected: both pass. Nothing outside `docs/` and `README.md` changed, so this is a +regression guard, not a real risk. + +- [ ] **Step 4: Read-through against the spec's success criteria** + +Confirm each of spec §5.2 by reading `docs/11-aws-bedrock-setup.md`: + +- Procedure prose density matches `06-discord-webhook-setup.md`. +- Every step except Step 4 carries a CLI verification; Step 4 is explicitly labelled + console-only. +- Both invoked models are named, and their `AccessDeniedException` cases are + distinguished in Troubleshooting. +- The Marketplace-vs-vendor-site disambiguation is unambiguous. +- The Anthropic EULA is conditional on the family, never blanket. +- No content duplicated from `bedrock-model-comparison.md` or `07-budget-protection.md` + beyond a one-line pointer plus the headline cost figure. + +- [ ] **Step 5: Push and open the PR** + +```bash +git push -u origin docs/aws-bedrock-setup +gh pr create --title "docs: AWS Bedrock setup walkthrough for new AWS accounts" --body "$(cat <<'EOF' +Adds `docs/11-aws-bedrock-setup.md` — CLI-first, end-to-end AWS setup for a brand-new +account, from local prerequisites through a verified end-to-end Discord post. + +Closes the gaps the remote collaborator hit: the deployer's own IAM principal, the +Marketplace-vs-vendor-site confusion, the missing container-runtime prerequisite (the +Lambda is a `DockerImageFunction`), and the second Bedrock model +(`amazon.titan-embed-text-v2:0`) that no doc previously named. + +Also moves the old `02-rehydration.md` §Bedrock setup section into it, re-points the +README, and updates the lesson-script prerequisite. + +Docs only — no changes under `src/`, `infra/`, or `tests/`. + +Spec: `docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md` + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +--- + +## Self-review notes + +Spec coverage check, section by section: + +| Spec section | Task | +|---|---| +| §1 Bedrock reachability | Task 1, Step 1 | +| §1 payment method / "no pay-as-you-go" | Task 1, Step 0 item 4 | +| §1 deployer IAM | Task 1, Step 2 | +| §1 bootstrap | Task 1, Step 3 | +| §1 container runtime | Task 1, Step 0 item 2 | +| §1 Titan second model | Task 2, Step 5 | +| §1 CLI-first verification | every step; assistant prompt block in Task 1 | +| §3.1 Steps 0–9 | Tasks 1–3 | +| §3.1 Reference (Region, switching, cost, troubleshooting) | Task 4 | +| §4.1 delete 02-rehydration §Bedrock setup | Task 5 | +| §4.2 README link + table row | Task 6 | +| §4.3 cross-reference search | Task 8, Steps 1–2 | +| §4.4 09-lesson-script prerequisite | Task 7 | +| §5.2 quality criteria | Task 8, Step 4 | +| §5.3 integration criteria | Task 8, Steps 1–3 | + +One deliberate divergence from the spec, decided while writing this plan: spec §3.1's +Step 3 was written as "run `cdk bootstrap` (first-time only)" with a command for the +reader to type. `scripts/deploy.sh:8-11` already runs it inside `npm run deploy`, so +instructing a manual run would be redundant and would imply the deploy doesn't handle it. +The step is reframed as "know what the first deploy bootstraps," keeping the idempotent +`describe-stacks` check and the permission-surface explanation, which are the parts that +carry real information. The spec has been updated to match. diff --git a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md index c6bba70..77589f8 100644 --- a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md +++ b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md @@ -10,11 +10,15 @@ The tutorial currently documents Bedrock setup in a single 20-line section (`docs/02-rehydration.md` §Bedrock setup, lines 100–119) that lives at the bottom of a file whose title is "Rehydration." That placement was the original mistake this spec corrects: rehydration and AWS account setup are different topics and serve different readers at different moments. -The new doc is end-to-end setup — what a reader needs to know *before* `cdk deploy` produces a working bot. It is the only place in the repo that should describe Bedrock prerequisites. Everything in the current `02-rehydration.md` §Bedrock setup section moves into it, plus three currently-unstated prerequisites that a brand-new AWS account will trip on: +The new doc is end-to-end setup — what a reader needs to know *before* `cdk deploy` produces a working bot. It is the only place in the repo that should describe Bedrock prerequisites. Everything in the current `02-rehydration.md` §Bedrock setup section moves into it, plus these currently-unstated prerequisites that a brand-new AWS account will trip on: -- **Bedrock may not be reachable in every account/region.** Verify Bedrock is accessible from the deploying account in `us-east-1` before attempting the procedure. Some account types (e.g. accounts that have only ever used Lightsail, or accounts in restricted Regions) do not have Bedrock in their service catalog. The doc's Step 1 covers the verification. -- **The deploying CLI principal needs permission to create the stack.** A brand-new IAM user cannot run `cdk deploy` without granting a wide set of IAM/Lambda/S3/Events/ECR/CFN permissions, none of which is currently mentioned in the repo. -- **`cdk bootstrap` is a first-time prerequisite.** Not currently mentioned anywhere in `README.md` or `docs/`. +- **Bedrock may not be reachable in every account/region.** Verify Bedrock is accessible from the deploying account in `us-east-1` before attempting the procedure. Some account types (e.g. accounts that have only ever used Lightsail, or accounts in restricted Regions) do not have Bedrock in their service catalog. The doc's Step 1 covers the verification. Bedrock is also not a Free Tier service — the account needs a valid payment method, which is what "there's no pay-as-you-go option" actually looks like from the console. +- **The deploying CLI principal needs permission to create the stack.** A brand-new IAM user cannot run `cdk deploy` without a wide set of IAM/Lambda/S3/Events/ECR/CFN permissions, none of which is currently mentioned in the repo. +- **`cdk bootstrap` runs on the first deploy and needs its own permissions.** `scripts/deploy.sh:8–11` already invokes `npx cdk bootstrap` inside `npm run deploy`, so the reader never types the command — but nothing explains that the first `npm run deploy` therefore does two very different things, or that the extra CloudFormation/S3/ECR/SSM permissions bootstrap needs are a common first-run `AccessDenied`. The doc explains it rather than instructing a redundant manual step. +- **A container runtime must be running locally.** `infra/stack.ts:84` uses `lambda.DockerImageFunction` with `DockerImageCode.fromImageAsset`, so `cdk deploy` builds an image locally and pushes it to ECR. Without a running Docker (or Finch/Podman) daemon the deploy fails with a message that says nothing about Bedrock or this tutorial. Unmentioned anywhere in the repo today. +- **Titan Text Embeddings V2 is a second model the pipeline invokes.** `src/embed/titan.ts:16` pins `amazon.titan-embed-text-v2:0` and `infra/stack.ts:111` grants it; every tick calls it for RAG. A reader who arranges access only for the chat model gets an `AccessDeniedException` naming a model no doc ever told them about. + +**Verification is CLI-first.** Each step carries an `aws` command that proves the step succeeded, so a reader can hand the bulk of the procedure to an AI assistant with CLI access rather than clicking through consoles. The AWS Marketplace subscription (Step 4) is the one genuine console-only detour and the doc says so explicitly. A pre-deploy `bedrock-runtime converse` probe collapses subscription, EULA, Region, and model-id mistakes into one sub-cent pass/fail *before* the reader spends a deploy cycle discovering them. **Constraints that shape every decision below:** @@ -40,93 +44,161 @@ The new doc is end-to-end setup — what a reader needs to know *before* `cdk de ### 3.1 Structure -Mirrors `docs/06-discord-webhook-setup.md`: numbered "Step N: …" sections for the procedure, then tail reference material. Target length 250–350 lines. +Mirrors `docs/06-discord-webhook-setup.md`: numbered "Step N: …" sections for the procedure, then tail reference material under a clear divider. The *procedure* holds `06`'s prose density and stays around 150–200 lines; Reference carries the rest. Total target 300–400 lines, which makes this the longest setup doc in `docs/` — accepted deliberately, because the CLI verification blocks are what make the procedure runnable rather than readable. + +Every step carries a CLI verification. The doc opens by telling the reader they can hand the procedure to an AI assistant with AWS CLI access, with a copy-pasteable framing prompt, and flags Step 4 as the one console-only detour. ``` # AWS Bedrock setup 2–3 sentence opener: what this doc covers, why it lives separately from 02-rehydration.md. + "Let your assistant drive it" note + copy-paste prompt block. + Callout: Step 4 (Marketplace) is console-only; everything else + is CLI-verifiable. + +## Step 0: Local prerequisites + - AWS CLI installed and authenticated: + aws sts get-caller-identity + - Node + npm install already done (README Quick start). + - A running container runtime (Docker Desktop, Finch, Podman): + docker info + Why: infra/stack.ts:84 uses lambda.DockerImageFunction, so + `cdk deploy` builds the image locally and pushes it to ECR. + Without a daemon the deploy fails with a message that mentions + neither Bedrock nor this tutorial. + - Account has a valid payment method. Bedrock is not a Free Tier + service; an account without one shows no pay-as-you-go option. ## Step 1: Confirm Bedrock is reachable in your account and region - - Open the AWS console in us-east-1 and navigate to Bedrock. + - Verify from the CLI rather than by hunting the console menu: + aws bedrock list-foundation-models --region us-east-1 \ + --query "modelSummaries[?contains(modelId,'glm')].modelId" + - A hit means Bedrock is reachable and the model exists in the + Region. AccessDenied / unknown-service / empty list each mean + something different — map each to its resolution. - If the service is unavailable in your account (some accounts that have only used Lightsail, or accounts in restricted - Regions, may not see it): the tutorial will not deploy. Stop - here and resolve before continuing — typical resolutions are - switching to a standard AWS account or to us-east-1. - - Standard accounts (including Free Tier) in commercial Regions - (us-east-1 in particular): proceed. + Regions): the tutorial will not deploy. Stop here — typical + resolutions are switching to a standard AWS account or to + us-east-1. ## Step 2: Grant the deploying CLI principal permission to create the stack - - Minimum IAM actions the principal needs: - iam:CreateRole, iam:AttachRolePolicy, iam:PassRole - lambda:CreateFunction, lambda:UpdateFunctionCode - s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging - events:CreateRule, events:PutTargets - ecr:CreateRepository, ecr:GetAuthorizationToken - cloudformation:CreateStack / UpdateStack / DeleteStack + pass-role - ssm:GetParameter (CDK bootstrap resources) - - Honest trade-off note: AdministratorAccess for the first deploy, - then scope down. A tighter policy is feasible but enumerating one - is out of scope for this doc. + - Confirm which principal is deploying: + aws sts get-caller-identity + - Representative (NOT exhaustive) action list, so the reader can + recognize the shape of what's needed: iam:CreateRole / + AttachRolePolicy / PassRole; lambda:CreateFunction / + UpdateFunctionCode; s3:CreateBucket / PutBucketPolicy / + PutBucketTagging; events:CreateRule / PutTargets; ecr: + CreateRepository / GetAuthorizationToken / InitiateLayerUpload / + UploadLayerPart / CompleteLayerUpload / PutImage; + cloudformation:CreateStack / UpdateStack / DeleteStack; + sts:AssumeRole (CDK bootstrap deploy roles); ssm:GetParameter. + - Framed as "representative" on purpose: an incomplete list + presented as a minimum is worse than no list. The recommendation + is AdministratorAccess for the first deploy, then scope down. - Explicit call-out: this is about the *deployer's* IAM principal, NOT the runtime Lambda role (which CDK generates automatically — - see infra/stack.ts:103–107). + see infra/stack.ts:103–113). -## Step 3: Run cdk bootstrap (first-time only) +## Step 3: Understand what the first deploy bootstraps + - You do NOT run cdk bootstrap by hand: scripts/deploy.sh:8–11 + already runs it inside `npm run deploy`. This step exists so + the first run's extra activity and extra permission surface + aren't a surprise. - What it does: provisions the CDKToolkit CloudFormation stack, - an S3 staging bucket, and an ECR repo in the deploying account - the first time CDK is used in a region. - - The command: npx cdk bootstrap aws:///us-east-1 - - Why this is unstated in the current README: it was an oversight. - -## Step 4: Subscribe to the model in AWS Marketplace + an S3 staging bucket, an ECR repo, and the CDK deploy roles in + the account the first time CDK is used in a Region. + - Already done? (idempotent check): + aws cloudformation describe-stacks --stack-name CDKToolkit \ + --region us-east-1 + - Why this matters: the first `npm run deploy` needs bootstrap-only + permissions (CFN, S3, ECR, SSM, iam:CreateRole) on top of the + stack's own, which is the most common first-run AccessDenied. + +## Step 4: Subscribe to the model in AWS Marketplace [console only] - Walk through the Marketplace subscription UI for zai.glm-4.7-flash. - Subscribing is free; you pay per token used through AWS billing. - Explicit disambiguation: zai.com (the vendor's own site, which has different plans with minimums) is *not* the right place. This tutorial uses the AWS Marketplace listing. - -## Step 5: Confirm model access in the Bedrock console - - Navigate to Bedrock → Model access in us-east-1. + - Say plainly that this is the one step an assistant cannot do + for you, and why. + +## Step 5: Confirm access to BOTH models this tutorial invokes + - Two models, not one: the configured chat model + (default zai.glm-4.7-flash) and the fixed embedding model + amazon.titan-embed-text-v2:0 (src/embed/titan.ts:16), which + every tick calls for RAG. + - Verify each: + aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier zai.glm-4.7-flash + aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier amazon.titan-embed-text-v2:0 - For zai.* models: nothing to click. The Marketplace subscription from Step 4 is the gate; Bedrock enables foundation-model access by default in commercial Regions once the subscription is in place. - - For anthropic.claude-* models: must accept the per-model EULA on - this page before the first InvokeModel call succeeds. This is - the one remaining reason to open the Model access screen for - those families. - -## Step 6: Deploy + - For amazon.titan-*: Amazon-family access is enabled by default + in most commercial-Region accounts, but not universally. If the + probe fails, enable it on Bedrock → Model access. + - For anthropic.claude-* models (only if switching the default): + must accept the per-model EULA on that page before the first + InvokeModel call succeeds. + +## Step 6: Prove Bedrock works before you deploy + - One real Converse call from the CLI, costing a fraction of a + cent: + aws bedrock-runtime converse --region us-east-1 \ + --model-id zai.glm-4.7-flash \ + --messages '[{"role":"user","content":[{"text":"hi"}]}]' + - Why this step exists: it collapses Marketplace-subscription, + EULA, Region, and model-id-format mistakes into a single + pass/fail *before* the reader spends a bootstrap + deploy cycle + discovering them. Each failure mode maps to a numbered step + above; cross-reference Troubleshooting. + - Optional second probe for the embedding model via + bedrock-runtime invoke-model. + +## Step 7: Deploy - Source .env.discord (set -a; . ./.env.discord; set +a). + - If you plan to use the on-demand trigger (Step 9), set + FETCH_TRIGGER_TOKEN NOW — it is read at synth time, so adding + it later requires a redeploy. - npm run deploy. - What success looks like: stack outputs include the agent Function URL and the smoke status URL; no AccessDenied in - CloudFormation events. + CloudFormation events. If it fails: + aws cloudformation describe-stack-events \ + --stack-name SqliteS3AgentTutorial --max-items 20 -## Step 7: Verify with npm run smoke +## Step 8: Verify with npm run smoke - Probes the status Function URL with SigV4 and asserts the URL actually requires it. - Does NOT invoke Bedrock; that's a separate check. - Read-only and safe to run any time. -## Step 8 (optional): Trigger a real fetch to invoke Bedrock - - The on-demand trigger path, gated by FETCH_TRIGGER_TOKEN. - - Confirms the full pipeline end-to-end: Lambda → S3 hydrate → - external source fetch → Bedrock Converse call → Discord post. - - The first Bedrock call here is where Marketplace-subscription - mistakes (Step 4) and EULA mistakes (Step 5, for Anthropic) - surface as AccessDeniedException. +## Step 9 (optional): Trigger a real fetch end-to-end + - The on-demand trigger path, gated by FETCH_TRIGGER_TOKEN — which + had to be set before Step 7's deploy. If you didn't, redeploy + with it set. + - Confirms the full pipeline: Lambda → S3 hydrate → external + source fetch → Bedrock Converse + Titan embed → Discord post. + - Points at README §"Triggering a fetch on demand" for the signed + curl rather than restating it. ## Reference ### Region availability - The procedure assumes us-east-1. What changes if a reader - moves: AWS_REGION + BEDROCK_REGION env vars; some ZAI models - are not available in every Region; Marketplace subscription - is per-Region. + moves: set AWS_REGION (scripts/deploy.sh:5 and the CDK env + both read it) — the deployed Lambda's BEDROCK_REGION is + hardcoded to the stack Region at infra/stack.ts:74, so it is + NOT an independent knob for a deploy. BEDROCK_REGION only + applies to local runs (src/config.ts:135). Some ZAI models are + not available in every Region, and the Marketplace + subscription is per-Region. ### Switching models - zai.glm-4.7-flash → anthropic.claude-*: requires EULA @@ -143,14 +215,23 @@ Mirrors `docs/06-discord-webhook-setup.md`: numbered "Step N: …" sections for README.md §Cost. ### Troubleshooting - - AccessDenied on cdk deploy → deployer IAM (Step 2). - - AccessDeniedException at first fetch → Marketplace + A table: symptom → which step to revisit → the CLI command that + confirms the diagnosis. + - "Cannot connect to the Docker daemon" on cdk deploy → Step 0. + - AccessDenied on cdk deploy → deployer IAM (Step 2); + describe-stack-events shows the failing action. + - AccessDeniedException naming the chat model → Marketplace subscription missing (Step 4) or Region mismatch. + - AccessDeniedException naming amazon.titan-embed-text-v2:0 → + Amazon-family model access (Step 5), a distinct failure from + the chat model's. - ValidationException at first fetch → bad model id format (most often: hand-added a prefix that the family rejects). - ResourceNotFoundException → bare model id got a prefix by mistake (e.g. global.zai.glm-4.7-flash — zai.* family accepts only bare id per src/format/families.ts). + - 403 on the on-demand trigger → FETCH_TRIGGER_TOKEN was not set + before the deploy (Step 7), or the request isn't SigV4-signed. ``` ### 3.2 What the new doc deliberately does NOT cover @@ -222,7 +303,10 @@ A reader following the new doc top-to-bottom on a brand-new AWS account reaches ### 5.2 Quality test (secondary audience) - All non-obvious decisions are explained, not just stated. -- The doc reads like `docs/06-discord-webhook-setup.md` and `docs/02-rehydration.md`: same prose density, same code-fence conventions, same heading depth, same tone. +- The procedure section reads like `docs/06-discord-webhook-setup.md` and `docs/02-rehydration.md`: same prose density, same code-fence conventions, same heading depth, same tone. (Total length exceeds `06` because of the CLI blocks and the Reference tail; that is intended.) +- Every step except Step 4 has a CLI command that verifies it, and Step 4 is explicitly labelled console-only. +- Both invoked models are named — the configurable chat model and the fixed `amazon.titan-embed-text-v2:0` — and their failure modes are distinguished in Troubleshooting. +- Every `aws` command in the doc has been checked against the installed CLI's syntax (correct subcommand, flag names, and argument shapes) — no invented flags. - The Marketplace subscription step is unambiguous about which site to subscribe on (AWS Marketplace, not zai.com). - The EULA caveat is clearly conditional on the model family (Anthropic only), not presented as a blanket requirement. - Every cross-link from and to the new doc resolves. @@ -232,7 +316,7 @@ A reader following the new doc top-to-bottom on a brand-new AWS account reaches - `docs/02-rehydration.md` is shorter by the deleted section and reads cleanly as a single-topic doc. - `docs/09-lesson-script.md` Prerequisite line points to the new doc and reads cleanly as a single prerequisite statement. - README's Quick start still makes sense end-to-end. -- No broken inbound links anywhere in the repo (`grep -r '02-rehydration.md#bedrock-setup' .` returns nothing). +- No broken inbound links in live docs: `grep -rn '02-rehydration.md#bedrock-setup' README.md docs/*.md src infra` returns nothing. Scoped deliberately — `docs/superpowers/plans/` and `docs/superpowers/specs/` are historical records and are not rewritten. - The new doc table row in the README links to a file that exists. ### 5.4 What we will NOT do From c1518d1ba3a254a3499c099968159aec67adb933 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:39:41 -0400 Subject: [PATCH 03/11] =?UTF-8?q?docs:=20add=20AWS=20Bedrock=20setup=20doc?= =?UTF-8?q?=20=E2=80=94=20prerequisites=20through=20bootstrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/11-aws-bedrock-setup.md | 135 +++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/11-aws-bedrock-setup.md diff --git a/docs/11-aws-bedrock-setup.md b/docs/11-aws-bedrock-setup.md new file mode 100644 index 0000000..627e406 --- /dev/null +++ b/docs/11-aws-bedrock-setup.md @@ -0,0 +1,135 @@ +# AWS Bedrock setup + +This is the AWS half of getting the tutorial running: everything a brand-new account +needs before `npm run deploy` produces a working bot. The Discord half is +[06-discord-webhook-setup.md](06-discord-webhook-setup.md). This used to live at the +bottom of [02-rehydration.md](02-rehydration.md), which was the wrong home — account +setup and rehydration are different topics, read at different moments. + +Every step below except Step 4 can be verified from the AWS CLI, which means you can +hand most of this to an AI assistant that has CLI access rather than clicking through +consoles. A prompt that works: + +``` +I'm setting up the sqlite-s3-agent-tutorial on a fresh AWS account. Walk me +through docs/11-aws-bedrock-setup.md using the AWS CLI in us-east-1: run each +step's verification command, tell me what the output means, and stop at the +first failure rather than continuing. Show me any command that creates, +subscribes to, or grants something before you run it. +``` + +> **Step 4 is the exception.** Subscribing to a model in AWS Marketplace has no +> CLI equivalent you can rely on — it is a console flow with a legal agreement. +> Expect to do that one yourself. + +## Step 0: Local prerequisites + +Four things must be true on your machine and account before anything else matters. + +**1. The AWS CLI is installed and authenticated.** + +```bash +# Should print your account id, user id, and principal ARN. +aws sts get-caller-identity +``` + +If this errors, install the AWS CLI and run `aws configure` (or `aws sso login`) +before continuing. Every other command in this doc assumes it works. + +**2. A container runtime is running.** + +```bash +# Should print server info, not "Cannot connect to the Docker daemon". +docker info +``` + +The tutorial's Lambda is a **container image** function (`infra/stack.ts:84` uses +`lambda.DockerImageFunction` with `DockerImageCode.fromImageAsset`), so `cdk deploy` +builds the image locally and pushes it to ECR. Without a running daemon the deploy +fails with a Docker error that mentions neither Bedrock nor this tutorial, which is a +confusing place to lose an hour. Docker Desktop, Finch, and Podman all work. + +**3. Dependencies are installed.** `npm install` and `npm test` from the README's +Quick start should both pass. Nothing here depends on AWS yet. + +**4. The account has a valid payment method.** Bedrock is not a Free Tier service. +An account without a payment method on file shows no pay-as-you-go option and will +refuse model subscriptions in Step 4 — if that is what you are seeing, this is why, +and it is fixed in **Billing and Cost Management → Payment preferences**, not in +Bedrock. + +## Step 1: Confirm Bedrock is reachable in your account and Region + +This whole tutorial pins `us-east-1`. Check that Bedrock answers there and that the +default model exists: + +```bash +aws bedrock list-foundation-models --region us-east-1 \ + --query "modelSummaries[?contains(modelId, 'glm')].modelId" --output text +``` + +Read the result: + +| Output | Meaning | What to do | +|---|---|---| +| One or more `zai.glm-…` ids | Bedrock is reachable and the model exists in the Region | Continue to Step 2 | +| Empty output, command succeeds | Bedrock answered, but this model family isn't offered to your account/Region | Try `--region us-east-1` explicitly; if still empty, see [bedrock-model-comparison.md](bedrock-model-comparison.md) and pick a model your account does list | +| `AccessDeniedException` | Your principal lacks `bedrock:ListFoundationModels` | Step 2 — this is an IAM problem, not a Bedrock one | +| `Could not connect to the endpoint URL` | Bedrock is not available in the Region you asked for | Use `us-east-1` | + +Some account types — accounts that have only ever used Lightsail, accounts in +restricted Regions, some organisation-managed accounts with service control policies — +do not have Bedrock at all. If that is your account, stop here: the tutorial will not +deploy. The fix is a standard AWS account in a commercial Region, not a workaround. + +## Step 2: Give the deploying principal permission to create the stack + +This step is about **your** IAM principal — the identity `aws sts get-caller-identity` +printed in Step 0. It is *not* about the Lambda's runtime role, which CDK generates for +you (`infra/stack.ts:103-113` grants exactly the two Bedrock models the code invokes, +and nothing else). A brand-new IAM user typically has none of what a CDK deploy needs. + +The deploy touches, at minimum: + +``` +iam:CreateRole, iam:AttachRolePolicy, iam:PassRole +lambda:CreateFunction, lambda:UpdateFunctionCode, lambda:CreateFunctionUrlConfig +s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging +events:PutRule, events:PutTargets +ecr:CreateRepository, ecr:GetAuthorizationToken, + ecr:InitiateLayerUpload, ecr:UploadLayerPart, + ecr:CompleteLayerUpload, ecr:PutImage +cloudformation:CreateStack, cloudformation:UpdateStack, cloudformation:DeleteStack +sts:AssumeRole (the CDK bootstrap deploy roles — see Step 3) +ssm:GetParameter (CDK bootstrap version lookup) +``` + +> **Treat that list as representative, not exhaustive.** CDK's exact call set shifts +> between versions, and an incomplete list presented as "the minimum" is worse than no +> list — it sends you hunting one denied action at a time. The pragmatic path is +> `AdministratorAccess` on a dedicated deploy principal for the first deploy, then +> scoping down once you can see in CloudTrail what was actually called. Enumerating a +> least-privilege deploy policy is out of scope for this tutorial. + +## Step 3: Know what the first deploy bootstraps + +You do not run `cdk bootstrap` by hand — `scripts/deploy.sh:8-11` already runs it as +the first thing `npm run deploy` does. This step exists so that the first run's extra +activity isn't a surprise. + +The first time CDK is used in an account/Region pair, it provisions a `CDKToolkit` +CloudFormation stack: an S3 staging bucket for assets, an ECR repository for container +images, an SSM parameter recording the bootstrap version, and a set of IAM roles CDK +assumes to do the deploy. Check whether that already happened: + +```bash +aws cloudformation describe-stacks --stack-name CDKToolkit --region us-east-1 \ + --query "Stacks[0].StackStatus" --output text +``` + +`CREATE_COMPLETE` or `UPDATE_COMPLETE` means bootstrap is done and the first deploy +will skip straight to your stack. `ValidationError … does not exist` means the first +`npm run deploy` will bootstrap first — expect it to take a few extra minutes and to +need the CloudFormation, S3, ECR, SSM, and `iam:CreateRole` permissions from Step 2. +Most first-run `AccessDenied` failures land here rather than on the tutorial's own +resources. \ No newline at end of file From 3f7a55016fe33961cb9afcd46d17255b4bc0bd2a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:41:40 -0400 Subject: [PATCH 04/11] =?UTF-8?q?docs:=20Bedrock=20setup=20=E2=80=94=20Mar?= =?UTF-8?q?ketplace,=20both=20model=20gates,=20pre-deploy=20probe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/11-aws-bedrock-setup.md | 104 ++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/docs/11-aws-bedrock-setup.md b/docs/11-aws-bedrock-setup.md index 627e406..47c99de 100644 --- a/docs/11-aws-bedrock-setup.md +++ b/docs/11-aws-bedrock-setup.md @@ -132,4 +132,106 @@ will skip straight to your stack. `ValidationError … does not exist` means the `npm run deploy` will bootstrap first — expect it to take a few extra minutes and to need the CloudFormation, S3, ECR, SSM, and `iam:CreateRole` permissions from Step 2. Most first-run `AccessDenied` failures land here rather than on the tutorial's own -resources. \ No newline at end of file +resources. + +## Step 4: Subscribe to the model in AWS Marketplace + +This is the console-only step, and it is the one people get wrong. + +1. Open the [AWS Marketplace](https://aws.amazon.com/marketplace) console in the same + account you authenticated in Step 0, and search for the model — `GLM-4.7-Flash` for + this tutorial's default. +2. Open the listing and click **View purchase options** / **Subscribe**. +3. Accept the terms. **Subscribing costs nothing.** There is no minimum, no monthly + fee, and no commitment — you pay per token consumed, billed through your normal AWS + invoice alongside Lambda and S3. +4. Wait for the subscription status to show as active. It is usually immediate but can + take a few minutes. + +> **Subscribe on AWS Marketplace, not on the vendor's own site.** The model vendor +> (for the default model, `zai.com`) sells its own API plans, some with minimum spends. +> Those are a different product with different credentials, and they will not make +> `bedrock:InvokeModel` work. What this tutorial needs is the AWS Marketplace listing, +> reached from the AWS console, billed to your AWS account. + +The subscription is **per Region**. Subscribing while your console is in `us-east-1` is +what the rest of this doc assumes. + +## Step 5: Confirm access to both models this tutorial invokes + +There are two, and readers regularly arrange access for only the first: + +| Model | Configurable? | Used for | +|---|---|---| +| `zai.glm-4.7-flash` (default) | Yes — `BEDROCK_MODEL_ID` (`src/config.ts:114`) | The friendly message + haiku, via Converse | +| `amazon.titan-embed-text-v2:0` | **No** — hardcoded at `src/embed/titan.ts:16` | Embedding each tick's message for RAG search | + +Every tick calls both. Verify each is visible to your account: + +```bash +# The chat model — Marketplace subscription from Step 4 is what gates this one. +aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier zai.glm-4.7-flash \ + --query "modelDetails.modelId" --output text + +# The embedding model — Amazon-family access, a separate gate. +aws bedrock get-foundation-model --region us-east-1 \ + --model-identifier amazon.titan-embed-text-v2:0 \ + --query "modelDetails.modelId" --output text +``` + +Both should echo the id back. What to do per family if one doesn't: + +- **`zai.*`** — nothing to click. The Marketplace subscription from Step 4 is the gate; + Bedrock enables foundation-model access by default in commercial Regions once the + subscription is in place, and the legacy manual *Bedrock → Model access* console flow + is no longer the gating step for this model. A failure here means Step 4 didn't take, + or took in a different Region. +- **`amazon.titan-*`** — Amazon-family models are enabled by default in most commercial + Region accounts, but not universally. If the probe fails, open **Bedrock → Model + access** in `us-east-1` and enable Titan Text Embeddings V2 there. +- **`anthropic.claude-*`** — only relevant if you switch the default. Anthropic models + require a per-model first-time-use EULA acceptance on the **Model access** page before + the first `InvokeModel` succeeds. That is a manual console action and is the one + remaining reason to open that screen. It does **not** apply to the default model. + +## Step 6: Prove Bedrock works before you deploy + +One real model call, costing a fraction of a cent, tells you whether Steps 1, 4, and 5 +actually landed: + +```bash +# Should return JSON containing an assistant message. +aws bedrock-runtime converse \ + --region us-east-1 \ + --model-id zai.glm-4.7-flash \ + --messages '[{"role":"user","content":[{"text":"Reply with the single word: ok"}]}]' \ + --query "output.message.content[0].text" --output text +``` + +And the embedding model: + +```bash +# Should write a JSON body containing a 1024-float "embedding" array. +aws bedrock-runtime invoke-model \ + --region us-east-1 \ + --model-id amazon.titan-embed-text-v2:0 \ + --content-type application/json \ + --cli-binary-format raw-in-base64-out \ + --body '{"inputText":"hello"}' \ + /tmp/titan-probe.json && head -c 80 /tmp/titan-probe.json && echo +``` + +**Why this step exists.** Without it, the first real Bedrock call happens inside a +deployed Lambda at Step 9, which means every subscription, EULA, Region, or model-id +mistake costs a full bootstrap-and-deploy cycle to discover and a CloudWatch log dive to +diagnose. Here the same mistakes surface in two seconds with the error text on your own +terminal. Map what you get to [Troubleshooting](#troubleshooting) below before moving on +— a failure at this step will not fix itself during deployment. + +> **Note on the model id.** Pass the **bare** id, exactly as above. Bedrock inference +> profile prefixes (`global.`, `us.`) are decided per model family by +> `src/format/families.ts` — the `zai.*` family accepts the bare id only, while +> `anthropic.claude-*` defaults to `global.`. The code adds the right prefix itself and +> throws at startup if you configure an id that already carries one. Never hand-edit a +> prefix onto `BEDROCK_MODEL_ID`. \ No newline at end of file From facdd64ac2ffe55fd8c9c6284adcd4b18b0daba0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:41:54 -0400 Subject: [PATCH 05/11] =?UTF-8?q?docs:=20Bedrock=20setup=20=E2=80=94=20dep?= =?UTF-8?q?loy,=20smoke=20verification,=20end-to-end=20trigger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/11-aws-bedrock-setup.md | 82 +++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/11-aws-bedrock-setup.md b/docs/11-aws-bedrock-setup.md index 47c99de..aea147e 100644 --- a/docs/11-aws-bedrock-setup.md +++ b/docs/11-aws-bedrock-setup.md @@ -234,4 +234,84 @@ terminal. Map what you get to [Troubleshooting](#troubleshooting) below before m > `src/format/families.ts` — the `zai.*` family accepts the bare id only, while > `anthropic.claude-*` defaults to `global.`. The code adds the right prefix itself and > throws at startup if you configure an id that already carries one. Never hand-edit a -> prefix onto `BEDROCK_MODEL_ID`. \ No newline at end of file +> prefix onto `BEDROCK_MODEL_ID`. + +## Step 7: Deploy + +Two environment variables are read at **synth** time, which means they must be set +before `npm run deploy`, not after: + +- `DISCORD_WEBHOOK_URL` — required. `infra/stack.ts` throws without it. +- `FETCH_TRIGGER_TOKEN` — optional, and only needed if you want the on-demand trigger + in Step 9. **Set it now if you want it at all** — adding it later means another + deploy. + +```bash +export AWS_PROFILE=your-profile # defaults to `default` +export AWS_REGION=us-east-1 # scripts/deploy.sh reads this + +# Source secrets from an untracked file rather than echoing them inline. +set -a; . ./.env.discord; set +a # .env.discord is gitignored +export FETCH_TRIGGER_TOKEN="$(openssl rand -hex 24)" # optional — see Step 9 + +npm run deploy +``` + +`npm run deploy` bootstraps (Step 3), builds the TypeScript, builds and pushes the +container image, and deploys the `SqliteS3AgentTutorial` stack. Success looks like four +stack outputs: + +``` +SqliteS3AgentTutorial.AgentFunctionName = ... +SqliteS3AgentTutorial.AgentFunctionUrl = https://....lambda-url.us-east-1.on.aws/ +SqliteS3AgentTutorial.LoopRuleName = ... +SqliteS3AgentTutorial.SnapshotBucketName = ... +``` + +If it fails, the CloudFormation event log names the exact denied action or failed +resource: + +```bash +aws cloudformation describe-stack-events \ + --stack-name SqliteS3AgentTutorial --region us-east-1 --max-items 20 \ + --query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \ + --output table +``` + +> **The schedule starts immediately.** The stack declares its EventBridge rule +> `enabled: true`, so the bot begins ticking every 5 minutes as soon as the deploy +> finishes. Run `npm run loop-stop` when you're done experimenting — see the README's +> Loop mode section and [07-budget-protection.md](07-budget-protection.md). + +## Step 8: Verify the deployment + +```bash +npm run smoke +``` + +This probes the status Function URL twice: once unsigned (expecting `403`, proving the +URL really does require IAM) and once SigV4-signed (expecting the status payload). It is +read-only and safe to run at any time, including mid-tick. + +It deliberately does **not** call Bedrock, post to Discord, or write to S3 — so a +passing smoke run proves the deployment and its IAM are sound, and proves nothing at all +about your model access. Step 6 was that check; Step 9 is the end-to-end one. + +## Step 9 (optional): Trigger a real fetch end-to-end + +The scheduled run does this every 5 minutes on its own — but if you'd rather not wait, +and you set `FETCH_TRIGGER_TOKEN` before deploying in Step 7, you can trigger one over +HTTP. The request needs **both** a valid token and SigV4 signing with a same-account +principal; the full signed `curl` is in the README's *Triggering a fetch on demand* +section and is not repeated here. + +A successful trigger exercises the entire pipeline: Lambda cold start → hydrate the +SQLite snapshot from S3 → fetch weather and crypto → Bedrock Converse for the message +and haiku → Titan embedding → conditional write back to S3 → Discord post. Within a few +seconds you should see a message in your Discord channel. + +If you skipped `FETCH_TRIGGER_TOKEN` at deploy time, either wait up to 5 minutes for the +scheduled tick or redeploy with the variable set. If the trigger returns `403`, that is +the token or the signature — not Bedrock. Bedrock problems show up as a tick that runs +and posts nothing; check the Lambda's CloudWatch logs and match the exception against +[Troubleshooting](#troubleshooting). \ No newline at end of file From 662b1893929862bb553fa4ed347ee06dc99cfa72 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:42:26 -0400 Subject: [PATCH 06/11] =?UTF-8?q?docs:=20Bedrock=20setup=20reference=20?= =?UTF-8?q?=E2=80=94=20Region,=20model=20switching,=20cost,=20troubleshoot?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/11-aws-bedrock-setup.md | 85 +++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/docs/11-aws-bedrock-setup.md b/docs/11-aws-bedrock-setup.md index aea147e..642839b 100644 --- a/docs/11-aws-bedrock-setup.md +++ b/docs/11-aws-bedrock-setup.md @@ -314,4 +314,87 @@ If you skipped `FETCH_TRIGGER_TOKEN` at deploy time, either wait up to 5 minutes scheduled tick or redeploy with the variable set. If the trigger returns `403`, that is the token or the signature — not Bedrock. Bedrock problems show up as a tick that runs and posts nothing; check the Lambda's CloudWatch logs and match the exception against -[Troubleshooting](#troubleshooting). \ No newline at end of file +[Troubleshooting](#troubleshooting). + +--- + +# Reference + +Everything above is the procedure. What follows is the material you come back for. + +## Region availability + +The procedure pins `us-east-1`. If you move: + +- **Set `AWS_REGION`.** `scripts/deploy.sh:5` reads it, and it becomes the CDK stack's + Region. That single variable is the whole change for a deploy. +- **`BEDROCK_REGION` is not a deploy knob.** `infra/stack.ts:74` hardcodes the deployed + Lambda's `BEDROCK_REGION` to the stack's own Region, so setting it in your shell before + a deploy does nothing. It only matters for local runs, where `src/config.ts:135` lets + it override `AWS_REGION` — useful if you want to run the writer locally against a + Bedrock Region different from the rest of your setup. +- **Model availability is per Region.** Not every model in + [bedrock-model-comparison.md](bedrock-model-comparison.md) exists everywhere; re-run + Step 1's `list-foundation-models` in the new Region before assuming. +- **The Marketplace subscription is per Region.** Moving Regions means subscribing again + (Step 4). This catches people who move from `us-east-1` after a working deploy. +- **Bootstrap is per account *and* Region.** The first deploy into a new Region + bootstraps again (Step 3). + +## Switching models + +`BEDROCK_MODEL_ID` selects the chat model at synth time (`infra/app.ts`, +`src/config.ts:114`). The embedding model is not configurable. To switch: + +1. Confirm the new model is listed and subscribed in your Region — Steps 1, 4, and 5. +2. For `anthropic.claude-*`, accept the per-model EULA on **Bedrock → Model access** + first. This is the one family where the console step is still mandatory. +3. Set the **bare** base id and redeploy: + +```bash +export BEDROCK_MODEL_ID=anthropic.claude-... # bare id, no global./us. prefix +npm run deploy +``` + +The inference-profile prefix is supplied by the model's family in +`src/format/families.ts`, not by you: `zai.*` takes the bare id only, `amazon.nova-*` +accepts bare or `us.`, and `anthropic.claude-*` defaults to `global.`. Configuring an id +that already carries a prefix throws at startup by design, and the generated IAM policy +is derived from the same family resolution — so hand-editing a prefix breaks the policy +and the call together. + +For which model to pick and what each costs, see +[bedrock-model-comparison.md](bedrock-model-comparison.md). That comparison is not +repeated here. + +## Cost + +At the default 5-minute cadence — 288 ticks/day, each one Converse call plus one Titan +embedding — `zai.glm-4.7-flash` runs roughly **$0.02–$0.04/day**. Lambda, S3, and +EventBridge at this volume are rounding errors next to that. + +The two things that actually change the number are cadence and a leaked +`FETCH_TRIGGER_TOKEN`, since an authorized caller with the token can drive Bedrock calls +as fast as they can sign requests. Set up a budget alert before leaving the loop running +unattended: [07-budget-protection.md](07-budget-protection.md) has the full breakdown +and the alarm setup. + +## Troubleshooting + +| Symptom | Cause | Go to | +|---|---|---| +| `Cannot connect to the Docker daemon` during `npm run deploy` | No container runtime running; the Lambda is a container image | Step 0 | +| `AccessDeniedException` on `list-foundation-models` | Deploying principal lacks Bedrock read permissions | Step 2 | +| `Could not connect to the endpoint URL` for a `bedrock` call | Bedrock not available in that Region | Step 1 | +| `AccessDenied` / `CREATE_FAILED` during `npm run deploy` | Deployer IAM, most often a bootstrap-only permission on the first run | Steps 2, 3 | +| `AccessDeniedException` naming the **chat** model | Marketplace subscription missing, or made in another Region | Steps 4, 1 | +| `AccessDeniedException` naming `amazon.titan-embed-text-v2:0` | Amazon-family model access not enabled — a *separate* gate from the chat model | Step 5 | +| `AccessDeniedException` on an `anthropic.claude-*` model | Per-model EULA not accepted | Step 5 | +| `ValidationException` on the first call | Malformed model id — usually a hand-added inference-profile prefix the family rejects | Step 6 note, `src/format/families.ts` | +| `ResourceNotFoundException` | A bare-id family got a prefix, e.g. `global.zai.glm-4.7-flash` | Step 6 note | +| Startup error: *"already carries the inference-profile prefix"* | `BEDROCK_MODEL_ID` was set to a prefixed id | Switching models, above | +| `403` from the on-demand trigger | `FETCH_TRIGGER_TOKEN` unset at deploy time, or the request isn't SigV4-signed | Steps 7, 9 | +| Deploy succeeds, ticks run, nothing posts to Discord | Bedrock or Discord failure inside the tick — `agent_runs.error` records it per source | CloudWatch logs, [06-discord-webhook-setup.md](06-discord-webhook-setup.md) | + +The fastest general diagnostic is Step 6's `converse` probe. If it passes and the +deployed bot still fails, the problem is not model access. \ No newline at end of file From 306da3ec2490b29d210386721a70508136860ee2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:43:00 -0400 Subject: [PATCH 07/11] docs(rehydration): move Bedrock setup out to 11-aws-bedrock-setup.md --- docs/02-rehydration.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/docs/02-rehydration.md b/docs/02-rehydration.md index d0d90b4..840e37a 100644 --- a/docs/02-rehydration.md +++ b/docs/02-rehydration.md @@ -96,24 +96,3 @@ redeploy. At the AWS cap the same math gives roughly 2.6 million ticks of headro about 25 years at 5-minute cadence. The RAG corpus (`agent_notifications` + `agent_embeddings`) is the dominant growth term; status reads and `agent_runs` rows are small by comparison. - -## Bedrock setup - -Before the first `fetch` invocation can succeed, the deploying account in `us-east-1` needs -two things: an active AWS Marketplace subscription for the configured `bedrockModelId` -(default `zai.glm-4.7-flash`), and an IAM policy that grants `bedrock:InvokeModel` against -it. Bedrock enables foundation-model access by default in commercial Regions once the -Marketplace subscription is in place — the legacy manual *Bedrock → Model access* console -flow is no longer the gating step for this model. If you point `bedrockModelId` at an -Anthropic model instead, Bedrock requires a separate first-time-use EULA acceptance on the -same page before that model will invoke; that step *is* still a manual console action and -is the one remaining reason to open the *Model access* screen. - -The CDK stack's IAM policy is generated at synth time from the configured model's family -(see `src/format/families.ts`) and is permissive enough to invoke the chosen model — but a -Marketplace subscription must already exist on the account, or `bedrock:InvokeModel` -returns `AccessDeniedException` regardless of what the IAM policy says. `cdk deploy` does -not check for the subscription, so the stack deploys cleanly and the first `fetch` fails — -which is why this tutorial calls it out before the first deploy rather than after. - -For Discord webhook setup, see [docs/06-discord-webhook-setup.md](06-discord-webhook-setup.md). From 10ba1c76baca0b572ee863fe7e94719e13849335 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:43:13 -0400 Subject: [PATCH 08/11] docs(readme): point Bedrock setup at 11-aws-bedrock-setup.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c12e74b..9ff0ab4 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Before your first deploy, ensure your AWS account in `us-east-1` has an active A Marketplace subscription for `zai.glm-4.7-flash` (Bedrock enables foundation-model access by default in commercial Regions once the Marketplace subscription is in place; the legacy manual *Bedrock → Model access* console flow is no longer the gate for this model) — see -[docs/02-rehydration.md](docs/02-rehydration.md#bedrock-setup) for what else is required and -what breaks if you skip it. +[docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) for the full setup, what else +is required, and what breaks if you skip it. ## Loop mode @@ -183,6 +183,7 @@ multi-writer escape hatch it looks like. | [docs/08-rag-vector-search.md](docs/08-rag-vector-search.md) | SQLite as a vector database too: sqlite-vec + Titan embeddings | | [docs/09-lesson-script.md](docs/09-lesson-script.md) | A 10-lesson script for teaching the RAG extension (frame, check-in questions, expected reasoning) | | [docs/10-concurrency.md](docs/10-concurrency.md) | Optimistic S3 rehydration, 412 handling, rebase-and-retry, the single-writer queue | +| [docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) | Account type, deployer IAM, Marketplace subscription, Region, EULA, first-deploy smoke | | [docs/bedrock-model-comparison.md](docs/bedrock-model-comparison.md) | Why `zai.glm-4.7-flash` is the default, and alternatives | ## Cost From 9521a1d8161f83a6949009fdecd8e67dbdf5517f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:43:24 -0400 Subject: [PATCH 09/11] docs(lesson-script): require completed base tutorial incl. Bedrock setup --- docs/09-lesson-script.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/09-lesson-script.md b/docs/09-lesson-script.md index bf849f2..e4d58bd 100644 --- a/docs/09-lesson-script.md +++ b/docs/09-lesson-script.md @@ -2,7 +2,7 @@ **Audience:** a teacher walking a student through the RAG extension. **Length:** 10 lessons, roughly 30 minutes including the closing test run. -**Prerequisite:** the student has read [08-rag-vector-search.md](08-rag-vector-search.md) and the base tutorial's [01-architecture.md](01-architecture.md). Familiarity with the writer/reader asymmetry in [01-architecture.md](01-architecture.md) and the per-source error isolation in [03-schema.md](03-schema.md) is assumed. +**Prerequisite:** the student has completed the base tutorial end-to-end, including the AWS setup in [11-aws-bedrock-setup.md](11-aws-bedrock-setup.md) — the RAG lesson assumes a deployed, working bot, and that doc is where to go if the base deploy is broken. The student has read [08-rag-vector-search.md](08-rag-vector-search.md) and the base tutorial's [01-architecture.md](01-architecture.md). Familiarity with the writer/reader asymmetry in [01-architecture.md](01-architecture.md) and the per-source error isolation in [03-schema.md](03-schema.md) is assumed. ## How to use this script From df614098db306d55366efd4fc49f761059dbe835 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 21:48:18 -0400 Subject: [PATCH 10/11] docs(spec): mark Bedrock setup design as implemented --- .../specs/2026-08-10-bedrock-setup-instructions-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md index 77589f8..16ebca5 100644 --- a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md +++ b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md @@ -1,7 +1,7 @@ # AWS Bedrock Setup Instructions — Design **Date:** 2026-08-10 -**Status:** Approved +**Status:** Implemented (PR `docs/aws-bedrock-setup`, 2026-08-10) **Scope:** Documentation-only change. Closes the gap between what the tutorial currently tells a reader about getting Bedrock working and what they actually need to know. No code changes. --- From 9bceaf7324c43d82d94606448ca4a3d19725a8bc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 22:32:12 -0400 Subject: [PATCH 11/11] =?UTF-8?q?docs(bedrock-setup):=20address=20review?= =?UTF-8?q?=20=E2=80=94=20model-agreement=20CLI=20path,=20availability=20p?= =?UTF-8?q?robe,=20synth-time=20model=20id,=20cost=20formula?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/11-aws-bedrock-setup.md: narrow 'every step is CLI' claim to 'most AWS-side steps'; soften AI prompt to require confirmation before any grant/subscribe call; swap get-foundation-model for get-foundation-model-availability (authorisation, agreement, entitlement, region); replace Anthropic EULA wording with First Time Use form; add BEDROCK_MODEL_ID to synth-time env vars; document AWS_IAM Function URL permissions and the 403 boundary; replace fixed $0.02–$0.04/day with a reproducible 288×512×$0.40/1e6 formula; clarify Step 6 converse verifies the CLI principal only; fix Reference / Region availability / Switching models / Cost / Troubleshooting heading hierarchy; add language tags to all fenced code blocks. - docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md: same content updates applied to the plan record, plus add aws-marketplace:Subscribe/Unsubscribe/ ViewSubscriptions to the deployer list, label AdministratorAccess as a temporary exception, add 07-budget-protection.md to the link-check expected output, and the spec/plan EULA claim now scopes to the default zai.glm-4.7-flash probe. - docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md: events:CreateRule → events:PutRule; collapse 'smoke status URL' wording into the single AgentFunctionUrl output; cost paragraph rewritten as a reproducible formula; remaining untagged markdown fence tagged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/11-aws-bedrock-setup.md | 192 ++++++++++++------ .../plans/2026-08-10-aws-bedrock-setup-doc.md | 141 +++++++++---- ...08-10-bedrock-setup-instructions-design.md | 20 +- 3 files changed, 242 insertions(+), 111 deletions(-) diff --git a/docs/11-aws-bedrock-setup.md b/docs/11-aws-bedrock-setup.md index 642839b..d31d07b 100644 --- a/docs/11-aws-bedrock-setup.md +++ b/docs/11-aws-bedrock-setup.md @@ -6,21 +6,30 @@ needs before `npm run deploy` produces a working bot. The Discord half is bottom of [02-rehydration.md](02-rehydration.md), which was the wrong home — account setup and rehydration are different topics, read at different moments. -Every step below except Step 4 can be verified from the AWS CLI, which means you can -hand most of this to an AI assistant that has CLI access rather than clicking through -consoles. A prompt that works: - -``` +Most AWS-side steps can be verified from the AWS CLI, which means you can hand +most of this to an AI assistant that has CLI access rather than clicking through +consoles. Step 0's local checks (`aws sts get-caller-identity`, `docker info`, +`npm test`) and the billing check in **Billing and Cost Management → Payment +preferences** are not AWS CLI checks — handle those yourself or read along. A +prompt that works: + +```text I'm setting up the sqlite-s3-agent-tutorial on a fresh AWS account. Walk me through docs/11-aws-bedrock-setup.md using the AWS CLI in us-east-1: run each step's verification command, tell me what the output means, and stop at the first failure rather than continuing. Show me any command that creates, -subscribes to, or grants something before you run it. +subscribes to, or grants something before you run it. Do not run any IAM, +Marketplace, or write-API command without showing it to me first. ``` -> **Step 4 is the exception.** Subscribing to a model in AWS Marketplace has no -> CLI equivalent you can rely on — it is a console flow with a legal agreement. -> Expect to do that one yourself. +> **Step 4 is mostly the exception.** The AWS Marketplace listing is a console +> flow with a legal agreement, but the underlying Bedrock model agreement can +> also be created from the CLI (`aws bedrock create-foundation-model-agreement`) +> or accepted implicitly on the model's first invocation. The first-invocation +> path needs `aws-marketplace:Subscribe`, `aws-marketplace:Unsubscribe`, and +> `aws-marketplace:ViewSubscriptions` on the invoking identity and can take up +> to 15 minutes to finalise; a click-through in the console has the same effect +> and is the route most readers should take. Expect to do this step yourself. ## Step 0: Local prerequisites @@ -43,7 +52,7 @@ before continuing. Every other command in this doc assumes it works. docker info ``` -The tutorial's Lambda is a **container image** function (`infra/stack.ts:84` uses +The tutorial's Lambda is a **container image** function (`infra/stack.ts:84-85` uses `lambda.DockerImageFunction` with `DockerImageCode.fromImageAsset`), so `cdk deploy` builds the image locally and pushes it to ECR. Without a running daemon the deploy fails with a Docker error that mentions neither Bedrock nor this tutorial, which is a @@ -73,9 +82,9 @@ Read the result: | Output | Meaning | What to do | |---|---|---| | One or more `zai.glm-…` ids | Bedrock is reachable and the model exists in the Region | Continue to Step 2 | -| Empty output, command succeeds | Bedrock answered, but this model family isn't offered to your account/Region | Try `--region us-east-1` explicitly; if still empty, see [bedrock-model-comparison.md](bedrock-model-comparison.md) and pick a model your account does list | +| Empty output, command succeeds | Bedrock answered, but this model family isn't offered to your account/Region | See [bedrock-model-comparison.md](bedrock-model-comparison.md) and pick a model your account does list; the model must also be available in the Region you queried | | `AccessDeniedException` | Your principal lacks `bedrock:ListFoundationModels` | Step 2 — this is an IAM problem, not a Bedrock one | -| `Could not connect to the endpoint URL` | Bedrock is not available in the Region you asked for | Use `us-east-1` | +| `Could not connect to the endpoint URL` | Bedrock is not available in the Region you asked for | Your account lacks Bedrock in this Region (Lightsail-only, restricted Region, or SCP-blocked); see the note below | Some account types — accounts that have only ever used Lightsail, accounts in restricted Regions, some organisation-managed accounts with service control policies — @@ -91,7 +100,7 @@ and nothing else). A brand-new IAM user typically has none of what a CDK deploy The deploy touches, at minimum: -``` +```text iam:CreateRole, iam:AttachRolePolicy, iam:PassRole lambda:CreateFunction, lambda:UpdateFunctionCode, lambda:CreateFunctionUrlConfig s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging @@ -102,14 +111,22 @@ ecr:CreateRepository, ecr:GetAuthorizationToken, cloudformation:CreateStack, cloudformation:UpdateStack, cloudformation:DeleteStack sts:AssumeRole (the CDK bootstrap deploy roles — see Step 3) ssm:GetParameter (CDK bootstrap version lookup) + +# Required only if this same principal performs Step 4's Marketplace subscribe +# or the first third-party model invocation (automatic subscription path): +aws-marketplace:Subscribe, aws-marketplace:Unsubscribe, +aws-marketplace:ViewSubscriptions ``` > **Treat that list as representative, not exhaustive.** CDK's exact call set shifts > between versions, and an incomplete list presented as "the minimum" is worse than no -> list — it sends you hunting one denied action at a time. The pragmatic path is -> `AdministratorAccess` on a dedicated deploy principal for the first deploy, then -> scoping down once you can see in CloudTrail what was actually called. Enumerating a -> least-privilege deploy policy is out of scope for this tutorial. +> list — it sends you hunting one denied action at a time. The pragmatic path is a +> short-lived deployer role for the first deploy (a fresh IAM user with +> `AdministratorAccess` works; rotate or delete it after the first successful deploy), +> then scoping down once you can see in CloudTrail what was actually called. If +> least-privilege scoping must wait, label the `AdministratorAccess` usage as a +> temporary exception rather than the steady state. Enumerating a least-privilege +> deploy policy is out of scope for this tutorial. ## Step 3: Know what the first deploy bootstraps @@ -154,8 +171,29 @@ This is the console-only step, and it is the one people get wrong. > `bedrock:InvokeModel` work. What this tutorial needs is the AWS Marketplace listing, > reached from the AWS console, billed to your AWS account. -The subscription is **per Region**. Subscribing while your console is in `us-east-1` is -what the rest of this doc assumes. +After subscribing in one Region, the same identity can request access to the model in +any other Region where that model is supported — the Marketplace subscription itself +is **not** per Region. What is per Region is *model availability*: re-running Step 1's +`list-foundation-models` in a new Region is the only way to confirm that a model exists +there before you move. Subscribing in `us-east-1` (this doc's pinned Region) is what +the rest of the steps assume. + +> **CLI alternative for Step 4.** You can also create the Bedrock model agreement +> directly without visiting the Marketplace listing: +> +> ```bash +> aws bedrock list-foundation-model-agreement-offers \ +> --region us-east-1 --model-id zai.glm-4.7-flash +> +> aws bedrock create-foundation-model-agreement \ +> --region us-east-1 \ +> --model-id zai.glm-4.7-flash \ +> --offer-token "" +> ``` +> +> Or rely on the implicit first-invocation subscription, which Bedrock performs in the +> background if the invoking identity carries the three `aws-marketplace:*` permissions +> from Step 2 — that path can take up to 15 minutes to finalise. ## Step 5: Confirm access to both models this tutorial invokes @@ -166,21 +204,24 @@ There are two, and readers regularly arrange access for only the first: | `zai.glm-4.7-flash` (default) | Yes — `BEDROCK_MODEL_ID` (`src/config.ts:114`) | The friendly message + haiku, via Converse | | `amazon.titan-embed-text-v2:0` | **No** — hardcoded at `src/embed/titan.ts:16` | Embedding each tick's message for RAG search | -Every tick calls both. Verify each is visible to your account: +Every tick calls both. `get-foundation-model` only returns metadata — to confirm your +account is actually authorised to invoke each model, use +`get-foundation-model-availability` and inspect `authorizationStatus`, +`agreementAvailability.status`, `entitlementAvailability`, and +`regionAvailability`: ```bash # The chat model — Marketplace subscription from Step 4 is what gates this one. -aws bedrock get-foundation-model --region us-east-1 \ - --model-identifier zai.glm-4.7-flash \ - --query "modelDetails.modelId" --output text +aws bedrock get-foundation-model-availability --region us-east-1 \ + --model-id zai.glm-4.7-flash # The embedding model — Amazon-family access, a separate gate. -aws bedrock get-foundation-model --region us-east-1 \ - --model-identifier amazon.titan-embed-text-v2:0 \ - --query "modelDetails.modelId" --output text +aws bedrock get-foundation-model-availability --region us-east-1 \ + --model-id amazon.titan-embed-text-v2:0 ``` -Both should echo the id back. What to do per family if one doesn't: +For each model, every field must come back `AVAILABLE` / `AUTHORIZED`. What to do per +family if one doesn't: - **`zai.*`** — nothing to click. The Marketplace subscription from Step 4 is the gate; Bedrock enables foundation-model access by default in commercial Regions once the @@ -188,12 +229,14 @@ Both should echo the id back. What to do per family if one doesn't: is no longer the gating step for this model. A failure here means Step 4 didn't take, or took in a different Region. - **`amazon.titan-*`** — Amazon-family models are enabled by default in most commercial - Region accounts, but not universally. If the probe fails, open **Bedrock → Model - access** in `us-east-1` and enable Titan Text Embeddings V2 there. + Region accounts, but not universally. If `agreementAvailability.status` is + `NOT_AVAILABLE`, open **Bedrock → Model access** in `us-east-1` and enable Titan Text + Embeddings V2 there. - **`anthropic.claude-*`** — only relevant if you switch the default. Anthropic models - require a per-model first-time-use EULA acceptance on the **Model access** page before - the first `InvokeModel` succeeds. That is a manual console action and is the one - remaining reason to open that screen. It does **not** apply to the default model. + require a First Time Use submission (use-case description and a website URL) on the + **Model access** page, or via the `PutUseCaseForModelAccess` API, before the first + `InvokeModel` succeeds. That is a manual one-time action and is the one remaining + reason to open that screen. It does **not** apply to the default model. ## Step 6: Prove Bedrock works before you deploy @@ -201,7 +244,7 @@ One real model call, costing a fraction of a cent, tells you whether Steps 1, 4, actually landed: ```bash -# Should return JSON containing an assistant message. +# Should print the model's reply — a single token, e.g. `ok`. aws bedrock-runtime converse \ --region us-east-1 \ --model-id zai.glm-4.7-flash \ @@ -212,22 +255,24 @@ aws bedrock-runtime converse \ And the embedding model: ```bash -# Should write a JSON body containing a 1024-float "embedding" array. +# Should write a JSON body containing a 256-float "embedding" array — +# matching the dimensions the deployed code requests (`src/embed/titan.ts:17`). aws bedrock-runtime invoke-model \ --region us-east-1 \ --model-id amazon.titan-embed-text-v2:0 \ --content-type application/json \ --cli-binary-format raw-in-base64-out \ - --body '{"inputText":"hello"}' \ + --body '{"inputText":"hello","dimensions":256,"normalize":true}' \ /tmp/titan-probe.json && head -c 80 /tmp/titan-probe.json && echo ``` **Why this step exists.** Without it, the first real Bedrock call happens inside a deployed Lambda at Step 9, which means every subscription, EULA, Region, or model-id -mistake costs a full bootstrap-and-deploy cycle to discover and a CloudWatch log dive to -diagnose. Here the same mistakes surface in two seconds with the error text on your own -terminal. Map what you get to [Troubleshooting](#troubleshooting) below before moving on -— a failure at this step will not fix itself during deployment. +mistake surfaces only at the next scheduled tick — up to five minutes of waiting, then a +CloudWatch log dive to read the actual error. The deploy itself succeeds either way; the +probe catches the same mistakes in two seconds with the error text on your own terminal. +Map what you get to [Troubleshooting](#troubleshooting) below before moving on — a failure +at this step will not fix itself during deployment. > **Note on the model id.** Pass the **bare** id, exactly as above. Bedrock inference > profile prefixes (`global.`, `us.`) are decided per model family by @@ -238,13 +283,17 @@ terminal. Map what you get to [Troubleshooting](#troubleshooting) below before m ## Step 7: Deploy -Two environment variables are read at **synth** time, which means they must be set +Three environment variables are read at **synth** time, which means they must be set before `npm run deploy`, not after: - `DISCORD_WEBHOOK_URL` — required. `infra/stack.ts` throws without it. - `FETCH_TRIGGER_TOKEN` — optional, and only needed if you want the on-demand trigger in Step 9. **Set it now if you want it at all** — adding it later means another deploy. +- `BEDROCK_MODEL_ID` — optional. Leaving it unset keeps the default + (`zai.glm-4.7-flash`, resolved at runtime by `src/config.ts:114`); setting it + before deploy changes the model baked into the IAM policy the Lambda runs under, + so a new value needs a redeploy to take effect. ```bash export AWS_PROFILE=your-profile # defaults to `default` @@ -261,7 +310,7 @@ npm run deploy container image, and deploys the `SqliteS3AgentTutorial` stack. Success looks like four stack outputs: -``` +```text SqliteS3AgentTutorial.AgentFunctionName = ... SqliteS3AgentTutorial.AgentFunctionUrl = https://....lambda-url.us-east-1.on.aws/ SqliteS3AgentTutorial.LoopRuleName = ... @@ -293,6 +342,12 @@ This probes the status Function URL twice: once unsigned (expecting `403`, provi URL really does require IAM) and once SigV4-signed (expecting the status payload). It is read-only and safe to run at any time, including mid-tick. +The Function URL is `AWS_IAM`-protected, so callers must SigV4-sign the request and +carry both `lambda:InvokeFunctionUrl` and `lambda:InvokeFunction` permissions on the +caller's principal. An IAM denial at the Function URL boundary returns `403` *before* +`FETCH_TRIGGER_TOKEN` is even read; a `403` after IAM is authorised means the token is +missing or wrong, not that Bedrock is unreachable. + It deliberately does **not** call Bedrock, post to Discord, or write to S3 — so a passing smoke run proves the deployment and its IAM are sound, and proves nothing at all about your model access. Step 6 was that check; Step 9 is the end-to-end one. @@ -318,11 +373,11 @@ and posts nothing; check the Lambda's CloudWatch logs and match the exception ag --- -# Reference +## Reference Everything above is the procedure. What follows is the material you come back for. -## Region availability +### Region availability The procedure pins `us-east-1`. If you move: @@ -341,14 +396,15 @@ The procedure pins `us-east-1`. If you move: - **Bootstrap is per account *and* Region.** The first deploy into a new Region bootstraps again (Step 3). -## Switching models +### Switching models `BEDROCK_MODEL_ID` selects the chat model at synth time (`infra/app.ts`, `src/config.ts:114`). The embedding model is not configurable. To switch: 1. Confirm the new model is listed and subscribed in your Region — Steps 1, 4, and 5. -2. For `anthropic.claude-*`, accept the per-model EULA on **Bedrock → Model access** - first. This is the one family where the console step is still mandatory. +2. For `anthropic.claude-*`, submit the First Time Use form (intended use case and + website URL) on **Bedrock → Model access** before the first invocation. This is the + one family where a console step is still mandatory. 3. Set the **bare** base id and redeploy: ```bash @@ -367,19 +423,33 @@ For which model to pick and what each costs, see [bedrock-model-comparison.md](bedrock-model-comparison.md). That comparison is not repeated here. -## Cost +### Cost + +Pricing on Bedrock changes; check the [Bedrock pricing page](https://aws.amazon.com/bedrock/pricing/) +before budgeting. As of the most recent AWS-published rates for `zai.glm-4.7-flash` in +`us-east-1` ($0.07 per 1M input tokens, $0.40 per 1M output tokens) and Titan Text +Embeddings V2 ($0.02 per 1M input tokens, no retries assumed), the worst case at the +default cadence is reproducible: + +```text +calls/day = 288 # 5-minute cadence +output_tokens/call = 512 # BEDROCK_MAX_OUTPUT_TOKENS cap +output-only/day = 288 × 512 × $0.40 / 1e6 ≈ $0.059 +``` -At the default 5-minute cadence — 288 ticks/day, each one Converse call plus one Titan -embedding — `zai.glm-4.7-flash` runs roughly **$0.02–$0.04/day**. Lambda, S3, and -EventBridge at this volume are rounding errors next to that. +Add input tokens for the actual prompt size (typically a few hundred tokens) and the +Titan embedding per tick (a few hundred tokens) and the realistic number lands well +below $0.10/day. The $0.02–$0.04 range quoted in earlier revisions of this doc is a +measured typical case, not a budget ceiling — a leaked `FETCH_TRIGGER_TOKEN` lets an +authorised caller drive Bedrock calls as fast as they can sign requests, and the only +real cap is the budget alarm below. Lambda, S3, and EventBridge at this volume are +rounding errors next to Bedrock. -The two things that actually change the number are cadence and a leaked -`FETCH_TRIGGER_TOKEN`, since an authorized caller with the token can drive Bedrock calls -as fast as they can sign requests. Set up a budget alert before leaving the loop running -unattended: [07-budget-protection.md](07-budget-protection.md) has the full breakdown -and the alarm setup. +Set up a budget alert before leaving the loop running unattended: +[07-budget-protection.md](07-budget-protection.md) has the full breakdown and the +alarm setup. -## Troubleshooting +### Troubleshooting | Symptom | Cause | Go to | |---|---|---| @@ -396,5 +466,11 @@ and the alarm setup. | `403` from the on-demand trigger | `FETCH_TRIGGER_TOKEN` unset at deploy time, or the request isn't SigV4-signed | Steps 7, 9 | | Deploy succeeds, ticks run, nothing posts to Discord | Bedrock or Discord failure inside the tick — `agent_runs.error` records it per source | CloudWatch logs, [06-discord-webhook-setup.md](06-discord-webhook-setup.md) | -The fastest general diagnostic is Step 6's `converse` probe. If it passes and the -deployed bot still fails, the problem is not model access. \ No newline at end of file +The fastest general diagnostic is Step 6's `converse` probe. **It verifies the AWS CLI +principal only, not the deployed Lambda:** the Lambda uses its own execution role and +its own `BEDROCK_REGION` and `BEDROCK_MODEL_ID`, baked in at deploy time. If the probe +passes and the deployed bot still fails on a Bedrock call, check the Lambda's role +policy (`iam:PassRole` only attaches it; the role's own trust and inline policies are +what grant `bedrock:InvokeModel`), confirm the deployed `BEDROCK_REGION` in the +Lambda's environment matches a Region where the model is offered, and read the actual +CloudWatch error before assuming model access is fine. diff --git a/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md b/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md index 7b9c4f2..27a298a 100644 --- a/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md +++ b/docs/superpowers/plans/2026-08-10-aws-bedrock-setup-doc.md @@ -173,7 +173,7 @@ and nothing else). A brand-new IAM user typically has none of what a CDK deploy The deploy touches, at minimum: -``` +```text iam:CreateRole, iam:AttachRolePolicy, iam:PassRole lambda:CreateFunction, lambda:UpdateFunctionCode, lambda:CreateFunctionUrlConfig s3:CreateBucket, s3:PutBucketPolicy, s3:PutBucketTagging @@ -184,14 +184,21 @@ ecr:CreateRepository, ecr:GetAuthorizationToken, cloudformation:CreateStack, cloudformation:UpdateStack, cloudformation:DeleteStack sts:AssumeRole (the CDK bootstrap deploy roles — see Step 3) ssm:GetParameter (CDK bootstrap version lookup) + +# Required only if this same principal performs Step 4's Marketplace subscribe +# or the first third-party model invocation (automatic subscription path): +aws-marketplace:Subscribe, aws-marketplace:Unsubscribe, +aws-marketplace:ViewSubscriptions ``` > **Treat that list as representative, not exhaustive.** CDK's exact call set shifts > between versions, and an incomplete list presented as "the minimum" is worse than no -> list — it sends you hunting one denied action at a time. The pragmatic path is -> `AdministratorAccess` on a dedicated deploy principal for the first deploy, then -> scoping down once you can see in CloudTrail what was actually called. Enumerating a -> least-privilege deploy policy is out of scope for this tutorial. +> list — it sends you hunting one denied action at a time. The pragmatic path is a +> short-lived deployer role for the first deploy (a fresh IAM user with +> `AdministratorAccess` works; rotate or delete it after the first successful deploy), +> then scope down once you can see in CloudTrail what was actually called. Label any +> `AdministratorAccess` usage as a temporary exception, not the steady state. +> Enumerating a least-privilege deploy policy is out of scope for this tutorial. ## Step 3: Know what the first deploy bootstraps @@ -227,7 +234,7 @@ grep -o '](0[0-9]-[a-z0-9-]*\.md\|](bedrock-model-comparison\.md' docs/11-aws-be done ``` -Expected: `OK 06-discord-webhook-setup.md`, `OK 02-rehydration.md`, `OK bedrock-model-comparison.md`. No `MISS` lines. +Expected: `OK 06-discord-webhook-setup.md`, `OK 02-rehydration.md`, `OK bedrock-model-comparison.md`, `OK 07-budget-protection.md`. No `MISS` lines. - [ ] **Step 3: Commit** @@ -250,15 +257,21 @@ Append exactly this to the end of `docs/11-aws-bedrock-setup.md`: ````markdown ## Step 4: Subscribe to the model in AWS Marketplace -This is the console-only step, and it is the one people get wrong. +The Marketplace listing is a console flow, but the underlying Bedrock model agreement +can also be created from the CLI (`create-foundation-model-agreement`) or accepted +implicitly on the model's first invocation. The first-invocation path needs +`aws-marketplace:Subscribe`, `aws-marketplace:Unsubscribe`, and +`aws-marketplace:ViewSubscriptions` on the invoking identity and can take up to 15 +minutes to finalise; a click-through in the console has the same effect. Most readers +should use the console flow: 1. Open the [AWS Marketplace](https://aws.amazon.com/marketplace) console in the same account you authenticated in Step 0, and search for the model — `GLM-4.7-Flash` for this tutorial's default. -2. Open the listing and click **View purchase options** / **Subscribe**. -3. Accept the terms. **Subscribing costs nothing.** There is no minimum, no monthly - fee, and no commitment — you pay per token consumed, billed through your normal AWS - invoice alongside Lambda and S3. +2. Open the listing, review the current pricing and terms (free to subscribe; you pay + only for tokens consumed), and click **View purchase options** / **Subscribe**. +3. Accept the terms. There is no minimum, no monthly fee, and no commitment — you pay + per token consumed, billed through your normal AWS invoice alongside Lambda and S3. 4. Wait for the subscription status to show as active. It is usually immediate but can take a few minutes. @@ -268,8 +281,29 @@ This is the console-only step, and it is the one people get wrong. > `bedrock:InvokeModel` work. What this tutorial needs is the AWS Marketplace listing, > reached from the AWS console, billed to your AWS account. -The subscription is **per Region**. Subscribing while your console is in `us-east-1` is -what the rest of this doc assumes. +After subscribing in one Region, the same identity can request access to the model in +any other Region where that model is supported — the Marketplace subscription itself is +**not** per Region. What is per Region is *model availability*: re-running Step 1's +`list-foundation-models` in a new Region is the only way to confirm that a model exists +there before you move. Subscribing in `us-east-1` (this doc's pinned Region) is what +the rest of the steps assume. + +> **CLI alternative for Step 4.** You can also create the Bedrock model agreement +> directly without visiting the Marketplace listing: +> +> ```bash +> aws bedrock list-foundation-model-agreement-offers \ +> --region us-east-1 --model-id zai.glm-4.7-flash +> +> aws bedrock create-foundation-model-agreement \ +> --region us-east-1 \ +> --model-id zai.glm-4.7-flash \ +> --offer-token "" +> ``` +> +> Or rely on the implicit first-invocation subscription, which Bedrock performs in the +> background if the invoking identity carries the three `aws-marketplace:*` permissions +> from Step 2 — that path can take up to 15 minutes to finalise. ## Step 5: Confirm access to both models this tutorial invokes @@ -280,21 +314,23 @@ There are two, and readers regularly arrange access for only the first: | `zai.glm-4.7-flash` (default) | Yes — `BEDROCK_MODEL_ID` (`src/config.ts:114`) | The friendly message + haiku, via Converse | | `amazon.titan-embed-text-v2:0` | **No** — hardcoded at `src/embed/titan.ts:16` | Embedding each tick's message for RAG search | -Every tick calls both. Verify each is visible to your account: +Every tick calls both. `get-foundation-model` only returns metadata — to confirm your +account is actually authorised to invoke each model, use +`get-foundation-model-availability` and inspect `authorizationStatus`, +`agreementAvailability.status`, `entitlementAvailability`, and `regionAvailability`: ```bash # The chat model — Marketplace subscription from Step 4 is what gates this one. -aws bedrock get-foundation-model --region us-east-1 \ - --model-identifier zai.glm-4.7-flash \ - --query "modelDetails.modelId" --output text +aws bedrock get-foundation-model-availability --region us-east-1 \ + --model-id zai.glm-4.7-flash # The embedding model — Amazon-family access, a separate gate. -aws bedrock get-foundation-model --region us-east-1 \ - --model-identifier amazon.titan-embed-text-v2:0 \ - --query "modelDetails.modelId" --output text +aws bedrock get-foundation-model-availability --region us-east-1 \ + --model-id amazon.titan-embed-text-v2:0 ``` -Both should echo the id back. What to do per family if one doesn't: +For each model, every field must come back `AVAILABLE` / `AUTHORIZED`. What to do per +family if one doesn't: - **`zai.*`** — nothing to click. The Marketplace subscription from Step 4 is the gate; Bedrock enables foundation-model access by default in commercial Regions once the @@ -302,20 +338,24 @@ Both should echo the id back. What to do per family if one doesn't: is no longer the gating step for this model. A failure here means Step 4 didn't take, or took in a different Region. - **`amazon.titan-*`** — Amazon-family models are enabled by default in most commercial - Region accounts, but not universally. If the probe fails, open **Bedrock → Model - access** in `us-east-1` and enable Titan Text Embeddings V2 there. + Region accounts, but not universally. If `agreementAvailability.status` is + `NOT_AVAILABLE`, open **Bedrock → Model access** in `us-east-1` and enable Titan Text + Embeddings V2 there. - **`anthropic.claude-*`** — only relevant if you switch the default. Anthropic models - require a per-model first-time-use EULA acceptance on the **Model access** page before - the first `InvokeModel` succeeds. That is a manual console action and is the one - remaining reason to open that screen. It does **not** apply to the default model. + require a First Time Use submission (use-case description and a website URL) on the + **Model access** page, or via the `PutUseCaseForModelAccess` API, before the first + `InvokeModel` succeeds. That is a manual one-time action and is the one remaining + reason to open that screen. It does **not** apply to the default model. ## Step 6: Prove Bedrock works before you deploy One real model call, costing a fraction of a cent, tells you whether Steps 1, 4, and 5 -actually landed: +actually landed. The `converse` call hardcodes the default `zai.glm-4.7-flash` model; +if `BEDROCK_MODEL_ID` is set to anything else, run this probe against that id instead +— the converse command does **not** validate EULA or access for a non-default model. ```bash -# Should return JSON containing an assistant message. +# Should print the model's reply — a single token, e.g. `ok`. aws bedrock-runtime converse \ --region us-east-1 \ --model-id zai.glm-4.7-flash \ @@ -326,22 +366,26 @@ aws bedrock-runtime converse \ And the embedding model: ```bash -# Should write a JSON body containing a 1024-float "embedding" array. +# Should write a JSON body containing a 256-float "embedding" array — +# matching the dimensions the deployed code requests (`src/embed/titan.ts:17`). aws bedrock-runtime invoke-model \ --region us-east-1 \ --model-id amazon.titan-embed-text-v2:0 \ --content-type application/json \ --cli-binary-format raw-in-base64-out \ - --body '{"inputText":"hello"}' \ + --body '{"inputText":"hello","dimensions":256,"normalize":true}' \ /tmp/titan-probe.json && head -c 80 /tmp/titan-probe.json && echo ``` **Why this step exists.** Without it, the first real Bedrock call happens inside a deployed Lambda at Step 9, which means every subscription, EULA, Region, or model-id -mistake costs a full bootstrap-and-deploy cycle to discover and a CloudWatch log dive to -diagnose. Here the same mistakes surface in two seconds with the error text on your own -terminal. Map what you get to [Troubleshooting](#troubleshooting) below before moving on -— a failure at this step will not fix itself during deployment. +mistake surfaces only at the next scheduled tick — up to five minutes of waiting, then +a CloudWatch log dive to read the actual error. The deploy itself succeeds either way; +the probe catches the same mistakes in two seconds with the error text on your own +terminal. Map what you get to [Troubleshooting](#troubleshooting) below before moving +on — a failure at this step will not fix itself during deployment. The converse probe +verifies the AWS CLI principal only, not the deployed Lambda; see Troubleshooting for +the Lambda-side checks when the probe passes but the deployed bot still fails. > **Note on the model id.** Pass the **bare** id, exactly as above. Bedrock inference > profile prefixes (`global.`, `us.`) are decided per model family by @@ -525,8 +569,9 @@ The procedure pins `us-east-1`. If you move: `src/config.ts:114`). The embedding model is not configurable. To switch: 1. Confirm the new model is listed and subscribed in your Region — Steps 1, 4, and 5. -2. For `anthropic.claude-*`, accept the per-model EULA on **Bedrock → Model access** - first. This is the one family where the console step is still mandatory. +2. For `anthropic.claude-*`, submit the First Time Use form (intended use case and + website URL) on **Bedrock → Model access** before the first invocation. This is the + one family where a console step is still mandatory. 3. Set the **bare** base id and redeploy: ```bash @@ -547,15 +592,21 @@ repeated here. ## Cost -At the default 5-minute cadence — 288 ticks/day, each one Converse call plus one Titan -embedding — `zai.glm-4.7-flash` runs roughly **$0.02–$0.04/day**. Lambda, S3, and -EventBridge at this volume are rounding errors next to that. - -The two things that actually change the number are cadence and a leaked -`FETCH_TRIGGER_TOKEN`, since an authorized caller with the token can drive Bedrock calls -as fast as they can sign requests. Set up a budget alert before leaving the loop running -unattended: [07-budget-protection.md](07-budget-protection.md) has the full breakdown -and the alarm setup. +Pricing on Bedrock changes; check the [Bedrock pricing page](https://aws.amazon.com/bedrock/pricing/) +before budgeting. As of the most recent AWS-published rates for `zai.glm-4.7-flash` in +`us-east-1` ($0.07 per 1M input tokens, $0.40 per 1M output tokens) and Titan Text +Embeddings V2 ($0.02 per 1M input tokens, no retries assumed), the worst case at the +default cadence is reproducible: 288 ticks/day × 512 output tokens × $0.40 / 1e6 ≈ +$0.059/day for the Converse output alone, before input tokens and Titan embeddings. +The $0.02–$0.04 range quoted in earlier revisions of this doc is a measured typical +case, not a budget ceiling — a leaked `FETCH_TRIGGER_TOKEN` lets an authorised caller +drive Bedrock calls as fast as they can sign requests, and the only real cap is the +budget alarm below. Lambda, S3, and EventBridge at this volume are rounding errors next +to Bedrock. + +Set up a budget alert before leaving the loop running unattended: +[07-budget-protection.md](07-budget-protection.md) has the full breakdown and the +alarm setup. ## Troubleshooting diff --git a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md index 16ebca5..3bd7dd4 100644 --- a/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md +++ b/docs/superpowers/specs/2026-08-10-bedrock-setup-instructions-design.md @@ -18,7 +18,7 @@ The new doc is end-to-end setup — what a reader needs to know *before* `cdk de - **A container runtime must be running locally.** `infra/stack.ts:84` uses `lambda.DockerImageFunction` with `DockerImageCode.fromImageAsset`, so `cdk deploy` builds an image locally and pushes it to ECR. Without a running Docker (or Finch/Podman) daemon the deploy fails with a message that says nothing about Bedrock or this tutorial. Unmentioned anywhere in the repo today. - **Titan Text Embeddings V2 is a second model the pipeline invokes.** `src/embed/titan.ts:16` pins `amazon.titan-embed-text-v2:0` and `infra/stack.ts:111` grants it; every tick calls it for RAG. A reader who arranges access only for the chat model gets an `AccessDeniedException` naming a model no doc ever told them about. -**Verification is CLI-first.** Each step carries an `aws` command that proves the step succeeded, so a reader can hand the bulk of the procedure to an AI assistant with CLI access rather than clicking through consoles. The AWS Marketplace subscription (Step 4) is the one genuine console-only detour and the doc says so explicitly. A pre-deploy `bedrock-runtime converse` probe collapses subscription, EULA, Region, and model-id mistakes into one sub-cent pass/fail *before* the reader spends a deploy cycle discovering them. +**Verification is CLI-first.** Each step carries an `aws` command that proves the step succeeded, so a reader can hand the bulk of the procedure to an AI assistant with CLI access rather than clicking through consoles. The AWS Marketplace subscription (Step 4) is the one genuine console-only detour and the doc says so explicitly. A pre-deploy `bedrock-runtime converse` probe (hardcoded against the default `zai.glm-4.7-flash`) collapses subscription, EULA, Region, and model-id mistakes into one sub-cent pass/fail *before* the reader spends a deploy cycle discovering them. **Constraints that shape every decision below:** @@ -48,7 +48,7 @@ Mirrors `docs/06-discord-webhook-setup.md`: numbered "Step N: …" sections for Every step carries a CLI verification. The doc opens by telling the reader they can hand the procedure to an AI assistant with AWS CLI access, with a copy-pasteable framing prompt, and flags Step 4 as the one console-only detour. -``` +```markdown # AWS Bedrock setup 2–3 sentence opener: what this doc covers, why it lives separately from 02-rehydration.md. @@ -89,7 +89,7 @@ Every step carries a CLI verification. The doc opens by telling the reader they recognize the shape of what's needed: iam:CreateRole / AttachRolePolicy / PassRole; lambda:CreateFunction / UpdateFunctionCode; s3:CreateBucket / PutBucketPolicy / - PutBucketTagging; events:CreateRule / PutTargets; ecr: + PutBucketTagging; events:PutRule / PutTargets; ecr: CreateRepository / GetAuthorizationToken / InitiateLayerUpload / UploadLayerPart / CompleteLayerUpload / PutImage; cloudformation:CreateStack / UpdateStack / DeleteStack; @@ -168,7 +168,8 @@ Every step carries a CLI verification. The doc opens by telling the reader they it later requires a redeploy. - npm run deploy. - What success looks like: stack outputs include the agent - Function URL and the smoke status URL; no AccessDenied in + Function URL (`AgentFunctionUrl`, which `npm run smoke` uses for the status + probe); no AccessDenied in CloudFormation events. If it fails: aws cloudformation describe-stack-events \ --stack-name SqliteS3AgentTutorial --max-items 20 @@ -211,8 +212,11 @@ Every step carries a CLI verification. The doc opens by telling the reader they ### Cost monitoring - Brief pointer to docs/07-budget-protection.md. - One-paragraph "what to expect at the default cadence": - ~$0.02–$0.04/day at the 5-minute loop cadence from - README.md §Cost. + a reproducible worst-case formula (288 ticks/day × 512 output tokens × + the model's output rate per million tokens; Titan embeddings V2 at + $0.02 per 1M input tokens; no retries assumed) at the 5-minute loop + cadence. The `$0.02–$0.04/day` figure quoted in earlier revisions is a + measured typical case, not a budget ceiling. ### Troubleshooting A table: symptom → which step to revisit → the CLI command that @@ -255,7 +259,7 @@ Lines 100–119 (the `## Bedrock setup` heading and its body) are removed. The f - **Lines 47–50:** currently link to `docs/02-rehydration.md#bedrock-setup`. Change to `docs/11-aws-bedrock-setup.md`. The phrasing stays — the warning that the deploy needs the Marketplace subscription before the first fetch is still true, just the link moves. - **Line 186 docs table:** add a new row next to the existing `bedrock-model-comparison.md` entry: - ``` + ```markdown | [docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) | Account type, deployer IAM, Marketplace subscription, Region, EULA, first-deploy smoke | ``` @@ -267,7 +271,7 @@ Before declaring done: search the entire repo for `02-rehydration.md#bedrock-set This is the RAG-extension lesson script. It assumes the student has already completed the base tutorial's Bedrock setup end-to-end, but currently names only `[08-rag-vector-search.md](08-rag-vector-search.md)` and `[01-architecture.md](01-architecture.md)` as prerequisites — neither of which is the canonical Bedrock-setup reference. Update line 5 to make the prereq explicit: -``` +```markdown **Prerequisite:** the student has completed the base tutorial end-to-end, including the Bedrock setup documented in [11-aws-bedrock-setup.md](11-aws-bedrock-setup.md). The student has read