thinkwork-cli 0.12.11 → 0.12.15

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 (474) hide show
  1. package/README.md +42 -38
  2. package/dist/{api-client-ASJWIG3Z.js → api-client-4VT3FWOH.js} +1 -1
  3. package/dist/{chunk-JEHV35EU.js → chunk-H7AB42ES.js} +340 -30
  4. package/dist/cli.js +22157 -9229
  5. package/dist/commands/enterprise/templates/deploy-repo/.github/workflows/deploy.yml +17 -6
  6. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +81 -9
  7. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +183 -21
  8. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +1 -2
  9. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +286 -0
  10. package/dist/drizzle/0000_petite_marvel_apes.sql +1188 -0
  11. package/dist/drizzle/0001_milky_surge.sql +103 -0
  12. package/dist/drizzle/0002_aberrant_mattie_franklin.sql +41 -0
  13. package/dist/drizzle/0003_exotic_ogun.sql +3 -0
  14. package/dist/drizzle/0004_glamorous_magneto.sql +2 -0
  15. package/dist/drizzle/0005_ancient_nocturne.sql +2 -0
  16. package/dist/drizzle/0006_new_colonel_america.sql +2 -0
  17. package/dist/drizzle/0007_unique_external_task_id.sql +31 -0
  18. package/dist/drizzle/0008_external_task_id_column.sql +43 -0
  19. package/dist/drizzle/0009_evaluations.sql +64 -0
  20. package/dist/drizzle/0010_eval_agent_template.sql +2 -0
  21. package/dist/drizzle/0011_eval_run_template.sql +2 -0
  22. package/dist/drizzle/0012_eval_seed_unique.sql +16 -0
  23. package/dist/drizzle/0013_motionless_white_queen.sql +140 -0
  24. package/dist/drizzle/0014_wiki_aggregation.sql +11 -0
  25. package/dist/drizzle/0015_pg_trgm_alias_title_indexes.sql +9 -0
  26. package/dist/drizzle/0016_wiki_schema_drops.sql +24 -0
  27. package/dist/drizzle/0017_wiki_places.sql +46 -0
  28. package/dist/drizzle/0018_agent_workspace_overlay.sql +26 -0
  29. package/dist/drizzle/0018_cool_firebird.sql +24 -0
  30. package/dist/drizzle/0018_skill_runs.sql +84 -0
  31. package/dist/drizzle/0019_condemned_dragon_man.sql +95 -0
  32. package/dist/drizzle/0019_webhook_skill_runs.sql +58 -0
  33. package/dist/drizzle/0020_crazy_the_anarchist.sql +9 -0
  34. package/dist/drizzle/0020_mutation_idempotency.sql +64 -0
  35. package/dist/drizzle/0021_crm_work_links.sql +76 -0
  36. package/dist/drizzle/0021_skill_runs_completion_hmac.sql +33 -0
  37. package/dist/drizzle/0022_add_billing_tables.sql +70 -0
  38. package/dist/drizzle/0022_seed_thinkwork_admin_permissions.sql +160 -0
  39. package/dist/drizzle/0022_stripe_billing_indexes.sql +48 -0
  40. package/dist/drizzle/0023_tenants_deactivation.sql +27 -0
  41. package/dist/drizzle/0024_tenant_mcp_admin_keys.sql +63 -0
  42. package/dist/drizzle/0025_v1_agent_architecture.sql +182 -0
  43. package/dist/drizzle/0026_thread_turns_add_kind.sql +28 -0
  44. package/dist/drizzle/0027_capability_catalog_and_manifests.sql +219 -0
  45. package/dist/drizzle/0028_aws_s3_extension.sql +26 -0
  46. package/dist/drizzle/0029_collapse_execution_types.sql +42 -0
  47. package/dist/drizzle/0030_retire_composition_primitives.sql +48 -0
  48. package/dist/drizzle/0031_thread_cleanup_drops.sql +131 -0
  49. package/dist/drizzle/0032_add_browser_automation_template_config.sql +24 -0
  50. package/dist/drizzle/0033_agent_operation_leases.sql +32 -0
  51. package/dist/drizzle/0034_agent_workspace_events.sql +116 -0
  52. package/dist/drizzle/0035_workspace_review_decisions.sql +19 -0
  53. package/dist/drizzle/0036_user_scoped_memory_wiki.sql +152 -0
  54. package/dist/drizzle/0037_user_profile_operating_model.sql +34 -0
  55. package/dist/drizzle/0042_agent_runtime_selector.sql +41 -0
  56. package/dist/drizzle/0043_wiki_search_tsv_tokenization.sql +48 -0
  57. package/dist/drizzle/0044_add_web_search_template_config.sql +7 -0
  58. package/dist/drizzle/0045_add_send_email_template_config.sql +7 -0
  59. package/dist/drizzle/0046_tenant_mcp_context_tools.sql +26 -0
  60. package/dist/drizzle/0047_add_context_engine_template_config.sql +7 -0
  61. package/dist/drizzle/0048_tenant_context_provider_settings.sql +22 -0
  62. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +29 -0
  63. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +170 -0
  64. package/dist/drizzle/0052_brain_v0_external_refs.sql +41 -0
  65. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +48 -0
  66. package/dist/drizzle/0054_wiki_compile_jobs_input.sql +28 -0
  67. package/dist/drizzle/0055_routines_stepfunctions_substrate.sql +202 -0
  68. package/dist/drizzle/0056_routine_step_events_dedup_idx.sql +25 -0
  69. package/dist/drizzle/0057_archive_legacy_python_routines.sql +36 -0
  70. package/dist/drizzle/0058_routines_visibility_and_owning_agent.sql +61 -0
  71. package/dist/drizzle/0061_routine_execution_asl_version_id.sql +29 -0
  72. package/dist/drizzle/0062_migrate_pi_to_flue.sql +110 -0
  73. package/dist/drizzle/0063_threads_session_data.sql +23 -0
  74. package/dist/drizzle/0064_tenant_credentials.sql +65 -0
  75. package/dist/drizzle/0067_thinkwork_computers_phase_one.sql +174 -0
  76. package/dist/drizzle/0068_drop_system_workflows_and_activation.sql +115 -0
  77. package/dist/drizzle/0069_compliance_schema.sql +364 -0
  78. package/dist/drizzle/0070_compliance_aurora_roles.sql +171 -0
  79. package/dist/drizzle/0072_threads_computer_ownership.sql +24 -0
  80. package/dist/drizzle/0073_compliance_tenant_anchor_state.sql +105 -0
  81. package/dist/drizzle/0074_compliance_event_hash_index.sql +49 -0
  82. package/dist/drizzle/0075_scheduled_jobs_computer_ownership.sql +27 -0
  83. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +69 -0
  84. package/dist/drizzle/0077_computers_primary_agent_id.sql +38 -0
  85. package/dist/drizzle/0078_tenant_customize_catalog.sql +46 -0
  86. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +53 -0
  87. package/dist/drizzle/0081_routines_catalog_slug.sql +42 -0
  88. package/dist/drizzle/0082_messages_parts_jsonb.sql +52 -0
  89. package/dist/drizzle/0083_computer_runbooks.sql +103 -0
  90. package/dist/drizzle/0084_artifacts_favorited_at.sql +32 -0
  91. package/dist/drizzle/0085_seed_thinkwork_computer_default_template.sql +46 -0
  92. package/dist/drizzle/0086_update_default_template_models.sql +19 -0
  93. package/dist/drizzle/0087_retire_oss_connectors.sql +77 -0
  94. package/dist/drizzle/0088_compliance_event_types_finance_pilot.sql +37 -0
  95. package/dist/drizzle/0089_remove_maniflow_eval_seeds.sql +81 -0
  96. package/dist/drizzle/0089_wiki_schema_extraction.sql +315 -0
  97. package/dist/drizzle/0090_brain_schema_extraction.sql +274 -0
  98. package/dist/drizzle/0091_drop_wiki_brain_compat_views.sql +118 -0
  99. package/dist/drizzle/0092_finish_oss_connector_retirement.sql +97 -0
  100. package/dist/drizzle/0093_eval_runs_scheduled_job_id.sql +23 -0
  101. package/dist/drizzle/0094_slack_workspace_app.sql +140 -0
  102. package/dist/drizzle/0095_eval_runs_computer_id.sql +22 -0
  103. package/dist/drizzle/0096_true_redteam_eval_seed_cleanup.sql +67 -0
  104. package/dist/drizzle/0097_shared_computers.sql +191 -0
  105. package/dist/drizzle/0098_business_ontology.sql +587 -0
  106. package/dist/drizzle/0099_thread_idle_learning.sql +145 -0
  107. package/dist/drizzle/0100_ontology_gated_brain_dynamic_subtypes.sql +7 -0
  108. package/dist/drizzle/0101_ontology_gated_unresolved_mentions_dynamic_subtypes.sql +7 -0
  109. package/dist/drizzle/0102_wiki_brain_owner_repair.sql +87 -0
  110. package/dist/drizzle/0103_brain_section_source_trigger_repair.sql +75 -0
  111. package/dist/drizzle/0104_expand_seed_ontology_for_user_memory.sql +166 -0
  112. package/dist/drizzle/0105_spaces_domain.sql +349 -0
  113. package/dist/drizzle/0106_expand_seed_ontology_for_triples.sql +173 -0
  114. package/dist/drizzle/0106_space_threads_participants.sql +197 -0
  115. package/dist/drizzle/0107_linked_task_mirror.sql +245 -0
  116. package/dist/drizzle/0108_message_mentions.sql +95 -0
  117. package/dist/drizzle/0109_space_owned_threads.sql +217 -0
  118. package/dist/drizzle/0110_thread_participant_read_state.sql +21 -0
  119. package/dist/drizzle/0111_agents_tenant_name_unique.sql +38 -0
  120. package/dist/drizzle/0112_recast_spaces_as_contextual_workrooms.sql +138 -0
  121. package/dist/drizzle/0113_agents_own_runtime_fields.sql +48 -0
  122. package/dist/drizzle/0114_migrate_templates_to_agents_and_spaces.sql +361 -0
  123. package/dist/drizzle/0115_remove_computer_eval_template_dependencies.sql +69 -0
  124. package/dist/drizzle/0116_backfill_thread_participants_access.sql +126 -0
  125. package/dist/drizzle/0117_space_access_mode.sql +40 -0
  126. package/dist/drizzle/0118_normalize_tenant_member_principal_type.sql +41 -0
  127. package/dist/drizzle/0119_space_knowledge_bases.sql +126 -0
  128. package/dist/drizzle/0120_space_automations.sql +58 -0
  129. package/dist/drizzle/0121_drop_computer_runbooks.sql +26 -0
  130. package/dist/drizzle/0122_space_email_triggers.sql +24 -0
  131. package/dist/drizzle/0123_single_platform_agent_and_overrides.sql +58 -0
  132. package/dist/drizzle/0124_thread_turns_finalized_at.sql +17 -0
  133. package/dist/drizzle/0125_drop_space_agent_assignments.sql +42 -0
  134. package/dist/drizzle/0126_migrate_flue_to_pi.sql +42 -0
  135. package/dist/drizzle/0127_runtime_type_observability.sql +28 -0
  136. package/dist/drizzle/0128_drop_eval_test_cases_agent_id.sql +37 -0
  137. package/dist/drizzle/0129_drop_code_factory_tables.sql +43 -0
  138. package/dist/drizzle/0129_eval_results_system_prompt.sql +30 -0
  139. package/dist/drizzle/0130_drop_teams_and_team_id_columns.sql +54 -0
  140. package/dist/drizzle/0131_drop_skill_catalog_and_tenant_skills.sql +33 -0
  141. package/dist/drizzle/0132_drop_computer_tables.sql +31 -0
  142. package/dist/drizzle/0133_drop_seed_view_tombstones.sql +79 -0
  143. package/dist/drizzle/0133_thread_turns_system_prompt.sql +22 -0
  144. package/dist/drizzle/0134_drop_dead_agent_runtime_tables.sql +53 -0
  145. package/dist/drizzle/0135_native_checklist_linked_tasks.sql +48 -0
  146. package/dist/drizzle/0136_goal_ledger.sql +55 -0
  147. package/dist/drizzle/0136_space_email_trigger_status.sql +66 -0
  148. package/dist/drizzle/0137_thread_participant_pins.sql +25 -0
  149. package/dist/drizzle/0138_users_cognito_sub.sql +30 -0
  150. package/dist/drizzle/0139_mobile_turn_client_id.sql +23 -0
  151. package/dist/drizzle/0140_workspace_folder_names.sql +36 -0
  152. package/dist/drizzle/0141_eval_runs_desktop_pi_provenance.sql +31 -0
  153. package/dist/drizzle/0142_pi_only_agent_runtime.sql +61 -0
  154. package/dist/drizzle/0143_add_web_extract_builtin_config.sql +28 -0
  155. package/dist/drizzle/0144_skill_catalog_index.sql +40 -0
  156. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +512 -0
  157. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +413 -0
  158. package/dist/drizzle/0147_knowledge_graph_scope_trigger_qualification.sql +211 -0
  159. package/dist/drizzle/0148_user_cost_attribution.sql +171 -0
  160. package/dist/drizzle/0149_managed_mcp_servers.sql +64 -0
  161. package/dist/drizzle/0149_user_model_approvals.sql +89 -0
  162. package/dist/drizzle/0150_bootstrap_first_admin_claim.sql +11 -0
  163. package/dist/drizzle/0151_managed_deployments.sql +91 -0
  164. package/dist/drizzle/0152_agent_profiles.sql +81 -0
  165. package/dist/drizzle/0153_message_mentions_agent_profile.sql +9 -0
  166. package/dist/drizzle/0154_customer_deployment_sessions.sql +61 -0
  167. package/dist/drizzle/0155_tenant_model_catalog.sql +152 -0
  168. package/dist/drizzle/0156_bootstrap_credential_leases.sql +52 -0
  169. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +79 -0
  170. package/dist/drizzle/0158_pending_user_questions.sql +80 -0
  171. package/dist/drizzle/0158_wiki_tenant_scope.sql +38 -0
  172. package/dist/drizzle/0159_agent_profiles_space_local.sql +49 -0
  173. package/dist/drizzle/0159_application_plugins.sql +193 -0
  174. package/dist/drizzle/0159_eval_scoring_version.sql +46 -0
  175. package/dist/drizzle/0160_compliance_event_types_plugins.sql +37 -0
  176. package/dist/drizzle/0160_eval_datasets.sql +75 -0
  177. package/dist/drizzle/0160_eval_results_thread_turn.sql +35 -0
  178. package/dist/drizzle/0161_eval_run_pinning.sql +54 -0
  179. package/dist/drizzle/0161_tenant_mcp_servers_plugin_source.sql +62 -0
  180. package/dist/drizzle/0162_eval_result_override.sql +49 -0
  181. package/dist/drizzle/0163_eval_replay_tool_allowlist.sql +50 -0
  182. package/dist/drizzle/0164_eval_replay_tool_override_mode.sql +52 -0
  183. package/dist/drizzle/0165_plugin_premium_entitlements.sql +153 -0
  184. package/dist/drizzle/0166_company_brain_substrate_contract.sql +386 -0
  185. package/dist/drizzle/0167_company_brain_artifact_manifest_runtime.sql +85 -0
  186. package/dist/drizzle/0168_eval_skill_gate.sql +44 -0
  187. package/dist/drizzle/0169_release_update_jobs.sql +67 -0
  188. package/dist/drizzle/0170_email_channel_plugin.sql +291 -0
  189. package/dist/drizzle/0171_linked_task_external_providers.sql +30 -0
  190. package/dist/drizzle/0172_email_channel_sendgrid_provider.sql +8 -0
  191. package/dist/drizzle/0173_auth_provider_resources.sql +83 -0
  192. package/dist/drizzle/0174_workos_auth_bridges.sql +37 -0
  193. package/dist/drizzle/0175_workos_auth_sessions.sql +38 -0
  194. package/dist/drizzle/0176_n8n_agent_step_runs.sql +107 -0
  195. package/dist/drizzle/0177_workflow_control_plane.sql +281 -0
  196. package/dist/drizzle/0178_workflow_backfill_existing_routines.sql +525 -0
  197. package/dist/drizzle/0179_tenant_goal_budget_settings.sql +21 -0
  198. package/dist/drizzle/0180_skill_drafts.sql +89 -0
  199. package/dist/drizzle/0181_skill_draft_publish_status.sql +21 -0
  200. package/dist/drizzle/0182_agent_loops.sql +260 -0
  201. package/dist/drizzle/0183_okf_artifact_manifests.sql +52 -0
  202. package/dist/drizzle/0184_skill_catalog_trust_cache.sql +35 -0
  203. package/dist/drizzle/0185_agent_loop_space.sql +16 -0
  204. package/dist/drizzle/0186_tenant_mcp_runtime_metadata.sql +10 -0
  205. package/dist/drizzle/0187_native_work_items.sql +220 -0
  206. package/dist/drizzle/0188_company_etl_plugin_rename.sql +99 -0
  207. package/dist/drizzle/0189_trace_cost_substrate.sql +247 -0
  208. package/dist/drizzle/0190_billing_export_reconciliation.sql +101 -0
  209. package/dist/drizzle/0191_open_engine_work_item_queue.sql +66 -0
  210. package/dist/drizzle/0192_work_item_labels.sql +44 -0
  211. package/dist/drizzle/0193_work_item_documents.sql +31 -0
  212. package/dist/drizzle/0194_memory_retain_attempts.sql +103 -0
  213. package/dist/drizzle/0194_work_item_comments.sql +35 -0
  214. package/dist/drizzle/0195_plugin_app_overlays.sql +45 -0
  215. package/dist/drizzle/0196_pi_extensions.sql +118 -0
  216. package/dist/drizzle/0197_eval_profiles.sql +150 -0
  217. package/dist/drizzle/0198_eval_runs_unstable.sql +27 -0
  218. package/dist/drizzle/0199_eval_runs_cost_partial.sql +27 -0
  219. package/dist/drizzle/meta/_journal.json +160 -0
  220. package/dist/plugins/README.md +162 -0
  221. package/dist/plugins/catalog/package.json +40 -0
  222. package/dist/plugins/catalog/scripts/build-catalog.ts +120 -0
  223. package/dist/plugins/catalog/scripts/generate-plugin-registry.ts +144 -0
  224. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +86 -0
  225. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +308 -0
  226. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +861 -0
  227. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +124 -0
  228. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +120 -0
  229. package/dist/plugins/catalog/src/catalog.ts +434 -0
  230. package/dist/plugins/catalog/src/contracts.ts +1498 -0
  231. package/dist/plugins/catalog/src/index.ts +4 -0
  232. package/dist/plugins/catalog/src/plugin-package.ts +171 -0
  233. package/dist/plugins/catalog/src/registry/generated-first-party.ts +76 -0
  234. package/dist/plugins/catalog/src/registry/index.ts +1 -0
  235. package/dist/plugins/catalog/tsconfig.json +8 -0
  236. package/dist/plugins/company-brain/README.md +37 -0
  237. package/dist/plugins/company-brain/package.json +33 -0
  238. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +11 -0
  239. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +458 -0
  240. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +539 -0
  241. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +312 -0
  242. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +449 -0
  243. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +655 -0
  244. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +474 -0
  245. package/dist/plugins/company-brain/src/api/cognee-client.ts +896 -0
  246. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +49 -0
  247. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +84 -0
  248. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +888 -0
  249. package/dist/plugins/company-brain/src/api/migration.ts +665 -0
  250. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +396 -0
  251. package/dist/plugins/company-brain/src/index.ts +62 -0
  252. package/dist/plugins/company-brain/src/manifest.ts +112 -0
  253. package/dist/plugins/company-brain/terraform/cognee/README.md +263 -0
  254. package/dist/plugins/company-brain/terraform/cognee/main.tf +766 -0
  255. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +144 -0
  256. package/dist/plugins/company-brain/terraform/cognee/variables.tf +402 -0
  257. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +562 -0
  258. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +74 -0
  259. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +447 -0
  260. package/dist/plugins/company-brain/test/api/migration.test.ts +465 -0
  261. package/dist/plugins/company-brain/test/manifest.test.ts +95 -0
  262. package/dist/plugins/company-brain/tsconfig.json +8 -0
  263. package/dist/plugins/company-data/README.md +49 -0
  264. package/dist/plugins/company-data/package.json +21 -0
  265. package/dist/plugins/company-data/src/index.ts +30 -0
  266. package/dist/plugins/company-data/src/manifest.ts +26 -0
  267. package/dist/plugins/company-data/test/manifest.test.ts +116 -0
  268. package/dist/plugins/company-data/tsconfig.json +8 -0
  269. package/dist/plugins/company-etl/README.md +46 -0
  270. package/dist/plugins/company-etl/package.json +21 -0
  271. package/dist/plugins/company-etl/src/index.ts +29 -0
  272. package/dist/plugins/company-etl/src/manifest.ts +26 -0
  273. package/dist/plugins/company-etl/test/manifest.test.ts +108 -0
  274. package/dist/plugins/company-etl/tsconfig.json +8 -0
  275. package/dist/plugins/email-channel/README.md +41 -0
  276. package/dist/plugins/email-channel/package.json +22 -0
  277. package/dist/plugins/email-channel/src/index.ts +36 -0
  278. package/dist/plugins/email-channel/src/manifest.ts +44 -0
  279. package/dist/plugins/email-channel/src/provider-contract.ts +36 -0
  280. package/dist/plugins/email-channel/test/manifest.test.ts +119 -0
  281. package/dist/plugins/email-channel/tsconfig.json +8 -0
  282. package/dist/plugins/lastmile/README.md +50 -0
  283. package/dist/plugins/lastmile/package.json +23 -0
  284. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +661 -0
  285. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +572 -0
  286. package/dist/plugins/lastmile/src/discovery.fixture.ts +64 -0
  287. package/dist/plugins/lastmile/src/index.ts +46 -0
  288. package/dist/plugins/lastmile/src/manifest.ts +139 -0
  289. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +266 -0
  290. package/dist/plugins/lastmile/test/discovery.test.ts +88 -0
  291. package/dist/plugins/lastmile/tsconfig.json +8 -0
  292. package/dist/plugins/n8n/README.md +201 -0
  293. package/dist/plugins/n8n/n8n-app/README.md +33 -0
  294. package/dist/plugins/n8n/n8n-app/package.json +23 -0
  295. package/dist/plugins/n8n/n8n-app/src/application-config.ts +45 -0
  296. package/dist/plugins/n8n/n8n-app/src/front-components/thinkwork-workflows.front-component.tsx +970 -0
  297. package/dist/plugins/n8n/n8n-app/src/lib/n8n-app-data.ts +190 -0
  298. package/dist/plugins/n8n/n8n-app/src/vite-env.d.ts +7 -0
  299. package/dist/plugins/n8n/n8n-app/tsconfig.json +15 -0
  300. package/dist/plugins/n8n/package.json +35 -0
  301. package/dist/plugins/n8n/runtime/Dockerfile +18 -0
  302. package/dist/plugins/n8n/runtime/n8n-task-runners.json +14 -0
  303. package/dist/plugins/n8n/runtime/n8n-task-runners.json.template +14 -0
  304. package/dist/plugins/n8n/runtime/package-lock.json +13 -0
  305. package/dist/plugins/n8n/runtime/package.json +7 -0
  306. package/dist/plugins/n8n/scripts/sync-thinkwork-app.mjs +268 -0
  307. package/dist/plugins/n8n/smoke/README.md +95 -0
  308. package/dist/plugins/n8n/smoke/n8n-agent-step-bridge-smoke.mjs +866 -0
  309. package/dist/plugins/n8n/smoke/n8n-integrated-app-smoke.mjs +262 -0
  310. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +409 -0
  311. package/dist/plugins/n8n/smoke/n8n-mcp-smoke.mjs +624 -0
  312. package/dist/plugins/n8n/src/deployment/image-build.ts +172 -0
  313. package/dist/plugins/n8n/src/deployment/managed-app.ts +429 -0
  314. package/dist/plugins/n8n/src/index.ts +79 -0
  315. package/dist/plugins/n8n/src/manifest.ts +403 -0
  316. package/dist/plugins/n8n/src/package-config.ts +186 -0
  317. package/dist/plugins/n8n/src/skills/n8n-workflow-operator/SKILL.md +76 -0
  318. package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  319. package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  320. package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/workflow-authoring.md +45 -0
  321. package/dist/plugins/n8n/terraform/n8n/README.md +113 -0
  322. package/dist/plugins/n8n/terraform/n8n/main.tf +918 -0
  323. package/dist/plugins/n8n/terraform/n8n/outputs.tf +124 -0
  324. package/dist/plugins/n8n/terraform/n8n/scripts/sync-database.py +250 -0
  325. package/dist/plugins/n8n/terraform/n8n/scripts/test_sync_database.py +55 -0
  326. package/dist/plugins/n8n/terraform/n8n/variables.tf +354 -0
  327. package/dist/plugins/n8n/test/image-build.test.ts +118 -0
  328. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +353 -0
  329. package/dist/plugins/n8n/test/manifest.test.ts +278 -0
  330. package/dist/plugins/n8n/test/package-config.test.ts +57 -0
  331. package/dist/plugins/n8n/tsconfig.json +8 -0
  332. package/dist/plugins/sendgrid/README.md +30 -0
  333. package/dist/plugins/sendgrid/package.json +22 -0
  334. package/dist/plugins/sendgrid/src/index.ts +36 -0
  335. package/dist/plugins/sendgrid/src/manifest.ts +37 -0
  336. package/dist/plugins/sendgrid/src/provider-contract.ts +14 -0
  337. package/dist/plugins/sendgrid/test/manifest.test.ts +80 -0
  338. package/dist/plugins/sendgrid/tsconfig.json +8 -0
  339. package/dist/plugins/twenty/README.md +78 -0
  340. package/dist/plugins/twenty/package.json +27 -0
  341. package/dist/plugins/twenty/scripts/__tests__/sync-thinkwork-app.test.mjs +67 -0
  342. package/dist/plugins/twenty/scripts/sync-thinkwork-app.mjs +185 -0
  343. package/dist/plugins/twenty/scripts/wire-thinkwork-workflow.mjs +658 -0
  344. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +359 -0
  345. package/dist/plugins/twenty/smoke/twenty-mcp-oauth-smoke.mjs +574 -0
  346. package/dist/plugins/twenty/src/api/cutover.ts +209 -0
  347. package/dist/plugins/twenty/src/deployment/managed-app.ts +253 -0
  348. package/dist/plugins/twenty/src/index.ts +55 -0
  349. package/dist/plugins/twenty/src/manifest.ts +259 -0
  350. package/dist/plugins/twenty/terraform/twenty/README.md +97 -0
  351. package/dist/plugins/twenty/terraform/twenty/main.tf +829 -0
  352. package/dist/plugins/twenty/terraform/twenty/outputs.tf +79 -0
  353. package/dist/plugins/twenty/terraform/twenty/variables.tf +268 -0
  354. package/dist/plugins/twenty/test/api/cutover.test.ts +183 -0
  355. package/dist/plugins/twenty/test/manifest.test.ts +463 -0
  356. package/dist/plugins/twenty/tsconfig.json +9 -0
  357. package/dist/plugins/twenty/twenty-app/.nvmrc +1 -0
  358. package/dist/plugins/twenty/twenty-app/README.md +148 -0
  359. package/dist/plugins/twenty/twenty-app/package.json +30 -0
  360. package/dist/plugins/twenty/twenty-app/src/application-config.ts +44 -0
  361. package/dist/plugins/twenty/twenty-app/src/constants/universal-identifiers.ts +18 -0
  362. package/dist/plugins/twenty/twenty-app/src/default-role.ts +24 -0
  363. package/dist/plugins/twenty/twenty-app/src/front-components/thinkwork-settings.front-component.tsx +299 -0
  364. package/dist/plugins/twenty/twenty-app/src/logic-functions/thinkwork-webhook.logic-function.ts +354 -0
  365. package/dist/plugins/twenty/twenty-app/tsconfig.json +14 -0
  366. package/dist/plugins/twenty/twenty-app/yarn.lock +4144 -0
  367. package/dist/plugins/workos-auth/package.json +22 -0
  368. package/dist/plugins/workos-auth/src/index.ts +30 -0
  369. package/dist/plugins/workos-auth/src/manifest.ts +40 -0
  370. package/dist/plugins/workos-auth/src/provider-contract.ts +39 -0
  371. package/dist/plugins/workos-auth/test/manifest.test.ts +94 -0
  372. package/dist/plugins/workos-auth/tsconfig.json +8 -0
  373. package/dist/scripts/bootstrap-workspace.sh +162 -0
  374. package/dist/scripts/post-deploy.sh +290 -0
  375. package/dist/terraform/examples/greenfield/main.tf +903 -75
  376. package/dist/terraform/examples/greenfield/terraform.tfvars.example +67 -8
  377. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +2 -2
  378. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +8 -9
  379. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +227 -0
  380. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +277 -0
  381. package/dist/terraform/modules/app/agentcore-memory/README.md +6 -6
  382. package/dist/terraform/modules/app/agentcore-memory/main.tf +1 -1
  383. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +40 -9
  384. package/dist/terraform/modules/app/agentcore-pi/README.md +61 -0
  385. package/dist/terraform/modules/app/agentcore-pi/main.tf +453 -0
  386. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +38 -0
  387. package/dist/terraform/modules/app/agentcore-pi/variables.tf +145 -0
  388. package/dist/terraform/modules/app/agentcore-platform/main.tf +83 -0
  389. package/dist/terraform/modules/app/agentcore-runtime/main.tf +9 -11
  390. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +5 -1
  391. package/dist/terraform/modules/app/customer-domain/main.tf +483 -0
  392. package/dist/terraform/modules/app/deployment-control-plane/README.md +47 -0
  393. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +24 -0
  394. package/dist/terraform/modules/app/deployment-control-plane/main.tf +614 -0
  395. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +54 -0
  396. package/dist/terraform/modules/app/deployment-control-plane/runner.py +4720 -0
  397. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +3026 -0
  398. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +96 -0
  399. package/dist/terraform/modules/app/hindsight-memory/README.md +25 -23
  400. package/dist/terraform/modules/app/hindsight-memory/main.tf +26 -2
  401. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +17 -50
  402. package/dist/terraform/modules/app/lambda-api/handlers.tf +1290 -679
  403. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +933 -0
  404. package/dist/terraform/modules/app/lambda-api/main.tf +14 -527
  405. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +3 -19
  406. package/dist/terraform/modules/app/lambda-api/outputs.tf +50 -6
  407. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +163 -0
  408. package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +3 -1
  409. package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +3 -1
  410. package/dist/terraform/modules/app/lambda-api/variables.tf +240 -53
  411. package/dist/terraform/modules/app/lambda-api/workspace-events.tf +2 -25
  412. package/dist/terraform/modules/app/ses-email/main.tf +143 -30
  413. package/dist/terraform/modules/app/static-site/main.tf +4 -3
  414. package/dist/terraform/modules/app/www-dns/main.tf +45 -5
  415. package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
  416. package/dist/terraform/modules/app/www-dns/variables.tf +24 -0
  417. package/dist/terraform/modules/data/aurora-postgres/main.tf +15 -1
  418. package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +1 -1
  419. package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +6 -0
  420. package/dist/terraform/modules/foundation/cognito/main.tf +167 -11
  421. package/dist/terraform/modules/foundation/cognito/outputs.tf +5 -0
  422. package/dist/terraform/modules/foundation/cognito/variables.tf +110 -0
  423. package/dist/terraform/modules/foundation/vpc/main.tf +45 -3
  424. package/dist/terraform/modules/foundation/vpc/outputs.tf +20 -0
  425. package/dist/terraform/modules/foundation/vpc/variables.tf +18 -0
  426. package/dist/terraform/modules/thinkwork/README.md +71 -0
  427. package/dist/terraform/modules/thinkwork/main.tf +1325 -174
  428. package/dist/terraform/modules/thinkwork/outputs.tf +457 -24
  429. package/dist/terraform/modules/thinkwork/variables.tf +1169 -93
  430. package/dist/terraform/schema.graphql +82 -9
  431. package/dist/workspace-defaults/files/AGENTS.md +344 -0
  432. package/dist/workspace-defaults/files/CONTEXT.md +8 -0
  433. package/dist/workspace-defaults/files/GUARDRAILS.md +51 -0
  434. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +65 -0
  435. package/dist/workspace-defaults/files/ROUTER.md +24 -0
  436. package/dist/workspace-defaults/files/SPACE.md +62 -0
  437. package/dist/workspace-defaults/files/TOOLS.md +65 -0
  438. package/dist/workspace-defaults/files/USER.md +40 -0
  439. package/dist/workspace-defaults/files/mcp.json +3 -0
  440. package/dist/workspace-defaults/files/memory/contacts.md +13 -0
  441. package/dist/workspace-defaults/files/memory/lessons.md +13 -0
  442. package/dist/workspace-defaults/files/memory/preferences.md +12 -0
  443. package/dist/workspace-defaults/files/skills/.gitkeep +1 -0
  444. package/dist/workspace-defaults/files/skills/artifact-builder/SKILL.md +79 -0
  445. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +166 -0
  446. package/dist/workspace-defaults/files/skills/automation-loop-designer/SKILL.md +120 -0
  447. package/dist/workspace-defaults/files/skills/skill-creator/LICENSE.txt +202 -0
  448. package/dist/workspace-defaults/files/skills/skill-creator/SKILL.md +485 -0
  449. package/dist/workspace-defaults/files/skills/skill-creator/UPSTREAM.json +124 -0
  450. package/dist/workspace-defaults/files/skills/skill-creator/agents/analyzer.md +274 -0
  451. package/dist/workspace-defaults/files/skills/skill-creator/agents/comparator.md +202 -0
  452. package/dist/workspace-defaults/files/skills/skill-creator/agents/grader.md +223 -0
  453. package/dist/workspace-defaults/files/skills/skill-creator/assets/eval_review.html +146 -0
  454. package/dist/workspace-defaults/files/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  455. package/dist/workspace-defaults/files/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  456. package/dist/workspace-defaults/files/skills/skill-creator/references/schemas.md +430 -0
  457. package/dist/workspace-defaults/files/skills/skill-creator/scripts/__init__.py +0 -0
  458. package/dist/workspace-defaults/files/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  459. package/dist/workspace-defaults/files/skills/skill-creator/scripts/generate_report.py +326 -0
  460. package/dist/workspace-defaults/files/skills/skill-creator/scripts/improve_description.py +247 -0
  461. package/dist/workspace-defaults/files/skills/skill-creator/scripts/package_skill.py +136 -0
  462. package/dist/workspace-defaults/files/skills/skill-creator/scripts/quick_validate.py +103 -0
  463. package/dist/workspace-defaults/files/skills/skill-creator/scripts/run_eval.py +310 -0
  464. package/dist/workspace-defaults/files/skills/skill-creator/scripts/run_loop.py +328 -0
  465. package/dist/workspace-defaults/files/skills/skill-creator/scripts/utils.py +47 -0
  466. package/package.json +1 -1
  467. package/dist/terraform/modules/app/agentcore-flue/README.md +0 -58
  468. package/dist/terraform/modules/app/agentcore-flue/main.tf +0 -323
  469. package/dist/terraform/modules/app/agentcore-flue/outputs.tf +0 -23
  470. package/dist/terraform/modules/app/agentcore-flue/variables.tf +0 -97
  471. package/dist/terraform/modules/app/computer-runtime/README.md +0 -15
  472. package/dist/terraform/modules/app/computer-runtime/main.tf +0 -406
  473. package/dist/terraform/modules/app/computer-runtime/outputs.tf +0 -75
  474. package/dist/terraform/modules/app/computer-runtime/variables.tf +0 -66
@@ -10,16 +10,58 @@ locals {
10
10
  use_local_zips = var.lambda_zips_dir != ""
11
11
  eval_fanout_queue_url = local.deploy_lambda_handlers ? aws_sqs_queue.eval_fanout[0].url : ""
12
12
  runtime = "nodejs20.x"
13
-
14
- # Common environment variables shared by all API handlers
15
- common_env = {
16
- STAGE = var.stage
17
- DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
18
- DATABASE_SECRET_ARN = var.graphql_db_secret_arn
19
- DATABASE_HOST = var.db_cluster_endpoint
20
- DATABASE_NAME = var.database_name
21
- BUCKET_NAME = var.bucket_name
22
- USER_POOL_ID = var.user_pool_id
13
+ cognee_env = var.cognee_enabled ? {
14
+ # graphql-http is close to Lambda's 4 KB environment ceiling. Keep Cognee
15
+ # status in one compact value; stable names are derived in the resolver.
16
+ COGNEE = "${var.cognee_backend_mode}|${var.cognee_endpoint}"
17
+ } : {}
18
+ # Twenty managed-app status is DB-served (managed_applications +
19
+ # deployment jobs — plan 2026-06-12-001 U10); the TWENTY config key is
20
+ # retired. Cognee's env-var status projection above is unchanged.
21
+ optional_integration_handler_names = concat(
22
+ var.deployment_control_plane_enabled ? [] : [
23
+ # Host-only onboarding/deployment API. Customer foundations disable the
24
+ # deployment control plane, so release-based customer installs must not
25
+ # require this Lambda artifact or expose these routes.
26
+ "deployment-sessions",
27
+ ],
28
+ var.enable_stripe_billing ? [] : [
29
+ "stripe-checkout",
30
+ "stripe-webhook",
31
+ "stripe-portal",
32
+ "stripe-subscription",
33
+ ],
34
+ var.enable_slack_workspace_app ? [] : [
35
+ "oauth-authorize",
36
+ "oauth-callback",
37
+ "slack-events",
38
+ "slack-slash-command",
39
+ "slack-interactivity",
40
+ "slack-oauth-install",
41
+ ],
42
+ )
43
+
44
+ # Config-class configuration shared by all API handlers. As of plan
45
+ # 2026-06-11-006 U6 these keys live ONLY in the SSM runtime-config
46
+ # document (runtime-config.tf) — they are NOT injected as Lambda env.
47
+ # Code reads them through @thinkwork/runtime-config's getConfig(), whose
48
+ # env-wins merge still honors a hand-set env var as an incident override.
49
+ # The reader-coverage fixture test in apps/cli fails CI if a key in this
50
+ # map still has a direct process.env reader. Identity values (STAGE,
51
+ # AWS_ACCOUNT_ID, NODE_OPTIONS) and secrets (DATABASE_URL,
52
+ # API_AUTH_SECRET, APPSYNC_API_KEY) stay out of this map — identity stays
53
+ # env forever, secrets live in Secrets Manager (R4), never in the String
54
+ # document.
55
+ config_env = merge({
56
+ DATABASE_SECRET_ARN = var.graphql_db_secret_arn
57
+ DATABASE_HOST = var.db_cluster_endpoint
58
+ DATABASE_NAME = var.database_name
59
+ # BUCKET_NAME and USER_POOL_ID were duplicate aliases of WORKSPACE_BUCKET
60
+ # and COGNITO_USER_POOL_ID; GRAPHQL_API_KEY duplicated APPSYNC_API_KEY;
61
+ # THINKWORK_API_SECRET and EMAIL_HMAC_SECRET duplicated API_AUTH_SECRET
62
+ # (~310 serialized bytes total). graphql-http sits at Lambda's hard 4KB
63
+ # env ceiling (#2375) — every reader falls back to the canonical name.
64
+ # Same precedent as the APP_URL/WEB_URL dedupe below.
23
65
  COGNITO_USER_POOL_ID = var.user_pool_id
24
66
  ADMIN_CLIENT_ID = var.admin_client_id
25
67
  MOBILE_CLIENT_ID = var.mobile_client_id
@@ -29,12 +71,7 @@ locals {
29
71
  MCP_OAUTH_REVOCATIONS_TABLE = aws_dynamodb_table.mcp_oauth_revocations.name
30
72
  COGNITO_APP_CLIENT_IDS = "${var.admin_client_id},${var.mobile_client_id}"
31
73
  APPSYNC_ENDPOINT = var.appsync_api_url
32
- APPSYNC_API_KEY = var.appsync_api_key
33
- GRAPHQL_API_KEY = var.appsync_api_key
34
- API_AUTH_SECRET = var.api_auth_secret
35
- THINKWORK_API_SECRET = var.api_auth_secret
36
- EMAIL_HMAC_SECRET = var.api_auth_secret
37
- THINKWORK_API_URL = "https://${aws_apigatewayv2_api.main.id}.execute-api.${var.region}.amazonaws.com"
74
+ THINKWORK_API_URL = local.api_base_url
38
75
  # Comma-separated allowlist of caller emails permitted to invoke
39
76
  # operator-gated mutations (updateTenantPolicy, sandbox fixture
40
77
  # setup, etc.). Resolved against ctx.auth.email, which is pulled
@@ -43,32 +80,32 @@ locals {
43
80
  # packages/api/src/lib/cognito-auth.ts). Empty ⇒ the gate
44
81
  # rejects every call, which is the safe default pre-rollout.
45
82
  THINKWORK_PLATFORM_OPERATOR_EMAILS = var.platform_operator_emails
46
- AGENTCORE_FUNCTION_NAME = var.agentcore_function_name
47
- AGENTCORE_FLUE_FUNCTION_NAME = var.agentcore_flue_function_name
48
- # SSM parameter names for the Bedrock AgentCore Runtime IDs (one per
49
- # runtime type). deploy.yml's "Update AgentCore Runtimes" job writes
50
- # these in `update-agentcore-runtime-image.sh`. eval-runner reads them
51
- # via `loadRuntimeId(runtimeType)` to start a Bedrock-control-plane
52
- # invocation against the right runtime — pre-U3 the flue path was
53
- # dead because the env var was never wired here.
54
- AGENTCORE_RUNTIME_SSM_STRANDS = "/thinkwork/${var.stage}/agentcore/runtime-id-strands"
55
- AGENTCORE_RUNTIME_SSM_FLUE = "/thinkwork/${var.stage}/agentcore/runtime-id-flue"
56
- WORKSPACE_BUCKET = var.bucket_name
57
- HINDSIGHT_ENDPOINT = var.hindsight_endpoint
58
- AGENTCORE_MEMORY_ID = var.agentcore_memory_id
59
- MEMORY_ENGINE = var.memory_engine
60
- # Skip the SSM indirection for cross-function ARN lookup. Terraform
61
- # already knows this ARN at apply time and the Lambda role's SSM
62
- # permission has been a recurring source of silent failures where
63
- # getChatAgentInvokeFnArn falls back to null and sendMessage loses
64
- # message_history on the wakeup-processor fallback path.
65
- CHAT_AGENT_INVOKE_FN_ARN = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke"
66
- ADMIN_URL = var.admin_url
67
- DOCS_URL = var.docs_url
68
- APPSYNC_REALTIME_URL = var.appsync_realtime_url
69
- ECR_REPOSITORY_URL = var.ecr_repository_url
70
- AWS_ACCOUNT_ID = var.account_id
71
- NODE_OPTIONS = "--enable-source-maps"
83
+ AGENTCORE_PI_FUNCTION_NAME = var.agentcore_pi_function_name
84
+ # WORKSPACE_RENDERER_FUNCTION_NAME is derived from the per-stage naming
85
+ # convention by deriveFunctionName("workspace-renderer") stored
86
+ # nowhere (R7).
87
+ WORKSPACE_BUCKET = var.bucket_name
88
+ HINDSIGHT_ENDPOINT = var.hindsight_endpoint
89
+ AGENTCORE_MEMORY_ID = var.agentcore_memory_id
90
+ MEMORY_ENGINE = var.memory_engine
91
+ # CHAT_AGENT_INVOKE_FN_ARN (~112 serialized bytes) was dropped for the
92
+ # 4KB env ceiling (#2375): getChatAgentInvokeFnArn and managed-dispatch
93
+ # now derive the ARN from the deterministic naming pattern
94
+ # (AWS_REGION + AWS_ACCOUNT_ID + STAGE) before the SSM fallback, so the
95
+ # silent-SSM-failure path that originally motivated the env var stays
96
+ # closed without spending env bytes.
97
+ # APP_URL/WEB_URL were duplicate aliases of ADMIN_URL (all = var.admin_url)
98
+ # and pushed this Lambda's env block past AWS's hard 4KB limit, failing
99
+ # every Terraform apply (UpdateFunctionConfiguration 400 InvalidParameter:
100
+ # environment variables exceeded the 4KB limit). Every reader resolves via
101
+ # ADMIN_URL stripe-webhook/stripe-portal use `APP_URL || WEB_URL ||
102
+ # ADMIN_URL`, mcp-oauth-client's candidate list includes ADMIN_URL, and
103
+ # deploymentStatus reads ADMIN_URL directly — so the single canonical
104
+ # ADMIN_URL is sufficient. Re-add aliases only after env vars move to SSM.
105
+ ADMIN_URL = var.admin_url
106
+ DOCS_URL = var.docs_url
107
+ APPSYNC_REALTIME_URL = var.appsync_realtime_url
108
+ ECR_REPOSITORY_URL = var.ecr_repository_url
72
109
  # Per-user OAuth wiring (Google Workspace today; Microsoft 365 follow-up).
73
110
  # Secret ARNs are the indirection; the actual client_id/client_secret
74
111
  # values live in Secrets Manager and are fetched by
@@ -76,56 +113,96 @@ locals {
76
113
  # OAUTH_CALLBACK_URL is the URL registered with Google/Azure OAuth apps.
77
114
  # REDIRECT_SUCCESS_URL is the fallback post-OAuth redirect when the
78
115
  # caller doesn't pass a per-request returnUrl (mobile passes thinkwork://).
79
- GOOGLE_PRODUCTIVITY_OAUTH_SECRET_ARN = aws_secretsmanager_secret.oauth_google_productivity.arn
80
- OAUTH_CALLBACK_URL = "https://${aws_apigatewayv2_api.main.id}.execute-api.${var.region}.amazonaws.com/api/oauth/callback"
81
- REDIRECT_SUCCESS_URL = var.redirect_success_url
82
- COMPANY_BRAIN_SOURCE_AGENT_MODEL_ID = var.company_brain_source_agent_model_id
116
+ GOOGLE_PRODUCTIVITY_OAUTH_SECRET_ARN = aws_secretsmanager_secret.oauth_google_productivity.arn
117
+ OAUTH_CALLBACK_URL = "https://${aws_apigatewayv2_api.main.id}.execute-api.${var.region}.amazonaws.com/api/oauth/callback"
118
+ REDIRECT_SUCCESS_URL = var.redirect_success_url
119
+ COMPANY_BRAIN_SOURCE_AGENT_MODEL_ID = var.company_brain_source_agent_model_id
120
+ COMPANY_BRAIN_BACKDOOR_INSTALL_KEY_SECRET_ARN = var.company_brain_backdoor_install_key_secret_arn
121
+ COMPANY_BRAIN_BACKDOOR_INSTALL_KEY_STAGES = var.company_brain_backdoor_install_key_stages
122
+ WWW_URL = var.www_url
123
+ },
83
124
  # Stripe billing — see stripe-secrets.tf. The ARN is the indirection;
84
125
  # the actual keys live in Secrets Manager and are fetched by
85
126
  # packages/api/src/lib/stripe-credentials.ts at cold-start. Price IDs
86
127
  # are non-secret per-stage config carried as a plain JSON env var so
87
128
  # staging/prod can use different products without a secret rotation.
88
- STRIPE_CREDENTIALS_SECRET_ARN = aws_secretsmanager_secret.stripe_api_credentials.arn
89
- STRIPE_PRICE_IDS_JSON = var.stripe_price_ids_json
90
- STRIPE_CHECKOUT_SUCCESS_URL = "${var.admin_url}/onboarding/welcome?session_id={CHECKOUT_SESSION_ID}"
91
- STRIPE_CHECKOUT_CANCEL_URL = "${var.www_url}/cloud"
92
- WWW_URL = var.www_url
93
- # Override the welcome email's From: address. Defaults to
94
- # hello@agents.thinkwork.ai (the already-verified SES inbound domain);
95
- # set to hello@thinkwork.ai once the bare-apex identity is verified in SES.
96
- STRIPE_WELCOME_FROM_EMAIL = var.stripe_welcome_from_email
97
- }
98
-
99
- # Computer runtime control handlers only need database access, service-auth,
100
- # the API callback URL, and ECS/EFS runtime wiring. Using the full common_env
101
- # pushes computer-manager over Lambda's 4KB environment-variable limit in dev.
102
- computer_runtime_control_base_env = {
103
- STAGE = var.stage
104
- DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
105
- API_AUTH_SECRET = var.api_auth_secret
106
- THINKWORK_API_URL = "https://${aws_apigatewayv2_api.main.id}.execute-api.${var.region}.amazonaws.com"
107
- NODE_OPTIONS = "--enable-source-maps"
108
- }
109
-
110
- computer_runtime_control_env = {
111
- COMPUTER_RUNTIME_CLUSTER_NAME = var.computer_runtime_cluster_name
112
- COMPUTER_RUNTIME_EFS_FILE_SYSTEM_ID = var.computer_runtime_efs_file_system_id
113
- COMPUTER_RUNTIME_SUBNET_IDS = join(",", var.computer_runtime_subnet_ids)
114
- COMPUTER_RUNTIME_ASSIGN_PUBLIC_IP = var.computer_runtime_assign_public_ip
115
- COMPUTER_RUNTIME_TASK_SG_ID = var.computer_runtime_task_sg_id
116
- COMPUTER_RUNTIME_EXECUTION_ROLE_ARN = var.computer_runtime_execution_role_arn
117
- COMPUTER_RUNTIME_TASK_ROLE_ARN = var.computer_runtime_task_role_arn
118
- COMPUTER_RUNTIME_LOG_GROUP_NAME = var.computer_runtime_log_group_name
119
- COMPUTER_RUNTIME_REPOSITORY_URL = var.computer_runtime_repository_url
120
- COMPUTER_RUNTIME_DEFAULT_CPU = tostring(var.computer_runtime_default_cpu)
121
- COMPUTER_RUNTIME_DEFAULT_MEMORY = tostring(var.computer_runtime_default_memory)
129
+ # The whole block is omitted when Stripe is disabled (~315 serialized
130
+ # bytes): the stripe-* handlers are excluded from deployment in that
131
+ # case and stripe-plans.ts defaults to "{}" — see #2375 (4KB env cap).
132
+ var.enable_stripe_billing ? {
133
+ STRIPE_CREDENTIALS_SECRET_ARN = aws_secretsmanager_secret.stripe_api_credentials[0].arn
134
+ STRIPE_PRICE_IDS_JSON = var.stripe_price_ids_json
135
+ STRIPE_CHECKOUT_SUCCESS_URL = "${var.admin_url}/onboarding/welcome?session_id={CHECKOUT_SESSION_ID}"
136
+ STRIPE_CHECKOUT_CANCEL_URL = "${var.www_url}/cloud"
137
+ # Override the welcome email's From: address. Defaults to
138
+ # hello@agents.thinkwork.ai (the already-verified SES inbound domain);
139
+ # set to hello@thinkwork.ai once the bare-apex identity is verified in SES.
140
+ STRIPE_WELCOME_FROM_EMAIL = var.stripe_welcome_from_email
141
+ } : {},
142
+ local.cognee_env,
143
+ )
144
+
145
+ # graphql-http-only config that also belongs in the runtime-config
146
+ # document. Kept out of config_env so the legacy env copies stay scoped
147
+ # to graphql-http (handler_extra_env below) instead of growing every
148
+ # handler's env during the R8 transition window.
149
+ graphql_http_config_env = {
150
+ ROUTINES_EXECUTION_ROLE_ARN = var.routines_execution_role_arn
151
+ ROUTINES_LOG_GROUP_ARN = var.routines_log_group_arn
152
+ # Settings > General starts release updates from the GraphQL API.
153
+ DEPLOYMENT_STATE_MACHINE_ARN = var.deployment_state_machine_arn
154
+ DEPLOYMENT_EVIDENCE_BUCKET = var.deployment_evidence_bucket
155
+ # Cognee user + Space memory captures use explicit add+cognify so accepted
156
+ # documents enter the scoped graph. GraphQL has a 30s Lambda ceiling, so
157
+ # indexing wait is intentionally short/best-effort; callers poll search for
158
+ # eventual retrieval instead of pinning the capture request.
159
+ COGNEE_INGEST_MODE = "add_cognify"
160
+ COGNEE_INDEX_TIMEOUT_MS = "8000"
161
+ COGNEE_INDEX_POLL_MS = "2000"
162
+ # THNK-37 — the GraphQL API is the runtime trust boundary for the
163
+ # GitHub-hosted signed plugin catalog. Browsers keep reading through
164
+ # GraphQL; API verifies the release asset with the trusted public key
165
+ # and persists only verified snapshots in the primary workspace bucket.
166
+ THINKWORK_PLUGIN_CATALOG_SOURCE = "github"
167
+ THINKWORK_PLUGIN_CATALOG_REPOSITORY = "thinkwork-ai/thinkwork"
168
+ THINKWORK_PLUGIN_CATALOG_RELEASE_TAG = "plugin-catalog-main"
169
+ THINKWORK_PLUGIN_CATALOG_ASSET_NAME = "thinkwork-plugin-catalog-main.json"
170
+ THINKWORK_PLUGIN_CATALOG_CACHE_TTL_SECONDS = "300"
171
+ THINKWORK_PLUGIN_CATALOG_USER_AGENT = "thinkwork-api/${var.stage}"
172
+ THINKWORK_PLUGIN_CATALOG_CACHE_BUCKET = var.bucket_name
173
+ THINKWORK_PLUGIN_CATALOG_CACHE_KEY = "system/plugin-catalog/github-release-cache.json"
174
+ THINKWORK_PLUGIN_CATALOG_GITHUB_TOKEN_SECRET_ARN = var.plugin_catalog_github_token_secret_arn
175
+ # Phase 3 U10 — compliance read resolvers (complianceEvents,
176
+ # complianceEvent, complianceEventByHash) connect to Aurora as
177
+ # the compliance_reader role. The existing secrets-manager grant in
178
+ # aws_iam_policy.api_data_plane (iam-grouped.tf) grants
179
+ # secretsmanager:GetSecretValue on the thinkwork/* wildcard, so no
180
+ # new IAM resource is needed.
181
+ COMPLIANCE_READER_SECRET_ARN = var.compliance_reader_secret_arn
182
+ }
183
+
184
+ # Identity env + the secrets still in their one-release transition
185
+ # window (R8). Config-class keys live ONLY in the SSM runtime-config
186
+ # document now — adding a key here is guarded by the identity-allowlist
187
+ # fixture test in apps/cli (R10). Follow-up release: DATABASE_URL,
188
+ # APPSYNC_API_KEY, and API_AUTH_SECRET drop too (readers already resolve
189
+ # via Secrets Manager prefetch when the env copies are absent), bringing
190
+ # every handler under the ≤1KB R1 target.
191
+ common_env = {
192
+ STAGE = var.stage
193
+ DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
194
+ APPSYNC_API_KEY = var.appsync_api_key
195
+ API_AUTH_SECRET = var.api_auth_secret
196
+ AWS_ACCOUNT_ID = var.account_id
197
+ NODE_OPTIONS = "--enable-source-maps"
122
198
  }
123
199
 
124
200
  # Per-handler env-var overrides. ARNs are constructed from the naming
125
- # pattern (same trick as lambda_api_cross_invoke in main.tf) so we don't
126
- # introduce a self-referential dependency inside the handler for_each.
201
+ # pattern (same trick as the api-cross-function-invoke statement in
202
+ # iam-grouped.tf) so we don't introduce a self-referential dependency
203
+ # inside the handler for_each.
127
204
  slack_handler_env = {
128
- SLACK_APP_CREDENTIALS_SECRET_ARN = aws_secretsmanager_secret.slack_app_credentials.arn
205
+ SLACK_APP_CREDENTIALS_SECRET_ARN = var.enable_slack_workspace_app ? aws_secretsmanager_secret.slack_app_credentials[0].arn : ""
129
206
  }
130
207
 
131
208
  handler_extra_env = {
@@ -133,10 +210,27 @@ locals {
133
210
  EXTENSION_PROXY_BACKENDS_JSON = var.extension_proxy_backends_json
134
211
  EXTENSION_PROXY_SIGNING_SECRET = var.extension_proxy_signing_secret
135
212
  }
213
+ "trace-invocation-reconciler" = {
214
+ BEDROCK_INVOCATION_LOG_GROUP = local.bedrock_invocation_log_group_name
215
+ }
136
216
  "job-schedule-manager" = {
137
217
  JOB_TRIGGER_ARN = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-trigger"
138
218
  JOB_TRIGGER_ROLE_ARN = var.job_scheduler_role_arn
139
219
  }
220
+ "deployment-sessions" = {
221
+ THINKWORK_DEPLOYMENT_STATE_MACHINE_ARN = var.deployment_state_machine_arn
222
+ THINKWORK_DEPLOYMENT_EVIDENCE_BUCKET = var.deployment_evidence_bucket
223
+ THINKWORK_RELEASE_VERSION = var.deployment_release_version
224
+ THINKWORK_RELEASE_MANIFEST_URL = var.deployment_release_manifest_url
225
+ THINKWORK_RELEASE_MANIFEST_SHA256 = var.deployment_release_manifest_sha256
226
+ THINKWORK_BOOTSTRAP_LEASE_SECRET_PREFIX = "thinkwork/${var.stage}/deployment-bootstrap-leases"
227
+ THINKWORK_BOOTSTRAP_LEASE_KMS_KEY_ID = var.bootstrap_credential_lease_kms_key_id
228
+ }
229
+ "graphql-http" = {
230
+ THINKWORK_RELEASE_VERSION = var.deployment_release_version
231
+ THINKWORK_RELEASE_MANIFEST_URL = var.deployment_release_manifest_url
232
+ THINKWORK_RELEASE_MANIFEST_SHA256 = var.deployment_release_manifest_sha256
233
+ }
140
234
  # Compounding Memory compile Lambda. Any Converse-compatible Bedrock
141
235
  # model works; the planner + section-writer cap themselves at ~500
142
236
  # records / 25 new pages per invocation so a 480 s timeout covers
@@ -147,6 +241,10 @@ locals {
147
241
  BEDROCK_MODEL_ID = var.wiki_compile_model_id
148
242
  WIKI_AGGREGATION_PASS_ENABLED = var.wiki_aggregation_pass_enabled
149
243
  WIKI_DETERMINISTIC_LINKING_ENABLED = var.wiki_deterministic_linking_enabled
244
+ # Wiki pipeline source dispatch (plan 2026-06-09-004 U10):
245
+ # 'planner' (default, LLM compile) | 'graph' (deterministic
246
+ # graph→wiki materializer over the knowledge-graph mirror).
247
+ WIKI_SOURCE = var.wiki_source
150
248
  # Name (not value) of the SecureString SSM parameter that holds the
151
249
  # Google Places API key. wiki-compile fetches + caches on cold start.
152
250
  # The parameter may contain a placeholder value at apply time — the
@@ -157,15 +255,15 @@ locals {
157
255
  BEDROCK_MODEL_ID = var.wiki_compile_model_id
158
256
  }
159
257
  "wiki-export" = {
160
- WIKI_EXPORT_BUCKET = aws_s3_bucket.wiki_exports.bucket
258
+ WIKI_EXPORT_BUCKET = aws_s3_bucket.wiki_exports.bucket
259
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
260
+ }
261
+ "okf-materialize" = {
262
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
161
263
  }
162
- # workspace-files invokes the workspace-files-efs sidecar (Request
163
- # Response) for Computer-target list/get to bypass the computer_tasks
164
- # queue. ARN constructed from the naming pattern to avoid a self-
165
- # referential dependency on the standalone Lambda resource defined at
166
- # the bottom of this file.
167
- "workspace-files" = {
168
- WORKSPACE_FILES_EFS_FN_ARN = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-files-efs"
264
+ "okf-efs-refresh" = {
265
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
266
+ OKF_EFS_ROOT = var.okf_efs_mount_path
169
267
  }
170
268
  "oauth-authorize" = local.slack_handler_env
171
269
  "oauth-callback" = local.slack_handler_env
@@ -173,23 +271,6 @@ locals {
173
271
  "slack-slash-command" = local.slack_handler_env
174
272
  "slack-interactivity" = local.slack_handler_env
175
273
  "slack-oauth-install" = local.slack_handler_env
176
- "slack-dispatch" = local.slack_handler_env
177
- # computer-terminal-start needs the cluster name to scope its
178
- # ECS ListTasks / DescribeTasks / ExecuteCommand calls.
179
- "computer-terminal-start" = {
180
- COMPUTER_RUNTIME_CLUSTER_NAME = var.computer_runtime_cluster_name
181
- }
182
- # computer-manager and computer-runtime-reconciler consume ECS/EFS task
183
- # config from packages/api/src/lib/computers/runtime-control.ts.
184
- # Scoping the COMPUTER_RUNTIME_* variables here (instead of in
185
- # local.common_env_vars) keeps the per-Lambda env-var payload under
186
- # the AWS 4KB hard limit — they were previously dumped into every
187
- # handler and pushed ~70 Lambdas over quota.
188
- "computer-manager" = local.computer_runtime_control_env
189
- "computer-runtime-reconciler" = local.computer_runtime_control_env
190
- "computer-runtime" = {
191
- REQUESTER_IDLE_MEMORY_LEARNING_ENABLED = tostring(var.requester_idle_memory_learning_enabled)
192
- }
193
274
  "thread-attachments-finalize" = {
194
275
  REQUESTER_IDLE_MEMORY_LEARNING_ENABLED = tostring(var.requester_idle_memory_learning_enabled)
195
276
  }
@@ -201,6 +282,48 @@ locals {
201
282
  CONTEXT_ENGINE_MEMORY_QUERY_MODE = "reflect"
202
283
  CONTEXT_ENGINE_MEMORY_TIMEOUT_MS = "20000"
203
284
  }
285
+ # Agent graph access (plan 2026-06-09-004 U8): stage gate for the Pi
286
+ # knowledge_graph_search tool; the per-agent tool policy gates on top.
287
+ "chat-agent-invoke" = {
288
+ KNOWLEDGE_GRAPH_TOOL_ENABLED = tostring(var.knowledge_graph_tool_enabled)
289
+ }
290
+ "memory-retain" = {
291
+ HINDSIGHT_TIMEOUT_MS = "75000"
292
+ }
293
+ # Bedrock KB provisioning. Per-handler (not common_env) so these don't bloat
294
+ # the already-near-4KB graphql-http env. Bedrock's RDS-backed KB needs the
295
+ # cluster ARN + the KB service role (passed at CreateKnowledgeBase time).
296
+ "knowledge-base-manager" = {
297
+ KB_SERVICE_ROLE_ARN = var.kb_service_role_arn
298
+ DATABASE_CLUSTER_ARN = var.db_cluster_arn
299
+ }
300
+ "knowledge-graph-thread-ingest" = {
301
+ COGNEE_ENDPOINT = var.cognee_endpoint
302
+ COGNEE_BACKEND_MODE = var.cognee_backend_mode
303
+ COGNEE_INGEST_MODE = "remember"
304
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
305
+ }
306
+ # Observations → Knowledge Graph worker (plan 2026-06-09-004 U5).
307
+ # add_cognify pins the incremental ingest path into the stable
308
+ # per-tenant dataset; the promotion-gate classifier reads
309
+ # OBSERVATION_CLASSIFIER_MODEL_ID (Bedrock IAM via the shared
310
+ # lambda_bedrock invoke policy).
311
+ "knowledge-graph-observations-ingest" = {
312
+ COGNEE_ENDPOINT = var.cognee_endpoint
313
+ COGNEE_BACKEND_MODE = var.cognee_backend_mode
314
+ COGNEE_INGEST_MODE = "add_cognify"
315
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
316
+ OBSERVATION_CLASSIFIER_MODEL_ID = var.observation_classifier_model_id
317
+ # Per-run candidate cap: bounds classifier cost AND keeps each Cognee
318
+ # cognify small enough to index within budget on the single dogfood
319
+ # task; truncated runs self-invoke (Event) to drain the backlog across
320
+ # successive runs. COGNEE_INDEX_TIMEOUT_MS raised well above the default
321
+ # 240s (dogfood indexing of a fresh dataset is slow) but under the 900s
322
+ # Lambda ceiling, leaving room for graph fetch + normalize + snapshot.
323
+ KG_OBS_MAX_CANDIDATES_PER_RUN = var.kg_obs_max_candidates_per_run
324
+ COGNEE_INDEX_TIMEOUT_MS = "700000"
325
+ COGNEE_INDEX_POLL_MS = "7000"
326
+ }
204
327
  # routine-task-python (Phase B U6) needs the AgentCore code-interpreter
205
328
  # id + the per-stage S3 routine-output bucket. The interpreter id is
206
329
  # provisioned by the agentcore-code-interpreter module and exposed via
@@ -215,64 +338,53 @@ locals {
215
338
  # graphql-http hosts the createRoutine / publishRoutineVersion / etc.
216
339
  # resolvers (Phase B U7) AND the routine-approval-bridge (Phase B
217
340
  # U8) which invokes routine-resume via the AWS SDK.
218
- "graphql-http" = {
219
- ROUTINES_EXECUTION_ROLE_ARN = var.routines_execution_role_arn
220
- ROUTINES_LOG_GROUP_ARN = var.routines_log_group_arn
221
- AWS_ACCOUNT_ID = var.account_id
222
- # routine-approval-bridge (Phase B U8) calls this function name
223
- # via the AWS SDK Lambda Invoke after a HITL decideInboxItem.
224
- # The bridge throws if unset terraform wiring is mandatory.
225
- ROUTINE_RESUME_FUNCTION_NAME = "thinkwork-${var.stage}-api-routine-resume"
226
- # triggerRoutineRun seeds this into the SFN execution input so the
227
- # inbox_approval recipe Task can find the callback Lambda via
228
- # $$.Execution.Input.inboxApprovalFunctionName.
229
- ROUTINE_APPROVAL_CALLBACK_FUNCTION_NAME = "thinkwork-${var.stage}-api-routine-approval-callback"
230
- EMAIL_SEND_FUNCTION_NAME = "thinkwork-${var.stage}-api-email-send"
231
- ROUTINE_TASK_PYTHON_FUNCTION_NAME = "thinkwork-${var.stage}-api-routine-task-python"
232
- ADMIN_OPS_MCP_FUNCTION_NAME = "thinkwork-${var.stage}-api-admin-ops-mcp"
233
- SLACK_SEND_FUNCTION_NAME = "thinkwork-${var.stage}-api-slack-send"
234
- # requester idle memory learning defaults on in API code so this
235
- # env-heavy Lambda can stay below AWS's 4 KB environment limit.
236
- # Phase 3 U10 — compliance read resolvers (complianceEvents,
237
- # complianceEvent, complianceEventByHash) connect to Aurora as
238
- # the compliance_reader role. The existing lambda_secrets policy
239
- # in main.tf grants secretsmanager:GetSecretValue on the
240
- # thinkwork/* wildcard, so no new IAM resource is needed.
241
- COMPLIANCE_READER_SECRET_ARN = var.compliance_reader_secret_arn
242
- # Phase 3 U11.U2 — createComplianceExport mutation dispatches a
243
- # jobId to a known-name SQS queue. We do NOT pass the queue URL
244
- # as an env var here: graphql-http's env block is already at the
245
- # AWS 4 KB ceiling, and adding another URL pushed the deploy over
246
- # the limit. The mutation derives the URL from STAGE + AWS_REGION
247
- # + AWS_ACCOUNT_ID, which the Lambda already has. The runner
248
- # Lambda (separate function below) keeps an explicit
249
- # COMPLIANCE_EXPORTS_QUEUE_URL because its env is small.
250
- }
341
+ # graphql-http's former env extras now ride the SSM runtime-config
342
+ # document (local.graphql_http_config_env feeds the document body in
343
+ # runtime-config.tf). The thinkwork-<stage>-api-* function names the
344
+ # routines bridge/dispatch paths use are derived from STAGE at call
345
+ # time (runtimeFunctionName/deriveFunctionName R7), and the
346
+ # compliance-export queue URL is derived from STAGE + AWS_REGION +
347
+ # AWS_ACCOUNT_ID, so none of them are stored anywhere.
251
348
  # U2 eval fan-out substrate. eval-runner does not dispatch to this
252
349
  # queue until U3; eval-worker is a throwing inert stub that redrives
253
350
  # accidental traffic to the DLQ.
254
351
  "eval-runner" = {
255
352
  EVAL_FANOUT_QUEUE_URL = local.eval_fanout_queue_url
256
353
  EVAL_DIRECT_AGENTCORE_MESSAGE_SHARDS = "20"
354
+ # SSM parameter name for the Pi Bedrock AgentCore Runtime ID. deploy.yml's
355
+ # runtime update job writes this in `update-agentcore-runtime-image.sh`;
356
+ # eval-runner reads it via `loadRuntimeId(runtimeType)`.
357
+ AGENTCORE_RUNTIME_SSM_PI = "/thinkwork/${var.stage}/agentcore/runtime-id-pi"
257
358
  }
258
359
  "eval-worker" = {
259
360
  EVAL_FANOUT_QUEUE_URL = local.eval_fanout_queue_url
260
361
  EVAL_AGENTCORE_EVALUATORS = "disabled"
362
+ # Mirrors the fan-out queue's redrive maxReceiveCount (same local in
363
+ # eval-fanout.tf — no drift possible) so the worker can detect the
364
+ # final SQS receive and record error/throttle instead of letting the
365
+ # case vanish into the DLQ without a result row.
366
+ EVAL_FANOUT_MAX_RECEIVE_COUNT = tostring(local.eval_fanout_max_receive_count)
367
+ # Enables the real Bedrock Converse llm-rubric judge (U12). Without
368
+ # this the worker passes no judge and non-refusal quality rubrics
369
+ # fall back to a heuristic that can never honestly score them — the
370
+ # vacuous-pass trust bug. EVAL_JUDGE_MODEL_ID matches the default in
371
+ # in-house.ts; the api-ai IAM policy already grants
372
+ # bedrock:InvokeModel(+WithResponseStream) on inference-profile/*,
373
+ # which authorizes Converse with this profile ID.
374
+ EVAL_LLM_JUDGE = "1"
375
+ EVAL_JUDGE_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
261
376
  }
262
- # job-trigger fires scheduled routine runs via SFN.StartExecution
263
- # (Phase B U7) the alias ARN comes from the row, but the Lambda
264
- # also reads AWS_ACCOUNT_ID for diagnostic logging. It also passes
265
- # the routine-approval-callback function name in the SFN execution
266
- # input so the inbox_approval recipe can fanout to it on .waitForTaskToken.
267
- "job-trigger" = {
268
- AWS_ACCOUNT_ID = var.account_id
269
- ROUTINE_APPROVAL_CALLBACK_FUNCTION_NAME = "thinkwork-${var.stage}-api-routine-approval-callback"
270
- EMAIL_SEND_FUNCTION_NAME = "thinkwork-${var.stage}-api-email-send"
271
- ROUTINE_TASK_PYTHON_FUNCTION_NAME = "thinkwork-${var.stage}-api-routine-task-python"
272
- ADMIN_OPS_MCP_FUNCTION_NAME = "thinkwork-${var.stage}-api-admin-ops-mcp"
273
- SLACK_SEND_FUNCTION_NAME = "thinkwork-${var.stage}-api-slack-send"
274
- THREAD_IDLE_MEMORY_LEARNING_FUNCTION_NAME = "thinkwork-${var.stage}-api-thread-idle-memory-learning"
377
+ # THNK-74 U4 optional AWS billing export importer. Missing bucket/key is
378
+ # a healthy no-op so stages can ship the reconciler before CUR/Data Export
379
+ # delivery is configured out-of-band in the AWS billing account.
380
+ "cost-bill-reconciler" = {
381
+ BILLING_EXPORT_BUCKET = var.billing_export_bucket_name
382
+ BILLING_EXPORT_MANIFEST_KEY = var.billing_export_manifest_key
383
+ BILLING_RECONCILIATION_TOLERANCE_USD = tostring(var.billing_reconciliation_tolerance_usd)
275
384
  }
385
+ # job-trigger's thinkwork-<stage>-api-* worker function names are
386
+ # derived from STAGE at call time (runtimeFunctionName — R7), and
387
+ # AWS_ACCOUNT_ID already rides common_env, so it needs no extras.
276
388
  # Phase 3 U4 Compliance outbox drainer.
277
389
  # Connects to Aurora as the compliance_drainer role (provisioned in
278
390
  # U2). The DATABASE_SECRET_ARN-style indirection is via
@@ -284,16 +396,86 @@ locals {
284
396
  }
285
397
  }
286
398
 
399
+ resource "aws_lambda_function" "skill_trust_runner" {
400
+ # Gated on the static ecr_repository_provisioned flag: the repository URL is
401
+ # an apply-time-unknown attribute in a fresh account, and count cannot
402
+ # depend on it ("Invalid count argument" — THINK-118 harness cycle 4).
403
+ count = local.deploy_lambda_handlers && var.ecr_repository_provisioned ? 1 : 0
404
+
405
+ function_name = "thinkwork-${var.stage}-skill-trust-runner"
406
+ role = aws_iam_role.lambda.arn
407
+ package_type = "Image"
408
+ image_uri = "${var.ecr_repository_url}:skill-trust-runner-latest"
409
+ timeout = 60
410
+ memory_size = 1024
411
+ architectures = ["x86_64"]
412
+
413
+ environment {
414
+ variables = {
415
+ SKILLSPECTOR_LOG_LEVEL = "WARNING"
416
+ SKILLSPECTOR_TIMEOUT_SECONDS = "45"
417
+ }
418
+ }
419
+
420
+ tags = {
421
+ Name = "thinkwork-${var.stage}-skill-trust-runner"
422
+ Handler = "skill-trust-runner"
423
+ }
424
+ }
425
+
287
426
  # ---------------------------------------------------------------------------
288
427
  # Helper: creates Lambda functions from a local zip directory or release S3 keys
289
428
  # ---------------------------------------------------------------------------
290
429
 
291
430
  resource "aws_lambda_function" "handler" {
292
- for_each = local.deploy_lambda_handlers ? toset([
431
+ for_each = local.deploy_lambda_handlers ? setsubtract(toset([
293
432
  "graphql-http",
294
433
  "chat-agent-invoke",
434
+ # Mobile agent harness: cloud Bedrock Converse proxy + completed-turn
435
+ # persistence. Routes for these live in local.api_routes; the function
436
+ # names must also be listed here (this set is the for_each source for
437
+ # aws_lambda_function.handler, NOT derived from api_routes).
438
+ "model-converse",
439
+ "record-turn",
440
+ "mobile-turn-session",
441
+ # Mobile local Pi built-in tools. These are ThinkWork platform tools, not
442
+ # MCP connector tools.
443
+ "mobile-tools",
444
+ # Agent-visible task status mutation tool. Service and first-party callers
445
+ # land here so linked_tasks remains database-authoritative.
446
+ "task-status-tool",
447
+ # Mobile agent harness MCP proxy. tools/list + tools/call routes live in
448
+ # local.api_routes; the function name must also be listed here (this set
449
+ # is the for_each source for aws_lambda_function.handler).
450
+ "mcp-proxy",
451
+ # Client Engagement app API. Calls Twenty REST API directly server-side;
452
+ # it is not backed by ThinkWork GraphQL or the agent MCP runtime.
453
+ "twenty-client-engagement",
454
+ # Desktop-local Pi tombstone endpoints. Kept temporarily so old packaged
455
+ # desktop clients receive a stable 410 while all supported Pi execution
456
+ # routes through managed AgentCore.
457
+ "desktop-runtime-session",
458
+ "desktop-workspace-prewarm",
459
+ "managed-delegation",
460
+ "desktop-eval-runs",
461
+ # chat-agent-finalize — POST /api/threads/{threadId}/finalize. The
462
+ # AgentCore runtime POSTs here at end-of-turn so the post-AgentCore
463
+ # bookkeeping (cost recording, message insert, AppSync notify,
464
+ # computer-task completion, memory retain dispatch) can run without
465
+ # chat-agent-invoke holding a Lambda open for the full turn duration.
466
+ # Bearer API_AUTH_SECRET. Idempotent on thread_turns.finalized_at
467
+ # (migration 0123). Plan: 2026-05-22-006.
468
+ "chat-agent-finalize",
469
+ # chat-agent-activity — POST /api/threads/{threadId}/activity. The Pi
470
+ # runtime POSTs here mid-turn so live agent activity (tool/skill/phase
471
+ # steps, coalesced text deltas) streams to the Spaces thread via
472
+ # thread_turn_events + the onThreadTurnStep AppSync subscription.
473
+ # Bearer API_AUTH_SECRET, best-effort (never fails the turn).
474
+ # Plan: 2026-06-03-001.
475
+ "chat-agent-activity",
295
476
  "wakeup-processor",
296
477
  "workspace-event-dispatcher",
478
+ "workspace-renderer",
297
479
  "agents",
298
480
  "agent-actions",
299
481
  "messages",
@@ -304,10 +486,11 @@ resource "aws_lambda_function" "handler" {
304
486
  "stripe-webhook",
305
487
  "stripe-portal",
306
488
  "stripe-subscription",
489
+ "deployment-sessions",
307
490
  "auth-me",
491
+ "public-auth-options",
492
+ "workos-auth",
308
493
  "extension-proxy",
309
- "teams",
310
- "team-members",
311
494
  "tenants",
312
495
  "users",
313
496
  "invites",
@@ -315,6 +498,7 @@ resource "aws_lambda_function" "handler" {
315
498
  "mcp-oauth",
316
499
  "mcp-user-memory",
317
500
  "mcp-context-engine",
501
+ "mcp-open-engine",
318
502
  "activity",
319
503
  "routines",
320
504
  "budgets",
@@ -326,6 +510,8 @@ resource "aws_lambda_function" "handler" {
326
510
  "job-trigger",
327
511
  "routine-task-weather-email",
328
512
  "webhooks",
513
+ "n8n-agent-step-bridge",
514
+ "n8n-agent-step-expirer",
329
515
  "webhooks-admin",
330
516
  "webhook-deliveries-cleanup",
331
517
  "skill-runs-reconciler",
@@ -333,45 +519,48 @@ resource "aws_lambda_function" "handler" {
333
519
  "webhook-crm-opportunity",
334
520
  "webhook-task-event",
335
521
  "workspace-files",
522
+ # Workspace source fetch authorization (dynamic workspace plan
523
+ # 2026-06-12-002 U4). Bearer API_AUTH_SECRET + x-tenant-id; the runtime
524
+ # fetch tool authorizes here, then downloads the returned S3 keys itself.
525
+ "workspace-fetch-source",
336
526
  "knowledge-base-manager",
337
527
  "knowledge-base-files",
338
528
  "email-send",
339
529
  "email-inbound",
530
+ "email-provider-webhook",
531
+ "email-readiness-probe",
340
532
  "slack-events",
341
533
  "slack-slash-command",
342
534
  "slack-interactivity",
343
535
  "slack-oauth-install",
344
- "slack-dispatch",
345
536
  "github-app",
346
- "github-repos",
347
537
  "memory",
348
538
  "memory-retain",
349
539
  "wiki-compile",
540
+ "knowledge-graph-thread-ingest",
541
+ "knowledge-graph-observations-ingest",
350
542
  "ontology-scan",
351
543
  "ontology-reprocess",
352
544
  "wiki-lint",
353
545
  "wiki-export",
546
+ "okf-materialize",
547
+ "okf-efs-refresh",
354
548
  "wiki-bootstrap-import",
355
549
  "artifact-deliver",
356
550
  "recipe-refresh",
357
551
  "agent-skills-list",
358
552
  "bootstrap-workspaces",
359
- "migrate-agents-to-computers",
360
- "computer-runtime",
361
- "computer-manager",
362
- "computer-runtime-reconciler",
363
- # Admin Terminal tab — POST /api/computers/{computerId}/terminal/start.
364
- # Returns the SSM Session Manager session envelope (sessionId,
365
- # streamUrl, tokenValue) so the browser can open a direct WebSocket
366
- # to ssmmessages. Plan:
367
- # docs/plans/2026-05-13-004-feat-computer-terminal-ecs-exec-plan.md.
368
- "computer-terminal-start",
369
- "code-factory",
370
553
  "eval-runner",
371
554
  "eval-worker",
372
555
  "eval-runs-reconciler",
556
+ # THNK-74 U4 — imports AWS Data Exports/CUR bill evidence and reconciles
557
+ # aggregate spend against ThinkWork accounting rows.
558
+ "cost-bill-reconciler",
559
+ # THNK-74 U3 — reconciles runtime-reported model usage against Bedrock
560
+ # model invocation logs and appends invocation-scope trace ledger facts.
561
+ "trace-invocation-reconciler",
373
562
  # AgentCore Code Sandbox narrow REST endpoints (plan Unit 10 + Unit 11).
374
- # Both are service-endpoint shape: the Strands container POSTs with
563
+ # Both are service-endpoint shape: the runtime POSTs with
375
564
  # Bearer API_AUTH_SECRET. No GraphQL resolver involvement, no extra IAM.
376
565
  "sandbox-quota-check",
377
566
  "sandbox-invocation-log",
@@ -411,12 +600,12 @@ resource "aws_lambda_function" "handler" {
411
600
  "routine-execution-callback",
412
601
  # Skill-run dispatcher runtime-config fetch (plan
413
602
  # docs/plans/2026-04-24-008-feat-skill-run-dispatcher-plan.md §U1). The
414
- # Strands container's `kind=run_skill` handler calls this with Bearer
415
- # API_AUTH_SECRET to pull the agent's template + skills + MCP + KBs
416
- # before building the headless agent turn.
603
+ # runtime skill dispatch calls this with Bearer API_AUTH_SECRET to pull
604
+ # the agent's template + skills + MCP + KBs before building the headless
605
+ # agent turn.
417
606
  "agents-runtime-config",
418
607
  # Admin-Ops MCP — JSON-RPC endpoint at POST /mcp/admin, exposes the
419
- # @thinkwork/admin-ops package as MCP tools for Strands agents.
608
+ # @thinkwork/admin-ops package as MCP tools for managed agents.
420
609
  "admin-ops-mcp",
421
610
  # MCP admin key management — per-tenant Bearer tokens for admin-ops.
422
611
  # Admin-ops-mcp authenticates incoming tokens by sha256-hash lookup
@@ -425,7 +614,8 @@ resource "aws_lambda_function" "handler" {
425
614
  # One-shot tenant provisioning: mints a tkm_ key + stores in Secrets
426
615
  # Manager at thinkwork/<stage>/mcp/<tenantId>/admin-ops + upserts
427
616
  # tenant_mcp_servers. SM IAM is already granted on thinkwork/* by
428
- # aws_iam_role_policy.lambda_secrets in main.tf (Create/Update/Get).
617
+ # the secrets-manager grant in aws_iam_policy.api_data_plane
618
+ # (iam-grouped.tf; Create/Update/Get).
429
619
  "mcp-admin-provision",
430
620
  # Plugin-installed MCP server admin approval (plan §U11, SI-5). Cognito
431
621
  # JWT admin caller → approve/reject. Approve computes url_hash =
@@ -435,10 +625,6 @@ resource "aws_lambda_function" "handler" {
435
625
  # Daily sweeper: auto-rejects MCP servers pending > 30 days. Triggered
436
626
  # by EventBridge schedule (mcp-approval-sweeper-daily).
437
627
  "mcp-approval-sweeper",
438
- # Plugin upload REST handler (plan §U10). Four routes:
439
- # POST /api/plugins/presign + /upload, GET /api/plugins (+ /:uploadId).
440
- # Cognito JWT; admin-role gated. Needs WORKSPACE_BUCKET env for S3.
441
- "plugin-upload",
442
628
  # Finance pilot U2 — thread-attachment upload (presign + finalize).
443
629
  # presign issues a 5-min PUT URL the end-user client uses to push
444
630
  # Excel/CSV bytes directly to S3; finalize sniffs magic bytes, scans
@@ -458,20 +644,17 @@ resource "aws_lambda_function" "handler" {
458
644
  # or GitHub ref and the handler normalizes vendor folder layouts into
459
645
  # the agent workspace.
460
646
  "folder-bundle-import",
461
- # Hourly sweeper: reaps orphan S3 staging from failed / interrupted
462
- # plugin install sagas + marks matching plugin_uploads rows 'failed'.
463
- "plugin-staging-sweeper",
464
- # Resolved Capability Manifest write endpoint (plan §U15). Strands
465
- # container POSTs one row per agent-session-start. Shared
647
+ # Resolved Capability Manifest write endpoint (plan §U15). The runtime
648
+ # POSTs one row per agent-session-start. Shared
466
649
  # API_AUTH_SECRET bearer (runtime→API; no tenant OAuth).
467
650
  "manifest-log",
468
- # SI-7 catalog-list read endpoint (plan §U15 pt 3/3). Strands
469
- # container fetches the allowed builtin-tool slug set once per
651
+ # SI-7 catalog-list read endpoint (plan §U15 pt 3/3). The runtime
652
+ # fetches the allowed builtin-tool slug set once per
470
653
  # session-start + feature-flag-gated enforcement filter drops
471
654
  # catalog-missing tools before Agent(tools=...). Shared
472
655
  # API_AUTH_SECRET bearer.
473
656
  "capability-catalog-list",
474
- # Brain v0 narrow write endpoint. Strands calls this with
657
+ # Brain v0 narrow write endpoint. Runtime callers use
475
658
  # Bearer API_AUTH_SECRET; GraphQL remains user/admin-facing only.
476
659
  "brain-agent-write",
477
660
  # Phase 3 U4 of the Compliance audit-event log
@@ -482,10 +665,9 @@ resource "aws_lambda_function" "handler" {
482
665
  # U2). EventBridge rate(1 minute) schedule + DLQ + MaxRetryAttempts=0
483
666
  # (defined in dedicated resources below).
484
667
  "compliance-outbox-drainer",
485
- # Phase 3 U6 of the Compliance audit-event log
486
- # (docs/plans/2026-05-07-007-feat-compliance-u6-strands-emit-path-plan.md).
668
+ # Phase 3 U6 of the Compliance audit-event log: runtime REST emit path.
487
669
  # Cross-runtime emit endpoint POST /api/compliance/events — Bearer
488
- # API_AUTH_SECRET, Strands Python client posts here with a
670
+ # API_AUTH_SECRET, runtime clients post here with a
489
671
  # client-supplied UUIDv7 event_id for idempotency. Connects to
490
672
  # Aurora via the master DATABASE_SECRET_ARN like every other narrow
491
673
  # handler (compliance_writer role is reserved for future hardening).
@@ -498,12 +680,24 @@ resource "aws_lambda_function" "handler" {
498
680
  # 60+ unrelated handlers. Pre-merge step: `terraform state mv`
499
681
  # the existing handler["compliance-anchor-watchdog"] address to the
500
682
  # new standalone resource (see U8b plan operator-step section).
501
- ]) : toset([])
683
+ ]), toset(local.optional_integration_handler_names)) : toset([])
502
684
 
503
685
  function_name = "thinkwork-${var.stage}-api-${each.key}"
504
686
  role = aws_iam_role.lambda.arn
505
687
  handler = "index.handler"
506
688
  runtime = local.runtime
689
+ # Parameters and Secrets extension: container-local cache for the SSM
690
+ # runtime-config document + Secrets Manager reads (runtime-config.tf).
691
+ layers = local.api_handler_layers
692
+
693
+ # The runtime-config document must exist before any function loses its
694
+ # env copies, or a mid-apply cold start could cache an empty document
695
+ # for a TTL window. Same reasoning for the platform secrets.
696
+ depends_on = [
697
+ aws_ssm_parameter.runtime_config,
698
+ aws_secretsmanager_secret_version.api_auth,
699
+ aws_secretsmanager_secret_version.appsync_api_key,
700
+ ]
507
701
  # eval-runner walks every test case sequentially, invoking an agent +
508
702
  # waiting up to 2 min for spans to propagate per test, so a 10-test run
509
703
  # can easily exceed the 30 s default. 900 s covers ~5-15 min sweeps.
@@ -513,8 +707,12 @@ resource "aws_lambda_function" "handler" {
513
707
  # routine-task-python wraps a 300s sandbox session and needs headroom
514
708
  # for the Start/Invoke/Stop/S3-offload round trip; 360s leaves ~60s
515
709
  # for AWS-call setup and offload after the sandbox's own ceiling.
516
- timeout = each.key == "wakeup-processor" ? 300 : each.key == "chat-agent-invoke" ? 300 : each.key == "workspace-event-dispatcher" ? 60 : each.key == "eval-runner" ? 900 : each.key == "eval-worker" ? 240 : each.key == "wiki-compile" ? 480 : each.key == "requester-memory-dreaming" ? 300 : each.key == "ontology-scan" ? 300 : each.key == "ontology-reprocess" ? 300 : each.key == "wiki-lint" ? 300 : each.key == "wiki-export" ? 600 : each.key == "wiki-bootstrap-import" ? 900 : each.key == "folder-bundle-import" ? 300 : each.key == "routine-task-python" ? 360 : 30
517
- memory_size = each.key == "graphql-http" ? 512 : each.key == "wakeup-processor" ? 512 : each.key == "workspace-event-dispatcher" ? 512 : each.key == "eval-runner" ? 512 : each.key == "eval-worker" ? 512 : each.key == "wiki-compile" ? 1024 : each.key == "requester-memory-dreaming" ? 512 : each.key == "ontology-scan" ? 512 : each.key == "wiki-export" ? 1024 : each.key == "wiki-bootstrap-import" ? 1024 : each.key == "folder-bundle-import" ? 1024 : 256
710
+ # chat-agent-invoke is now the SETUP phase only (plan 2026-05-22-006 U3):
711
+ # validates the agent, builds the AgentCore invoke payload, dispatches
712
+ # Event-mode, and returns. Setup is ~5s in practice; 60s gives 12×
713
+ # headroom for transient slowness.
714
+ timeout = each.key == "wakeup-processor" ? 300 : each.key == "chat-agent-invoke" ? 60 : each.key == "chat-agent-finalize" ? 60 : each.key == "workspace-event-dispatcher" ? 60 : each.key == "eval-runner" ? 900 : each.key == "eval-worker" ? 240 : each.key == "wiki-compile" ? 480 : each.key == "knowledge-graph-thread-ingest" ? 300 : each.key == "knowledge-graph-observations-ingest" ? 480 : each.key == "requester-memory-dreaming" ? 300 : each.key == "ontology-scan" ? 300 : each.key == "ontology-reprocess" ? 300 : each.key == "wiki-lint" ? 300 : each.key == "wiki-export" ? 600 : each.key == "okf-materialize" ? 600 : each.key == "okf-efs-refresh" ? 600 : each.key == "wiki-bootstrap-import" ? 900 : each.key == "folder-bundle-import" ? 300 : each.key == "routine-task-python" ? 360 : each.key == "model-converse" ? 60 : each.key == "memory-retain" ? 90 : 30
715
+ memory_size = each.key == "graphql-http" ? 512 : each.key == "wakeup-processor" ? 512 : each.key == "workspace-event-dispatcher" ? 512 : each.key == "eval-runner" ? 512 : each.key == "eval-worker" ? 512 : each.key == "wiki-compile" ? 1024 : each.key == "knowledge-graph-thread-ingest" ? 1024 : each.key == "knowledge-graph-observations-ingest" ? 1024 : each.key == "requester-memory-dreaming" ? 512 : each.key == "ontology-scan" ? 512 : each.key == "wiki-export" ? 1024 : each.key == "okf-materialize" ? 1024 : each.key == "okf-efs-refresh" ? 1024 : each.key == "wiki-bootstrap-import" ? 1024 : each.key == "folder-bundle-import" ? 1024 : 256
518
716
 
519
717
  filename = local.use_local_zips ? "${var.lambda_zips_dir}/${each.key}.zip" : null
520
718
  source_code_hash = local.use_local_zips ? filebase64sha256("${var.lambda_zips_dir}/${each.key}.zip") : null
@@ -530,12 +728,45 @@ resource "aws_lambda_function" "handler" {
530
728
 
531
729
  environment {
532
730
  variables = merge(
533
- contains(["computer-manager", "computer-runtime-reconciler"], each.key) ? local.computer_runtime_control_base_env : local.common_env,
731
+ local.common_env,
534
732
  { FUNCTION_NAME = each.key },
535
733
  lookup(local.handler_extra_env, each.key, {}),
536
734
  )
537
735
  }
538
736
 
737
+ dynamic "vpc_config" {
738
+ for_each = (
739
+ (
740
+ contains(["knowledge-graph-thread-ingest", "knowledge-graph-observations-ingest"], each.key) ||
741
+ (each.key == "graphql-http" && var.memory_engine == "cognee")
742
+ ) && local.cognee_worker_vpc_enabled
743
+ ) ? [1] : each.key == "okf-efs-refresh" && local.okf_efs_vpc_enabled ? [1] : []
744
+
745
+ content {
746
+ subnet_ids = each.key == "okf-efs-refresh" ? var.okf_efs_subnet_ids : var.cognee_worker_subnet_ids
747
+ security_group_ids = each.key == "okf-efs-refresh" ? var.okf_efs_security_group_ids : var.cognee_worker_security_group_ids
748
+ }
749
+ }
750
+
751
+ dynamic "file_system_config" {
752
+ for_each = each.key == "okf-efs-refresh" && local.okf_efs_vpc_enabled ? [1] : []
753
+
754
+ content {
755
+ arn = var.okf_efs_refresh_access_point_arn
756
+ local_mount_path = var.okf_efs_mount_path
757
+ }
758
+ }
759
+
760
+ lifecycle {
761
+ precondition {
762
+ condition = each.key != "okf-efs-refresh" || var.okf_efs_refresh_access_point_arn == "" || (
763
+ length(var.okf_efs_mount_target_ids) == length(var.okf_efs_subnet_ids) &&
764
+ alltrue([for id in var.okf_efs_mount_target_ids : id != ""])
765
+ )
766
+ error_message = "okf-efs-refresh requires an available EFS mount target dependency for every configured subnet."
767
+ }
768
+ }
769
+
539
770
  tags = {
540
771
  Name = "thinkwork-${var.stage}-api-${each.key}"
541
772
  Handler = each.key
@@ -568,19 +799,22 @@ resource "aws_sqs_queue" "wiki_compile_dlq" {
568
799
  }
569
800
  }
570
801
 
571
- resource "aws_iam_role_policy" "wiki_compile_dlq_send" {
572
- count = local.deploy_lambda_handlers ? 1 : 0
573
- name = "thinkwork-${var.stage}-wiki-compile-dlq-send"
574
- role = aws_iam_role.lambda.id
575
-
576
- policy = jsonencode({
577
- Version = "2012-10-17"
578
- Statement = [{
579
- Effect = "Allow"
580
- Action = ["sqs:SendMessage"]
581
- Resource = aws_sqs_queue.wiki_compile_dlq[0].arn
582
- }]
583
- })
802
+ # chat-agent-invoke retry-0 (plan 2026-05-22-006 U3). After the
803
+ # direct-callback finalize refactor, chat-agent-invoke is Event-mode-
804
+ # dispatched by the GraphQL resolver and itself dispatches AgentCore
805
+ # Event-mode. There is no synchronous wait anymore — the Lambda returns
806
+ # in ~5s. Retries pinned to 0 so the 2-default-retry storm that produced
807
+ # 5-min stall cascades cannot recur. Setup failures (agent lookup,
808
+ # runtime config resolve, throttling) surface exactly once via the
809
+ # inline error-message-insert path. No DLQ destination: the shared
810
+ # Lambda execution role's inline policy budget is already at the
811
+ # 10240-byte AWS hard cap, and operator replay of a setup failure is
812
+ # rarely useful (the user has already retried by re-sending the message).
813
+ resource "aws_lambda_function_event_invoke_config" "chat_agent_invoke" {
814
+ count = local.deploy_lambda_handlers ? 1 : 0
815
+ function_name = aws_lambda_function.handler["chat-agent-invoke"].function_name
816
+ maximum_retry_attempts = 0
817
+ maximum_event_age_in_seconds = 3600
584
818
  }
585
819
 
586
820
  resource "aws_lambda_function_event_invoke_config" "wiki_compile" {
@@ -609,21 +843,6 @@ resource "aws_sqs_queue" "ontology_scan_dlq" {
609
843
  }
610
844
  }
611
845
 
612
- resource "aws_iam_role_policy" "ontology_scan_dlq_send" {
613
- count = local.deploy_lambda_handlers ? 1 : 0
614
- name = "thinkwork-${var.stage}-ontology-scan-dlq-send"
615
- role = aws_iam_role.lambda.id
616
-
617
- policy = jsonencode({
618
- Version = "2012-10-17"
619
- Statement = [{
620
- Effect = "Allow"
621
- Action = ["sqs:SendMessage"]
622
- Resource = aws_sqs_queue.ontology_scan_dlq[0].arn
623
- }]
624
- })
625
- }
626
-
627
846
  resource "aws_lambda_function_event_invoke_config" "ontology_scan" {
628
847
  count = local.deploy_lambda_handlers ? 1 : 0
629
848
  function_name = aws_lambda_function.handler["ontology-scan"].function_name
@@ -649,21 +868,6 @@ resource "aws_sqs_queue" "ontology_reprocess_dlq" {
649
868
  }
650
869
  }
651
870
 
652
- resource "aws_iam_role_policy" "ontology_reprocess_dlq_send" {
653
- count = local.deploy_lambda_handlers ? 1 : 0
654
- name = "thinkwork-${var.stage}-ontology-reprocess-dlq-send"
655
- role = aws_iam_role.lambda.id
656
-
657
- policy = jsonencode({
658
- Version = "2012-10-17"
659
- Statement = [{
660
- Effect = "Allow"
661
- Action = ["sqs:SendMessage"]
662
- Resource = aws_sqs_queue.ontology_reprocess_dlq[0].arn
663
- }]
664
- })
665
- }
666
-
667
871
  resource "aws_lambda_function_event_invoke_config" "ontology_reprocess" {
668
872
  count = local.deploy_lambda_handlers ? 1 : 0
669
873
  function_name = aws_lambda_function.handler["ontology-reprocess"].function_name
@@ -692,7 +896,7 @@ resource "aws_lambda_function_event_invoke_config" "routine_approval_callback" {
692
896
  maximum_event_age_in_seconds = 3600
693
897
  }
694
898
 
695
- # Per-turn auto-retain: the runtime (Strands + Flue) Event-invokes
899
+ # Per-turn auto-retain: the runtime Event-invokes
696
900
  # memory-retain after every chat turn. AWS Lambda's default async-retry
697
901
  # policy is 2 attempts; without overriding it, a transient failure on the
698
902
  # canonical-transcript fetch or adapter write retries the entire writeback
@@ -708,6 +912,32 @@ resource "aws_lambda_function_event_invoke_config" "memory_retain" {
708
912
  maximum_event_age_in_seconds = 3600
709
913
  }
710
914
 
915
+ # Product-owned retry path for memory-retain. Lambda async retries remain
916
+ # disabled above; this scheduled drain claims due rows from
917
+ # memory_retain_attempts and replays bounded retry payloads idempotently.
918
+ resource "aws_scheduler_schedule" "memory_retain_retry_drainer" {
919
+ count = local.deploy_lambda_handlers ? 1 : 0
920
+
921
+ name = "thinkwork-${var.stage}-memory-retain-retry-drainer"
922
+ group_name = "default"
923
+ schedule_expression = "rate(1 minutes)"
924
+ state = "ENABLED"
925
+
926
+ flexible_time_window {
927
+ mode = "OFF"
928
+ }
929
+
930
+ target {
931
+ arn = aws_lambda_function.handler["memory-retain"].arn
932
+ role_arn = aws_iam_role.scheduler.arn
933
+ input = jsonencode({ kind = "drain_due", limit = 25 })
934
+
935
+ retry_policy {
936
+ maximum_retry_attempts = 0
937
+ }
938
+ }
939
+ }
940
+
711
941
  # ---------------------------------------------------------------------------
712
942
  # Phase 3 U4: compliance-outbox-drainer DLQ + async retry config
713
943
  #
@@ -728,21 +958,6 @@ resource "aws_sqs_queue" "compliance_drainer_dlq" {
728
958
  }
729
959
  }
730
960
 
731
- resource "aws_iam_role_policy" "compliance_drainer_dlq_send" {
732
- count = local.deploy_lambda_handlers ? 1 : 0
733
- name = "compliance-drainer-dlq-send"
734
- role = aws_iam_role.lambda.id
735
-
736
- policy = jsonencode({
737
- Version = "2012-10-17"
738
- Statement = [{
739
- Effect = "Allow"
740
- Action = ["sqs:SendMessage"]
741
- Resource = aws_sqs_queue.compliance_drainer_dlq[0].arn
742
- }]
743
- })
744
- }
745
-
746
961
  resource "aws_lambda_function_event_invoke_config" "compliance_outbox_drainer" {
747
962
  count = local.deploy_lambda_handlers ? 1 : 0
748
963
  function_name = aws_lambda_function.handler["compliance-outbox-drainer"].function_name
@@ -785,280 +1000,358 @@ resource "aws_scheduler_schedule" "compliance_outbox_drainer" {
785
1000
  locals {
786
1001
  # Map of route_key → handler name for API Gateway
787
1002
  api_routes = local.deploy_lambda_handlers ? {
788
- # GraphQL the main API entry point
789
- "POST /graphql" = "graphql-http"
790
- "GET /graphql" = "graphql-http"
791
-
792
- # Health check (keep placeholder alive too)
793
- # "GET /health" is handled by placeholder
794
-
795
- # Agents
796
- "ANY /api/agents/{proxy+}" = "agents"
797
- "ANY /api/agents" = "agents"
798
-
799
- # Agent actions (start/stop/heartbeat/budget)
800
- "ANY /api/agent-actions/{proxy+}" = "agent-actions"
801
-
802
- # Messages
803
- "ANY /api/messages/{proxy+}" = "messages"
804
- "ANY /api/messages" = "messages"
805
-
806
- # Teams
807
- "ANY /api/teams/{proxy+}" = "teams"
808
- "ANY /api/teams" = "teams"
809
- "ANY /api/team-members/{proxy+}" = "team-members"
810
-
811
- # Tenants
812
- "ANY /api/tenants/{proxy+}" = "tenants"
813
- "ANY /api/tenants" = "tenants"
814
-
815
- # Users
816
- "ANY /api/users/{proxy+}" = "users"
817
- "ANY /api/users" = "users"
818
-
819
- # Invites
820
- "ANY /api/invites/{proxy+}" = "invites"
821
- "ANY /api/invites" = "invites"
822
-
823
- # Compliance audit-event emit (Phase 3 U6) — narrow Bearer
824
- # API_AUTH_SECRET endpoint, Strands Python client posts here.
825
- "POST /api/compliance/events" = "compliance-events"
826
-
827
- # Skills
828
- "ANY /api/skills/{proxy+}" = "skills"
829
- "ANY /api/skills" = "skills"
830
-
831
- # User Memory MCP OAuth/resource-server unblocker. These endpoints are
832
- # enough for `codex mcp login thinkwork-user-memory-dev` to discover OAuth,
833
- # register as a public PKCE client, sign the user in through Cognito, and
834
- # receive a bearer token for the User Memory MCP resource.
835
- "GET /.well-known/oauth-protected-resource" = "mcp-oauth"
836
- "GET /.well-known/oauth-protected-resource/{proxy+}" = "mcp-oauth"
837
- "GET /.well-known/oauth-authorization-server" = "mcp-oauth"
838
- "GET /.well-known/openid-configuration" = "mcp-oauth"
839
- "GET /mcp/oauth/jwks" = "mcp-oauth"
840
- "POST /mcp/oauth/register" = "mcp-oauth"
841
- "GET /mcp/oauth/authorize" = "mcp-oauth"
842
- "GET /mcp/oauth/callback" = "mcp-oauth"
843
- "POST /mcp/oauth/token" = "mcp-oauth"
844
- "POST /mcp/oauth/revoke" = "mcp-oauth"
845
- "ANY /mcp/user-memory" = "mcp-user-memory"
846
- "ANY /mcp/context-engine" = "mcp-context-engine"
847
-
848
- # Brain v0 service-auth writeback.
849
- "POST /api/brain/agent-write" = "brain-agent-write"
850
- "OPTIONS /api/brain/agent-write" = "brain-agent-write"
851
-
852
- # Activity
853
- "ANY /api/activity/{proxy+}" = "activity"
854
- "ANY /api/activity" = "activity"
855
-
856
- # Connections + OAuth
857
- "ANY /api/connections/{proxy+}" = "connections"
858
- "ANY /api/connections" = "connections"
859
- "GET /api/oauth/authorize" = "oauth-authorize"
860
- "GET /api/oauth/callback" = "oauth-callback"
861
-
862
- # Stripe billing (unauthenticated checkout is pre-signup; webhook is
863
- # server-to-server with Stripe signature verification).
864
- "POST /api/stripe/checkout-session" = "stripe-checkout"
865
- "OPTIONS /api/stripe/checkout-session" = "stripe-checkout"
866
- "POST /api/stripe/webhook" = "stripe-webhook"
867
- "POST /api/stripe/portal-session" = "stripe-portal"
868
- "OPTIONS /api/stripe/portal-session" = "stripe-portal"
869
- "GET /api/stripe/subscription" = "stripe-subscription"
870
- "OPTIONS /api/stripe/subscription" = "stripe-subscription"
871
- "GET /api/auth/me" = "auth-me"
872
- "OPTIONS /api/auth/me" = "auth-me"
873
- "ANY /api/extensions/{extensionId}" = "extension-proxy"
874
- "ANY /api/extensions/{extensionId}/{proxy+}" = "extension-proxy"
875
-
876
- # Routines
877
- "ANY /api/routines/{proxy+}" = "routines"
878
- "ANY /api/routines" = "routines"
879
-
880
- # Budgets
881
- "ANY /api/budgets/{proxy+}" = "budgets"
882
- "ANY /api/budgets" = "budgets"
883
-
884
- # Guardrails
885
- "ANY /api/guardrails/{proxy+}" = "guardrails"
886
- "ANY /api/guardrails" = "guardrails"
887
-
888
- # Scheduled Jobs
889
- "ANY /api/scheduled-jobs/{proxy+}" = "scheduled-jobs"
890
- "ANY /api/scheduled-jobs" = "scheduled-jobs"
891
- "ANY /api/thread-turns/{proxy+}" = "scheduled-jobs"
892
- "ANY /api/thread-turns" = "scheduled-jobs"
893
-
894
- # Job Schedule Manager (EventBridge CRUD)
895
- "ANY /api/job-schedules/{proxy+}" = "job-schedule-manager"
896
- "ANY /api/job-schedules" = "job-schedule-manager"
897
-
898
- # Integration webhooks (Unit 8 — composable-skills). Each integration
899
- # has its own Lambda + a specific route under /webhooks/{integration}/
900
- # {tenantId}. Specific routes take precedence over the {proxy+}
901
- # catch-all below, which still owns the legacy PRD-19 webhook-token
902
- # surface.
903
- "POST /webhooks/crm-opportunity/{tenantId}" = "webhook-crm-opportunity"
904
- "POST /webhooks/task-event/{tenantId}" = "webhook-task-event"
905
-
906
- # Webhooks (public trigger) — legacy PRD-19 tokenized webhooks.
907
- "POST /webhooks/{proxy+}" = "webhooks"
908
-
909
- # Webhooks admin
910
- "ANY /api/webhooks/{proxy+}" = "webhooks-admin"
911
- "ANY /api/webhooks" = "webhooks-admin"
912
-
913
- # Workspace files
914
- "ANY /api/workspaces/{proxy+}" = "workspace-files"
915
-
916
- # Phase-one Computer migration. Service-auth only; operator tooling calls
917
- # dry-run first and apply only after conflict review.
918
- "POST /api/migrations/agents-to-computers" = "migrate-agents-to-computers"
919
- "OPTIONS /api/migrations/agents-to-computers" = "migrate-agents-to-computers"
920
-
921
- # Knowledge bases
922
- "ANY /api/knowledge-bases/{proxy+}" = "knowledge-base-files"
923
-
924
- # Email
925
- "POST /api/email/send" = "email-send"
926
-
927
- # Slack workspace app ingress. These unauthenticated public endpoints
928
- # verify Slack signatures in handler code before any tenant work happens.
929
- "POST /slack/events" = "slack-events"
930
- "POST /slack/slash-command" = "slack-slash-command"
931
- "POST /slack/interactivity" = "slack-interactivity"
932
- "GET /slack/oauth/install" = "slack-oauth-install"
933
- "POST /slack/oauth/install" = "slack-oauth-install"
934
-
935
- # Memory
936
- "ANY /api/memory/{proxy+}" = "memory"
937
-
938
- # Artifacts
939
- "POST /api/artifacts/{proxy+}" = "artifact-deliver"
940
-
941
- # Recipes
942
- "POST /api/recipe-refresh" = "recipe-refresh"
943
-
944
- # GitHub App
945
- "ANY /api/github-app/{proxy+}" = "github-app"
946
- "POST /api/github/webhook" = "github-app"
947
-
948
- # AgentCore Code Sandbox (plan Unit 10 + Unit 11). Strands container
949
- # calls both with Bearer API_AUTH_SECRET before + after every
950
- # executeCode. 429 on quota denial, 201 on audit-row insert.
951
- "POST /api/sandbox/quota/check-and-increment" = "sandbox-quota-check"
952
- "POST /api/sandbox/invocations" = "sandbox-invocation-log"
953
-
954
- # Routines ASL validator (plan 2026-05-01-004 §U5). Bearer
955
- # API_AUTH_SECRET. Chat builder + publish flow POST the candidate
956
- # ASL document; returns { valid, errors, warnings }.
957
- "POST /api/routines/validate" = "routine-asl-validator"
958
- "OPTIONS /api/routines/validate" = "routine-asl-validator"
959
-
960
- # Routines step-event ingest (plan 2026-05-01-005 §U9). Task wrappers
961
- # (routine-task-python, routine-resume) POST per-step status
962
- # transitions; the EventBridge rule in routines-stepfunctions/main.tf
963
- # POSTs SFN execution-state-change events here for the agent_invoke
964
- # recipe path (no wrapper Lambda). Bearer API_AUTH_SECRET. Idempotent
965
- # via partial unique index on (execution_id, node_id, status,
966
- # started_at) — see migration 0056.
967
- "POST /api/routines/step" = "routine-step-callback"
968
- "OPTIONS /api/routines/step" = "routine-step-callback"
969
- "POST /api/routines/execution" = "routine-execution-callback"
970
- "OPTIONS /api/routines/execution" = "routine-execution-callback"
971
-
972
- # Skill-run dispatcher runtime-config fetch. Service-auth GET.
973
- "GET /api/agents/runtime-config" = "agents-runtime-config"
974
-
975
- # ThinkWork Computer runtime callback API. ECS tasks call outbound with
976
- # Bearer API_AUTH_SECRET to fetch config, heartbeat, claim one task, append
977
- # product/audit events, and complete/fail tasks.
978
- "ANY /api/computers/runtime/{proxy+}" = "computer-runtime"
979
-
980
- # Admin Terminal tab — opens an ECS Exec session into the running
981
- # Computer task and returns {sessionId, streamUrl, tokenValue} to the
982
- # browser, which then connects WebSocket directly to ssmmessages.
983
- "POST /api/computers/{computerId}/terminal/start" = "computer-terminal-start"
984
- "OPTIONS /api/computers/{computerId}/terminal/start" = "computer-terminal-start"
985
-
986
- # ThinkWork Computer manager API. Internal service-auth endpoint used by
987
- # admin operations to reconcile per-Computer ECS service desired state.
988
- "POST /api/computers/manager" = "computer-manager"
989
- "OPTIONS /api/computers/manager" = "computer-manager"
990
-
991
- # Admin-Ops MCP server — single JSON-RPC endpoint. Strands agents
992
- # (and anyone else) POST with Bearer <tenant-scoped token> issued by
993
- # the mcp-admin-keys handler below. The shared API_AUTH_SECRET is
994
- # retained as a break-glass superuser path for bootstrap/debug.
995
- "POST /mcp/admin" = "admin-ops-mcp"
996
-
997
- # MCP admin key management — per-tenant Bearer token CRUD. Tokens
998
- # are shown ONCE at creation (POST returns raw value); server stores
999
- # sha256 hash only. These specific routes take precedence over the
1000
- # existing `ANY /api/tenants/{proxy+}` route (tenants handler) per
1001
- # API Gateway v2's most-specific-match rule.
1002
- "POST /api/tenants/{tenantId}/mcp-admin-keys" = "mcp-admin-keys"
1003
- "GET /api/tenants/{tenantId}/mcp-admin-keys" = "mcp-admin-keys"
1004
- "DELETE /api/tenants/{tenantId}/mcp-admin-keys/{keyId}" = "mcp-admin-keys"
1005
-
1006
- # One-shot tenant provisioning for the admin-ops MCP. Mints a fresh
1007
- # tkm_ key + stores it in Secrets Manager at
1008
- # thinkwork/<stage>/mcp/<tenantId>/admin-ops + upserts the
1009
- # tenant_mcp_servers row so the runtime picks the server up for
1010
- # any agent that gets it assigned via agent_mcp_servers.
1011
- "POST /api/tenants/{tenantId}/mcp-admin-provision" = "mcp-admin-provision"
1012
-
1013
- # MCP server admin approval (plan §U11, SI-5). Plugin-uploaded MCP
1014
- # servers land with status='pending'; these routes flip them to
1015
- # approved/rejected. Cognito JWT only (mcp-approval handler rejects
1016
- # apikey callers) — the admin SPA is the sole UI surface.
1017
- "POST /api/tenants/{tenantId}/mcp-servers/{serverId}/approve" = "mcp-approval"
1018
- "OPTIONS /api/tenants/{tenantId}/mcp-servers/{serverId}/approve" = "mcp-approval"
1019
- "POST /api/tenants/{tenantId}/mcp-servers/{serverId}/reject" = "mcp-approval"
1020
- "OPTIONS /api/tenants/{tenantId}/mcp-servers/{serverId}/reject" = "mcp-approval"
1021
-
1022
- # Plugin upload admin surface (plan §U10). Admin SPA drives the full
1023
- # flow: POST /presign → browser PUT to presigned S3 URL → POST /upload
1024
- # (validator + three-phase install saga). GET routes back the admin's
1025
- # plugin history view. handleCors() short-circuits OPTIONS before auth
1026
- # — required for the browser to preflight successfully.
1027
- "POST /api/plugins/presign" = "plugin-upload"
1028
- "OPTIONS /api/plugins/presign" = "plugin-upload"
1029
- "POST /api/plugins/upload" = "plugin-upload"
1030
- "OPTIONS /api/plugins/upload" = "plugin-upload"
1031
- "GET /api/plugins" = "plugin-upload"
1032
- "OPTIONS /api/plugins" = "plugin-upload"
1033
- "GET /api/plugins/{uploadId}" = "plugin-upload"
1034
- "OPTIONS /api/plugins/{uploadId}" = "plugin-upload"
1035
-
1036
- # Finance pilot U2 — thread-attachment upload (presign + finalize).
1037
- # Cognito JWT; tenant pinned via threads.tenant_id lookup. OPTIONS
1038
- # is handled inside the Lambda before auth.
1039
- "POST /api/threads/{threadId}/attachments/presign" = "thread-attachments-presign"
1040
- "OPTIONS /api/threads/{threadId}/attachments/presign" = "thread-attachments-presign"
1041
- "POST /api/threads/{threadId}/attachments/finalize" = "thread-attachments-finalize"
1042
- "OPTIONS /api/threads/{threadId}/attachments/finalize" = "thread-attachments-finalize"
1043
-
1044
- # U9-remainder of finance pilot — tenant-pinned download endpoint.
1045
- "GET /api/threads/{threadId}/attachments/{attachmentId}/download" = "thread-attachment-download"
1046
- "OPTIONS /api/threads/{threadId}/attachments/{attachmentId}/download" = "thread-attachment-download"
1047
-
1048
- # Fat-folder bundle import. OPTIONS is handled inside the Lambda before auth.
1049
- "POST /api/agents/{agentId}/import-bundle" = "folder-bundle-import"
1050
- "OPTIONS /api/agents/{agentId}/import-bundle" = "folder-bundle-import"
1051
-
1052
- # Resolved Capability Manifest write endpoint (plan §U15). Strands
1053
- # container posts one row per agent-session-start. Shared
1054
- # API_AUTH_SECRET; no tenant OAuth.
1055
- "POST /api/runtime/manifests" = "manifest-log"
1056
- "OPTIONS /api/runtime/manifests" = "manifest-log"
1057
-
1058
- # SI-7 catalog-list read (plan §U15 pt 3/3). Strands container fetches
1059
- # the allowed slug set once per session-start. Shared API_AUTH_SECRET.
1060
- "GET /api/runtime/capability-catalog" = "capability-catalog-list"
1061
- "OPTIONS /api/runtime/capability-catalog" = "capability-catalog-list"
1003
+ for route_key, handler_name in {
1004
+ # GraphQL the main API entry point
1005
+ "POST /graphql" = "graphql-http"
1006
+ "GET /graphql" = "graphql-http"
1007
+
1008
+ # Health check (keep placeholder alive too)
1009
+ # "GET /health" is handled by placeholder
1010
+
1011
+ # Public login capabilities. Unauthenticated by design; the handler
1012
+ # resolves tenant-scoped OAuth options only from trusted API Gateway
1013
+ # domain metadata and fails closed for unknown/shared hosts.
1014
+ "GET /api/auth/options" = "public-auth-options"
1015
+ "OPTIONS /api/auth/options" = "public-auth-options"
1016
+ "GET /api/auth/workos/authorize" = "workos-auth"
1017
+ "OPTIONS /api/auth/workos/authorize" = "workos-auth"
1018
+ "GET /api/auth/workos/callback" = "workos-auth"
1019
+ "OPTIONS /api/auth/workos/callback" = "workos-auth"
1020
+ "POST /api/auth/workos/bridge" = "workos-auth"
1021
+ "OPTIONS /api/auth/workos/bridge" = "workos-auth"
1022
+ "POST /api/auth/workos/logout" = "workos-auth"
1023
+ "OPTIONS /api/auth/workos/logout" = "workos-auth"
1024
+
1025
+ # Agents
1026
+ "ANY /api/agents/{proxy+}" = "agents"
1027
+ "ANY /api/agents" = "agents"
1028
+
1029
+ # Agent actions (start/stop/heartbeat/budget)
1030
+ "ANY /api/agent-actions/{proxy+}" = "agent-actions"
1031
+
1032
+ # Desktop-local Pi tombstones. Specific routes before broad REST handlers;
1033
+ # OPTIONS is handled inside the Lambda before auth.
1034
+ "POST /api/desktop/runtime-session" = "desktop-runtime-session"
1035
+ "OPTIONS /api/desktop/runtime-session" = "desktop-runtime-session"
1036
+ "POST /api/desktop/workspace-prewarm" = "desktop-workspace-prewarm"
1037
+ "OPTIONS /api/desktop/workspace-prewarm" = "desktop-workspace-prewarm"
1038
+ "POST /api/desktop/managed-delegation" = "managed-delegation"
1039
+ "OPTIONS /api/desktop/managed-delegation" = "managed-delegation"
1040
+ "POST /api/desktop/eval-runs" = "desktop-eval-runs"
1041
+ "OPTIONS /api/desktop/eval-runs" = "desktop-eval-runs"
1042
+ "POST /api/desktop/eval-runs/{runId}/sessions" = "desktop-eval-runs"
1043
+ "OPTIONS /api/desktop/eval-runs/{runId}/sessions" = "desktop-eval-runs"
1044
+ "POST /api/desktop/eval-runs/{runId}/results" = "desktop-eval-runs"
1045
+ "OPTIONS /api/desktop/eval-runs/{runId}/results" = "desktop-eval-runs"
1046
+
1047
+ # Mobile agent harness model proxy (cloud Bedrock Converse). OPTIONS is
1048
+ # handled inside the Lambda before auth.
1049
+ "POST /api/model/converse" = "model-converse"
1050
+ "OPTIONS /api/model/converse" = "model-converse"
1051
+
1052
+ # Mobile agent harness turn persistence (append a completed turn). OPTIONS
1053
+ # handled inside the Lambda before auth.
1054
+ "POST /api/threads/record-turn" = "record-turn"
1055
+ "OPTIONS /api/threads/record-turn" = "record-turn"
1056
+ "POST /api/mobile/turn-session" = "mobile-turn-session"
1057
+ "OPTIONS /api/mobile/turn-session" = "mobile-turn-session"
1058
+
1059
+ # Mobile local Pi built-in tool proxy. This is intentionally separate from
1060
+ # /api/mcp because web_search is a ThinkWork platform capability, not an
1061
+ # MCP connector.
1062
+ "POST /api/mobile/tools/web-search" = "mobile-tools"
1063
+ "OPTIONS /api/mobile/tools/web-search" = "mobile-tools"
1064
+ "POST /api/tasks/status" = "task-status-tool"
1065
+ "OPTIONS /api/tasks/status" = "task-status-tool"
1066
+ "POST /api/work-items/status" = "task-status-tool"
1067
+ "OPTIONS /api/work-items/status" = "task-status-tool"
1068
+
1069
+ # Mobile agent harness MCP proxy — tenant-scoped tools/list + tools/call
1070
+ # over the signed-in user's Cognito idToken. One Lambda, two routes;
1071
+ # OPTIONS handled inside the Lambda before auth.
1072
+ "POST /api/mcp/tools/list" = "mcp-proxy"
1073
+ "OPTIONS /api/mcp/tools/list" = "mcp-proxy"
1074
+ "POST /api/mcp/tools/call" = "mcp-proxy"
1075
+ "OPTIONS /api/mcp/tools/call" = "mcp-proxy"
1076
+
1077
+ # Twenty Client Engagement app: browser -> ThinkWork REST Lambda ->
1078
+ # Twenty REST API with the caller's plugin activation.
1079
+ "ANY /api/plugin-apps/twenty/client-engagement" = "twenty-client-engagement"
1080
+ "ANY /api/plugin-apps/twenty/client-engagement/{proxy+}" = "twenty-client-engagement"
1081
+
1082
+ # Messages
1083
+ "ANY /api/messages/{proxy+}" = "messages"
1084
+ "ANY /api/messages" = "messages"
1085
+
1086
+ # Tenants
1087
+ "ANY /api/tenants/{proxy+}" = "tenants"
1088
+ "ANY /api/tenants" = "tenants"
1089
+
1090
+ # Users
1091
+ "ANY /api/users/{proxy+}" = "users"
1092
+ "ANY /api/users" = "users"
1093
+
1094
+ # Invites
1095
+ "ANY /api/invites/{proxy+}" = "invites"
1096
+ "ANY /api/invites" = "invites"
1097
+
1098
+ # Compliance audit-event emit (Phase 3 U6) — narrow Bearer
1099
+ # API_AUTH_SECRET endpoint, runtime clients post here.
1100
+ "POST /api/compliance/events" = "compliance-events"
1101
+
1102
+ # Skills
1103
+ "ANY /api/skills/{proxy+}" = "skills"
1104
+ "ANY /api/skills" = "skills"
1105
+
1106
+ # User Memory MCP OAuth/resource-server unblocker. These endpoints are
1107
+ # enough for `codex mcp login thinkwork-user-memory-dev` to discover OAuth,
1108
+ # register as a public PKCE client, sign the user in through Cognito, and
1109
+ # receive a bearer token for the User Memory MCP resource.
1110
+ "GET /.well-known/oauth-protected-resource" = "mcp-oauth"
1111
+ "GET /.well-known/oauth-protected-resource/{proxy+}" = "mcp-oauth"
1112
+ "GET /.well-known/oauth-authorization-server" = "mcp-oauth"
1113
+ "GET /.well-known/openid-configuration" = "mcp-oauth"
1114
+ "GET /mcp/oauth/jwks" = "mcp-oauth"
1115
+ "POST /mcp/oauth/register" = "mcp-oauth"
1116
+ "GET /mcp/oauth/authorize" = "mcp-oauth"
1117
+ "GET /mcp/oauth/callback" = "mcp-oauth"
1118
+ "POST /mcp/oauth/token" = "mcp-oauth"
1119
+ "POST /mcp/oauth/revoke" = "mcp-oauth"
1120
+ "ANY /mcp/user-memory" = "mcp-user-memory"
1121
+ "ANY /mcp/context-engine" = "mcp-context-engine"
1122
+ "ANY /mcp/open-engine" = "mcp-open-engine"
1123
+
1124
+ # Brain v0 service-auth writeback.
1125
+ "POST /api/brain/agent-write" = "brain-agent-write"
1126
+ "OPTIONS /api/brain/agent-write" = "brain-agent-write"
1127
+
1128
+ # Activity
1129
+ "ANY /api/activity/{proxy+}" = "activity"
1130
+ "ANY /api/activity" = "activity"
1131
+
1132
+ # Connections + OAuth
1133
+ "ANY /api/connections/{proxy+}" = "connections"
1134
+ "ANY /api/connections" = "connections"
1135
+ "GET /api/oauth/authorize" = "oauth-authorize"
1136
+ "GET /api/oauth/callback" = "oauth-callback"
1137
+
1138
+ # Stripe billing (unauthenticated — checkout is pre-signup; webhook is
1139
+ # server-to-server with Stripe signature verification).
1140
+ "POST /api/stripe/checkout-session" = "stripe-checkout"
1141
+ "OPTIONS /api/stripe/checkout-session" = "stripe-checkout"
1142
+ "POST /api/stripe/webhook" = "stripe-webhook"
1143
+ "POST /api/stripe/portal-session" = "stripe-portal"
1144
+ "OPTIONS /api/stripe/portal-session" = "stripe-portal"
1145
+ "GET /api/stripe/subscription" = "stripe-subscription"
1146
+ "OPTIONS /api/stripe/subscription" = "stripe-subscription"
1147
+ "POST /api/deployment-sessions" = "deployment-sessions"
1148
+ "OPTIONS /api/deployment-sessions" = "deployment-sessions"
1149
+ "GET /api/deployment-sessions/{sessionId}" = "deployment-sessions"
1150
+ "OPTIONS /api/deployment-sessions/{sessionId}" = "deployment-sessions"
1151
+ "POST /api/deployment-sessions/{sessionId}/bootstrap-credential-lease" = "deployment-sessions"
1152
+ "DELETE /api/deployment-sessions/{sessionId}/bootstrap-credential-lease" = "deployment-sessions"
1153
+ "OPTIONS /api/deployment-sessions/{sessionId}/bootstrap-credential-lease" = "deployment-sessions"
1154
+ "POST /api/deployment-sessions/{sessionId}/authority-transfer" = "deployment-sessions"
1155
+ "OPTIONS /api/deployment-sessions/{sessionId}/authority-transfer" = "deployment-sessions"
1156
+ "POST /api/deployment-sessions/{sessionId}/start" = "deployment-sessions"
1157
+ "OPTIONS /api/deployment-sessions/{sessionId}/start" = "deployment-sessions"
1158
+ "POST /api/deployment-sessions/{sessionId}/teardown" = "deployment-sessions"
1159
+ "OPTIONS /api/deployment-sessions/{sessionId}/teardown" = "deployment-sessions"
1160
+ "GET /api/auth/me" = "auth-me"
1161
+ "OPTIONS /api/auth/me" = "auth-me"
1162
+ "ANY /api/extensions/{extensionId}" = "extension-proxy"
1163
+ "ANY /api/extensions/{extensionId}/{proxy+}" = "extension-proxy"
1164
+
1165
+ # Routines
1166
+ "ANY /api/routines/{proxy+}" = "routines"
1167
+ "ANY /api/routines" = "routines"
1168
+
1169
+ # Budgets
1170
+ "ANY /api/budgets/{proxy+}" = "budgets"
1171
+ "ANY /api/budgets" = "budgets"
1172
+
1173
+ # Guardrails
1174
+ "ANY /api/guardrails/{proxy+}" = "guardrails"
1175
+ "ANY /api/guardrails" = "guardrails"
1176
+
1177
+ # Scheduled Jobs
1178
+ "ANY /api/scheduled-jobs/{proxy+}" = "scheduled-jobs"
1179
+ "ANY /api/scheduled-jobs" = "scheduled-jobs"
1180
+ "ANY /api/thread-turns/{proxy+}" = "scheduled-jobs"
1181
+ "ANY /api/thread-turns" = "scheduled-jobs"
1182
+ "ANY /api/trigger-runs/{proxy+}" = "scheduled-jobs"
1183
+ "ANY /api/trigger-runs" = "scheduled-jobs"
1184
+
1185
+ # Job Schedule Manager (EventBridge CRUD)
1186
+ "ANY /api/job-schedules/{proxy+}" = "job-schedule-manager"
1187
+ "ANY /api/job-schedules" = "job-schedule-manager"
1188
+
1189
+ # Integration webhooks (Unit 8 — composable-skills). Each integration
1190
+ # has its own Lambda + a specific route under /webhooks/{integration}/
1191
+ # {tenantId}. Specific routes take precedence over the {proxy+}
1192
+ # catch-all below, which still owns the legacy PRD-19 webhook-token
1193
+ # surface.
1194
+ "POST /webhooks/crm-opportunity/{tenantId}" = "webhook-crm-opportunity"
1195
+ "POST /webhooks/task-event/{tenantId}" = "webhook-task-event"
1196
+
1197
+ # Webhooks (public trigger) legacy PRD-19 tokenized webhooks.
1198
+ "POST /webhooks/{proxy+}" = "webhooks"
1199
+
1200
+ # n8n agent-step bridge — public tenant-scoped credential endpoint for
1201
+ # stock n8n HTTP Request nodes.
1202
+ "POST /api/integrations/n8n/agent-steps" = "n8n-agent-step-bridge"
1203
+ "OPTIONS /api/integrations/n8n/agent-steps" = "n8n-agent-step-bridge"
1204
+
1205
+ # Webhooks admin
1206
+ "ANY /api/webhooks/{proxy+}" = "webhooks-admin"
1207
+ "ANY /api/webhooks" = "webhooks-admin"
1208
+
1209
+ # Workspace source fetch authorization (dynamic workspace U4).
1210
+ # Registered before the /api/workspaces/{proxy+} catch-all — API
1211
+ # Gateway HTTP APIs route to the most specific match, so these two
1212
+ # take precedence over the workspace-files proxy route.
1213
+ "POST /api/workspaces/fetch-source" = "workspace-fetch-source"
1214
+ "OPTIONS /api/workspaces/fetch-source" = "workspace-fetch-source"
1215
+
1216
+ # Workspace files
1217
+ "ANY /api/workspaces/{proxy+}" = "workspace-files"
1218
+
1219
+ # Knowledge bases
1220
+ "ANY /api/knowledge-bases/{proxy+}" = "knowledge-base-files"
1221
+
1222
+ # Email
1223
+ "POST /api/email/send" = "email-send"
1224
+ "POST /api/email/provider-webhook/{providerInstallId}" = "email-provider-webhook"
1225
+ "POST /api/email/readiness/probe" = "email-readiness-probe"
1226
+
1227
+ # Slack workspace app ingress. These unauthenticated public endpoints
1228
+ # verify Slack signatures in handler code before any tenant work happens.
1229
+ "POST /slack/events" = "slack-events"
1230
+ "POST /slack/slash-command" = "slack-slash-command"
1231
+ "POST /slack/interactivity" = "slack-interactivity"
1232
+ "GET /slack/oauth/install" = "slack-oauth-install"
1233
+ "POST /slack/oauth/install" = "slack-oauth-install"
1234
+
1235
+ # Memory
1236
+ "ANY /api/memory/{proxy+}" = "memory"
1237
+
1238
+ # Artifacts
1239
+ "POST /api/artifacts/{proxy+}" = "artifact-deliver"
1240
+
1241
+ # Recipes
1242
+ "POST /api/recipe-refresh" = "recipe-refresh"
1243
+
1244
+ # GitHub App
1245
+ "ANY /api/github-app/{proxy+}" = "github-app"
1246
+ "POST /api/github/webhook" = "github-app"
1247
+
1248
+ # AgentCore Code Sandbox (plan Unit 10 + Unit 11). The runtime calls
1249
+ # both with Bearer API_AUTH_SECRET before + after every
1250
+ # executeCode. 429 on quota denial, 201 on audit-row insert.
1251
+ "POST /api/sandbox/quota/check-and-increment" = "sandbox-quota-check"
1252
+ "POST /api/sandbox/invocations" = "sandbox-invocation-log"
1253
+
1254
+ # Routines ASL validator (plan 2026-05-01-004 §U5). Bearer
1255
+ # API_AUTH_SECRET. Chat builder + publish flow POST the candidate
1256
+ # ASL document; returns { valid, errors, warnings }.
1257
+ "POST /api/routines/validate" = "routine-asl-validator"
1258
+ "OPTIONS /api/routines/validate" = "routine-asl-validator"
1259
+
1260
+ # Routines step-event ingest (plan 2026-05-01-005 §U9). Task wrappers
1261
+ # (routine-task-python, routine-resume) POST per-step status
1262
+ # transitions; the EventBridge rule in routines-stepfunctions/main.tf
1263
+ # POSTs SFN execution-state-change events here for the agent_invoke
1264
+ # recipe path (no wrapper Lambda). Bearer API_AUTH_SECRET. Idempotent
1265
+ # via partial unique index on (execution_id, node_id, status,
1266
+ # started_at) — see migration 0056.
1267
+ "POST /api/routines/step" = "routine-step-callback"
1268
+ "OPTIONS /api/routines/step" = "routine-step-callback"
1269
+ "POST /api/routines/execution" = "routine-execution-callback"
1270
+ "OPTIONS /api/routines/execution" = "routine-execution-callback"
1271
+
1272
+ # chat-agent-finalize — AgentCore runtime POSTs here at end-of-turn so
1273
+ # the post-AgentCore bookkeeping runs out-of-band from chat-agent-invoke.
1274
+ # Bearer API_AUTH_SECRET. Plan 2026-05-22-006.
1275
+ "POST /api/threads/{threadId}/finalize" = "chat-agent-finalize"
1276
+ "OPTIONS /api/threads/{threadId}/finalize" = "chat-agent-finalize"
1277
+
1278
+ # chat-agent-activity — Pi runtime POSTs here mid-turn to stream live
1279
+ # agent activity to the Spaces thread. Bearer API_AUTH_SECRET, best-effort.
1280
+ # Plan 2026-06-03-001.
1281
+ "POST /api/threads/{threadId}/activity" = "chat-agent-activity"
1282
+ "OPTIONS /api/threads/{threadId}/activity" = "chat-agent-activity"
1283
+
1284
+ # ask_user_question intake — the Pi runtime POSTs a question batch
1285
+ # here (awaited) before returning its sentinel tool result. Served by
1286
+ # the SAME chat-agent-activity Lambda (route discrimination in the
1287
+ # handler — no new Lambda). Bearer API_AUTH_SECRET + thread-turn
1288
+ # ownership join; 409 on the one-pending-per-thread partial unique
1289
+ # index. Plan 2026-06-09-005 U2.
1290
+ "POST /api/threads/{threadId}/questions" = "chat-agent-activity"
1291
+ "OPTIONS /api/threads/{threadId}/questions" = "chat-agent-activity"
1292
+
1293
+ # Skill-run dispatcher runtime-config fetch. Service-auth GET.
1294
+ "GET /api/agents/runtime-config" = "agents-runtime-config"
1295
+
1296
+ # Admin-Ops MCP server — single JSON-RPC endpoint. Managed agents
1297
+ # (and anyone else) POST with Bearer <tenant-scoped token> issued by
1298
+ # the mcp-admin-keys handler below. The shared API_AUTH_SECRET is
1299
+ # retained as a break-glass superuser path for bootstrap/debug.
1300
+ "POST /mcp/admin" = "admin-ops-mcp"
1301
+
1302
+ # MCP admin key management — per-tenant Bearer token CRUD. Tokens
1303
+ # are shown ONCE at creation (POST returns raw value); server stores
1304
+ # sha256 hash only. These specific routes take precedence over the
1305
+ # existing `ANY /api/tenants/{proxy+}` route (tenants handler) per
1306
+ # API Gateway v2's most-specific-match rule.
1307
+ "POST /api/tenants/{tenantId}/mcp-admin-keys" = "mcp-admin-keys"
1308
+ "GET /api/tenants/{tenantId}/mcp-admin-keys" = "mcp-admin-keys"
1309
+ "DELETE /api/tenants/{tenantId}/mcp-admin-keys/{keyId}" = "mcp-admin-keys"
1310
+
1311
+ # One-shot tenant provisioning for the admin-ops MCP. Mints a fresh
1312
+ # tkm_ key + stores it in Secrets Manager at
1313
+ # thinkwork/<stage>/mcp/<tenantId>/admin-ops + upserts the
1314
+ # tenant_mcp_servers row so the runtime picks the server up for
1315
+ # any agent that gets it assigned via agent_mcp_servers.
1316
+ "POST /api/tenants/{tenantId}/mcp-admin-provision" = "mcp-admin-provision"
1317
+
1318
+ # MCP server admin approval (plan §U11, SI-5). Externally-sourced MCP
1319
+ # servers land with status='pending'; these routes flip them to
1320
+ # approved/rejected. Cognito JWT only (mcp-approval handler rejects
1321
+ # apikey callers) — the admin SPA is the sole UI surface.
1322
+ "POST /api/tenants/{tenantId}/mcp-servers/{serverId}/approve" = "mcp-approval"
1323
+ "OPTIONS /api/tenants/{tenantId}/mcp-servers/{serverId}/approve" = "mcp-approval"
1324
+ "POST /api/tenants/{tenantId}/mcp-servers/{serverId}/reject" = "mcp-approval"
1325
+ "OPTIONS /api/tenants/{tenantId}/mcp-servers/{serverId}/reject" = "mcp-approval"
1326
+
1327
+ # Finance pilot U2 — thread-attachment upload (presign + finalize).
1328
+ # Cognito JWT; tenant pinned via threads.tenant_id lookup. OPTIONS
1329
+ # is handled inside the Lambda before auth.
1330
+ "POST /api/threads/{threadId}/attachments/presign" = "thread-attachments-presign"
1331
+ "OPTIONS /api/threads/{threadId}/attachments/presign" = "thread-attachments-presign"
1332
+ "POST /api/threads/{threadId}/attachments/finalize" = "thread-attachments-finalize"
1333
+ "OPTIONS /api/threads/{threadId}/attachments/finalize" = "thread-attachments-finalize"
1334
+
1335
+ # U9-remainder of finance pilot — tenant-pinned download endpoint.
1336
+ "GET /api/threads/{threadId}/attachments/{attachmentId}/download" = "thread-attachment-download"
1337
+ "OPTIONS /api/threads/{threadId}/attachments/{attachmentId}/download" = "thread-attachment-download"
1338
+
1339
+ # Fat-folder bundle import. OPTIONS is handled inside the Lambda before auth.
1340
+ "POST /api/agents/{agentId}/import-bundle" = "folder-bundle-import"
1341
+ "OPTIONS /api/agents/{agentId}/import-bundle" = "folder-bundle-import"
1342
+
1343
+ # Resolved Capability Manifest write endpoint (plan §U15). The runtime
1344
+ # posts one row per agent-session-start. Shared
1345
+ # API_AUTH_SECRET; no tenant OAuth.
1346
+ "POST /api/runtime/manifests" = "manifest-log"
1347
+ "OPTIONS /api/runtime/manifests" = "manifest-log"
1348
+
1349
+ # SI-7 catalog-list read (plan §U15 pt 3/3). The runtime fetches
1350
+ # the allowed slug set once per session-start. Shared API_AUTH_SECRET.
1351
+ "GET /api/runtime/capability-catalog" = "capability-catalog-list"
1352
+ "OPTIONS /api/runtime/capability-catalog" = "capability-catalog-list"
1353
+ } : route_key => handler_name
1354
+ if !contains(local.optional_integration_handler_names, handler_name)
1062
1355
  } : {}
1063
1356
  }
1064
1357
 
@@ -1089,6 +1382,49 @@ resource "aws_lambda_permission" "handler_apigw" {
1089
1382
  source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
1090
1383
  }
1091
1384
 
1385
+ ################################################################################
1386
+ # EventBridge → routine-execution-callback
1387
+ ################################################################################
1388
+
1389
+ resource "aws_cloudwatch_event_rule" "routine_sfn_state_change" {
1390
+ count = local.deploy_lambda_handlers ? 1 : 0
1391
+ name = "thinkwork-${var.stage}-routines-sfn-state-change"
1392
+ description = "Forward routine Step Functions execution state changes to the callback Lambda."
1393
+
1394
+ event_pattern = jsonencode({
1395
+ source = ["aws.states"]
1396
+ "detail-type" = ["Step Functions Execution Status Change"]
1397
+ detail = {
1398
+ stateMachineArn = [
1399
+ {
1400
+ prefix = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:thinkwork-${var.stage}-routine-"
1401
+ },
1402
+ ]
1403
+ }
1404
+ })
1405
+
1406
+ tags = {
1407
+ Name = "thinkwork-${var.stage}-routines-sfn-state-change"
1408
+ Stage = var.stage
1409
+ }
1410
+ }
1411
+
1412
+ resource "aws_cloudwatch_event_target" "routine_sfn_state_change" {
1413
+ count = local.deploy_lambda_handlers ? 1 : 0
1414
+ rule = aws_cloudwatch_event_rule.routine_sfn_state_change[0].name
1415
+ target_id = "routine-execution-callback"
1416
+ arn = aws_lambda_function.handler["routine-execution-callback"].arn
1417
+ }
1418
+
1419
+ resource "aws_lambda_permission" "routine_sfn_state_change" {
1420
+ count = local.deploy_lambda_handlers ? 1 : 0
1421
+ statement_id = "AllowEventBridgeInvokeRoutineExecutionCallback"
1422
+ action = "lambda:InvokeFunction"
1423
+ function_name = aws_lambda_function.handler["routine-execution-callback"].function_name
1424
+ principal = "events.amazonaws.com"
1425
+ source_arn = aws_cloudwatch_event_rule.routine_sfn_state_change[0].arn
1426
+ }
1427
+
1092
1428
  # ---------------------------------------------------------------------------
1093
1429
  # Wakeup Processor — EventBridge schedule (every 1 min)
1094
1430
  # ---------------------------------------------------------------------------
@@ -1112,15 +1448,15 @@ resource "aws_scheduler_schedule" "wakeup_processor" {
1112
1448
  }
1113
1449
 
1114
1450
  # ---------------------------------------------------------------------------
1115
- # webhook_deliveries retention crondaily delete of rows older than 90 days
1451
+ # n8n agent-step expirerresumes waiting n8n executions on timeout/retry
1116
1452
  # ---------------------------------------------------------------------------
1117
1453
 
1118
- resource "aws_scheduler_schedule" "webhook_deliveries_cleanup" {
1454
+ resource "aws_scheduler_schedule" "n8n_agent_step_expirer" {
1119
1455
  count = local.deploy_lambda_handlers ? 1 : 0
1120
1456
 
1121
- name = "thinkwork-${var.stage}-webhook-deliveries-cleanup"
1457
+ name = "thinkwork-${var.stage}-n8n-agent-step-expirer"
1122
1458
  group_name = "default"
1123
- schedule_expression = "cron(0 4 * * ? *)" # daily at 04:00 UTC
1459
+ schedule_expression = "rate(1 minutes)"
1124
1460
  state = "ENABLED"
1125
1461
 
1126
1462
  flexible_time_window {
@@ -1128,24 +1464,21 @@ resource "aws_scheduler_schedule" "webhook_deliveries_cleanup" {
1128
1464
  }
1129
1465
 
1130
1466
  target {
1131
- arn = aws_lambda_function.handler["webhook-deliveries-cleanup"].arn
1467
+ arn = aws_lambda_function.handler["n8n-agent-step-expirer"].arn
1132
1468
  role_arn = aws_iam_role.scheduler.arn
1133
1469
  }
1134
1470
  }
1135
1471
 
1136
1472
  # ---------------------------------------------------------------------------
1137
- # Plugin staging sweeperhourly orphan-S3 cleanup for interrupted install
1138
- # sagas (plan §U10). WORKSPACE_BUCKET env on the Lambda role already grants
1139
- # the list+delete IAM; this schedule is the hourly trigger. The sweeper's
1140
- # own cutoff constant (60 min) is independent of this cron cadence.
1473
+ # webhook_deliveries retention crondaily delete of rows older than 90 days
1141
1474
  # ---------------------------------------------------------------------------
1142
1475
 
1143
- resource "aws_scheduler_schedule" "plugin_staging_sweeper" {
1476
+ resource "aws_scheduler_schedule" "webhook_deliveries_cleanup" {
1144
1477
  count = local.deploy_lambda_handlers ? 1 : 0
1145
1478
 
1146
- name = "thinkwork-${var.stage}-plugin-staging-sweeper"
1479
+ name = "thinkwork-${var.stage}-webhook-deliveries-cleanup"
1147
1480
  group_name = "default"
1148
- schedule_expression = "rate(1 hour)"
1481
+ schedule_expression = "cron(0 4 * * ? *)" # daily at 04:00 UTC
1149
1482
  state = "ENABLED"
1150
1483
 
1151
1484
  flexible_time_window {
@@ -1153,7 +1486,7 @@ resource "aws_scheduler_schedule" "plugin_staging_sweeper" {
1153
1486
  }
1154
1487
 
1155
1488
  target {
1156
- arn = aws_lambda_function.handler["plugin-staging-sweeper"].arn
1489
+ arn = aws_lambda_function.handler["webhook-deliveries-cleanup"].arn
1157
1490
  role_arn = aws_iam_role.scheduler.arn
1158
1491
  }
1159
1492
  }
@@ -1257,17 +1590,16 @@ resource "aws_scheduler_schedule" "eval_runs_reconciler" {
1257
1590
  }
1258
1591
 
1259
1592
  # ---------------------------------------------------------------------------
1260
- # Stall monitor marks stalled thread turns and runbook steps failed every
1261
- # minute. This is the global backstop for agent/runtime crashes; the Computer
1262
- # heartbeat also reconciles its own stale runbook tasks while it is alive.
1593
+ # trace_invocation_reconcilerreconciles runtime LLM usage against Bedrock
1594
+ # invocation logs every 5 min. Idempotent at the source-evidence/fact level.
1263
1595
  # ---------------------------------------------------------------------------
1264
1596
 
1265
- resource "aws_scheduler_schedule" "stall_monitor" {
1597
+ resource "aws_scheduler_schedule" "trace_invocation_reconciler" {
1266
1598
  count = local.deploy_lambda_handlers ? 1 : 0
1267
1599
 
1268
- name = "thinkwork-${var.stage}-stall-monitor"
1600
+ name = "thinkwork-${var.stage}-trace-invocation-reconciler"
1269
1601
  group_name = "default"
1270
- schedule_expression = "rate(1 minutes)"
1602
+ schedule_expression = "rate(5 minutes)"
1271
1603
  state = "ENABLED"
1272
1604
 
1273
1605
  flexible_time_window {
@@ -1275,24 +1607,169 @@ resource "aws_scheduler_schedule" "stall_monitor" {
1275
1607
  }
1276
1608
 
1277
1609
  target {
1278
- arn = aws_lambda_function.handler["cron-stall-monitor"].arn
1610
+ arn = aws_lambda_function.handler["trace-invocation-reconciler"].arn
1279
1611
  role_arn = aws_iam_role.scheduler.arn
1280
1612
  }
1281
1613
  }
1282
1614
 
1615
+ # Bedrock model invocation logs are the provider-observed evidence source for
1616
+ # trace invocation reconciliation. The Bedrock setting is account/region scoped,
1617
+ # so keep the destination stage-neutral and let each stage's reconciler match
1618
+ # only rows present in its own database. Because both the log group name and
1619
+ # the account logging configuration are singletons per account+region, only
1620
+ # ONE stage may manage them (var.manage_bedrock_invocation_logging) — a second
1621
+ # stack collides on the log group and would clobber, then destroy, the
1622
+ # account-level config out from under the managing stage (harness cycle-5).
1623
+ locals {
1624
+ bedrock_invocation_log_group_name = "/thinkwork/bedrock/model-invocations"
1625
+ bedrock_invocation_log_group_arn = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/thinkwork/bedrock/model-invocations"
1626
+ }
1627
+
1628
+ # Adding count re-addressed these resources (`x` → `x[0]`); without moved
1629
+ # blocks, existing states (dev) plan destroy+create — losing invocation-log
1630
+ # history and racing the same-name recreate (the private_nat
1631
+ # RouteAlreadyExists failure class from the cycle-4 fix).
1632
+ moved {
1633
+ from = aws_cloudwatch_log_group.bedrock_model_invocations
1634
+ to = aws_cloudwatch_log_group.bedrock_model_invocations[0]
1635
+ }
1636
+
1637
+ moved {
1638
+ from = aws_iam_role.bedrock_model_invocation_logging
1639
+ to = aws_iam_role.bedrock_model_invocation_logging[0]
1640
+ }
1641
+
1642
+ moved {
1643
+ from = aws_iam_role_policy.bedrock_model_invocation_logging
1644
+ to = aws_iam_role_policy.bedrock_model_invocation_logging[0]
1645
+ }
1646
+
1647
+ moved {
1648
+ from = aws_bedrock_model_invocation_logging_configuration.this
1649
+ to = aws_bedrock_model_invocation_logging_configuration.this[0]
1650
+ }
1651
+
1652
+ resource "aws_cloudwatch_log_group" "bedrock_model_invocations" {
1653
+ count = var.manage_bedrock_invocation_logging ? 1 : 0
1654
+ name = local.bedrock_invocation_log_group_name
1655
+ retention_in_days = 30
1656
+
1657
+ tags = {
1658
+ Name = "thinkwork-bedrock-model-invocations"
1659
+ Stage = var.stage
1660
+ Component = "bedrock-model-invocation-logging"
1661
+ }
1662
+ }
1663
+
1664
+ resource "aws_iam_role" "bedrock_model_invocation_logging" {
1665
+ count = var.manage_bedrock_invocation_logging ? 1 : 0
1666
+ name = "thinkwork-${var.stage}-bedrock-model-invocation-logging"
1667
+
1668
+ assume_role_policy = jsonencode({
1669
+ Version = "2012-10-17"
1670
+ Statement = [
1671
+ {
1672
+ Effect = "Allow"
1673
+ Principal = {
1674
+ Service = "bedrock.amazonaws.com"
1675
+ }
1676
+ Action = "sts:AssumeRole"
1677
+ Condition = {
1678
+ StringEquals = {
1679
+ "aws:SourceAccount" = var.account_id
1680
+ }
1681
+ }
1682
+ },
1683
+ ]
1684
+ })
1685
+
1686
+ tags = {
1687
+ Name = "thinkwork-${var.stage}-bedrock-model-invocation-logging"
1688
+ Stage = var.stage
1689
+ Component = "bedrock-model-invocation-logging"
1690
+ }
1691
+ }
1692
+
1693
+ resource "aws_iam_role_policy" "bedrock_model_invocation_logging" {
1694
+ count = var.manage_bedrock_invocation_logging ? 1 : 0
1695
+ name = "write-bedrock-model-invocation-logs"
1696
+ role = aws_iam_role.bedrock_model_invocation_logging[0].id
1697
+
1698
+ policy = jsonencode({
1699
+ Version = "2012-10-17"
1700
+ Statement = [
1701
+ {
1702
+ Sid = "WriteCloudWatchInvocationLogs"
1703
+ Effect = "Allow"
1704
+ Action = [
1705
+ "logs:CreateLogStream",
1706
+ "logs:DescribeLogStreams",
1707
+ "logs:PutLogEvents",
1708
+ ]
1709
+ Resource = [
1710
+ local.bedrock_invocation_log_group_arn,
1711
+ "${local.bedrock_invocation_log_group_arn}:log-stream:aws/bedrock/modelinvocations",
1712
+ "${local.bedrock_invocation_log_group_arn}:log-stream:aws/bedrock/modelinvocations*",
1713
+ ]
1714
+ },
1715
+ {
1716
+ Sid = "WriteLargeInvocationPayloads"
1717
+ Effect = "Allow"
1718
+ Action = [
1719
+ "s3:GetBucketLocation",
1720
+ "s3:ListBucket",
1721
+ ]
1722
+ Resource = var.bucket_arn
1723
+ },
1724
+ {
1725
+ Sid = "PutLargeInvocationPayloads"
1726
+ Effect = "Allow"
1727
+ Action = "s3:PutObject"
1728
+ Resource = "${var.bucket_arn}/bedrock/model-invocation-logs/*"
1729
+ },
1730
+ ]
1731
+ })
1732
+ }
1733
+
1734
+ resource "aws_bedrock_model_invocation_logging_configuration" "this" {
1735
+ count = var.manage_bedrock_invocation_logging ? 1 : 0
1736
+
1737
+ logging_config {
1738
+ text_data_delivery_enabled = true
1739
+ image_data_delivery_enabled = false
1740
+ embedding_data_delivery_enabled = false
1741
+ video_data_delivery_enabled = false
1742
+
1743
+ cloudwatch_config {
1744
+ log_group_name = aws_cloudwatch_log_group.bedrock_model_invocations[0].name
1745
+ role_arn = aws_iam_role.bedrock_model_invocation_logging[0].arn
1746
+
1747
+ large_data_delivery_s3_config {
1748
+ bucket_name = var.bucket_name
1749
+ key_prefix = "bedrock/model-invocation-logs/large/"
1750
+ }
1751
+ }
1752
+
1753
+ s3_config {
1754
+ bucket_name = var.bucket_name
1755
+ key_prefix = "bedrock/model-invocation-logs/"
1756
+ }
1757
+ }
1758
+
1759
+ depends_on = [aws_iam_role_policy.bedrock_model_invocation_logging]
1760
+ }
1761
+
1283
1762
  # ---------------------------------------------------------------------------
1284
- # ThinkWork Computer runtime reconciler keeps active Computers aligned with
1285
- # desired_runtime_status by provisioning/starting/stopping ECS services in
1286
- # bounded batches. The handler is conservative and records per-Computer events
1287
- # for every attempted action.
1763
+ # cost_bill_reconciler imports AWS billing export evidence daily. Targeted
1764
+ # operator invokes can reconcile a specific manifest/import immediately.
1288
1765
  # ---------------------------------------------------------------------------
1289
1766
 
1290
- resource "aws_scheduler_schedule" "computer_runtime_reconciler" {
1767
+ resource "aws_scheduler_schedule" "cost_bill_reconciler" {
1291
1768
  count = local.deploy_lambda_handlers ? 1 : 0
1292
1769
 
1293
- name = "thinkwork-${var.stage}-computer-runtime-reconciler"
1770
+ name = "thinkwork-${var.stage}-cost-bill-reconciler"
1294
1771
  group_name = "default"
1295
- schedule_expression = "rate(5 minutes)"
1772
+ schedule_expression = "cron(30 5 * * ? *)"
1296
1773
  state = "ENABLED"
1297
1774
 
1298
1775
  flexible_time_window {
@@ -1300,17 +1777,23 @@ resource "aws_scheduler_schedule" "computer_runtime_reconciler" {
1300
1777
  }
1301
1778
 
1302
1779
  target {
1303
- arn = aws_lambda_function.handler["computer-runtime-reconciler"].arn
1780
+ arn = aws_lambda_function.handler["cost-bill-reconciler"].arn
1304
1781
  role_arn = aws_iam_role.scheduler.arn
1305
1782
  }
1306
1783
  }
1307
1784
 
1308
- resource "aws_scheduler_schedule" "slack_dispatch" {
1785
+ # ---------------------------------------------------------------------------
1786
+ # Stall monitor — marks stalled thread turns and runbook steps failed every
1787
+ # minute. This is the global backstop for agent/runtime crashes; the Computer
1788
+ # heartbeat also reconciles its own stale runbook tasks while it is alive.
1789
+ # ---------------------------------------------------------------------------
1790
+
1791
+ resource "aws_scheduler_schedule" "stall_monitor" {
1309
1792
  count = local.deploy_lambda_handlers ? 1 : 0
1310
1793
 
1311
- name = "thinkwork-${var.stage}-slack-dispatch"
1794
+ name = "thinkwork-${var.stage}-stall-monitor"
1312
1795
  group_name = "default"
1313
- schedule_expression = "rate(1 minute)"
1796
+ schedule_expression = "rate(1 minutes)"
1314
1797
  state = "ENABLED"
1315
1798
 
1316
1799
  flexible_time_window {
@@ -1318,12 +1801,12 @@ resource "aws_scheduler_schedule" "slack_dispatch" {
1318
1801
  }
1319
1802
 
1320
1803
  target {
1321
- arn = aws_lambda_function.handler["slack-dispatch"].arn
1804
+ arn = aws_lambda_function.handler["cron-stall-monitor"].arn
1322
1805
  role_arn = aws_iam_role.scheduler.arn
1323
- input = jsonencode({ limit = 25 })
1324
1806
  }
1325
1807
  }
1326
1808
 
1809
+
1327
1810
  # ---------------------------------------------------------------------------
1328
1811
  # Compounding Memory — nightly hygiene + export
1329
1812
  # ---------------------------------------------------------------------------
@@ -1346,6 +1829,29 @@ resource "aws_scheduler_schedule" "wiki_compile_drainer" {
1346
1829
  }
1347
1830
  }
1348
1831
 
1832
+ # Observations → Knowledge Graph sweep (plan 2026-06-09-004 U5). Enumerates
1833
+ # tenants and runs an incremental observations ingest per tenant; the stable
1834
+ # source_ref's active-run dedupe drops overlap with operator-started runs and
1835
+ # the in-handler stale-run reaper clears stranded rows past the run ceiling.
1836
+ resource "aws_scheduler_schedule" "knowledge_graph_observations_ingest" {
1837
+ count = local.deploy_lambda_handlers ? 1 : 0
1838
+
1839
+ name = "thinkwork-${var.stage}-knowledge-graph-observations-ingest"
1840
+ group_name = "default"
1841
+ schedule_expression = "rate(30 minutes)"
1842
+ state = "ENABLED"
1843
+
1844
+ flexible_time_window {
1845
+ mode = "OFF"
1846
+ }
1847
+
1848
+ target {
1849
+ arn = aws_lambda_function.handler["knowledge-graph-observations-ingest"].arn
1850
+ role_arn = aws_iam_role.scheduler.arn
1851
+ input = jsonencode({ sweep = true, trigger = "scheduled" })
1852
+ }
1853
+ }
1854
+
1349
1855
  resource "aws_scheduler_schedule" "wiki_lint" {
1350
1856
  count = local.deploy_lambda_handlers ? 1 : 0
1351
1857
 
@@ -1416,18 +1922,180 @@ resource "aws_s3_bucket_lifecycle_configuration" "wiki_exports" {
1416
1922
  }
1417
1923
  }
1418
1924
 
1419
- resource "aws_iam_role_policy" "lambda_wiki_exports_s3" {
1420
- name = "wiki-exports-s3"
1421
- role = aws_iam_role.lambda.id
1925
+ # Canonical Company Brain artifact store. Unlike wiki_exports, this bucket
1926
+ # is the durable replay/projection substrate for Brain source artifacts,
1927
+ # ingestion manifests, migration snapshots, vault projections, and exports.
1928
+ resource "aws_s3_bucket" "brain_artifacts" {
1929
+ bucket = "thinkwork-${var.stage}-brain-artifacts"
1930
+ force_destroy = var.stage == "dev"
1422
1931
 
1423
- policy = jsonencode({
1424
- Version = "2012-10-17"
1425
- Statement = [{
1426
- Effect = "Allow"
1427
- Action = ["s3:PutObject", "s3:AbortMultipartUpload"]
1428
- Resource = "${aws_s3_bucket.wiki_exports.arn}/*"
1429
- }]
1430
- })
1932
+ tags = {
1933
+ Name = "thinkwork-${var.stage}-brain-artifacts"
1934
+ Purpose = "company-brain-artifacts"
1935
+ }
1936
+ }
1937
+
1938
+ resource "aws_s3_bucket_public_access_block" "brain_artifacts" {
1939
+ bucket = aws_s3_bucket.brain_artifacts.id
1940
+ block_public_acls = true
1941
+ block_public_policy = true
1942
+ ignore_public_acls = true
1943
+ restrict_public_buckets = true
1944
+ }
1945
+
1946
+ resource "aws_s3_bucket_versioning" "brain_artifacts" {
1947
+ bucket = aws_s3_bucket.brain_artifacts.id
1948
+
1949
+ versioning_configuration {
1950
+ status = "Enabled"
1951
+ }
1952
+ }
1953
+
1954
+ resource "aws_s3_bucket_server_side_encryption_configuration" "brain_artifacts" {
1955
+ bucket = aws_s3_bucket.brain_artifacts.id
1956
+
1957
+ rule {
1958
+ apply_server_side_encryption_by_default {
1959
+ sse_algorithm = var.brain_artifacts_kms_key_arn != "" ? "aws:kms" : "AES256"
1960
+ kms_master_key_id = var.brain_artifacts_kms_key_arn != "" ? var.brain_artifacts_kms_key_arn : null
1961
+ }
1962
+
1963
+ bucket_key_enabled = var.brain_artifacts_kms_key_arn != ""
1964
+ }
1965
+ }
1966
+
1967
+ resource "aws_s3_bucket_lifecycle_configuration" "brain_artifacts" {
1968
+ bucket = aws_s3_bucket.brain_artifacts.id
1969
+
1970
+ rule {
1971
+ id = "source-artifacts-transition"
1972
+ status = "Enabled"
1973
+
1974
+ filter {
1975
+ prefix = "source-artifacts/"
1976
+ }
1977
+
1978
+ transition {
1979
+ days = 90
1980
+ storage_class = "STANDARD_IA"
1981
+ }
1982
+
1983
+ noncurrent_version_transition {
1984
+ noncurrent_days = 90
1985
+ storage_class = "STANDARD_IA"
1986
+ }
1987
+ }
1988
+
1989
+ rule {
1990
+ id = "ingestion-manifests-transition"
1991
+ status = "Enabled"
1992
+
1993
+ filter {
1994
+ prefix = "ingestion-manifests/"
1995
+ }
1996
+
1997
+ transition {
1998
+ days = 90
1999
+ storage_class = "STANDARD_IA"
2000
+ }
2001
+
2002
+ noncurrent_version_transition {
2003
+ noncurrent_days = 90
2004
+ storage_class = "STANDARD_IA"
2005
+ }
2006
+ }
2007
+
2008
+ rule {
2009
+ id = "migration-snapshots-transition"
2010
+ status = "Enabled"
2011
+
2012
+ filter {
2013
+ prefix = "migration-snapshots/"
2014
+ }
2015
+
2016
+ transition {
2017
+ days = 90
2018
+ storage_class = "STANDARD_IA"
2019
+ }
2020
+
2021
+ noncurrent_version_transition {
2022
+ noncurrent_days = 90
2023
+ storage_class = "STANDARD_IA"
2024
+ }
2025
+ }
2026
+
2027
+ rule {
2028
+ id = "vault-projections-transition"
2029
+ status = "Enabled"
2030
+
2031
+ filter {
2032
+ prefix = "vault-projections/"
2033
+ }
2034
+
2035
+ transition {
2036
+ days = 90
2037
+ storage_class = "STANDARD_IA"
2038
+ }
2039
+
2040
+ noncurrent_version_transition {
2041
+ noncurrent_days = 90
2042
+ storage_class = "STANDARD_IA"
2043
+ }
2044
+ }
2045
+
2046
+ rule {
2047
+ id = "exports-expiration"
2048
+ status = "Enabled"
2049
+
2050
+ filter {
2051
+ prefix = "exports/"
2052
+ }
2053
+
2054
+ transition {
2055
+ days = 30
2056
+ storage_class = "STANDARD_IA"
2057
+ }
2058
+
2059
+ expiration {
2060
+ days = 365
2061
+ }
2062
+
2063
+ noncurrent_version_expiration {
2064
+ noncurrent_days = 365
2065
+ }
2066
+ }
2067
+ }
2068
+
2069
+ data "aws_iam_policy_document" "brain_artifacts_bucket" {
2070
+ statement {
2071
+ sid = "DenyInsecureTransport"
2072
+ effect = "Deny"
2073
+
2074
+ principals {
2075
+ type = "*"
2076
+ identifiers = ["*"]
2077
+ }
2078
+
2079
+ actions = ["s3:*"]
2080
+
2081
+ resources = [
2082
+ aws_s3_bucket.brain_artifacts.arn,
2083
+ "${aws_s3_bucket.brain_artifacts.arn}/*",
2084
+ ]
2085
+
2086
+ condition {
2087
+ test = "Bool"
2088
+ variable = "aws:SecureTransport"
2089
+ values = ["false"]
2090
+ }
2091
+ }
2092
+ }
2093
+
2094
+ resource "aws_s3_bucket_policy" "brain_artifacts" {
2095
+ bucket = aws_s3_bucket.brain_artifacts.id
2096
+ policy = data.aws_iam_policy_document.brain_artifacts_bucket.json
2097
+
2098
+ depends_on = [aws_s3_bucket_public_access_block.brain_artifacts]
1431
2099
  }
1432
2100
 
1433
2101
  resource "aws_iam_role" "scheduler" {
@@ -1462,20 +2130,16 @@ resource "aws_iam_role_policy" "scheduler_invoke" {
1462
2130
  Statement = [{
1463
2131
  Effect = "Allow"
1464
2132
  Action = ["lambda:InvokeFunction"]
1465
- # Includes every for_each handler PLUS the standalone Phase 3 U8a
1466
- # anchor Lambda (which is intentionally outside the for_each set
1467
- # because it uses the U7 IAM role, not the shared aws_iam_role.lambda).
1468
- # Splat (`[*]`) expansion handles count=0 cleanly when handlers are
1469
- # disabled; an indexed reference (`[0].arn`) would throw on graph eval.
1470
- # Phase 3 U8b — watchdog moved to standalone resource; its ARN must
1471
- # be added to the splat list explicitly (SEC-U8B-005). The splat
1472
- # `[*].arn` form handles count = 0 cleanly when handlers are disabled;
1473
- # an indexed `[0].arn` would throw on graph eval.
1474
- Resource = local.deploy_lambda_handlers ? concat(
1475
- [for k, v in aws_lambda_function.handler : v.arn],
1476
- aws_lambda_function.compliance_anchor[*].arn,
1477
- aws_lambda_function.compliance_anchor_watchdog[*].arn,
1478
- ) : []
2133
+ # One name-pattern wildcard instead of enumerating every handler ARN:
2134
+ # the enumerated form scaled with handler count × stage-name length and
2135
+ # blew IAM's 10,240-byte inline-policy cap for stages ~11 characters
2136
+ # (harness cycle-7 ledger entry; dev's 3-char stage never saw it). The
2137
+ # api- prefix covers every for_each handler plus the standalone
2138
+ # compliance-anchor and watchdog Lambdas (all named
2139
+ # thinkwork-<stage>-api-*).
2140
+ Resource = local.deploy_lambda_handlers ? [
2141
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-*",
2142
+ ] : []
1479
2143
  }]
1480
2144
  })
1481
2145
  }
@@ -1512,6 +2176,46 @@ resource "aws_ssm_parameter" "google_places_api_key" {
1512
2176
  }
1513
2177
  }
1514
2178
 
2179
+ ########################################################################
2180
+ # SecureString parameter for the graphql-http Lambda's OWN Cloudflare
2181
+ # namespace token (plan 2026-06-12-002 U5, KTD7). Tenant slug validation
2182
+ # lists records under <slug>.thinkwork.ai so signup cannot take a name
2183
+ # delegated to a customer deployment. The token is zone-scoped DNS:Edit
2184
+ # on thinkwork.ai, minted SEPARATELY from the CI CLOUDFLARE_API_TOKEN
2185
+ # (independent rotation; a signup-path compromise doesn't burn CI), and
2186
+ # is deliberately NOT a Lambda env var — graphql-http's env sits at the
2187
+ # 4KB ceiling (#2375) — nor a runtime-config document key (secrets stay
2188
+ # out of the plain String document).
2189
+ #
2190
+ # Seeded with a placeholder: @thinkwork/api treats the placeholder (or a
2191
+ # missing parameter) as "namespace check unconfigured" and SKIPS the
2192
+ # Cloudflare leg with a loud log (ship-inert posture, so pre-token dev
2193
+ # stages keep creating tenants). The customer-domain runbook makes the
2194
+ # real token mandatory for stages whose deployments share the namespace.
2195
+ # Populate/rotate via:
2196
+ # aws ssm put-parameter --overwrite \
2197
+ # --name /thinkwork/<stage>/cloudflare-namespace-token \
2198
+ # --type SecureString --value <TOKEN>
2199
+ #
2200
+ # IAM: read access rides the existing ssm:GetParameter grant on
2201
+ # parameter/thinkwork/<stage>/* in aws_iam_policy.api_data_plane
2202
+ # (iam-grouped.tf), plus its kms:Decrypt-via-SSM statement.
2203
+ ########################################################################
2204
+
2205
+ resource "aws_ssm_parameter" "cloudflare_namespace_token" {
2206
+ name = "/thinkwork/${var.stage}/cloudflare-namespace-token"
2207
+ type = "SecureString"
2208
+ value = "PLACEHOLDER_SET_VIA_CLI"
2209
+ description = "Zone-scoped Cloudflare DNS:Edit token for the thinkwork.ai namespace signup check (plan 2026-06-12-002 U5/KTD7). Placeholder = check skipped; set the real token via CLI."
2210
+
2211
+ lifecycle {
2212
+ # Initial population and rotation happen via `aws ssm put-parameter
2213
+ # --overwrite`, never via terraform var (the value must not transit
2214
+ # tfvars / state more than necessary).
2215
+ ignore_changes = [value]
2216
+ }
2217
+ }
2218
+
1515
2219
  resource "aws_ssm_parameter" "lambda_arns" {
1516
2220
  for_each = local.deploy_lambda_handlers ? {
1517
2221
  "chat-agent-invoke-fn-arn" = aws_lambda_function.handler["chat-agent-invoke"].arn
@@ -1816,24 +2520,6 @@ resource "aws_sqs_queue" "compliance_exports" {
1816
2520
  }
1817
2521
  }
1818
2522
 
1819
- # graphql-http needs sqs:SendMessage on the new queue to dispatch jobIds
1820
- # from the createComplianceExport mutation. Attached to the shared
1821
- # lambda role (which graphql-http assumes); scope is queue-specific.
1822
- resource "aws_iam_role_policy" "compliance_exports_send" {
1823
- count = local.deploy_lambda_handlers ? 1 : 0
1824
- name = "compliance-exports-send"
1825
- role = aws_iam_role.lambda.id
1826
-
1827
- policy = jsonencode({
1828
- Version = "2012-10-17"
1829
- Statement = [{
1830
- Effect = "Allow"
1831
- Action = ["sqs:SendMessage"]
1832
- Resource = aws_sqs_queue.compliance_exports[0].arn
1833
- }]
1834
- })
1835
- }
1836
-
1837
2523
  # Runner role's SQS receive grants — only the runner consumes the queue.
1838
2524
  resource "aws_iam_role_policy" "compliance_exports_runner_sqs" {
1839
2525
  count = local.deploy_lambda_handlers ? 1 : 0
@@ -1937,78 +2623,3 @@ resource "aws_cloudwatch_metric_alarm" "compliance_exports_dlq_depth" {
1937
2623
  QueueName = aws_sqs_queue.compliance_exports_dlq[0].name
1938
2624
  }
1939
2625
  }
1940
-
1941
- # ---------------------------------------------------------------------------
1942
- # workspace-files-efs — STANDALONE Lambda that reads any Computer's workspace
1943
- # files directly off the shared EFS file system. Bypasses the
1944
- # computer_tasks queue for list/get operations so the admin Computer
1945
- # Workspace tab is independent of runtime liveness or write-queue backlog.
1946
- #
1947
- # Plan: docs/plans/2026-05-13-XXX-feat-admin-computer-efs-listing-plan.md
1948
- #
1949
- # The Lambda mounts the `workspace_admin` access point at /mnt/efs. That
1950
- # access point is rooted at /tenants on the shared EFS, so the handler
1951
- # can address any Computer's workspace as
1952
- # /mnt/efs/<tenantId>/computers/<computerId>/<path...>
1953
- # (matches the layout written by `computerWorkspacePath` in
1954
- # packages/api/src/lib/computers/runtime-control.ts:40).
1955
- #
1956
- # VPC config: same subnet set the Computer ECS tasks use (so the mount
1957
- # targets are reachable). Dedicated security group with an EFS-SG ingress
1958
- # rule defined as a sibling of the task-SG rule in the computer-runtime
1959
- # module — keeps Lambda traffic auditable separately.
1960
- #
1961
- # Writes intentionally stay on the existing computer_tasks queue path.
1962
- # Mutations have ordering semantics with the runtime's in-process state;
1963
- # changing them is out of scope for this PR.
1964
- # ---------------------------------------------------------------------------
1965
-
1966
- resource "aws_lambda_function" "workspace_files_efs" {
1967
- count = local.deploy_lambda_handlers ? 1 : 0
1968
-
1969
- function_name = "thinkwork-${var.stage}-api-workspace-files-efs"
1970
- role = aws_iam_role.lambda.arn
1971
- handler = "index.handler"
1972
- runtime = local.runtime
1973
- timeout = 30
1974
- memory_size = 512
1975
-
1976
- filename = local.use_local_zips ? "${var.lambda_zips_dir}/workspace-files-efs.zip" : null
1977
- source_code_hash = local.use_local_zips ? filebase64sha256("${var.lambda_zips_dir}/workspace-files-efs.zip") : null
1978
- s3_bucket = local.use_remote_lambda_artifacts ? var.lambda_artifact_bucket : null
1979
- s3_key = local.use_remote_lambda_artifacts ? "${local.lambda_artifact_prefix}/workspace-files-efs.zip" : null
1980
-
1981
- vpc_config {
1982
- subnet_ids = var.computer_runtime_subnet_ids
1983
- security_group_ids = [var.workspace_admin_lambda_sg_id]
1984
- }
1985
-
1986
- file_system_config {
1987
- arn = var.workspace_admin_efs_access_point_arn
1988
- local_mount_path = "/mnt/efs"
1989
- }
1990
-
1991
- environment {
1992
- variables = {
1993
- STAGE = var.stage
1994
- AWS_NODEJS_CONNECTION_REUSE_ENABLED = "1"
1995
- WORKSPACE_EFS_ROOT = "/mnt/efs"
1996
- }
1997
- }
1998
-
1999
- tags = {
2000
- Name = "thinkwork-${var.stage}-api-workspace-files-efs"
2001
- Handler = "workspace-files-efs"
2002
- }
2003
- }
2004
-
2005
- # VPC-attached Lambdas need permission to manage ENIs. The shared lambda
2006
- # role doesn't grant this by default because most handlers run outside a
2007
- # VPC. AWSLambdaVPCAccessExecutionRole gives Create/Describe/DeleteNetwork
2008
- # Interface — minimum scope for VPC Lambdas.
2009
- resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
2010
- count = local.deploy_lambda_handlers ? 1 : 0
2011
-
2012
- role = aws_iam_role.lambda.name
2013
- policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
2014
- }