This guide tells you what to change and where when you modify WREN: styling and branding, authentication, the API, frontend, application configuration, data and documents, the Feedback Engine, shared types, deployment, and troubleshooting.
It does not cover how to deploy or operate WREN in detail. For that, see the operational documentation linked at the end.
The product display name is WREN. AWS resources and this repository use the wren prefix.
Related docs:
- REST API
- Security overview
- Deployment guide
- Dependency management
- AWS managed keys
- Feedback engine
- Database migrations
- CDK package
Work through this before changing anything:
- Decide which layer owns the behavior: frontend, API, database, Feedback Engine, or infrastructure.
- Read the relevant guide linked above before changing deployment or security behavior.
- Prefer a configuration change over a code change when the behavior is already configurable (model id, usage limits, prompts).
- Reuse an existing service boundary instead of creating a duplicate implementation.
- Update shared types in
@wren/typesbefore changing the consumers. - Add or update tests for behavior that changes.
- Run the relevant local checks before deploying.
- Do not edit an already-applied database migration.
- Do not introduce a
replacementProsefield into feedback DTOs or persistence. - For infrastructure changes, run
npx cdk synthbefore deploying.
| If you need to change… | Start here |
|---|---|
| UI styling | frontend/src/styles/ |
| Product / display name | frontend/src/config/env.ts, CDK app config |
| Authentication | backend/auth/, cdk/lib/stacks/CognitoStack.ts |
| API endpoint (route) | cdk/lib/openapi.yaml |
| API business logic | the relevant backend/<service>/ |
| UI page | frontend/src/pages/ |
| UI feature | frontend/src/features/ |
| Database schema | backend/database/migrations/ |
| Document processing | backend/document/ |
| Feedback generation | backend/engine/python/ |
| Feedback API / orchestration | backend/feedback/ |
| Shared DTOs | types/ |
| AWS infrastructure | cdk/lib/stacks/ |
| Model configuration | Admin config / SSM / cdk/lib/config.ts |
| Deployment | deployment-guide.md |
Not every change needs a deploy. Use this to avoid rebuilding the whole app for a configuration tweak:
| Change type | Requires a deploy? |
|---|---|
| Frontend component | Yes |
| Lambda (Node) code | Yes |
| Database migration | Yes |
| CDK infrastructure | Yes |
| Feedback Engine Python code | New image + Lambda update |
| SSM model setting | No code deploy |
| Usage limits | No code deploy |
| Admin prompt configuration | No code deploy |
| Display name | Depends on the path used (config vs deploy) |
- WREN — Project Modification Guide
- Before You Modify WREN
- Where Should I Look?
- Table of Contents
- 1. Styling and Branding
- 2. Authentication and Authorization
- 3. Extending the API
- 4. Modifying Frontend Components
- 5. Application Configuration
- 6. Data and Documents
- 7. Modifying the Feedback Engine
- 8. Shared Types (
@wren/types) - 9. Deployment and Testing
- 10. Encryption
- 11. Troubleshooting
- 12. Related Operational Documentation
The React web app uses Tailwind CSS 4 and a token-first theme. Raw hex colors and raw px font sizes are not allowed in component .tsx files; the ESLint rule wren/no-raw-design-values fails lint on violations.
The TypeScript token files are the canonical source for design values. The Tailwind @theme block mirrors those values and must be updated whenever the tokens change:
- Edit the TypeScript tokens in
frontend/src/styles/tokens/(colors.ts,typography.ts,spacing.ts,radii.ts,shadows.ts). - Mirror the same values in the Tailwind
@themeblock infrontend/src/styles/theme.css.
Example — change the primary button fill:
// frontend/src/styles/tokens/colors.ts
export const palette = {
// ...
figmaTextSecondary: '#1a365d', // was #2c2c2c
};/* frontend/src/styles/theme.css — keep in sync with tokens */
@theme {
--color-accent-primary: #1a365d;
--color-text-secondary: #1a365d;
}In components, use named utilities only, for example bg-accent-primary, text-text-primary, text-body, p-lg, rounded-md. Global imports live in frontend/src/styles/index.css. Fonts are Inter (UI) and Source Serif 4 (document body).
The display name is configuration, not a hardcoded string. Do not hardcode LEON or WREN as a product string in UI or Lambda responses.
Do not confuse these two values: appName controls what users see, while projectName controls AWS resource naming.
| Concept | Example | Used for |
|---|---|---|
| Display name | WREN |
UI text and email copy |
| Project name | wren |
AWS resource naming (wren-*) |
| Environment | dev |
Environment separation |
| Stack prefix | wren-dev |
CDK stack and resource grouping |
To change the display name:
| Layer | How to change |
|---|---|
| Frontend | VITE_APP_NAME (see frontend/.env.example). Read only via appConfig.appName in frontend/src/config/env.ts. |
| Amplify | CDK injects VITE_APP_NAME from context appName (cdk/lib/stacks/AmplifyStack.ts). |
| Cognito emails | Use config.appName in cdk/lib/stacks/CognitoStack.ts, already parameterized. |
| CDK synth | npx cdk deploy -c appName="My Coach" updates SSM /{stackPrefix}/config/app-name. |
scripts/check-app-name.mjs fails the build if the display-name token appears as a whole quoted string outside cdk/lib/config.ts. Resource names stay wren-* from projectName; only the UI and email label changes. The document title, landing heading, nav, and the "Ask {name}" label all read appConfig.appName.
The deploy-time default for WREN_APP_NAME is LEON, kept for backward compatibility with existing deployments. New deployments should set it to the intended display name. See Advanced deployment configuration.
Users sign in with Amazon Cognito (email and password, optional MFA). All REST API methods require Cognito authentication; OPTIONS requests are handled separately for CORS. First login provisions an app_user with the Student role.
Roles are stored in the database, not read from Cognito groups on every request.
| Role | Typical access |
|---|---|
Student |
Own documents, debriefs, Ask WREN, notifications |
DepartmentAdmin |
Catalog and config for administered departments |
TechnicalAdmin |
Platform config, usage policy, model id, user search, roles |
Terminology. The application role is
TechnicalAdmin. The frontend refers to this area as System Admin (SystemAdminLayout, routes under/system-admin). They are the same thing.
Key files:
cdk/lib/stacks/CognitoStack.ts— user pool, app client, password policy, PreSignUp email-domain allowlist,SystemAdmingroupbackend/auth/src/authorizer.ts— JWT verify and identity mappingbackend/auth/src/capabilityMatrix.ts— per-operation allow/denyfrontend/src/features/auth/— Cognito session and token providerfrontend/src/App.tsx—ProtectedRoutewithallow={['TechnicalAdmin']}, etc.frontend/src/services/apiClient.ts— attachesAuthorization: Beareron every call
Local UI uses VITE_USE_LIVE_API=false (the default), which runs an in-memory mock and does not enforce login. Set it to true plus Cognito IDs to hit a deployed API.
Self-signup is limited to the exact hostnames in WREN_ALLOWED_EMAIL_DOMAINS (for example ubc.ca,student.ubc.ca), which npm run deploy requires. Matching is exact with no wildcards: list every hostname you want to allow. The Cognito PreSignUp Lambda on CognitoStack is the real gate and fails closed: an empty list denies every new sign-up (isSignupEmailDomainAllowed in types/src/signupEmailDomains.ts, with a CDK copy in cdk/lib/signupEmailDomains.ts). Amplify injects the same list as VITE_ALLOWED_EMAIL_DOMAINS so the sign-up form can fail clearly. When running cdk deploy on the Cognito stack directly, pass -c allowedEmailDomains=... (or set it once in ~/.cdk.json) or new sign-ups are blocked; npm run deploy without the variable refuses to run rather than clearing the list. Existing users are not signed out or blocked from login.
See the deployment guide.
There is no Technical Admin until you create one. Add the intended Cognito user to the SystemAdmin group before their first login. After that first login, Postgres becomes the source of truth for roles. See the deployment guide.
The pool is currently native Cognito (email and password). To add Microsoft Entra ID, Okta, or another OIDC/SAML IdP:
- In
cdk/lib/stacks/CognitoStack.ts, add aCfnUserPoolIdentityProvider(OIDC or SAML) with issuer, client id/secret, and attribute mapping foremailandsub. - Attach the provider to the Cognito app client (
supportedIdentityProviders). - Register callback URLs on the IdP: the Amplify app URL plus the Cognito hosted-UI or web-app callback.
- Update
frontend/src/features/auth/cognitoAuthService.tsto start the federated flow (hosted UI orsignInWithRedirect) using the provider name you registered. - Keep authorization in Postgres. Do not grant
TechnicalAdminfrom IdP groups on every request. Optionally map a single bootstrap group the same waySystemAdminis used today (backend/auth/src/provisionUser.ts). - Redeploy Cognito, and Amplify if callback URLs change.
AWS docs:
- OIDC: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html
- SAML: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html
The API is spec-first. Routes are declared in cdk/lib/openapi.yaml and deployed by SpecRestApi in cdk/lib/stacks/ApiStack.ts. Most features attach to an existing Lambda (Document, Feedback, Admin, Audit, Notification).
Which approach should I use?
- Add a route to an existing Lambda when the operation belongs to an existing service boundary such as Document, Admin, Feedback, or Notification.
- Create a new Lambda when the operation introduces a distinct execution boundary, permissions model, runtime, scaling requirement, or failure domain.
- Do not create a new Lambda solely to avoid adding another route to an existing service.
Authentication default. API methods are authenticated by default.
ApiStackappliesCognitoAuthorizerautomatically to every method exceptOPTIONS; do not add asecurityblock merely to enable Cognito auth. If a route must be unauthenticated, follow the existingsecurity: []exception pattern inApiStack.ts(the same one used for CORSOPTIONS) — removingsecuritydoes not make a route public. Verify any new route's authorization behavior before deployment.
- Implement the branch in the service handler, for example
backend/admin/src/lambda-handler.ts, matching onresourceandhttpMethod. - If the operation is privileged, add a
Capabilityintypes/src/authorization.tsand map it inbackend/auth/src/capabilityMatrix.ts. CallrequireCapabilitybefore any mutation. - Add request/response Zod schemas in
types/if the payload is a domain DTO. Never add areplacementProsefield. - Add the path and method to
cdk/lib/openapi.yamlwithx-amazon-apigateway-integrationpointing at the sameFn::SubARN as sibling routes (AdminFunctionArn,DocumentFunctionArn, and so on). - Call the API from the React web app only through
frontend/src/services(ApiClient), validating the response with Zod. - Redeploy:
npx cdk deploy wren-dev-Api, andwren-dev-Computeif handler code changed.
Example OpenAPI stub:
/v1/my-feature:
post:
summary: Example privileged action
tags: [Admin]
operationId: myFeature
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
responses:
"200":
description: OK
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: POST
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AdminFunctionArn}/invocations"ApiStack injects the CORS OPTIONS method on every path and applies CognitoAuthorizer to every other method (see the authentication-default note above).
Change → Test → Deploy → Verify for a new route:
- Change: handler branch, capability, types, OpenAPI path, frontend service method.
- Test: unit test the handler; validate the client response with Zod; add an authorization test if the route is privileged.
- Deploy:
wren-dev-Api, pluswren-dev-Computeif handler code changed. - Verify: an authenticated request succeeds, the expected
401/403behavior holds, and the response shape matches the schema.
Do not in the API layer:
- Do not call
fetchdirectly from feature components; go through the sharedApiClient. - Do not duplicate DTO definitions; import from
@wren/types. - Do not bypass
requireCapabilityfor privileged mutations.
- Create
backend/<service>/src/lambda-handler.ts. - Register a
NodejsFunctionincdk/lib/stacks/ComputeStack.ts(VPC private subnets, per-role DB secret, timeout, memory). If the Lambda needs database access, add or reuse the appropriate Postgres role incdk/lib/appDbRoles.ts; create a migration if the role or its grants are new. - Pass the function ARN into
ApiStackand add a substitution inApiStack.ts(MyFunctionArn). - Reference that placeholder from OpenAPI.
- Grant
apigateway.amazonaws.cominvoke permission, following the existingfromFunctionAttributespattern inApiStack.ts.
Entry points Compute already bundles:
| Lambda | Source |
|---|---|
| Authorizer | backend/auth/src/authorizer.ts |
| Document | backend/document/src/lambda-handler.ts |
| Admin | backend/admin/src/lambda-handler.ts |
| Feedback orchestrator | backend/feedback/src/lambda-handler.ts |
| Notification | backend/notification/src/lambda-handler.ts |
| Feedback Engine | Python container from ECR {prefix}-feedback-engine:bootstrap |
Layout under frontend/src/:
| Path | Role |
|---|---|
pages/ |
Route screens for student, department admin, and system admin |
features/ |
Feature UI: auth, dashboard, editor, debrief, ask-wren, notifications, and so on |
components/ui/ |
shadcn/Radix primitives |
services/ |
Shared API client; do not fetch around it |
app/ |
Providers |
config/env.ts |
VITE_* config |
- Create a page under
frontend/src/pages/and export it fromfrontend/src/pages/index.tsor the existing barrel. - Register the route in
frontend/src/App.tsx, wrapped withProtectedRouteand the right layout.
// frontend/src/App.tsx
<Route element={<ProtectedRoute allow={['TechnicalAdmin']} />}>
<Route element={<SystemAdminLayout />}>
<Route path="/system-admin/new-feature" element={<NewFeaturePage />} />
</Route>
</Route>Layouts by area: StudentLayout for students, DepartmentAdminLayout for department admins, and SystemAdminLayout for Technical Admins. The document editor is a full-viewport route (/dashboard/document/:documentId) without the student shell.
cd frontend
cp .env.example .env.local
# VITE_USE_LIVE_API=false → mock, no login
npm run devDo not in the frontend:
- Do not hardcode product names; use
appConfig.appName. - Do not introduce raw design values (hex colors, raw
px); use tokens. - Do not bypass the shared API client.
There is no shared marketing Footer.tsx. Landing copy lives in frontend/src/pages/users/LandingPage.tsx.
The AI disclaimer must stay in one component: frontend/src/components/ui/AiDisclaimerNotice.tsx (AI_DISCLAIMER_DEFAULT_MESSAGE). Pass message= only for a shorter variant, for example "Ask WREN". Do not duplicate the legal paragraph on the dashboard or debrief modal.
The repository license is MIT (the license field in the root package.json and the README License section). Update both files if the distribution terms change.
Debrief and Ask WREN call Amazon Bedrock. The model id is configuration, not a hardcoded string in orchestration logic.
Recommended: change the model through the Technical Admin configuration/API when possible. The other mechanisms exist for deployment and recovery paths.
| Mechanism | Path / API |
|---|---|
| CDK default | DEFAULT_BEDROCK_MODEL_ID in cdk/lib/config.ts (us.anthropic.claude-sonnet-4-6) |
| Synth override | npx cdk deploy … -c bedrockModelId=us.anthropic.claude-… |
| Runtime SSM | /{stackPrefix}/config/bedrock-model-id |
| Admin UI / API | GET/PUT /v1/admin/config/model (Technical Admin). modelId must be on the allowed inference-profile list. |
CLI:
aws ssm put-parameter \
--name "/wren-dev/config/bedrock-model-id" \
--value "us.anthropic.claude-sonnet-4-6" \
--type String \
--overwrite \
--region <your-data-plane-region>When changing the model:
- Prefer cross-region inference profile ids when your account uses them. Enable the model in the data-plane Region, and in any other Regions the profile requires.
- Treat the profile geography as a residency decision, not only an availability setting. A
us.*profile may route AI Debrief and Ask WREN prompts, draft content or excerpts, conversation context, and responses to US AWS Regions even when the caller and persistent stores are inca-central-1. - Compute IAM already allows
bedrock:InvokeModeloninference-profile/us.anthropic.*,us.meta.*, and the corresponding foundation-model ARNs (crisBedrockInvokeResourcesinComputeStack.ts).
The Feedback Engine also receives BEDROCK_MODEL_ID at deploy time, but runtime code resolves the model in this precedence order:
platform_usage_policy.bedrock_model_idin the database- SSM parameter
/{stackPrefix}/config/bedrock-model-id - The
BEDROCK_MODEL_IDenvironment variable set at deploy time - The CDK deployment default
After a model change, confirm the resolved model id in the Feedback Engine CloudWatch logs (/aws/lambda/wren-dev-feedback-engine) before treating the change as complete. Admin prompt fields use a separate Bedrock Guardrail (PROMPT_ATTACK) from the Config stack; see bedrock-guardrails.md.
Push a new Feedback Engine image if Python invocation code changes. SSM-only model swaps do not require a new image, since the engine reads SSM at runtime.
Lifetime caps are stored in Postgres (platform_usage_policy) and enforced in the Admin, Document, and Feedback paths. Changing them does not require a code deploy.
| Limit | Typical meaning |
|---|---|
messageLimit |
Ask WREN / user messages |
debriefGenerationLimit |
AI Debrief jobs |
documentUploadLimit |
Uploads |
draftsPerDocumentLimit |
Draft versions per document |
The Ask WREN messageLimit is a durable, per-document lifetime cap: the counter lives in Postgres (document.ask_message_count, incremented by reserveAskMessage), not on the TTL'd DynamoDB conversation item, so it cannot be reset by letting a session expire.
- Technical Admin UI:
SystemUsageLimitsForm(frontend/src/features/system-admin/components/SystemUsageLimitsForm.tsx) - API:
GET/PUT /v1/admin/config/usage-limits - Overrides:
POST /v1/overridesgrants extra actions for one student and document type, with an expiry.
A 429 with USAGE_LIMIT_REACHED means a cap was hit. See the API guide.
Migrations are forward-only, numbered .sql files in backend/database/migrations/. They are bundled into the Database stack's migration Lambda and applied on deploy, tracked in schema_migrations.
The schema currently ships as a single consolidated baseline, 0001_baseline.sql. While the project is still pre-release, schema changes are folded back into that baseline rather than added as separate numbered files, so it stays the one source of the current schema.
-- backend/database/migrations/0001_baseline.sql — add to the baseline
CREATE TABLE IF NOT EXISTS my_feature (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);Once the baseline has been applied to a shared or production environment, stop editing it. From that point, schema changes must be new forward-only files (
0002_…,0003_…); never edit an already-applied migration.
Invariants, enforced in SQL and backend/database/schema.test.ts:
- Drafts are immutable. There is no
UPDATE; a new version is a new row. - There is no
replacement_prosecolumn on feedback. activity_eventhas no foreign key to documents, so it survives a document delete.
Apply migrations by redeploying Database; MigrationRunner copies backend/database/migrations into the Lambda. For a one-off against RDS Proxy, use psql with sslmode=require (see the migrations README).
If you add a new app DB role, update cdk/lib/appDbRoles.ts, grant it in SQL, and let the password-sync custom resource run.
Do not in the database layer:
- Do not edit an applied migration.
- Do not modify immutable draft rows.
- Do not bypass the migration runner for normal schema changes.
Writers upload .docx or .pdf, or paste into the editor.
Flow:
POST /v1/uploads/presignreturns a 15-minute pre-signed URL with KMS fields and a content-type lock; the browser POSTs directly to S3.POST /v1/documentsorPOST /v1/documents/{id}/draftswithsource.kind: fileandkey.- The Document Lambda extracts Word content directly. PDF work is queued to the ingest worker, which routes all PDFs through Amazon Textract (
AnalyzeDocumentfor single-page or image input; asyncStartDocumentAnalysisfor multi-page PDFs in S3). There is no separate digital-text path today — the Tesseract/LiteParse engines were removed, andresolveOcrEnginealways returnstextract. - Poll
GET /v1/documents/{id}/ingest/statusforqueued,processing,completed, orfailed.
Key files:
backend/document/src/lambda-handler.ts— HTTP and presignbackend/document/src/ingestWorker.ts— async ingestbackend/document/src/textractOcr.tsandpdfToTipTap.ts— extraction- S3 lifecycle: tagged orphan uploads expire after 1 day (
StorageResources.ts)
To support another file type, extend the content-type allow-lists in the Document Lambda and the presign conditions, and add tests. Keep size limits aligned with the usage policy.
The Feedback Engine is not a RAG system, by decision. It compares drafts and generates Socratic questions.
Reference grounding is full-text. handler.load_reference_material reads every UTF-8 file under document-types/{documentTypeId}/ in the reference bucket, and the prompt assembler takes them in S3 key order up to 4,000 characters per file and 12,000 in total (wren_engine/debrief_budget.py). To control which files survive the cap, prefix file names (01_, 02_, …). When the cap cuts anything the engine logs a warning and emits WREN/FeedbackEngine EMF metrics (ReferenceCapApplied, ReferenceFilesTruncated, ReferenceFilesDropped, ReferenceCharsOmitted). If those fire regularly for a Document Type, revisit the decision (see design.md "Reference grounding (full-text)").
To change coaching behavior:
| Concern | Where |
|---|---|
| System and type prompts | Department Admin type config (typePromptText); save-time no-ghostwriting and Bedrock prompt-attack checks |
| Prompt assembly and untrusted student block | backend/engine/python/wren_engine/prompt_assembly.py |
| Output allow-list (no rewrites) | backend/engine/python/wren_engine/output_guardrail.py |
| Diff, anchors, progress | backend/engine/python/ (see engine README) |
| Job enqueue and REST | backend/feedback/src/lambda-handler.ts |
| Queue and timeout | ComputeStack.ts: engine timeout 15 minutes, SQS visibility 16 minutes, batchSize: 1 |
After Python engine changes:
- Build and push the image to ECR
{stackPrefix}-feedback-enginetagbootstrap(see the deployment guide). - Update the Lambda through the pipeline or
update-function-code. - Confirm Bedrock model access and check CloudWatch
/aws/lambda/wren-dev-feedback-engine.
Realtime completion uses AppSync onDebriefReady. The UI falls back to GET .../feedback/status if VITE_APPSYNC_URL is unset.
Do not in the Feedback Engine:
- Do not generate replacement prose.
- Do not treat student-authored content as trusted instructions.
- Do not add retrieval unless the reference-cap metrics show corpora have outgrown the prompt budget.
Domain DTOs live in types/. Frontend and Lambdas must import from @wren/types rather than duplicating shapes.
When you add an entity:
- Add a strict Zod schema in
types/src/. - Export it from
types/src/index.ts. - Add the new type to the compile-time
LacksReplacementProseguards inindex.ts. - Run
npm run -w @wren/types typecheckandnpx jest types/src.
Repo root (workspaces):
npm ciCDK (from cdk/ after root npm ci). These commands are examples for an already-initialized development environment. For dependency ordering, first deployment, or infrastructure changes, use the deployment guide.
Important:
npx cdk deploy --allrequires the Feedback Engine bootstrap image to already exist in ECR.
npm ci
npx cdk synth
npx cdk deploy wren-dev-Compute wren-dev-Api
# or, once the ECR bootstrap image exists:
npx cdk deploy --allFrontend:
cd frontend
npm ci
npm run dev # Vite
npm run build # tsc -b && vite build
npm run lint
npm test # vitestPython engine local tests: see backend/engine/python/README.md.
For the full first-time deployment order, Bedrock and ECR setup, Amplify, the first-admin flow, and all advanced deployment variables, see the deployment guide. Context keys are documented in the CDK README.
Storage uses a customer-managed KMS key with rotation enabled (StorageResources.ts), covering the documents and reference S3 buckets and the DynamoDB conversation table. RDS and Secrets Manager use AWS-managed keys unless you explicitly configure a CMK. Presigned uploads include x-amz-server-side-encryption and the key id when DOCUMENTS_KMS_KEY_ARN is set; keep this in sync with the bucket key.
For CDK property names, IAM grants, and the step-by-step CMK changes for each store, see AWS Managed Keys.
Do not turn off
enforceSSLorBlockPublicAccess.BLOCK_ALLon the buckets.
CORS. API Gateway OPTIONS and Lambda responses echo
Access-Control-Allow-Originfor local Vite, the configuredcustomDomain(pluswww), extra-c corsAllowedOrigins=...values, and Amplify default hosts — not*. The documents bucket CORS list is the same exact origins plushttps://*.amplifyapp.com(S3 cannot use a regex). The reference bucket has no CORS: admin uploads go through the Admin Lambda, not a browser presign.
- Lambda timeout: document ingest 3 minutes, Feedback Engine 15 minutes, authorizer 20 seconds. Raise
timeoutormemorySizeinComputeStack.ts. SQS visibility must stay greater than the consumer timeout. - VPC / DB: Lambdas use RDS Proxy (
DB_PROXY_HOST) and per-role secrets (wren_document,wren_admin, and so on). Check Secrets Manager{prefix}/database/...and the security groups; the proxy allows the VPC CIDR on port 5432. - 401: expired Cognito token or missing
Authorization. Refresh the session. Authorizer log group:/aws/lambda/wren-dev-authorizer. - 403: role, capability, or department scope. Inspect
GET /v1/auth/me. - Debrief never finishes: check Feedback Engine and Bedrock model access, the DLQ alarm, and queue depth. Poll
/feedback/statusif AppSync is unset. - CORS / edge protection: API Gateway responses include CORS headers. WAF rules apply at the CloudFront edge; do not assume the direct API Gateway URL receives the same edge protections.
- Theme lint failures: move hex or
pxvalues intofrontend/src/styles/tokens/andtheme.css. - Display-name CI: do not put
"WREN"as a quoted product string in new source; useappConfigor the CDKappName. - Logging: CloudWatch is the primary debug surface. Do not log JWTs or full draft bodies.
API Gateway throttling: 100 req/s, 200 burst. WAF rate limit (CloudFront): 2000 requests per 5 minutes per IP.
This guide covers how to modify WREN. For how to deploy and operate it, see:
- Deployment guide — first-time deployment, first-admin flow, advanced deployment configuration, and monitoring and operational ownership.
- AWS managed keys — full encryption architecture and CMK changes.
- Security overview — security model and controls.