thinkwork-cli 0.13.2 → 1.0.0

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 (231) hide show
  1. package/README.md +3 -14
  2. package/dist/{api-client-JYZXYQJR.js → api-client-4P4JZOBX.js} +1 -1
  3. package/dist/{chunk-Z2F6RJFR.js → chunk-4OMDWYZQ.js} +4 -5
  4. package/dist/{chunk-ILZKG5WV.js → chunk-AD2NIWB5.js} +434 -69
  5. package/dist/chunk-MWQ7RO76.js +137 -0
  6. package/dist/{chunk-DVPBI5HM.js → chunk-OLUQ4U2Z.js} +26 -26
  7. package/dist/{chunk-QOERT44A.js → chunk-YQCYD6NE.js} +57 -44
  8. package/dist/cli.js +4400 -16431
  9. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
  10. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
  11. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
  12. package/dist/{dist-es-CRV46D5B.js → dist-es-2OTNTL4N.js} +4 -4
  13. package/dist/{dist-es-ENBUC3JP.js → dist-es-2XVAH4BQ.js} +2 -2
  14. package/dist/{dist-es-DCY4IC3Y.js → dist-es-3DPDUJAH.js} +2 -2
  15. package/dist/{dist-es-G7PTG75M.js → dist-es-BNZTBBVC.js} +1 -1
  16. package/dist/{dist-es-GKDBE7E6.js → dist-es-CD2RBDAC.js} +1 -1
  17. package/dist/{dist-es-R7FGL2IB.js → dist-es-I4FW57PS.js} +1 -1
  18. package/dist/{dist-es-USORBXZ5.js → dist-es-KVFWJNDY.js} +4 -4
  19. package/dist/{dist-es-HY3535OI.js → dist-es-PS73DWNL.js} +2 -2
  20. package/dist/{dist-es-LDE437AM.js → dist-es-S3NZQFGT.js} +1 -1
  21. package/dist/{dist-es-G54LBMRN.js → dist-es-SGCA4P32.js} +1 -1
  22. package/dist/{dist-es-Q57SZXZ7.js → dist-es-WBM6J4RA.js} +2 -2
  23. package/dist/{dist-es-P26FWFLT.js → dist-es-ZGVADYIF.js} +8 -8
  24. package/dist/drizzle/0227_analyst_reader_role.sql +17 -26
  25. package/dist/drizzle/0230_analyst_rls.sql +28 -45
  26. package/dist/drizzle/0261_native_auth_control_plane.sql +7 -3
  27. package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
  28. package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
  29. package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
  30. package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
  31. package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
  32. package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
  33. package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
  34. package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
  35. package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
  36. package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
  37. package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
  38. package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
  39. package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
  40. package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
  41. package/dist/drizzle/0284_user_brain_claims.sql +69 -0
  42. package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
  43. package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
  44. package/dist/drizzle/0287_brain_subject.sql +25 -0
  45. package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
  46. package/dist/{loadSso-DJVB7BXE.js → loadSso-ETCXTR4I.js} +2 -2
  47. package/dist/{loadSso-JAMNA3WY.js → loadSso-UBD7KUHE.js} +2 -2
  48. package/dist/plugins/README.md +2 -2
  49. package/dist/plugins/catalog/package.json +0 -3
  50. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -3
  51. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
  52. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -25
  53. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -6
  54. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -15
  55. package/dist/plugins/catalog/src/contracts.ts +15 -0
  56. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -18
  57. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
  58. package/dist/plugins/twenty/package.json +2 -1
  59. package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
  60. package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
  61. package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
  62. package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
  63. package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
  64. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
  65. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
  66. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
  67. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
  69. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
  70. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
  71. package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
  72. package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
  73. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
  74. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
  75. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
  76. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
  77. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
  78. package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
  79. package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
  80. package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
  81. package/dist/plugins/twenty/test/manifest.test.ts +2 -4
  82. package/dist/scripts/post-deploy.sh +79 -1
  83. package/dist/{signin-UGDWTGS4.js → signin-5QZNRSOO.js} +2 -2
  84. package/dist/{sso-oidc-Q4VXANXY.js → sso-oidc-XK673P54.js} +2 -2
  85. package/dist/{sts-CWLSE3GH.js → sts-OXI2UALF.js} +6 -134
  86. package/dist/{sts-DNZLNVOV.js → sts-SQW4NBK3.js} +6 -134
  87. package/dist/terraform/examples/greenfield/main.tf +233 -175
  88. package/dist/terraform/examples/greenfield/terraform.tfvars.example +12 -8
  89. package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
  90. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
  91. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
  92. package/dist/terraform/modules/app/agentcore-identity/main.tf +106 -26
  93. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +6 -6
  94. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +216 -99
  95. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +6 -9
  96. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
  97. package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
  98. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +22 -7
  99. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +17 -46
  100. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +65 -3
  101. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
  102. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
  103. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
  104. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
  105. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
  106. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
  107. package/dist/terraform/modules/app/agentcore-identity/variables.tf +6 -0
  108. package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
  109. package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
  110. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
  111. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  112. package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
  113. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
  114. package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
  115. package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
  116. package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
  117. package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
  118. package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
  119. package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
  120. package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
  121. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
  122. package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
  123. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
  124. package/dist/terraform/modules/app/deployment-control-plane/runner.py +2641 -124
  125. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2034 -154
  126. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
  127. package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
  128. package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
  129. package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
  130. package/dist/terraform/modules/app/lambda-api/handlers.tf +411 -723
  131. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +262 -198
  132. package/dist/terraform/modules/app/lambda-api/main.tf +51 -13
  133. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +4 -181
  134. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
  135. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
  136. package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -45
  137. package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
  138. package/dist/terraform/modules/app/lambda-api/variables.tf +219 -126
  139. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
  140. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
  141. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
  142. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
  143. package/dist/terraform/modules/app/static-site/main.tf +1 -1
  144. package/dist/terraform/modules/app/www-dns/main.tf +9 -27
  145. package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
  146. package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
  147. package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
  148. package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
  149. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
  150. package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
  151. package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
  152. package/dist/terraform/modules/foundation/cognito/main.tf +81 -8
  153. package/dist/terraform/modules/foundation/cognito/outputs.tf +5 -0
  154. package/dist/terraform/modules/foundation/cognito/variables.tf +24 -12
  155. package/dist/terraform/modules/thinkwork/README.md +5 -1
  156. package/dist/terraform/modules/thinkwork/main.tf +376 -374
  157. package/dist/terraform/modules/thinkwork/outputs.tf +12 -158
  158. package/dist/terraform/modules/thinkwork/variables.tf +246 -89
  159. package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
  160. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
  161. package/dist/workspace-defaults/files/ROUTER.md +1 -1
  162. package/dist/workspace-defaults/files/TOOLS.md +9 -39
  163. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
  164. package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
  165. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
  166. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  167. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  168. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
  169. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
  170. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
  171. package/package.json +1 -1
  172. package/dist/drizzle/0261_managed_harness_multiplayer.sql +0 -313
  173. package/dist/drizzle/0262_parallel_harness_threads.sql +0 -9
  174. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +0 -113
  175. package/dist/plugins/company-data/README.md +0 -49
  176. package/dist/plugins/company-data/package.json +0 -21
  177. package/dist/plugins/company-data/src/index.ts +0 -30
  178. package/dist/plugins/company-data/src/manifest.ts +0 -26
  179. package/dist/plugins/company-data/test/manifest.test.ts +0 -116
  180. package/dist/plugins/company-data/tsconfig.json +0 -8
  181. package/dist/plugins/company-etl/README.md +0 -46
  182. package/dist/plugins/company-etl/package.json +0 -21
  183. package/dist/plugins/company-etl/src/index.ts +0 -29
  184. package/dist/plugins/company-etl/src/manifest.ts +0 -26
  185. package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
  186. package/dist/plugins/company-etl/tsconfig.json +0 -8
  187. package/dist/plugins/lastmile/README.md +0 -50
  188. package/dist/plugins/lastmile/package.json +0 -23
  189. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
  190. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
  191. package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
  192. package/dist/plugins/lastmile/src/index.ts +0 -46
  193. package/dist/plugins/lastmile/src/manifest.ts +0 -139
  194. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
  195. package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
  196. package/dist/plugins/lastmile/tsconfig.json +0 -8
  197. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
  198. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
  199. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
  200. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
  201. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
  202. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
  203. package/dist/terraform/modules/app/agentcore-gateway/main.tf +0 -305
  204. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +0 -44
  205. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +0 -93
  206. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +0 -33
  207. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +0 -543
  208. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +0 -70
  209. package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -330
  210. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -72
  211. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +0 -5
  212. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +0 -430
  213. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +0 -180
  214. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +0 -193
  215. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +0 -5
  216. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +0 -5
  217. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +0 -5
  218. package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -96
  219. package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
  220. package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
  221. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +0 -88
  222. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +0 -91
  223. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
  224. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
  225. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
  226. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
  227. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
  228. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
  229. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
  230. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
  231. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/variables.tf +0 -0
@@ -30,6 +30,270 @@ Full deploy/update plans fail closed when they would delete customer-domain
30
30
  resources or remove web CloudFront aliases unless
31
31
  `allowCustomerDomainRemoval=true` is set for an intentional reviewed retirement.
32
32
 
33
+ ## Running the runner inside the VPC (THINK-1082)
34
+
35
+ The runner's schema step (`push_database_schema`) opens `psql` to the Aurora
36
+ cluster. By default the CodeBuild project has no `vpc_config`, so that
37
+ connection uses the cluster's public endpoint and depends on the world-open
38
+ `0.0.0.0/0 -> 5432` ingress the `aurora-postgres` module seeds on the database
39
+ security group. A customer that revokes that rule (deliberate lockdown) breaks
40
+ every deploy at the psql step with `Connection timed out`, and Terraform never
41
+ restores it (`ignore_changes = [ingress]`).
42
+
43
+ `runner_in_vpc = true` (with `vpc_id`, `subnet_ids`, `db_security_group_id`)
44
+ moves the build into the VPC's private subnets behind a dedicated security
45
+ group (`thinkwork-<stage>-deployment-runner-sg`) and adds a standalone
46
+ `aws_security_group_rule` granting that group tcp/5432 on the database security
47
+ group — the same pattern the app modules use. The private subnets must route
48
+ egress through a NAT gateway: a VPC-attached build has no public IP and still
49
+ needs GitHub (release manifest), apt/pip, STS, S3, and Secrets Manager. The
50
+ flag is ship-inert: `false` leaves the CodeBuild project byte-identical to
51
+ today.
52
+
53
+ ### Turning it on for a stage
54
+
55
+ There are two places the control plane can live, and the flag has to reach
56
+ both the network (NAT, owned by the `thinkwork` module) and the CodeBuild
57
+ project (owned by whichever root provisioned it):
58
+
59
+ 1. **The stage's `thinkwork` module input** — `deployment_runner_in_vpc = true`.
60
+ It keeps the NAT gateway on regardless of the deprecated `okf_wiki_*` flags
61
+ and, when this module is embedded (`enable_deployment_control_plane`, the
62
+ dev/greenfield path), passes the VPC, private subnets, and database security
63
+ group straight into it. For a greenfield root: set the
64
+ `deployment_runner_in_vpc` variable (`terraform.tfvars` or
65
+ `TF_VAR_deployment_runner_in_vpc=true`). For a controller-managed customer
66
+ stage the generated root reads it from the runner-secrets document:
67
+
68
+ ```bash
69
+ # /thinkwork/<stage>/deployment/runner-secrets — add the key, keep the rest
70
+ aws secretsmanager put-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
71
+ --secret-string "$(aws secretsmanager get-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
72
+ --query SecretString --output text | jq '. + {deploymentRunnerInVpc: true}')"
73
+ ```
74
+
75
+ (A `deploymentRunnerInVpc: true` key on the controller payload overrides
76
+ per-deploy; the secret is the durable home.)
77
+
78
+ 2. **The bootstrapped control plane** (customer accounts — the customer-update
79
+ root always applies with `enable_deployment_control_plane = false`, so the
80
+ customer deploy never touches its own CodeBuild project). Re-run the
81
+ bootstrap with the network named explicitly; it re-applies this module in
82
+ its own state (`deployment-control-plane/<stage>.tfstate`):
83
+
84
+ ```bash
85
+ thinkwork enterprise bootstrap --customer <slug> --stage <stage> \
86
+ --runner-in-vpc \
87
+ --runner-vpc-id vpc-... \
88
+ --runner-subnet-ids subnet-...,subnet-... \
89
+ --runner-db-security-group-id sg-...
90
+ ```
91
+
92
+ Use the VPC's **private** subnets (Name tag `thinkwork-<stage>-priv-<az>`)
93
+ and the database security group (Name tag `thinkwork-<stage>-db-sg`;
94
+ `module.database.db_security_group_id` in the stage's Terraform state).
95
+
96
+ ### Rollout: the first enable is a two-build story
97
+
98
+ Order matters. Do step 1 first and let it deploy: it creates the NAT gateway
99
+ (EIP + route on the private route tables) in the customer apply. Then do step 2.
100
+
101
+ When the flag turns the embedded control plane on (step 1 alone on a
102
+ dev/greenfield stage), the deploy that enables it updates the CodeBuild project
103
+ during its own Terraform step — but that build is still executing in the OLD,
104
+ non-VPC project. Its psql step therefore still needs the database reachable
105
+ over the public endpoint one last time. If the world-open rule is already gone,
106
+ that first run fails at the psql step after a successful apply; the RETRY runs
107
+ inside the VPC and succeeds. Do not read the first failure as the change not
108
+ working. The same holds for a customer stage if step 2 is applied while a
109
+ deploy is in flight.
110
+
111
+ What the first enable creates: the NAT gateway + EIP and private default
112
+ routes (if not already present), `aws_security_group.runner`,
113
+ `aws_security_group_rule.db_from_runner` (tcp/5432 on the database security
114
+ group from the runner group), and `vpc_config` on the CodeBuild project. The
115
+ CodeBuild role already holds `ec2:*`, which covers the ENI permissions a
116
+ VPC-attached build needs; nothing else in IAM changes.
117
+
118
+ ## Closing the database port (THINK-1082)
119
+
120
+ Every database-connected Lambda of the agent — the 100+ `thinkwork-<stage>-api-*`
121
+ handlers (each carries `DATABASE_URL`), `compliance-anchor`,
122
+ `compliance-export-runner`, `agentcore-admin`, the `agentcore-pi` runtime and
123
+ the capability broker — reaches Aurora the same way the runner's `psql` does:
124
+ over the cluster's public endpoint, through the world-open `0.0.0.0/0 -> 5432`
125
+ rule the `aurora-postgres` module seeds. Revoking that rule on a running stage
126
+ before those clients are inside the VPC takes the entire app down
127
+ (`auth-me` -> DB timeout -> 401 "No tenant assigned"; two hours on a customer
128
+ stage on 2026-08-25).
129
+
130
+ Four stage-level inputs close the port safely. All are ship-inert (defaults =
131
+ today) and live in the same homes as `deploymentRunnerInVpc`: the
132
+ runner-secrets document `/thinkwork/<stage>/deployment/runner-secrets` is the
133
+ durable home, a controller-payload key overrides per deploy, and a
134
+ greenfield root sets the `thinkwork` module variable directly (`dbAccess` is
135
+ runner-only — it has no module variable).
136
+
137
+ | Input (module / runner-secrets key) | Default | Effect when changed |
138
+ | ------------------------------------------------------ | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
139
+ | `api_lambdas_in_vpc` / `apiLambdasInVpc` | `false` | Every DB-connected Lambda attaches to the private subnets behind `thinkwork-<stage>-api-lambda-sg`; the DB SG admits it on 5432; NAT on. |
140
+ | — / `dbAccess` | `psql` | `data-api`: the runner's schema steps (probes, migrations, ledger, seeds, first-admin bootstrap) run over the RDS Data API — HTTPS + IAM, no network path. The runner stays outside the VPC. |
141
+ | `db_public_ingress_cidrs` / `dbPublicIngressCidrs` | `["0.0.0.0/0"]` | CIDRs seeded as 5432 ingress on a **new** DB SG. `[]` = none. Does **not** revoke a live rule (the group ignores ingress drift). |
142
+ | `db_publicly_accessible` / `dbPubliclyAccessible` | `true` | Public IP on the cluster instance. `false` removes it (immediate modify). |
143
+ | `deployment_runner_in_vpc` / `deploymentRunnerInVpc` | `false` | The runner CodeBuild attaches to the VPC (section above). Not required once `dbAccess` is `data-api`; kept as an alternative. |
144
+
145
+ ### Order for a running stage
146
+
147
+ Do the steps in this order and verify between them; each is its own deploy.
148
+
149
+ 1. **Lambdas into the VPC.** Set `apiLambdasInVpc: true` (runner-secrets, or
150
+ `api_lambdas_in_vpc = true` on a greenfield root) and deploy. The apply
151
+ creates the NAT gateway (if not already present), the api-lambda security
152
+ group, the `db_from_api_lambda` rule, and re-configures every function with
153
+ `vpc_config` (expect a few minutes of Lambda updates; ENI creation is
154
+ per-subnet, one-time). Verify the app: sign in, `GET /auth/me` returns the
155
+ tenant, a chat turn completes (Pi and the broker are on the same lane).
156
+ The Lambdas now reach the database privately; the public rule is no longer
157
+ load-bearing for them.
158
+
159
+ ```bash
160
+ aws secretsmanager put-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
161
+ --secret-string "$(aws secretsmanager get-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
162
+ --query SecretString --output text | jq '. + {apiLambdasInVpc: true}')"
163
+ ```
164
+
165
+ 2. **Runner onto the Data API.** Set `dbAccess: "data-api"` the same way and
166
+ deploy once. That deploy's schema step — the `to_regclass` probes, the
167
+ migration ledger, every pending migration file, the platform seed and the
168
+ first-admin bootstrap — runs over the RDS Data API (`rds-data:*` on the
169
+ cluster, granted to the CodeBuild role by this module; the cluster has
170
+ `enable_http_endpoint = true` from the `aurora-postgres` module). The
171
+ runner never opens a TCP connection to the database and stays outside the
172
+ VPC. Verify in the build log:
173
+
174
+ ```
175
+ [runner] database access: data-api
176
+ ```
177
+
178
+ and that the migration lines (`[migrations] applying …`) completed. The
179
+ public path is now used by nothing.
180
+
181
+ ```bash
182
+ aws secretsmanager put-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
183
+ --secret-string "$(aws secretsmanager get-secret-value --secret-id /thinkwork/<stage>/deployment/runner-secrets \
184
+ --query SecretString --output text | jq '. + {dbAccess: "data-api"}')"
185
+ ```
186
+
187
+ How the lane bridges what `psql` did: scripts are split into single
188
+ statements (dollar-quote, quote and comment aware; drizzle
189
+ `--> statement-breakpoint` markers count as boundaries), `:'stage'`-style
190
+ variables are interpolated, and the psql meta-commands the migration files
191
+ use (`\set`, `\echo`, `\if :{?name}` / `\else` / `\endif`, `\quit`) are
192
+ interpreted — any other meta-command is refused by name. Every script runs
193
+ inside one Data API transaction (its own `BEGIN`/`COMMIT` map onto it;
194
+ `CREATE INDEX CONCURRENTLY` commits and runs standalone) so statements
195
+ serialise on one connection and a DDL statement that outlives the API's
196
+ 45 s call window (`continueAfterTimeout`) can never race the next one.
197
+ The first failing statement rolls the script back and fails the deploy
198
+ before the file is recorded in `platform_schema_migrations` — the same
199
+ `ON_ERROR_STOP` contract as before. A statement over the Data API's 64 KB
200
+ limit (0230's generated RLS block) is shipped in pieces to a temp table on
201
+ the transaction's connection and executed there.
202
+
203
+ 3. **Close the port.** Set `dbPublicIngressCidrs: []` and
204
+ `dbPubliclyAccessible: false` and deploy. Terraform stops seeding the
205
+ world-open rule and drops the public IP, but because the DB security group
206
+ ignores ingress drift the live `0.0.0.0/0` rule is still there — revoke it
207
+ by hand, once:
208
+
209
+ ```bash
210
+ aws ec2 revoke-security-group-ingress --group-id <thinkwork-<stage>-db-sg id> \
211
+ --protocol tcp --port 5432 --cidr 0.0.0.0/0
212
+ ```
213
+
214
+ Then verify the app again (auth-me, a chat turn, a brief) **and** run one
215
+ more deploy so the runner's Data API schema step is proven with the port
216
+ closed.
217
+
218
+ Notes:
219
+
220
+ - The runner-in-VPC path (`deploymentRunnerInVpc`, section above) remains
221
+ available but is not required: with `dbAccess: "data-api"` the runner has
222
+ no database network path to protect. (Its first enable hung in Terraform's
223
+ plan phase on 2026-08-25, which is why the Data API lane exists.)
224
+ - Every run logs its lane once — `[runner] database access: psql|data-api` —
225
+ right after the runner files are written; check it before trusting a
226
+ "closed port" deploy.
227
+ - The RDS Data API (`enable_http_endpoint`) needs IAM, not a network path, so
228
+ operator SQL (`aws rds-data execute-statement`), the Pi session store and
229
+ every `db_cluster_arn` consumer keep working at every step.
230
+ - ECS workloads (Twenty, n8n) already attach standalone
231
+ `aws_security_group_rule` grants for their own task security groups and are
232
+ unaffected by the close.
233
+ - VPC-attached Lambdas route all internet egress (Bedrock, Anthropic, Slack,
234
+ GitHub, model providers, AWS APIs without an interface endpoint) through the
235
+ NAT gateway — NAT data-processing charges apply to that traffic. Interface
236
+ endpoints for secretsmanager, sts, logs, rds-data, athena, bedrock-agentcore
237
+ and xray plus the S3 gateway keep that in-VPC.
238
+ - Rolling back is the reverse order: re-add the CIDR rule by hand (or set
239
+ `dbPublicIngressCidrs` back and recreate), set `dbAccess` back to `psql`,
240
+ then flip `apiLambdasInVpc` off.
241
+
242
+ ## Saved-plan approval
243
+
244
+ High-risk foundation changes can use a two-execution controller protocol. An
245
+ `action=plan` execution must set `operation.plan=true`,
246
+ `operation.apply=false`, and a `targetAction`. It uploads the opaque saved plan,
247
+ the rendered JSON plan, and a content-addressed approval descriptor. A later
248
+ `action=apply` execution must set `operation.plan=false`,
249
+ `operation.apply=true`, and reference that exact descriptor. The runner refuses
250
+ the apply if the release pin, deployment configuration, Terraform variables,
251
+ state lineage/serial/content, saved-plan digest, or rendered JSON digest differs.
252
+ It applies the accepted plan directly and never replans.
253
+
254
+ Existing deployment and managed-application callers retain their established
255
+ behavior while their product approval surfaces migrate to this protocol. The
256
+ separate saved-plan path is mandatory for incident recovery and other explicitly
257
+ guarded operations.
258
+
259
+ ## TEI `.380` proof-plane recovery
260
+
261
+ The runner contains a one-time, exact-scope recovery contract for the interrupted
262
+ TEI `.380` apply. It is bound to the observed account, region, state object,
263
+ lineage, serial, state hash, stale lock ID/digest, rejected plan hash, original
264
+ Twenty OAuth secret, and original turn-assertion KMS key. The managed `recover`
265
+ action:
266
+
267
+ 1. proves it is the sole deployment execution and build;
268
+ 2. restores the original secret without reading its value;
269
+ 3. cancels deletion and re-enables the original KMS key;
270
+ 4. invokes Terraform's backend-aware `force-unlock` for only the bound stale
271
+ lock (never a direct DynamoDB delete); and
272
+ 5. proves the state object and digest are unchanged while writing redacted
273
+ evidence.
274
+
275
+ The subsequent proof-enabled plan accepts that evidence and declaratively
276
+ imports the recovered secret and KMS key. It must use the saved-plan approval
277
+ protocol and pass a zero-delete review before any apply.
278
+
279
+ ## Node.js 22 control-runtime transition
280
+
281
+ The AgentCore control SDK requires Node.js 20 or newer, and the ThinkWork
282
+ toolchain requires Node.js 22 or newer. New and reconciled CodeBuild projects
283
+ therefore select Node.js 22 in the checked-in buildspec, and release CI executes
284
+ every packaged control-runtime entrypoint under that exact major.
285
+
286
+ An established deployment whose live CodeBuild project still embeds the older
287
+ Node.js 18 buildspec upgrades in 2 managed stages. First, the existing Python
288
+ runner loads the ESM-bounded release under Node.js 18 only long enough to produce
289
+ and apply an independently accepted Terraform plan. That transition must perform
290
+ no AgentCore SDK API operation; the plan may only update CodeBuild to Node.js 22
291
+ in place alongside the intended application changes, with zero delete or replace
292
+ actions. Then read back the live buildspec, refresh the runner through the
293
+ release-remediation API using the manifest's standalone runner asset and digest,
294
+ and rerun the controller under Node.js 22. Do not claim Node.js 18 support from
295
+ this bounded transition. Subsequent updates are entirely Terraform-owned.
296
+
33
297
  Platform deploys stay on the root Terraform state key:
34
298
  `thinkwork/<stage>/terraform.tfstate`.
35
299
 
@@ -2,10 +2,12 @@ version: 0.2
2
2
 
3
3
  phases:
4
4
  install:
5
+ runtime-versions:
6
+ nodejs: 22
5
7
  commands:
6
8
  - |
7
9
  if ! command -v terraform >/dev/null 2>&1; then
8
- curl -fsSLo /tmp/terraform.zip https://releases.hashicorp.com/terraform/1.8.5/terraform_1.8.5_linux_amd64.zip
10
+ curl -fsSLo /tmp/terraform.zip https://releases.hashicorp.com/terraform/1.9.8/terraform_1.9.8_linux_amd64.zip
9
11
  unzip -oq /tmp/terraform.zip -d /usr/local/bin
10
12
  fi
11
13
  if ! command -v psql >/dev/null 2>&1; then
@@ -17,6 +19,7 @@ phases:
17
19
  fi
18
20
  fi
19
21
  - terraform version
22
+ - node --version
20
23
  - psql --version
21
24
  build:
22
25
  commands:
@@ -27,6 +27,13 @@ locals {
27
27
  state_machine_log_group = "/aws/vendedlogs/states/${local.state_machine_name}"
28
28
  terraform_module_is_git = startswith(var.terraform_module_source, "git::") || startswith(var.terraform_module_source, "github.com/")
29
29
  terraform_module_version = var.terraform_module_version != "" ? var.terraform_module_version : (local.terraform_module_is_git ? "" : trimprefix(var.release_version, "v"))
30
+ runner_sg_name = "${local.codebuild_project_name}-sg"
31
+ # runner_in_vpc needs every network input; checked as a precondition on the
32
+ # CodeBuild project because cross-variable validation is not available at
33
+ # the repo's Terraform floor (1.8.5).
34
+ runner_vpc_inputs_complete = !var.runner_in_vpc || (
35
+ var.vpc_id != "" && length(var.subnet_ids) > 0 && var.db_security_group_id != ""
36
+ )
30
37
 
31
38
  deployment_parameters = {
32
39
  for key, value in {
@@ -297,6 +304,25 @@ resource "aws_iam_role_policy" "codebuild" {
297
304
  ["arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/${var.stage}/deployment/*"]
298
305
  )
299
306
  },
307
+ {
308
+ # THINK-1082: the runner's database steps over the RDS Data API
309
+ # (runner-secrets `dbAccess: "data-api"`) — HTTPS + IAM instead of a
310
+ # psql connection through the cluster's public endpoint, so the
311
+ # world-open 5432 rule can close with the runner still outside the
312
+ # VPC. Ungated: harmless while dbAccess is psql. The credentials
313
+ # secret the Data API signs with (graphql_db_secret_arn) is already
314
+ # readable through the secretsmanager:* grant below — the same secret
315
+ # `psql` reads today.
316
+ Effect = "Allow"
317
+ Action = [
318
+ "rds-data:BatchExecuteStatement",
319
+ "rds-data:BeginTransaction",
320
+ "rds-data:CommitTransaction",
321
+ "rds-data:ExecuteStatement",
322
+ "rds-data:RollbackTransaction",
323
+ ]
324
+ Resource = "arn:aws:rds:${var.region}:${var.account_id}:cluster:thinkwork-${var.stage}-db"
325
+ },
300
326
  {
301
327
  Effect = "Allow"
302
328
  Action = [
@@ -345,6 +371,51 @@ resource "aws_iam_role_policy" "codebuild" {
345
371
  })
346
372
  }
347
373
 
374
+ ################################################################################
375
+ # Runner-in-VPC network (THINK-1082)
376
+ #
377
+ # Off by default (ship-inert). When on, the build attaches to the VPC's private
378
+ # subnets with this security group and the database security group grants it
379
+ # tcp/5432 — the same standalone-rule pattern the app modules use, so the
380
+ # grant survives the Aurora module's `ignore_changes = [ingress]`. The
381
+ # CodeBuild role already carries ec2:* on Resource "*", which covers the ENI
382
+ # permissions a VPC-attached build needs (CreateNetworkInterface,
383
+ # CreateNetworkInterfacePermission, DeleteNetworkInterface, Describe*).
384
+ ################################################################################
385
+
386
+ resource "aws_security_group" "runner" {
387
+ count = var.runner_in_vpc ? 1 : 0
388
+
389
+ name = local.runner_sg_name
390
+ description = "ThinkWork deployment runner (CodeBuild in VPC) for ${var.stage}."
391
+ vpc_id = var.vpc_id
392
+
393
+ egress {
394
+ from_port = 0
395
+ to_port = 0
396
+ protocol = "-1"
397
+ cidr_blocks = ["0.0.0.0/0"]
398
+ }
399
+
400
+ tags = {
401
+ Name = local.runner_sg_name
402
+ Stage = var.stage
403
+ Purpose = "deployment-runner"
404
+ }
405
+ }
406
+
407
+ resource "aws_security_group_rule" "db_from_runner" {
408
+ count = var.runner_in_vpc ? 1 : 0
409
+
410
+ type = "ingress"
411
+ description = "ThinkWork deployment runner (VPC)"
412
+ from_port = 5432
413
+ to_port = 5432
414
+ protocol = "tcp"
415
+ security_group_id = var.db_security_group_id
416
+ source_security_group_id = aws_security_group.runner[0].id
417
+ }
418
+
348
419
  resource "aws_codebuild_project" "runner" {
349
420
  name = local.codebuild_project_name
350
421
  description = "ThinkWork deployment runner for ${var.stage}."
@@ -470,6 +541,24 @@ resource "aws_codebuild_project" "runner" {
470
541
  buildspec = file("${path.module}/buildspec.yml")
471
542
  }
472
543
 
544
+ # Absent entirely when runner_in_vpc is false, so the default project is
545
+ # byte-identical to the pre-THINK-1082 shape.
546
+ dynamic "vpc_config" {
547
+ for_each = var.runner_in_vpc ? [1] : []
548
+ content {
549
+ vpc_id = var.vpc_id
550
+ subnets = var.subnet_ids
551
+ security_group_ids = [aws_security_group.runner[0].id]
552
+ }
553
+ }
554
+
555
+ lifecycle {
556
+ precondition {
557
+ condition = local.runner_vpc_inputs_complete
558
+ error_message = "runner_in_vpc requires non-empty vpc_id, subnet_ids, and db_security_group_id."
559
+ }
560
+ }
561
+
473
562
  tags = {
474
563
  Name = local.codebuild_project_name
475
564
  Stage = var.stage
@@ -574,6 +663,11 @@ resource "aws_sfn_state_machine" "deployment" {
574
663
  Type = "PLAINTEXT"
575
664
  "Value.$" = "$.sessionId"
576
665
  },
666
+ {
667
+ Name = "THINKWORK_DEPLOYMENT_EXECUTION_ARN"
668
+ Type = "PLAINTEXT"
669
+ "Value.$" = "$$.Execution.Id"
670
+ },
577
671
  {
578
672
  Name = "THINKWORK_DEPLOYMENT_INPUT"
579
673
  Type = "PLAINTEXT"
@@ -52,3 +52,8 @@ output "secret_arns" {
52
52
  description = "Placeholder deployment secret ARNs keyed by purpose."
53
53
  value = { for key, secret in aws_secretsmanager_secret.deployment : key => secret.arn }
54
54
  }
55
+
56
+ output "runner_security_group_id" {
57
+ description = "Security group attached to the deployment runner when runner_in_vpc is true; null otherwise."
58
+ value = var.runner_in_vpc ? aws_security_group.runner[0].id : null
59
+ }