thinkwork-cli 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +18 -2
  3. package/dist/cli.js +3004 -215
  4. package/dist/terraform/examples/greenfield/main.tf +325 -19
  5. package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
  6. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
  7. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
  8. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
  9. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
  10. package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
  11. package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
  12. package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
  13. package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
  14. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
  15. package/dist/terraform/modules/app/agentcore-runtime/main.tf +204 -4
  16. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
  17. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
  18. package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
  19. package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
  20. package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
  21. package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
  22. package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
  23. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
  24. package/dist/terraform/modules/app/lambda-api/handlers.tf +1557 -42
  25. package/dist/terraform/modules/app/lambda-api/main.tf +299 -15
  26. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
  27. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
  28. package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
  29. package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
  30. package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
  31. package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
  32. package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
  33. package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
  34. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
  35. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
  36. package/dist/terraform/modules/app/static-site/main.tf +146 -5
  37. package/dist/terraform/modules/app/www-dns/main.tf +118 -15
  38. package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
  39. package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
  40. package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
  41. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
  42. package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
  43. package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
  44. package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
  45. package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
  46. package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
  47. package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
  48. package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
  49. package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
  50. package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
  51. package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
  52. package/dist/terraform/modules/foundation/cognito/variables.tf +5 -2
  53. package/dist/terraform/modules/thinkwork/main.tf +439 -21
  54. package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
  55. package/dist/terraform/modules/thinkwork/variables.tf +165 -6
  56. package/dist/terraform/schema.graphql +45 -0
  57. package/package.json +15 -14
@@ -111,6 +111,12 @@ variable "lambda_zips_dir" {
111
111
  default = ""
112
112
  }
113
113
 
114
+ variable "enable_workspace_orchestration" {
115
+ description = "Enable S3 EventBridge/SQS routing and the workspace event dispatcher for folder-native workspace orchestration."
116
+ type = bool
117
+ default = false
118
+ }
119
+
114
120
  variable "api_auth_secret" {
115
121
  description = "Shared secret for inter-service API authentication"
116
122
  type = string
@@ -136,26 +142,203 @@ variable "ses_inbound_domain" {
136
142
  default = ""
137
143
  }
138
144
 
139
- variable "lastmile_tasks_api_url" {
145
+ variable "stripe_price_ids_json" {
146
+ description = "JSON object mapping internal plan names to Stripe price IDs for this stage, e.g. {\"starter\":\"price_...\",\"team\":\"price_...\"}. Non-secret; per-stage. Exposed to Lambdas as STRIPE_PRICE_IDS_JSON env var. The secret keys themselves live in AWS Secrets Manager at thinkwork/<stage>/stripe/api-credentials — never in tfvars."
147
+ type = string
148
+ default = "{}"
149
+ }
150
+
151
+ variable "wiki_compile_model_id" {
152
+ description = <<-EOT
153
+ Bedrock model id the wiki-compile Lambda uses for the leaf planner,
154
+ aggregation planner, and section writer. Any Converse-compatible
155
+ model works; change without a code deploy.
156
+
157
+ Default: openai.gpt-oss-120b-1:0 (strong output quality at a lower
158
+ per-minute throttle risk than Claude Haiku 4.5 on shared dev
159
+ accounts). Swap to us.anthropic.claude-haiku-4-5-20251001-v1:0 for
160
+ Claude, or amazon.nova-micro-v1:0 for a low-cost spike.
161
+ EOT
162
+ type = string
163
+ default = "openai.gpt-oss-120b-1:0"
164
+ }
165
+
166
+ variable "company_brain_source_agent_model_id" {
167
+ description = <<-EOT
168
+ Bedrock model id the GraphQL Company Brain source-agent runtime uses
169
+ for JSON tool/action turns. Defaults to Claude Haiku 4.5 for reliable
170
+ action JSON while wiki compile can stay on gpt-oss for throughput.
171
+ EOT
172
+ type = string
173
+ default = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
174
+ }
175
+
176
+ variable "wiki_aggregation_pass_enabled" {
177
+ description = <<-EOT
178
+ Feature flag for the wiki aggregation pass — the second LLM call
179
+ per compile job that builds parent/hub rollup sections and promotes
180
+ dense sections into their own topic pages.
181
+
182
+ Accepts a string so the Lambda reads the env var verbatim; must be
183
+ "true" / "1" / "yes" to enable. Set to "false" to stop the pipeline
184
+ after the leaf pass (no rollups, no promotions).
185
+ EOT
186
+ type = string
187
+ default = "true"
188
+ }
189
+
190
+ variable "google_places_api_key" {
191
+ description = <<-EOT
192
+ Google Places API (New) key used by wiki-compile to enrich POI records
193
+ with city/state/country hierarchy during compile. When empty, compile
194
+ gracefully degrades to metadata-only place rows (no hierarchy, no
195
+ backing pages), so this is opt-in. Stored as a SecureString at
196
+ /thinkwork/<stage>/google-places/api-key — see
197
+ terraform/modules/app/lambda-api/handlers.tf for the SSM resource.
198
+
199
+ The parameter's value has lifecycle.ignore_changes set, so you can
200
+ rotate via `aws ssm put-parameter --overwrite` without terraform
201
+ fighting you on the next apply.
202
+ EOT
203
+ type = string
204
+ default = ""
205
+ sensitive = true
206
+ }
207
+
208
+ variable "mapbox_public_token" {
140
209
  description = <<-EOT
141
- OPTIONAL fallback base URL for the LastMile Tasks REST API.
210
+ Mapbox public pk.* token consumed by apps/computer's MapView primitive
211
+ (in @thinkwork/computer-stdlib) for inline map tile rendering inside
212
+ generated applets. Flows through to scripts/build-computer.sh →
213
+ apps/computer/.env.production as VITE_MAPBOX_PUBLIC_TOKEN.
214
+
215
+ Mapbox tokens are designed to ship in public bundles; URL allowlist
216
+ on the Mapbox dashboard is the security boundary. Restrict the token
217
+ to the deployed `computer.<apex>` host (and any dev hosts).
218
+
219
+ Empty string is acceptable: MapView falls back to OpenStreetMap tiles
220
+ when the build-time env var is unset, so dev environments without an
221
+ operator-provisioned token still render maps.
222
+ EOT
223
+ type = string
224
+ default = ""
225
+ sensitive = true
226
+ }
142
227
 
143
- Prefer setting the URL per-tenant via the admin Connectors → LastMile
144
- page (stored in webhooks.config.baseUrl); that value takes precedence.
145
- This variable only fires when the per-tenant config is empty, and is
146
- mainly useful for single-tenant dev stacks and bootstrap scenarios.
228
+ variable "nova_act_api_key" {
229
+ description = <<-EOT
230
+ Nova Act API key used by the Strands Browser Automation tool. When empty,
231
+ Terraform creates /thinkwork/<stage>/agentcore/nova-act-api-key with a
232
+ placeholder value; populate or rotate the real key with:
233
+ aws ssm put-parameter --overwrite --name /thinkwork/<stage>/agentcore/nova-act-api-key --type SecureString --value <KEY>
147
234
 
148
- Leave blank (default) unless you specifically need the env-var
149
- fallback. Example: https://api-dev.lastmile-tei.com.
235
+ The parameter's value has lifecycle.ignore_changes set, so operator
236
+ rotation sticks across applies.
150
237
  EOT
151
238
  type = string
152
239
  default = ""
240
+ sensitive = true
241
+ }
242
+
243
+ variable "wiki_deterministic_linking_enabled" {
244
+ description = <<-EOT
245
+ Feature flag for deterministic compile-time link emission:
246
+ - city/journal parent references from parent-expander candidates
247
+ - entity↔entity co-mention edges via wiki_section_sources
248
+
249
+ Accepts a string so the Lambda reads the env var verbatim; must be
250
+ "true" / "1" / "yes" to enable. Rollback is a targeted DELETE:
251
+ `DELETE FROM wiki_page_links WHERE context LIKE 'deterministic:%' OR
252
+ context LIKE 'co_mention:%'` — provenance is preserved on every row.
253
+ EOT
254
+ type = string
255
+ default = "true"
256
+ }
257
+
258
+ variable "agentcore_code_interpreter_id" {
259
+ description = "AgentCore Code Interpreter id used by routine-task-python for SFN python recipe states."
260
+ type = string
261
+ default = ""
262
+ }
263
+
264
+ variable "mcp_custom_domain" {
265
+ description = <<-EOT
266
+ MCP custom domain (e.g. "mcp.thinkwork.ai"). Empty disables the
267
+ custom-domain setup — the MCP endpoint stays reachable at the API
268
+ Gateway execute-api URL. When set, an ACM cert is created on the
269
+ first apply; flip `mcp_custom_domain_ready = true` on a second
270
+ apply after DNS validation completes. See
271
+ docs/solutions/patterns/mcp-custom-domain-setup-2026-04-23.md.
272
+ EOT
273
+ type = string
274
+ default = ""
275
+ }
276
+
277
+ variable "mcp_custom_domain_ready" {
278
+ description = <<-EOT
279
+ Second-apply gate for the MCP custom domain. Stays false on the
280
+ first apply (cert creation + DNS validation). Flip to true after
281
+ ACM shows the cert as ISSUED so the second apply can create the
282
+ API Gateway custom domain + mapping. Ignored when
283
+ mcp_custom_domain is empty.
284
+ EOT
285
+ type = bool
286
+ default = false
153
287
  }
154
288
 
155
289
  locals {
156
290
  www_dns_enabled = var.www_domain != "" && var.cloudflare_zone_id != ""
157
291
  docs_domain = var.www_domain != "" ? "docs.${var.www_domain}" : ""
158
292
  admin_domain = var.www_domain != "" ? "admin.${var.www_domain}" : ""
293
+ computer_domain = var.www_domain != "" ? "computer.${var.www_domain}" : ""
294
+ sandbox_domain = var.www_domain != "" ? "sandbox.${var.www_domain}" : ""
295
+ api_domain = var.www_domain != "" ? "api.${var.www_domain}" : ""
296
+ }
297
+
298
+ resource "aws_acm_certificate" "computer_sandbox" {
299
+ count = local.www_dns_enabled ? 1 : 0
300
+
301
+ domain_name = local.sandbox_domain
302
+ validation_method = "DNS"
303
+
304
+ lifecycle {
305
+ create_before_destroy = true
306
+ }
307
+
308
+ tags = {
309
+ Name = "thinkwork-${var.stage}-computer-sandbox"
310
+ }
311
+ }
312
+
313
+ resource "cloudflare_record" "computer_sandbox_acm_validation" {
314
+ for_each = {
315
+ for dvo in flatten([
316
+ for cert in aws_acm_certificate.computer_sandbox : tolist(cert.domain_validation_options)
317
+ ]) : dvo.domain_name => {
318
+ name = dvo.resource_record_name
319
+ value = dvo.resource_record_value
320
+ type = dvo.resource_record_type
321
+ }
322
+ }
323
+
324
+ zone_id = var.cloudflare_zone_id
325
+ name = trimsuffix(each.value.name, ".")
326
+ content = trimsuffix(each.value.value, ".")
327
+ type = each.value.type
328
+ ttl = 60
329
+ proxied = false
330
+ comment = "ACM DNS validation for ${each.key}"
331
+
332
+ allow_overwrite = true
333
+ }
334
+
335
+ resource "aws_acm_certificate_validation" "computer_sandbox" {
336
+ count = local.www_dns_enabled ? 1 : 0
337
+
338
+ certificate_arn = aws_acm_certificate.computer_sandbox[0].arn
339
+ validation_record_fqdns = [
340
+ for record in cloudflare_record.computer_sandbox_acm_validation : record.hostname
341
+ ]
159
342
  }
160
343
 
161
344
  module "thinkwork" {
@@ -165,14 +348,15 @@ module "thinkwork" {
165
348
  region = var.region
166
349
  account_id = var.account_id
167
350
 
168
- db_password = var.db_password
169
- database_engine = var.database_engine
170
- enable_hindsight = var.enable_hindsight
171
- google_oauth_client_id = var.google_oauth_client_id
172
- google_oauth_client_secret = var.google_oauth_client_secret
173
- pre_signup_lambda_zip = var.pre_signup_lambda_zip
174
- lambda_zips_dir = var.lambda_zips_dir
175
- api_auth_secret = var.api_auth_secret
351
+ db_password = var.db_password
352
+ database_engine = var.database_engine
353
+ enable_hindsight = var.enable_hindsight
354
+ google_oauth_client_id = var.google_oauth_client_id
355
+ google_oauth_client_secret = var.google_oauth_client_secret
356
+ pre_signup_lambda_zip = var.pre_signup_lambda_zip
357
+ lambda_zips_dir = var.lambda_zips_dir
358
+ enable_workspace_orchestration = var.enable_workspace_orchestration
359
+ api_auth_secret = var.api_auth_secret
176
360
 
177
361
  # Public website custom domain (optional — wired only when www_domain is set)
178
362
  www_domain = var.www_domain
@@ -188,12 +372,46 @@ module "thinkwork" {
188
372
  admin_domain = local.www_dns_enabled ? local.admin_domain : ""
189
373
  admin_certificate_arn = local.www_dns_enabled ? module.www_dns[0].certificate_arn : ""
190
374
 
375
+ # Computer SPA custom domain (derived from www_domain — computer.<apex>).
376
+ # Same ACM cert covers it via the include_computer SAN gate on www_dns.
377
+ computer_domain = local.www_dns_enabled ? local.computer_domain : ""
378
+ computer_certificate_arn = local.www_dns_enabled ? module.www_dns[0].certificate_arn : ""
379
+
380
+ # Computer iframe sandbox (derived from www_domain — sandbox.<apex>).
381
+ # Uses its own ACM certificate so sandbox bootstrap/rotation cannot replace
382
+ # the shared apex/www/docs/admin/computer/api certificate.
383
+ computer_sandbox_domain = local.www_dns_enabled ? local.sandbox_domain : ""
384
+ computer_sandbox_certificate_arn = local.www_dns_enabled ? aws_acm_certificate_validation.computer_sandbox[0].certificate_arn : ""
385
+ computer_sandbox_allowed_parent_origins = local.www_dns_enabled ? "https://${local.computer_domain},https://${local.admin_domain}" : ""
386
+
191
387
  # SES inbound email subdomain (delegated Route53 subzone).
192
388
  ses_inbound_domain = var.ses_inbound_domain
193
389
 
194
- # LastMile Tasks REST API base URL feature-flags the outbound task
195
- # sync. Empty string keeps mobile-created tasks in sync_status='local'.
196
- lastmile_tasks_api_url = var.lastmile_tasks_api_url
390
+ # Wiki compile Lambda config. Pinned so unrelated terraform applies
391
+ # don't wipe the Bedrock model or the aggregation flag back to
392
+ # whatever the Lambda env defaults to.
393
+ wiki_compile_model_id = var.wiki_compile_model_id
394
+ company_brain_source_agent_model_id = var.company_brain_source_agent_model_id
395
+ wiki_aggregation_pass_enabled = var.wiki_aggregation_pass_enabled
396
+ wiki_deterministic_linking_enabled = var.wiki_deterministic_linking_enabled
397
+ google_places_api_key = var.google_places_api_key
398
+ nova_act_api_key = var.nova_act_api_key
399
+ agentcore_code_interpreter_id = var.agentcore_code_interpreter_id
400
+
401
+ # Mapbox public token for apps/computer MapView primitive. Flows through
402
+ # to scripts/build-computer.sh → VITE_MAPBOX_PUBLIC_TOKEN.
403
+ mapbox_public_token = var.mapbox_public_token
404
+
405
+ # Stripe billing — internal-plan → price-id map (per-stage, non-secret).
406
+ stripe_price_ids_json = var.stripe_price_ids_json
407
+
408
+ # MCP custom domain (e.g. mcp.thinkwork.ai). Two-apply flow: the first
409
+ # apply creates the ACM cert (mcp_custom_domain_ready=false), the
410
+ # second apply attaches the domain + API mapping after DNS validation
411
+ # (mcp_custom_domain_ready=true). Runbook:
412
+ # docs/solutions/patterns/mcp-custom-domain-setup-2026-04-23.md
413
+ mcp_custom_domain = var.mcp_custom_domain
414
+ mcp_custom_domain_ready = var.mcp_custom_domain_ready
197
415
 
198
416
  # Greenfield: create everything (all defaults are true)
199
417
  }
@@ -221,6 +439,27 @@ module "www_dns" {
221
439
  # Admin: same cycle-avoidance pattern.
222
440
  include_admin = true
223
441
  admin_cloudfront_domain_name = module.thinkwork.admin_distribution_domain
442
+
443
+ # Computer: same cycle-avoidance pattern as docs/admin. Phase one of the
444
+ # two-phase first apply (#971) used a static empty string here to dodge an
445
+ # "Invalid count argument" plan error while module.thinkwork.computer_site
446
+ # didn't yet exist in state. Now that the distribution is created and its
447
+ # domain is known, this can read the real output — `!= ""` resolves to a
448
+ # static true and the Cloudflare CNAME for computer.<domain> gets created
449
+ # on this apply.
450
+ include_computer = true
451
+ computer_cloudfront_domain_name = module.thinkwork.computer_distribution_domain
452
+
453
+ # Computer iframe sandbox: same cycle-avoidance pattern as computer.
454
+ include_computer_sandbox = true
455
+ computer_sandbox_cloudfront_domain_name = module.thinkwork.computer_sandbox_distribution_domain
456
+
457
+ # API custom domain (api.<apex>). Same cycle-avoidance — the ACM cert SAN
458
+ # list is gated on include_api (a plain bool), while api_gateway_id (which
459
+ # the cert doesn't depend on) is read at record-creation time.
460
+ include_api = true
461
+ api_gateway_id = module.thinkwork.api_id
462
+ api_gateway_stage_name = "$default"
224
463
  }
225
464
 
226
465
  ################################################################################
@@ -257,6 +496,11 @@ output "api_endpoint" {
257
496
  value = module.thinkwork.api_endpoint
258
497
  }
259
498
 
499
+ output "api_domain" {
500
+ description = "Custom domain for the HTTP API (e.g. api.thinkwork.ai). Empty string when www_domain/cloudflare_zone_id aren't configured. Read by scripts/build-www.sh to set PUBLIC_API_URL at build time."
501
+ value = local.www_dns_enabled ? local.api_domain : ""
502
+ }
503
+
260
504
  output "appsync_api_url" {
261
505
  description = "AppSync GraphQL URL"
262
506
  value = module.thinkwork.appsync_api_url
@@ -273,6 +517,12 @@ output "appsync_api_key" {
273
517
  sensitive = true
274
518
  }
275
519
 
520
+ output "mapbox_public_token" {
521
+ description = "Mapbox public token used by apps/computer MapView. Read by scripts/build-computer.sh to inline VITE_MAPBOX_PUBLIC_TOKEN at build time; empty string lets MapView fall back to OpenStreetMap tiles."
522
+ value = module.thinkwork.mapbox_public_token
523
+ sensitive = true
524
+ }
525
+
276
526
  output "auth_domain" {
277
527
  description = "Cognito hosted UI domain"
278
528
  value = module.thinkwork.auth_domain
@@ -348,6 +598,41 @@ output "admin_bucket_name" {
348
598
  value = module.thinkwork.admin_bucket_name
349
599
  }
350
600
 
601
+ output "computer_url" {
602
+ description = "Computer app URL"
603
+ value = local.www_dns_enabled ? "https://${local.computer_domain}" : "https://${module.thinkwork.computer_distribution_domain}"
604
+ }
605
+
606
+ output "computer_distribution_id" {
607
+ description = "CloudFront distribution ID for computer (for cache invalidation)"
608
+ value = module.thinkwork.computer_distribution_id
609
+ }
610
+
611
+ output "computer_bucket_name" {
612
+ description = "S3 bucket for computer app assets"
613
+ value = module.thinkwork.computer_bucket_name
614
+ }
615
+
616
+ output "computer_sandbox_distribution_id" {
617
+ description = "CloudFront distribution ID for the Computer iframe sandbox (for cache invalidation)"
618
+ value = module.thinkwork.computer_sandbox_distribution_id
619
+ }
620
+
621
+ output "computer_sandbox_bucket_name" {
622
+ description = "S3 bucket for the Computer iframe sandbox shell assets"
623
+ value = module.thinkwork.computer_sandbox_bucket_name
624
+ }
625
+
626
+ output "computer_sandbox_url" {
627
+ description = "Computer iframe sandbox URL"
628
+ value = module.thinkwork.computer_sandbox_url
629
+ }
630
+
631
+ output "computer_sandbox_allowed_parent_origins" {
632
+ description = "Trusted parent origins for the Computer iframe sandbox"
633
+ value = module.thinkwork.computer_sandbox_allowed_parent_origins
634
+ }
635
+
351
636
  output "docs_url" {
352
637
  description = "Docs site URL"
353
638
  value = local.www_dns_enabled ? "https://${local.docs_domain}" : "https://${module.thinkwork.docs_distribution_domain}"
@@ -397,3 +682,24 @@ output "ses_inbound_mx_target" {
397
682
  description = "MX target host for the email subdomain. Already written into the subzone by Terraform — informational."
398
683
  value = module.thinkwork.ses_inbound_mx_target
399
684
  }
685
+
686
+ # MCP custom domain — consumed by `pnpm cf:sync-mcp`.
687
+ output "mcp_custom_domain" {
688
+ description = "Configured MCP custom domain (e.g., mcp.thinkwork.ai), or empty when disabled."
689
+ value = module.thinkwork.mcp_custom_domain
690
+ }
691
+
692
+ output "mcp_custom_domain_cert_arn" {
693
+ description = "ACM cert ARN for the MCP custom domain. Pass to `pnpm cf:sync-mcp --cert-arn` in direct-args mode."
694
+ value = module.thinkwork.mcp_custom_domain_cert_arn
695
+ }
696
+
697
+ output "mcp_custom_domain_validation" {
698
+ description = "DNS validation records to add to Cloudflare for ACM to issue the cert. Each record: { name, type, value }."
699
+ value = module.thinkwork.mcp_custom_domain_validation
700
+ }
701
+
702
+ output "mcp_custom_domain_target" {
703
+ description = "Regional target for the final mcp CNAME — only populated on the second apply after mcp_custom_domain_ready=true. { target_domain_name, hosted_zone_id } or null."
704
+ value = module.thinkwork.mcp_custom_domain_target
705
+ }
@@ -30,6 +30,20 @@ db_password = "CHANGE_ME_strong_password_here"
30
30
  # Pre-signup Lambda (optional — leave empty if not using custom pre-signup logic)
31
31
  # pre_signup_lambda_zip = "./lambdas/pre-signup.zip"
32
32
 
33
+ # Google Places API key (optional — leave empty to run compile pipeline
34
+ # without live place-hierarchy enrichment; records fall back to
35
+ # metadata-only rows and no country/city backing pages are auto-created).
36
+ #
37
+ # When set, terraform creates a SSM SecureString parameter at
38
+ # /thinkwork/<stage>/google-places/api-key and the wiki-compile Lambda
39
+ # reads it on cold start. Alternative: leave blank here and set the key
40
+ # manually after apply via:
41
+ # aws ssm put-parameter --type SecureString --overwrite \
42
+ # --name /thinkwork/<stage>/google-places/api-key --value <KEY>
43
+ # (terraform's lifecycle.ignore_changes on the value keeps your manual
44
+ # value from being overwritten on future applies.)
45
+ # google_places_api_key = ""
46
+
33
47
  # Public website (apps/www) custom domain.
34
48
  # Leave www_domain empty to skip the custom domain and serve on the raw
35
49
  # CloudFront URL. When set, you must also provide cloudflare_zone_id below —
@@ -0,0 +1,61 @@
1
+ # Sandbox base image for the AgentCore Code Interpreter.
2
+ #
3
+ # Builds a Python 3.12 container with a small, pinned set of libraries
4
+ # template authors can rely on without paying the first-turn `pip install`
5
+ # tax. Installs sitecustomize.py so the stdout/stderr token redactor is
6
+ # active before any user code or framework traceback runs on cold start.
7
+ #
8
+ # Image tagging is CI-driven (see scripts/build_and_push_sandbox_base.sh).
9
+ # Tags are immutable; a bump ships as a reviewable PR.
10
+ #
11
+ # Build context is the repository root so the COPY line can reach the
12
+ # Python source under packages/agentcore-strands/agent-container-sandbox/.
13
+ #
14
+ # docker build -f terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base .
15
+
16
+ FROM python:3.12-slim AS base
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # System packages — minimal. The sandbox runs agent code, not a full distro.
20
+ # ---------------------------------------------------------------------------
21
+ RUN apt-get update && apt-get install -y --no-install-recommends \
22
+ ca-certificates \
23
+ curl \
24
+ git \
25
+ && rm -rf /var/lib/apt/lists/*
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Blessed Python libraries — pinned to explicit versions per plan R14/R24.
29
+ # Bumps happen in reviewable PRs. Runtime `pip install` is still allowed
30
+ # (per R15; T2 residual is named), but these baseline packages install once
31
+ # at image bake and don't pay the per-turn cost.
32
+ # ---------------------------------------------------------------------------
33
+ RUN pip install --no-cache-dir \
34
+ pandas==2.2.3 \
35
+ numpy==2.1.3 \
36
+ requests==2.32.3 \
37
+ boto3==1.38.0 \
38
+ httpx==0.28.1 \
39
+ pyyaml==6.0.2 \
40
+ openpyxl==3.1.5 \
41
+ python-dateutil==2.9.0.post0
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # sitecustomize.py — the R13 stdout/stderr token redactor. Python
45
+ # auto-imports any module named "sitecustomize" on interpreter startup, so
46
+ # placing it in site-packages makes the redactor load before any user code
47
+ # (and before the framework traceback on cold start). See the module
48
+ # docstring for the honestly-scoped invariant and named residual gaps.
49
+ # ---------------------------------------------------------------------------
50
+ COPY packages/agentcore-strands/agent-container-sandbox/sitecustomize.py \
51
+ /usr/local/lib/python3.12/site-packages/sitecustomize.py
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Startup assertion — fail loudly if the module isn't wired up or
55
+ # THINKWORK_SANDBOX_SCRUBBER=off slips through to prod. Runs in the same
56
+ # image smoke-test CI job that pushes the tag.
57
+ # ---------------------------------------------------------------------------
58
+ RUN python -c "import sitecustomize; assert sitecustomize.installed(), 'sitecustomize did not install its stdio wrapper on import'; print('sitecustomize: OK')"
59
+
60
+ # AgentCore Code Interpreter supplies its own entrypoint; we provide the
61
+ # libraries + scrubber only. No CMD/ENTRYPOINT override.
@@ -0,0 +1,54 @@
1
+ # `agentcore-code-interpreter` — stage-level base image substrate
2
+
3
+ Stage-scoped artifacts for the AgentCore Code Interpreter sandbox. **Per-tenant
4
+ Code Interpreter instances are created at runtime** by the `agentcore-admin`
5
+ Lambda (see `docs/adrs/per-tenant-aws-resource-fanout.md`) — this module
6
+ stops at the substrate everything else depends on.
7
+
8
+ ## What it creates
9
+
10
+ - **ECR repository** `thinkwork-{stage}-sandbox-base` — immutable tags, last 10
11
+ kept, scan-on-push.
12
+ - **Outputs** consumed by downstream modules + the provisioning Lambda:
13
+ - `ecr_repository_url` — where the blessed image lives.
14
+ - `environment_ids` + `environments` — the v1 environment catalog (`default-public`,
15
+ `internal-only`) with network modes.
16
+ - `tenant_role_trust_policy_template` / `tenant_role_inline_policy_template` —
17
+ JSON templates the provisioning Lambda substitutes `{tenant_id}` into at
18
+ `CreateRole` time (plan Unit 5).
19
+
20
+ ## Files
21
+
22
+ | File | Purpose |
23
+ |---|---|
24
+ | `main.tf` | ECR + outputs. No per-tenant resources. |
25
+ | `Dockerfile.sandbox-base` | Python 3.12 + pinned libs + `sitecustomize.py`. Build context is the **repo root** so the COPY can reach `packages/agentcore-strands/agent-container-sandbox/sitecustomize.py`. |
26
+ | `scripts/build_and_push_sandbox_base.sh` | CI builds + pushes. Not called from Terraform — image lifecycle is a reviewable-PR concern. |
27
+
28
+ The Python source `sitecustomize.py` and its pytest suite `test_sitecustomize.py`
29
+ live under `packages/agentcore-strands/agent-container-sandbox/` (colocated with
30
+ the rest of the Strands agent-container Python, per handoff P2 #10). That's
31
+ where `uv run pytest` finds them without extra config.
32
+
33
+ ## What this module does **not** do
34
+
35
+ - Create per-tenant Code Interpreter instances — that's Unit 5 (agentcore-admin Lambda).
36
+ - Build or push the Docker image — that's CI, via the shell script above.
37
+ - Grant the provisioning Lambda IAM permissions on the new resources — that's added in Unit 5 when the Lambda resource lands.
38
+
39
+ ## Bumping the image
40
+
41
+ 1. Edit `Dockerfile.sandbox-base` (pin a new pandas / add a lib / etc.).
42
+ 2. Edit `packages/agentcore-strands/agent-container-sandbox/sitecustomize.py` if the R13 invariant or its coverage gaps shift.
43
+ 3. PR. CI runs `pytest` on the scrubber plus a build-then-startup-assertion smoke test.
44
+ 4. After merge, `build_and_push_sandbox_base.sh` tags with the merge-commit SHA.
45
+
46
+ ## Named residual
47
+
48
+ The R13 invariant this module's scrubber enforces is **honestly scoped** to
49
+ Python-stdio-mediated writes + known-shape CloudWatch patterns. See the
50
+ brainstorm and the `sitecustomize.py` module docstring for the full list of
51
+ stdio-bypass classes (os.write, subprocess env dumps, C-extension direct
52
+ writes, multiprocessing workers, adversarial split-writes) that the Unit 12
53
+ pattern backstop catches partially and the v2 in-process credential proxy
54
+ addresses structurally.