thinkwork-cli 0.9.0 → 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.
- package/LICENSE +202 -0
- package/README.md +2 -2
- package/dist/cli.js +1187 -315
- package/dist/terraform/examples/greenfield/main.tf +325 -19
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
- package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
- package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
- package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
- package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
- package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +165 -0
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
- package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
- package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
- package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
- package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
- package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +1454 -43
- package/dist/terraform/modules/app/lambda-api/main.tf +221 -12
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
- package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
- package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
- package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
- package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
- package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
- package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
- package/dist/terraform/modules/app/static-site/main.tf +146 -5
- package/dist/terraform/modules/app/www-dns/main.tf +118 -15
- package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
- package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
- package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
- package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +2 -2
- package/dist/terraform/modules/thinkwork/main.tf +439 -21
- package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
- package/dist/terraform/modules/thinkwork/variables.tf +153 -2
- package/dist/terraform/schema.graphql +17 -0
- package/package.json +15 -14
|
@@ -10,7 +10,15 @@
|
|
|
10
10
|
################################################################################
|
|
11
11
|
|
|
12
12
|
locals {
|
|
13
|
-
bucket_name
|
|
13
|
+
bucket_name = var.bucket_name != "" ? var.bucket_name : "thinkwork-${var.stage}-storage"
|
|
14
|
+
backups_bucket_name = "thinkwork-${var.stage}-backups"
|
|
15
|
+
compliance_anchor_bucket_name = "thinkwork-${var.stage}-compliance-anchors"
|
|
16
|
+
compliance_exports_bucket_name = "thinkwork-${var.stage}-compliance-exports"
|
|
17
|
+
computer_task_subnet_ids = (
|
|
18
|
+
length(module.vpc.public_subnet_ids) > 0
|
|
19
|
+
? module.vpc.public_subnet_ids
|
|
20
|
+
: module.vpc.private_subnet_ids
|
|
21
|
+
)
|
|
14
22
|
|
|
15
23
|
# Hindsight is an optional add-on. Preferred toggle: var.enable_hindsight.
|
|
16
24
|
# For one release we also honor the legacy var.memory_engine == "hindsight"
|
|
@@ -76,15 +84,28 @@ module "cognito" {
|
|
|
76
84
|
google_oauth_client_secret = var.google_oauth_client_secret
|
|
77
85
|
pre_signup_lambda_zip = var.pre_signup_lambda_zip
|
|
78
86
|
|
|
87
|
+
# Single ThinkworkAdmin Cognito client serves both admin and computer SPAs.
|
|
88
|
+
# apps/computer reuses this client by design — same humans, same tenant
|
|
89
|
+
# invitations, single sign-in across both surfaces. Each origin (admin
|
|
90
|
+
# distribution, admin custom domain, computer distribution, computer custom
|
|
91
|
+
# domain, plus localhost dev for both) needs both bare and /auth/callback
|
|
92
|
+
# entries because the OAuth flow lands on /auth/callback and the SPA's
|
|
93
|
+
# post-OAuth redirect lands on the bare origin.
|
|
79
94
|
admin_callback_urls = concat(
|
|
80
95
|
var.admin_callback_urls,
|
|
81
96
|
["https://${module.admin_site.distribution_domain}", "https://${module.admin_site.distribution_domain}/auth/callback"],
|
|
82
|
-
var.admin_domain != "" ? ["https://${var.admin_domain}", "https://${var.admin_domain}/auth/callback"] : []
|
|
97
|
+
var.admin_domain != "" ? ["https://${var.admin_domain}", "https://${var.admin_domain}/auth/callback"] : [],
|
|
98
|
+
["https://${module.computer_site.distribution_domain}", "https://${module.computer_site.distribution_domain}/auth/callback"],
|
|
99
|
+
var.computer_domain != "" ? ["https://${var.computer_domain}", "https://${var.computer_domain}/auth/callback"] : [],
|
|
100
|
+
["http://localhost:5180", "http://localhost:5180/auth/callback"]
|
|
83
101
|
)
|
|
84
102
|
admin_logout_urls = concat(
|
|
85
103
|
var.admin_logout_urls,
|
|
86
104
|
["https://${module.admin_site.distribution_domain}"],
|
|
87
|
-
var.admin_domain != "" ? ["https://${var.admin_domain}"] : []
|
|
105
|
+
var.admin_domain != "" ? ["https://${var.admin_domain}"] : [],
|
|
106
|
+
["https://${module.computer_site.distribution_domain}"],
|
|
107
|
+
var.computer_domain != "" ? ["https://${var.computer_domain}"] : [],
|
|
108
|
+
["http://localhost:5180"]
|
|
88
109
|
)
|
|
89
110
|
mobile_callback_urls = var.mobile_callback_urls
|
|
90
111
|
mobile_logout_urls = var.mobile_logout_urls
|
|
@@ -107,6 +128,60 @@ module "s3" {
|
|
|
107
128
|
bucket_name = local.bucket_name
|
|
108
129
|
}
|
|
109
130
|
|
|
131
|
+
module "s3_backups" {
|
|
132
|
+
source = "../data/s3-backups-bucket"
|
|
133
|
+
|
|
134
|
+
stage = var.stage
|
|
135
|
+
bucket_name = local.backups_bucket_name
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Phase 3 U7 — WORM-protected S3 bucket for SOC2 Type 1 tamper-evident audit
|
|
139
|
+
# anchoring. Inert in this PR: the IAM role exists but no Lambda assumes it
|
|
140
|
+
# until U8a (master plan Decision #9 — inert→live seam swap). The bucket
|
|
141
|
+
# itself is fully provisioned (Object Lock enabled at create time, KMS-
|
|
142
|
+
# encrypted, lifecycle to Glacier IR at 90 days, deny-DeleteObject bucket
|
|
143
|
+
# policy). See `terraform/modules/data/compliance-audit-bucket/README.md`.
|
|
144
|
+
module "compliance_anchors" {
|
|
145
|
+
source = "../data/compliance-audit-bucket"
|
|
146
|
+
|
|
147
|
+
stage = var.stage
|
|
148
|
+
account_id = var.account_id
|
|
149
|
+
region = var.region
|
|
150
|
+
bucket_name = local.compliance_anchor_bucket_name
|
|
151
|
+
kms_key_arn = module.kms.key_arn
|
|
152
|
+
mode = var.compliance_anchor_object_lock_mode
|
|
153
|
+
retention_days = var.compliance_anchor_retention_days
|
|
154
|
+
|
|
155
|
+
# Phase 3 U8a — anchor Lambda's IAM role gets `secretsmanager:GetSecretValue`
|
|
156
|
+
# on these two compliance secrets (anchor connects as compliance_reader for
|
|
157
|
+
# SELECT, compliance_drainer for tenant_anchor_state UPDATE).
|
|
158
|
+
compliance_reader_secret_arn = module.database.compliance_reader_secret_arn
|
|
159
|
+
compliance_drainer_secret_arn = module.database.compliance_drainer_secret_arn
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Phase 3 U11.U2 — Compliance exports bucket + runner IAM role.
|
|
163
|
+
#
|
|
164
|
+
# Ephemeral S3 bucket with 7-day lifecycle expiration. NOT Object Lock
|
|
165
|
+
# — exports are derivable from compliance.audit_events; the bucket is
|
|
166
|
+
# delivery plumbing, not the system of record. The runner Lambda assumes
|
|
167
|
+
# `module.compliance_exports.runner_role_arn` and writes CSV/NDJSON
|
|
168
|
+
# artifacts under any key (no per-prefix grant needed). U11.U2 ships the
|
|
169
|
+
# function with a stub body; U11.U3 swaps in the live runner.
|
|
170
|
+
module "compliance_exports" {
|
|
171
|
+
source = "../data/compliance-exports-bucket"
|
|
172
|
+
|
|
173
|
+
stage = var.stage
|
|
174
|
+
account_id = var.account_id
|
|
175
|
+
region = var.region
|
|
176
|
+
bucket_name = local.compliance_exports_bucket_name
|
|
177
|
+
# Runner Lambda reads writer-pool DB credentials at module-load to
|
|
178
|
+
# construct the Aurora connection string for INSERT/UPDATE on
|
|
179
|
+
# compliance.export_jobs and SELECT on compliance.audit_events.
|
|
180
|
+
# Without this grant the runner throws AccessDenied at first SQS
|
|
181
|
+
# invocation (deploy run 25561658625 failed the smoke gate this way).
|
|
182
|
+
database_secret_arn = module.database.graphql_db_secret_arn
|
|
183
|
+
}
|
|
184
|
+
|
|
110
185
|
module "database" {
|
|
111
186
|
source = "../data/aurora-postgres"
|
|
112
187
|
|
|
@@ -124,6 +199,15 @@ module "database" {
|
|
|
124
199
|
|
|
125
200
|
database_name = var.database_name
|
|
126
201
|
database_engine = var.database_engine
|
|
202
|
+
|
|
203
|
+
# Enables the `aws_s3` Aurora extension and attaches an IAM role that can
|
|
204
|
+
# PutObject into the backups bucket's pre-drop/* prefix. Used by
|
|
205
|
+
# destructive migrations (e.g. U5 of the thread-detail cleanup plan) to
|
|
206
|
+
# snapshot row data before DROP TABLE. `enable_aws_s3` is the plan-time
|
|
207
|
+
# gate (the bucket's ARN is known-after-apply on greenfield, so it can't
|
|
208
|
+
# drive `count` directly); the ARN still feeds the IAM policy body.
|
|
209
|
+
backups_bucket_arn = module.s3_backups.bucket_arn
|
|
210
|
+
enable_aws_s3 = var.database_engine == "aurora-serverless"
|
|
127
211
|
}
|
|
128
212
|
|
|
129
213
|
module "bedrock_kb" {
|
|
@@ -153,6 +237,19 @@ module "appsync" {
|
|
|
153
237
|
subscription_schema = local.subscription_schema
|
|
154
238
|
}
|
|
155
239
|
|
|
240
|
+
module "computer_runtime" {
|
|
241
|
+
source = "../app/computer-runtime"
|
|
242
|
+
|
|
243
|
+
stage = var.stage
|
|
244
|
+
account_id = var.account_id
|
|
245
|
+
region = var.region
|
|
246
|
+
vpc_id = module.vpc.vpc_id
|
|
247
|
+
subnet_ids = module.vpc.private_subnet_ids
|
|
248
|
+
task_subnet_ids = local.computer_task_subnet_ids
|
|
249
|
+
assign_public_ip = length(module.vpc.public_subnet_ids) > 0
|
|
250
|
+
appsync_api_arn = module.appsync.graphql_api_arn
|
|
251
|
+
}
|
|
252
|
+
|
|
156
253
|
module "api" {
|
|
157
254
|
source = "../app/lambda-api"
|
|
158
255
|
|
|
@@ -168,33 +265,121 @@ module "api" {
|
|
|
168
265
|
graphql_db_secret_arn = module.database.graphql_db_secret_arn
|
|
169
266
|
database_name = var.database_name
|
|
170
267
|
|
|
268
|
+
# Phase 3 U4 — compliance-outbox-drainer connects as `compliance_drainer`
|
|
269
|
+
# via this dedicated secret (provisioned in U2 / PR #887, populated by
|
|
270
|
+
# the compliance-bootstrap CI step in deploy.yml).
|
|
271
|
+
compliance_drainer_secret_arn = module.database.compliance_drainer_secret_arn
|
|
272
|
+
|
|
273
|
+
# Phase 3 U7 — anchor bucket + IAM role wiring. U8a now uses these to
|
|
274
|
+
# provision the standalone anchor Lambda function + watchdog + schedules.
|
|
275
|
+
compliance_anchor_bucket_arn = module.compliance_anchors.bucket_arn
|
|
276
|
+
compliance_anchor_bucket_name = module.compliance_anchors.bucket_name
|
|
277
|
+
compliance_anchor_lambda_role_arn = module.compliance_anchors.lambda_role_arn
|
|
278
|
+
compliance_anchor_lambda_role_name = module.compliance_anchors.lambda_role_name
|
|
279
|
+
|
|
280
|
+
# Phase 3 U8b — sibling watchdog role (kms:DescribeKey only on the CMK,
|
|
281
|
+
# s3:ListBucket prefix-conditioned). The watchdog moves OFF the shared
|
|
282
|
+
# lambda role onto this dedicated role; the move is a `terraform state
|
|
283
|
+
# mv` operator step documented in the U8b plan.
|
|
284
|
+
compliance_anchor_watchdog_role_arn = module.compliance_anchors.watchdog_role_arn
|
|
285
|
+
compliance_anchor_watchdog_role_name = module.compliance_anchors.watchdog_role_name
|
|
286
|
+
|
|
287
|
+
# Phase 3 U8a — anchor Lambda runtime config. compliance_reader for
|
|
288
|
+
# least-privilege SELECT on audit_events; retention_days forwarded as
|
|
289
|
+
# the COMPLIANCE_ANCHOR_RETENTION_DAYS env var (consumed by U8b's
|
|
290
|
+
# live function; pre-plumbed in U8a per Decision #11).
|
|
291
|
+
compliance_reader_secret_arn = module.database.compliance_reader_secret_arn
|
|
292
|
+
compliance_anchor_object_lock_retention_days = var.compliance_anchor_retention_days
|
|
293
|
+
|
|
294
|
+
# Phase 3 U8b — KMS key + Object Lock mode forwarded as
|
|
295
|
+
# COMPLIANCE_ANCHOR_KMS_KEY_ARN and COMPLIANCE_ANCHOR_OBJECT_LOCK_MODE
|
|
296
|
+
# env vars on the anchor Lambda. The live `_anchor_fn_live` requires
|
|
297
|
+
# both: KMS for SSE-KMS PutObject, mode for the per-object retention
|
|
298
|
+
# override applied to anchors/.
|
|
299
|
+
compliance_anchor_kms_key_arn = module.compliance_anchors.kms_key_arn
|
|
300
|
+
compliance_anchor_object_lock_mode = module.compliance_anchors.object_lock_mode
|
|
301
|
+
|
|
302
|
+
# Phase 3 U11.U2 — exports bucket + runner role wiring. The U11.U1
|
|
303
|
+
# createComplianceExport mutation dispatches jobIds to the SQS queue
|
|
304
|
+
# provisioned inside lambda-api; the runner Lambda assumes the role
|
|
305
|
+
# below and writes CSV/NDJSON artifacts to the bucket.
|
|
306
|
+
compliance_exports_bucket_name = module.compliance_exports.bucket_name
|
|
307
|
+
compliance_exports_runner_role_arn = module.compliance_exports.runner_role_arn
|
|
308
|
+
compliance_exports_runner_role_name = module.compliance_exports.runner_role_name
|
|
309
|
+
|
|
171
310
|
bucket_name = module.s3.bucket_name
|
|
172
311
|
bucket_arn = module.s3.bucket_arn
|
|
173
312
|
|
|
174
|
-
user_pool_id
|
|
175
|
-
user_pool_arn
|
|
176
|
-
admin_client_id
|
|
177
|
-
mobile_client_id
|
|
313
|
+
user_pool_id = module.cognito.user_pool_id
|
|
314
|
+
user_pool_arn = module.cognito.user_pool_arn
|
|
315
|
+
admin_client_id = module.cognito.admin_client_id
|
|
316
|
+
mobile_client_id = module.cognito.mobile_client_id
|
|
317
|
+
cognito_auth_domain = module.cognito.auth_domain
|
|
178
318
|
|
|
179
319
|
appsync_api_url = module.appsync.graphql_api_url
|
|
180
320
|
appsync_api_key = module.appsync.graphql_api_key
|
|
181
321
|
|
|
182
322
|
kb_service_role_arn = module.bedrock_kb.kb_service_role_arn
|
|
183
323
|
|
|
184
|
-
lambda_zips_dir
|
|
185
|
-
api_auth_secret
|
|
186
|
-
db_password
|
|
187
|
-
agentcore_function_name
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
324
|
+
lambda_zips_dir = var.lambda_zips_dir
|
|
325
|
+
api_auth_secret = var.api_auth_secret
|
|
326
|
+
db_password = var.db_password
|
|
327
|
+
agentcore_function_name = module.agentcore.agentcore_function_name
|
|
328
|
+
agentcore_flue_function_name = module.agentcore_flue.agentcore_flue_function_name
|
|
329
|
+
agentcore_function_arn = module.agentcore.agentcore_function_arn
|
|
330
|
+
agentcore_flue_function_arn = module.agentcore_flue.agentcore_flue_function_arn
|
|
331
|
+
hindsight_endpoint = local.hindsight_enabled ? module.hindsight[0].hindsight_endpoint : ""
|
|
332
|
+
agentcore_memory_id = module.agentcore_memory.memory_id
|
|
333
|
+
memory_engine = local.resolved_memory_engine
|
|
334
|
+
admin_url = var.admin_domain != "" ? "https://${var.admin_domain}" : "https://${module.admin_site.distribution_domain}"
|
|
335
|
+
docs_url = "https://${module.docs_site.distribution_domain}"
|
|
336
|
+
www_url = var.www_domain != "" ? "https://${var.www_domain}" : "https://${module.www_site.distribution_domain}"
|
|
337
|
+
stripe_price_ids_json = var.stripe_price_ids_json
|
|
338
|
+
appsync_realtime_url = module.appsync.graphql_realtime_url
|
|
339
|
+
ecr_repository_url = module.agentcore.ecr_repository_url
|
|
340
|
+
job_scheduler_role_arn = module.job_triggers.job_scheduler_role_arn
|
|
341
|
+
routines_execution_role_arn = module.routines_stepfunctions.execution_role_arn
|
|
342
|
+
routines_log_group_arn = module.routines_stepfunctions.log_group_arn
|
|
343
|
+
agentcore_code_interpreter_id = var.agentcore_code_interpreter_id
|
|
344
|
+
wiki_compile_model_id = var.wiki_compile_model_id
|
|
345
|
+
company_brain_source_agent_model_id = var.company_brain_source_agent_model_id
|
|
346
|
+
wiki_aggregation_pass_enabled = var.wiki_aggregation_pass_enabled
|
|
347
|
+
wiki_deterministic_linking_enabled = var.wiki_deterministic_linking_enabled
|
|
348
|
+
google_places_api_key = var.google_places_api_key
|
|
349
|
+
enable_workspace_orchestration = var.enable_workspace_orchestration
|
|
350
|
+
computer_runtime_cluster_name = module.computer_runtime.cluster_name
|
|
351
|
+
computer_runtime_cluster_arn = module.computer_runtime.cluster_arn
|
|
352
|
+
computer_runtime_efs_file_system_id = module.computer_runtime.efs_file_system_id
|
|
353
|
+
computer_runtime_subnet_ids = module.computer_runtime.task_subnet_ids
|
|
354
|
+
computer_runtime_assign_public_ip = module.computer_runtime.assign_public_ip
|
|
355
|
+
computer_runtime_task_sg_id = module.computer_runtime.task_security_group_id
|
|
356
|
+
computer_runtime_execution_role_arn = module.computer_runtime.execution_role_arn
|
|
357
|
+
computer_runtime_task_role_arn = module.computer_runtime.task_role_arn
|
|
358
|
+
computer_runtime_log_group_name = module.computer_runtime.log_group_name
|
|
359
|
+
computer_runtime_repository_url = module.computer_runtime.repository_url
|
|
360
|
+
computer_runtime_default_cpu = module.computer_runtime.default_cpu
|
|
361
|
+
computer_runtime_default_memory = module.computer_runtime.default_memory
|
|
362
|
+
computer_runtime_manager_policy_arn = module.computer_runtime.manager_policy_arn
|
|
363
|
+
|
|
364
|
+
# workspace-files-efs sidecar: VPC-attached Lambda that reads any Computer's
|
|
365
|
+
# workspace files directly off the shared EFS (bypasses the
|
|
366
|
+
# computer_tasks queue for list/get). See plan
|
|
367
|
+
# docs/plans/2026-05-13-XXX-feat-admin-computer-efs-listing-plan.md.
|
|
368
|
+
workspace_admin_efs_access_point_arn = module.computer_runtime.workspace_admin_access_point_arn
|
|
369
|
+
workspace_admin_lambda_sg_id = module.computer_runtime.workspace_admin_lambda_sg_id
|
|
370
|
+
|
|
371
|
+
# Per-user OAuth client credentials — fed to Secrets Manager in
|
|
372
|
+
# app/lambda-api/oauth-secrets.tf. Reuses the same google_oauth_client_*
|
|
373
|
+
# tfvars that already flow to the Cognito federated-signin module.
|
|
374
|
+
google_oauth_client_id = var.google_oauth_client_id
|
|
375
|
+
google_oauth_client_secret = var.google_oauth_client_secret
|
|
376
|
+
redirect_success_url = var.redirect_success_url
|
|
377
|
+
platform_operator_emails = var.platform_operator_emails
|
|
378
|
+
|
|
379
|
+
mcp_custom_domain = var.mcp_custom_domain
|
|
380
|
+
mcp_custom_domain_ready = var.mcp_custom_domain_ready
|
|
381
|
+
|
|
382
|
+
depends_on = [module.cognito]
|
|
198
383
|
}
|
|
199
384
|
|
|
200
385
|
################################################################################
|
|
@@ -224,6 +409,99 @@ module "agentcore" {
|
|
|
224
409
|
hindsight_endpoint = local.hindsight_enabled ? module.hindsight[0].hindsight_endpoint : ""
|
|
225
410
|
agentcore_memory_id = module.agentcore_memory.memory_id
|
|
226
411
|
memory_engine = local.resolved_memory_engine
|
|
412
|
+
|
|
413
|
+
# Threaded through so the container's run_skill_dispatch can POST
|
|
414
|
+
# terminal state back to /api/skills/complete. The lambda-api module
|
|
415
|
+
# is declared above at line 156 as `module "api"`, so the reference
|
|
416
|
+
# is `module.api` — not `module.lambda_api` (which doesn't exist and
|
|
417
|
+
# broke terraform apply on every merge since #389).
|
|
418
|
+
api_endpoint = module.api.api_endpoint
|
|
419
|
+
api_auth_secret = var.api_auth_secret
|
|
420
|
+
nova_act_api_key = var.nova_act_api_key
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
################################################################################
|
|
424
|
+
# AgentCore Flue — Plan §005 U2 splits the Flue Lambda + log group + IAM role
|
|
425
|
+
# + event-invoke config out of the Strands `agentcore-runtime` module into a
|
|
426
|
+
# dedicated module so Flue can carry its own permissions surface independently.
|
|
427
|
+
# The shared ECR repo and async DLQ stay with `module.agentcore` and are
|
|
428
|
+
# injected here.
|
|
429
|
+
################################################################################
|
|
430
|
+
|
|
431
|
+
module "agentcore_flue" {
|
|
432
|
+
source = "../app/agentcore-flue"
|
|
433
|
+
|
|
434
|
+
stage = var.stage
|
|
435
|
+
account_id = var.account_id
|
|
436
|
+
region = var.region
|
|
437
|
+
bucket_name = module.s3.bucket_name
|
|
438
|
+
|
|
439
|
+
ecr_repository_url = module.agentcore.ecr_repository_url
|
|
440
|
+
async_dlq_arn = module.agentcore.agentcore_async_dlq_arn
|
|
441
|
+
|
|
442
|
+
hindsight_endpoint = local.hindsight_enabled ? module.hindsight[0].hindsight_endpoint : ""
|
|
443
|
+
agentcore_memory_id = module.agentcore_memory.memory_id
|
|
444
|
+
memory_engine = local.resolved_memory_engine
|
|
445
|
+
|
|
446
|
+
api_endpoint = module.api.api_endpoint
|
|
447
|
+
api_auth_secret = var.api_auth_secret
|
|
448
|
+
|
|
449
|
+
# Plan §005 U4 — AuroraSessionStore uses the RDS Data API. Cluster ARN
|
|
450
|
+
# + secret come from the existing aurora-postgres module so Flue and
|
|
451
|
+
# graphql-http hit the same cluster + same credential rotation surface.
|
|
452
|
+
db_cluster_arn = module.database.db_cluster_arn
|
|
453
|
+
db_secret_arn = module.database.graphql_db_secret_arn
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# Plan §005 U2 — cross-module state migration. The Flue resources moved from
|
|
457
|
+
# `module.agentcore` to `module.agentcore_flue`; the underlying AWS resource
|
|
458
|
+
# attributes (function_name, log group name, ARN) are unchanged from U1, so
|
|
459
|
+
# this is pure state-address realignment without destroy+create.
|
|
460
|
+
#
|
|
461
|
+
# Two `moved {}` blocks per resource form a CHAIN that covers both possible
|
|
462
|
+
# starting states:
|
|
463
|
+
# * Stages that never applied U1 (operator-managed greenfield, or any
|
|
464
|
+
# stage that skipped the U1 deploy) have state at
|
|
465
|
+
# `module.agentcore.aws_*.agentcore_pi` — the first block migrates that
|
|
466
|
+
# to `module.agentcore.aws_*.agentcore_flue` (U1's destination), then
|
|
467
|
+
# the second block migrates THAT to the new module.
|
|
468
|
+
# * Stages that applied U1 (e.g. dev) have state at
|
|
469
|
+
# `module.agentcore.aws_*.agentcore_flue` — only the second block
|
|
470
|
+
# fires.
|
|
471
|
+
#
|
|
472
|
+
# Terraform follows the chain transitively. The earlier shape (both
|
|
473
|
+
# blocks pointing directly at `module.agentcore_flue.…`) was rejected
|
|
474
|
+
# with "Ambiguous move statements" because each destination can only have
|
|
475
|
+
# one source — chaining through the intermediate disambiguates while still
|
|
476
|
+
# covering both starting states.
|
|
477
|
+
moved {
|
|
478
|
+
from = module.agentcore.aws_cloudwatch_log_group.agentcore_pi
|
|
479
|
+
to = module.agentcore.aws_cloudwatch_log_group.agentcore_flue
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
moved {
|
|
483
|
+
from = module.agentcore.aws_cloudwatch_log_group.agentcore_flue
|
|
484
|
+
to = module.agentcore_flue.aws_cloudwatch_log_group.agentcore_flue
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
moved {
|
|
488
|
+
from = module.agentcore.aws_lambda_function.agentcore_pi
|
|
489
|
+
to = module.agentcore.aws_lambda_function.agentcore_flue
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
moved {
|
|
493
|
+
from = module.agentcore.aws_lambda_function.agentcore_flue
|
|
494
|
+
to = module.agentcore_flue.aws_lambda_function.agentcore_flue
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
moved {
|
|
498
|
+
from = module.agentcore.aws_lambda_function_event_invoke_config.agentcore_pi
|
|
499
|
+
to = module.agentcore.aws_lambda_function_event_invoke_config.agentcore_flue
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
moved {
|
|
503
|
+
from = module.agentcore.aws_lambda_function_event_invoke_config.agentcore_flue
|
|
504
|
+
to = module.agentcore_flue.aws_lambda_function_event_invoke_config.agentcore_flue
|
|
227
505
|
}
|
|
228
506
|
|
|
229
507
|
module "crons" {
|
|
@@ -242,6 +520,22 @@ module "job_triggers" {
|
|
|
242
520
|
region = var.region
|
|
243
521
|
}
|
|
244
522
|
|
|
523
|
+
module "routines_stepfunctions" {
|
|
524
|
+
source = "../app/routines-stepfunctions"
|
|
525
|
+
|
|
526
|
+
stage = var.stage
|
|
527
|
+
account_id = var.account_id
|
|
528
|
+
region = var.region
|
|
529
|
+
|
|
530
|
+
# Phase B U9: EventBridge → routine-execution-callback. Constructed
|
|
531
|
+
# from the lambda-api naming convention rather than referencing the
|
|
532
|
+
# module output directly to avoid a cycle (lambda-api consumes
|
|
533
|
+
# routines_execution_role_arn from this module). The function exists
|
|
534
|
+
# for_each-iterated under aws_lambda_function.handler[*] in lambda-api;
|
|
535
|
+
# the ARN follows the deterministic naming pattern.
|
|
536
|
+
execution_callback_lambda_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-execution-callback"
|
|
537
|
+
}
|
|
538
|
+
|
|
245
539
|
module "hindsight" {
|
|
246
540
|
count = local.hindsight_enabled ? 1 : 0
|
|
247
541
|
source = "../app/hindsight-memory"
|
|
@@ -283,6 +577,130 @@ module "admin_site" {
|
|
|
283
577
|
certificate_arn = var.admin_certificate_arn
|
|
284
578
|
}
|
|
285
579
|
|
|
580
|
+
################################################################################
|
|
581
|
+
# Computer Static Site (apps/computer — end-user surface at computer.thinkwork.ai)
|
|
582
|
+
################################################################################
|
|
583
|
+
|
|
584
|
+
locals {
|
|
585
|
+
# Host CSP for the Computer SPA (plan-012 U10 / contract v1 §CSP profile).
|
|
586
|
+
# Generated apps always execute in the sandbox iframe shell, so the parent
|
|
587
|
+
# origin never needs blob: script/worker execution for transformed modules.
|
|
588
|
+
computer_host_script_src = "'self'"
|
|
589
|
+
computer_host_worker_src = "'self'"
|
|
590
|
+
computer_host_frame_src = local.computer_sandbox_enabled ? "https://${var.computer_sandbox_domain}" : "'none'"
|
|
591
|
+
computer_host_frame_ancestors = join(" ", compact([
|
|
592
|
+
"'self'",
|
|
593
|
+
var.admin_domain != "" ? "https://${var.admin_domain}" : "",
|
|
594
|
+
"https://${module.admin_site.distribution_domain}",
|
|
595
|
+
]))
|
|
596
|
+
|
|
597
|
+
computer_host_csp = "default-src 'self'; script-src ${local.computer_host_script_src}; style-src 'self' 'unsafe-inline'; worker-src ${local.computer_host_worker_src}; frame-src ${local.computer_host_frame_src}; connect-src 'self' https://*.execute-api.${var.region}.amazonaws.com https://*.appsync-api.${var.region}.amazonaws.com wss://*.appsync-realtime-api.${var.region}.amazonaws.com https://cognito-idp.${var.region}.amazonaws.com https://*.auth.${var.region}.amazoncognito.com; img-src 'self' data: blob: ${local.computer_sandbox_map_img_src}; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors ${local.computer_host_frame_ancestors};"
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
module "computer_site" {
|
|
601
|
+
source = "../app/static-site"
|
|
602
|
+
|
|
603
|
+
stage = var.stage
|
|
604
|
+
site_name = "computer"
|
|
605
|
+
is_spa = true
|
|
606
|
+
custom_domain = var.computer_domain
|
|
607
|
+
certificate_arn = var.computer_certificate_arn
|
|
608
|
+
|
|
609
|
+
# Plan-012 U10: host CSP defends the parent origin. Iframe-shell's
|
|
610
|
+
# own CSP (set on computer_sandbox_site below) carries the
|
|
611
|
+
# `connect-src 'none'` + `frame-ancestors` allowlist defense as
|
|
612
|
+
# belt-and-suspenders.
|
|
613
|
+
inline_response_headers = {
|
|
614
|
+
content_security_policy = local.computer_host_csp
|
|
615
|
+
content_type_options_override = true
|
|
616
|
+
strict_transport_security = {
|
|
617
|
+
max_age_sec = 63072000
|
|
618
|
+
include_subdomains = true
|
|
619
|
+
preload = true
|
|
620
|
+
override = true
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
################################################################################
|
|
626
|
+
# Computer Sandbox Static Site (sandbox.thinkwork.ai — LLM-fragment iframe host)
|
|
627
|
+
#
|
|
628
|
+
# Plan-012 U3. Cross-origin sandbox subdomain that hosts the iframe-shell
|
|
629
|
+
# bundle. The iframe document is loaded from this distribution by the
|
|
630
|
+
# Computer SPA (computer_site above) via `<iframe sandbox="allow-scripts"
|
|
631
|
+
# src="https://sandbox.thinkwork.ai/iframe-shell.html">`. Because the
|
|
632
|
+
# sandbox attribute omits `allow-same-origin`, the iframe runs at an opaque
|
|
633
|
+
# origin — the parent uses `targetOrigin: "*"` for postMessage delivery
|
|
634
|
+
# and trust comes from pinned src + iframe-side parent-origin allowlist +
|
|
635
|
+
# channelId nonce + no-secrets-in-payload (see contract v1).
|
|
636
|
+
#
|
|
637
|
+
# Bucket is empty in this PR. U9 populates it with the iframe-shell bundle
|
|
638
|
+
# via scripts/build-computer.sh.
|
|
639
|
+
#
|
|
640
|
+
# Iframe CSP profile (per contract v1 §CSP profile):
|
|
641
|
+
# default-src 'none'; script-src 'self' blob:; worker-src 'self' blob:;
|
|
642
|
+
# style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:
|
|
643
|
+
# https://*.tile.openstreetmap.org https://api.mapbox.com;
|
|
644
|
+
# font-src 'self' data:; connect-src 'none';
|
|
645
|
+
# frame-src https://www.openstreetmap.org; object-src 'none';
|
|
646
|
+
# base-uri 'self'; frame-ancestors <var.computer_sandbox_allowed_parent_origins>;
|
|
647
|
+
#
|
|
648
|
+
# Provisioning is gated on var.computer_sandbox_domain — leave empty in
|
|
649
|
+
# stages that haven't allocated the subdomain yet.
|
|
650
|
+
################################################################################
|
|
651
|
+
|
|
652
|
+
locals {
|
|
653
|
+
computer_sandbox_enabled = var.computer_sandbox_domain != ""
|
|
654
|
+
|
|
655
|
+
computer_sandbox_frame_ancestors = local.computer_sandbox_enabled && var.computer_sandbox_allowed_parent_origins != "" ? join(" ", split(",", replace(var.computer_sandbox_allowed_parent_origins, " ", ""))) : "'none'"
|
|
656
|
+
|
|
657
|
+
computer_sandbox_map_img_src = "https://*.tile.openstreetmap.org https://api.mapbox.com"
|
|
658
|
+
computer_sandbox_map_frame_src = "https://www.openstreetmap.org"
|
|
659
|
+
|
|
660
|
+
computer_sandbox_csp = "default-src 'none'; script-src 'self' blob:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: ${local.computer_sandbox_map_img_src}; font-src 'self' data:; connect-src 'none'; frame-src ${local.computer_sandbox_map_frame_src}; object-src 'none'; base-uri 'self'; frame-ancestors ${local.computer_sandbox_frame_ancestors};"
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
module "computer_sandbox_site" {
|
|
664
|
+
source = "../app/static-site"
|
|
665
|
+
count = local.computer_sandbox_enabled ? 1 : 0
|
|
666
|
+
|
|
667
|
+
stage = var.stage
|
|
668
|
+
site_name = "computer-sandbox"
|
|
669
|
+
is_spa = false
|
|
670
|
+
custom_domain = var.computer_sandbox_domain
|
|
671
|
+
certificate_arn = var.computer_sandbox_certificate_arn
|
|
672
|
+
|
|
673
|
+
# Iframe CSP — load-bearing for the cross-origin sandbox security
|
|
674
|
+
# boundary. connect-src 'none' is the defense-in-depth invariant: even
|
|
675
|
+
# if the host CSP regresses, the iframe cannot exfiltrate via fetch /
|
|
676
|
+
# XHR / WebSocket because the browser blocks the request inside the
|
|
677
|
+
# iframe scope.
|
|
678
|
+
inline_response_headers = {
|
|
679
|
+
content_security_policy = local.computer_sandbox_csp
|
|
680
|
+
content_type_options_override = true
|
|
681
|
+
# The iframe document runs with an opaque "null" origin because the
|
|
682
|
+
# parent sets sandbox="allow-scripts" without allow-same-origin.
|
|
683
|
+
# Module-script and asset requests from that document are therefore
|
|
684
|
+
# CORS requests back to this distribution. Allow public reads from
|
|
685
|
+
# any origin; credentials are false and the sandbox CSP still keeps
|
|
686
|
+
# connect-src 'none'.
|
|
687
|
+
cors = {
|
|
688
|
+
allow_origins = ["*"]
|
|
689
|
+
allow_methods = ["GET", "HEAD", "OPTIONS"]
|
|
690
|
+
allow_headers = ["*"]
|
|
691
|
+
allow_credentials = false
|
|
692
|
+
max_age_sec = 600
|
|
693
|
+
origin_override = true
|
|
694
|
+
}
|
|
695
|
+
strict_transport_security = {
|
|
696
|
+
max_age_sec = 63072000 # 2 years
|
|
697
|
+
include_subdomains = true
|
|
698
|
+
preload = true
|
|
699
|
+
override = true
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
286
704
|
################################################################################
|
|
287
705
|
# Docs Static Site
|
|
288
706
|
################################################################################
|