thinkwork-cli 0.13.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +3 -14
  2. package/dist/{api-client-JYZXYQJR.js → api-client-4P4JZOBX.js} +1 -1
  3. package/dist/{chunk-Z2F6RJFR.js → chunk-4OMDWYZQ.js} +4 -5
  4. package/dist/{chunk-ILZKG5WV.js → chunk-AD2NIWB5.js} +434 -69
  5. package/dist/chunk-MWQ7RO76.js +137 -0
  6. package/dist/{chunk-DVPBI5HM.js → chunk-OLUQ4U2Z.js} +26 -26
  7. package/dist/{chunk-QOERT44A.js → chunk-YQCYD6NE.js} +57 -44
  8. package/dist/cli.js +4400 -16431
  9. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
  10. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
  11. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
  12. package/dist/{dist-es-CRV46D5B.js → dist-es-2OTNTL4N.js} +4 -4
  13. package/dist/{dist-es-ENBUC3JP.js → dist-es-2XVAH4BQ.js} +2 -2
  14. package/dist/{dist-es-DCY4IC3Y.js → dist-es-3DPDUJAH.js} +2 -2
  15. package/dist/{dist-es-G7PTG75M.js → dist-es-BNZTBBVC.js} +1 -1
  16. package/dist/{dist-es-GKDBE7E6.js → dist-es-CD2RBDAC.js} +1 -1
  17. package/dist/{dist-es-R7FGL2IB.js → dist-es-I4FW57PS.js} +1 -1
  18. package/dist/{dist-es-USORBXZ5.js → dist-es-KVFWJNDY.js} +4 -4
  19. package/dist/{dist-es-HY3535OI.js → dist-es-PS73DWNL.js} +2 -2
  20. package/dist/{dist-es-LDE437AM.js → dist-es-S3NZQFGT.js} +1 -1
  21. package/dist/{dist-es-G54LBMRN.js → dist-es-SGCA4P32.js} +1 -1
  22. package/dist/{dist-es-Q57SZXZ7.js → dist-es-WBM6J4RA.js} +2 -2
  23. package/dist/{dist-es-P26FWFLT.js → dist-es-ZGVADYIF.js} +8 -8
  24. package/dist/drizzle/0227_analyst_reader_role.sql +17 -26
  25. package/dist/drizzle/0230_analyst_rls.sql +28 -45
  26. package/dist/drizzle/0261_native_auth_control_plane.sql +7 -3
  27. package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
  28. package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
  29. package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
  30. package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
  31. package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
  32. package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
  33. package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
  34. package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
  35. package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
  36. package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
  37. package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
  38. package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
  39. package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
  40. package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
  41. package/dist/drizzle/0284_user_brain_claims.sql +69 -0
  42. package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
  43. package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
  44. package/dist/drizzle/0287_brain_subject.sql +25 -0
  45. package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
  46. package/dist/{loadSso-DJVB7BXE.js → loadSso-ETCXTR4I.js} +2 -2
  47. package/dist/{loadSso-JAMNA3WY.js → loadSso-UBD7KUHE.js} +2 -2
  48. package/dist/plugins/README.md +2 -2
  49. package/dist/plugins/catalog/package.json +0 -3
  50. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -3
  51. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
  52. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -25
  53. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -6
  54. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -15
  55. package/dist/plugins/catalog/src/contracts.ts +15 -0
  56. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -18
  57. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
  58. package/dist/plugins/twenty/package.json +2 -1
  59. package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
  60. package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
  61. package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
  62. package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
  63. package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
  64. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
  65. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
  66. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
  67. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
  69. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
  70. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
  71. package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
  72. package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
  73. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
  74. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
  75. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
  76. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
  77. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
  78. package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
  79. package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
  80. package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
  81. package/dist/plugins/twenty/test/manifest.test.ts +2 -4
  82. package/dist/scripts/post-deploy.sh +79 -1
  83. package/dist/{signin-UGDWTGS4.js → signin-5QZNRSOO.js} +2 -2
  84. package/dist/{sso-oidc-Q4VXANXY.js → sso-oidc-XK673P54.js} +2 -2
  85. package/dist/{sts-CWLSE3GH.js → sts-OXI2UALF.js} +6 -134
  86. package/dist/{sts-DNZLNVOV.js → sts-SQW4NBK3.js} +6 -134
  87. package/dist/terraform/examples/greenfield/main.tf +233 -175
  88. package/dist/terraform/examples/greenfield/terraform.tfvars.example +12 -8
  89. package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
  90. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
  91. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
  92. package/dist/terraform/modules/app/agentcore-identity/main.tf +106 -26
  93. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +6 -6
  94. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +216 -99
  95. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +6 -9
  96. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
  97. package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
  98. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +22 -7
  99. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +17 -46
  100. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +65 -3
  101. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
  102. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
  103. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
  104. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
  105. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
  106. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
  107. package/dist/terraform/modules/app/agentcore-identity/variables.tf +6 -0
  108. package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
  109. package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
  110. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
  111. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  112. package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
  113. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
  114. package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
  115. package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
  116. package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
  117. package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
  118. package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
  119. package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
  120. package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
  121. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
  122. package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
  123. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
  124. package/dist/terraform/modules/app/deployment-control-plane/runner.py +2641 -124
  125. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2034 -154
  126. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
  127. package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
  128. package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
  129. package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
  130. package/dist/terraform/modules/app/lambda-api/handlers.tf +411 -723
  131. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +262 -198
  132. package/dist/terraform/modules/app/lambda-api/main.tf +51 -13
  133. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +4 -181
  134. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
  135. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
  136. package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -45
  137. package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
  138. package/dist/terraform/modules/app/lambda-api/variables.tf +219 -126
  139. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
  140. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
  141. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
  142. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
  143. package/dist/terraform/modules/app/static-site/main.tf +1 -1
  144. package/dist/terraform/modules/app/www-dns/main.tf +9 -27
  145. package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
  146. package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
  147. package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
  148. package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
  149. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
  150. package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
  151. package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
  152. package/dist/terraform/modules/foundation/cognito/main.tf +81 -8
  153. package/dist/terraform/modules/foundation/cognito/outputs.tf +5 -0
  154. package/dist/terraform/modules/foundation/cognito/variables.tf +24 -12
  155. package/dist/terraform/modules/thinkwork/README.md +5 -1
  156. package/dist/terraform/modules/thinkwork/main.tf +376 -374
  157. package/dist/terraform/modules/thinkwork/outputs.tf +12 -158
  158. package/dist/terraform/modules/thinkwork/variables.tf +246 -89
  159. package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
  160. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
  161. package/dist/workspace-defaults/files/ROUTER.md +1 -1
  162. package/dist/workspace-defaults/files/TOOLS.md +9 -39
  163. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
  164. package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
  165. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
  166. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  167. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  168. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
  169. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
  170. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
  171. package/package.json +1 -1
  172. package/dist/drizzle/0261_managed_harness_multiplayer.sql +0 -313
  173. package/dist/drizzle/0262_parallel_harness_threads.sql +0 -9
  174. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +0 -113
  175. package/dist/plugins/company-data/README.md +0 -49
  176. package/dist/plugins/company-data/package.json +0 -21
  177. package/dist/plugins/company-data/src/index.ts +0 -30
  178. package/dist/plugins/company-data/src/manifest.ts +0 -26
  179. package/dist/plugins/company-data/test/manifest.test.ts +0 -116
  180. package/dist/plugins/company-data/tsconfig.json +0 -8
  181. package/dist/plugins/company-etl/README.md +0 -46
  182. package/dist/plugins/company-etl/package.json +0 -21
  183. package/dist/plugins/company-etl/src/index.ts +0 -29
  184. package/dist/plugins/company-etl/src/manifest.ts +0 -26
  185. package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
  186. package/dist/plugins/company-etl/tsconfig.json +0 -8
  187. package/dist/plugins/lastmile/README.md +0 -50
  188. package/dist/plugins/lastmile/package.json +0 -23
  189. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
  190. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
  191. package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
  192. package/dist/plugins/lastmile/src/index.ts +0 -46
  193. package/dist/plugins/lastmile/src/manifest.ts +0 -139
  194. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
  195. package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
  196. package/dist/plugins/lastmile/tsconfig.json +0 -8
  197. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
  198. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
  199. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
  200. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
  201. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
  202. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
  203. package/dist/terraform/modules/app/agentcore-gateway/main.tf +0 -305
  204. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +0 -44
  205. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +0 -93
  206. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +0 -33
  207. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +0 -543
  208. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +0 -70
  209. package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -330
  210. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -72
  211. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +0 -5
  212. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +0 -430
  213. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +0 -180
  214. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +0 -193
  215. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +0 -5
  216. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +0 -5
  217. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +0 -5
  218. package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -96
  219. package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
  220. package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
  221. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +0 -88
  222. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +0 -91
  223. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
  224. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
  225. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
  226. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
  227. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
  228. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
  229. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
  230. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
  231. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/variables.tf +0 -0
@@ -94,3 +94,36 @@ variable "create_secret_placeholders" {
94
94
  type = bool
95
95
  default = true
96
96
  }
97
+
98
+ # ── Runner-in-VPC (THINK-1082) ────────────────────────────────────────────────
99
+ # The runner's push_database_schema step opens psql to the Aurora cluster. By
100
+ # default the CodeBuild project has no vpc_config, so that connection rides the
101
+ # cluster's PUBLIC endpoint and depends on a world-open 0.0.0.0/0 → 5432 rule
102
+ # on the DB security group. Customers that lock that rule down (deliberate
103
+ # hardening) break every deploy at the psql step. When runner_in_vpc is on, the
104
+ # build runs inside the VPC's private subnets with its own security group and
105
+ # the DB grants 5432 to that group — no world-open port required.
106
+
107
+ variable "runner_in_vpc" {
108
+ description = "Run the deployment runner CodeBuild inside the VPC (private subnets + runner security group) and grant it 5432 on the database security group. Requires vpc_id, subnet_ids, and db_security_group_id. The private subnets must route egress through a NAT gateway (GitHub, apt/pip, STS/S3/Secrets Manager)."
109
+ type = bool
110
+ default = false
111
+ }
112
+
113
+ variable "vpc_id" {
114
+ description = "VPC that hosts the runner when runner_in_vpc is true."
115
+ type = string
116
+ default = ""
117
+ }
118
+
119
+ variable "subnet_ids" {
120
+ description = "Private subnet IDs (NAT-routed) the runner attaches to when runner_in_vpc is true."
121
+ type = list(string)
122
+ default = []
123
+ }
124
+
125
+ variable "db_security_group_id" {
126
+ description = "Database security group that receives the tcp/5432 ingress from the runner security group when runner_in_vpc is true."
127
+ type = string
128
+ default = ""
129
+ }
@@ -0,0 +1,72 @@
1
+ ################################################################################
2
+ # THINK-643/644 — DynamoDB short-lived state store
3
+ #
4
+ # `auth_subscription_tickets` in Aurora is pure churn: 60-second single-use
5
+ # AppSync tickets, plus the per-principal connect counter that backstops a
6
+ # looping client. One customer stage accumulated 551k rows of it on the primary
7
+ # database. DynamoDB is the right shape — native TTL sweeps expired items with
8
+ # no vacuum pressure, conditional writes give the same single-use guarantee the
9
+ # `status = 'issued'` predicate gave, and the table is reachable from the
10
+ # non-VPC auth Lambdas.
11
+ #
12
+ # THINK-644 widens the same table to webhook idempotency receipts, which have
13
+ # the identical defect in a different table: `webhook_idempotency` has no
14
+ # retention at all — a row survives until its parent webhook is deleted — so a
15
+ # chatty provider grows Aurora forever for records nobody reads after a day.
16
+ #
17
+ # Ship-inert: the table and its IAM grant are count-gated on
18
+ # var.auth_state_store == "dynamo", so a stage that has not flipped the flag
19
+ # plans to ZERO changes from this file.
20
+ ################################################################################
21
+
22
+ locals {
23
+ auth_state_enabled = var.auth_state_store == "dynamo"
24
+ auth_state_count = local.auth_state_enabled ? 1 : 0
25
+ auth_state_table_name = "thinkwork-${var.stage}-auth-state"
26
+ }
27
+
28
+ resource "aws_dynamodb_table" "auth_state" {
29
+ count = local.auth_state_count
30
+
31
+ name = local.auth_state_table_name
32
+ billing_mode = "PAY_PER_REQUEST"
33
+ hash_key = "pk"
34
+ range_key = "sk"
35
+
36
+ attribute {
37
+ name = "pk"
38
+ type = "S"
39
+ }
40
+ attribute {
41
+ name = "sk"
42
+ type = "S"
43
+ }
44
+
45
+ # Epoch seconds. Tickets set this to their own expiry plus an hour of grace
46
+ # so a replay still finds the consumed tombstone (TTL deletion is best-effort
47
+ # and can lag by up to 48h); rate-limit counters set it to two window widths;
48
+ # webhook idempotency receipts (THINK-644) set it to 7 days, comfortably past
49
+ # any real provider's retry window. Because deletion lags, every reader
50
+ # compares expires_at itself rather than trusting an item's presence.
51
+ ttl {
52
+ attribute_name = "expires_at"
53
+ enabled = true
54
+ }
55
+
56
+ server_side_encryption {
57
+ enabled = true
58
+ }
59
+
60
+ # Deliberately OFF. Every item in this table is auth-flow scratch with a
61
+ # lifetime measured in minutes — a point-in-time restore of it would restore
62
+ # nothing anyone can use, and PITR bills continuously against the write churn
63
+ # this table exists to absorb.
64
+ point_in_time_recovery {
65
+ enabled = false
66
+ }
67
+
68
+ tags = {
69
+ Name = local.auth_state_table_name
70
+ Purpose = "auth-flow-state"
71
+ }
72
+ }
@@ -0,0 +1,466 @@
1
+ ################################################################################
2
+ # THINK-915 — chat turn-latency observability (metrics, dashboard, alarm,
3
+ # saved Logs Insights query).
4
+ #
5
+ # WHY: a 52 s turn shipped with a hidden ~20-24 s AgentCore cold start (the gap
6
+ # between `api.runtime_dispatch.invoke` starting and the container's
7
+ # `runtime.invocation.received`). Nothing alarmed; a human noticed. Every chat
8
+ # turn already emits structured `agentcore_phase` lines, but they were only
9
+ # reachable through ad-hoc Logs Insights runs (scripts/latency-dashboard.sh).
10
+ # This file turns two of those phases into real CloudWatch metrics, puts them on
11
+ # a per-stage dashboard next to the Lambda-native signals, and alarms on the
12
+ # p95 of the whole runtime call.
13
+ #
14
+ # ── Phase lines and where they land ──────────────────────────────────────────
15
+ # api.agentcore.dispatch chat-agent-invoke /aws/lambda/thinkwork-<stage>-api-chat-agent-invoke
16
+ # api.runtime_dispatch.invoke agentcore-runtime-dispatch /aws/lambda/thinkwork-<stage>-api-agentcore-runtime-dispatch
17
+ # api.finalize.process chat-agent-finalize /aws/lambda/thinkwork-<stage>-api-chat-agent-finalize
18
+ # runtime.* Pi container /aws/bedrock-agentcore/runtimes/thinkwork_<stage>_pi-<id>-DEFAULT
19
+ #
20
+ # ── Constraint 1: JSON filter patterns need whole-line JSON ──────────────────
21
+ # CloudWatch metric filters only apply a JSON filter pattern (`{ $.event = … }`)
22
+ # to log events that parse as JSON end to end. The Node Lambda runtime prefixes
23
+ # `console.*` output with `<timestamp>\t<requestId>\tINFO\t…`, which is not
24
+ # valid JSON — so the Lambda-side phase lines were unusable by metric filters
25
+ # even though Logs Insights tolerates the prefix. THINK-915 therefore switched
26
+ # packages/api/src/lib/agentcore-phase-log.ts to write the record straight to
27
+ # `process.stdout` (the Pi container emitter has always done this), which
28
+ # reaches CloudWatch unprefixed. Nothing else about the record changed, so the
29
+ # existing Logs Insights queries keep working.
30
+ #
31
+ # ── Constraint 2: metric-filter dimensions must be JSON references ───────────
32
+ # `metric_transformation.dimensions` values may only be `$.field` references
33
+ # into the matched log event — a literal `Stage = var.stage` (as used by the
34
+ # PutMetricData-sourced Thinkwork/Costs alarms) is rejected. The phase record
35
+ # carries no stage field, so the stage is carried in the namespace instead:
36
+ # `Thinkwork/Chat/<stage>`. Same isolation, no emitter change.
37
+ #
38
+ # ── Constraint 3: implicit log groups ────────────────────────────────────────
39
+ # The API handler log groups are created implicitly by Lambda on first
40
+ # invocation and are NOT Terraform-managed (see memory-alarms.tf: adding metric
41
+ # filters over them broke greenfield applies with ResourceNotFoundException).
42
+ # So the metric filters — and the alarm that depends on one of them — are gated
43
+ # behind `enable_chat_turn_latency_metric_filters`, default OFF. Flip it on for
44
+ # a stage that has already served chat traffic. The dashboard, the Lambda-native
45
+ # alarm, and the saved query have no such dependency and are on by default.
46
+ #
47
+ # ── Constraint 4: the runtime log group name contains a generated id ─────────
48
+ # The AgentCore runtime log group is `/aws/bedrock-agentcore/runtimes/
49
+ # thinkwork_<stage>_pi-<runtimeId>-DEFAULT`, and the runtime is reconciled by
50
+ # scripts/post-deploy.sh (runtime id in SSM), not by Terraform — so no resource
51
+ # here knows the name. It is supplied explicitly via
52
+ # `chat_turn_runtime_log_group_name` (see the revised note in locals below —
53
+ # plan-time prefix discovery broke customer deployment-runner plans). While it
54
+ # is unset the runtime-sourced pieces (AgentLoopMs filter, its dashboard
55
+ # widgets, that leg of the saved query) are simply skipped.
56
+ ################################################################################
57
+
58
+ locals {
59
+ chat_latency_enabled = local.deploy_lambda_handlers && var.enable_chat_turn_latency_observability
60
+
61
+ # See constraint 2 — the stage lives in the namespace, not a dimension.
62
+ chat_latency_namespace = "${var.chat_turn_latency_metric_namespace}/${var.stage}"
63
+
64
+ chat_latency_dispatch_fn = local.chat_latency_enabled ? aws_lambda_function.handler["agentcore-runtime-dispatch"].function_name : ""
65
+ chat_latency_invoke_fn = local.chat_latency_enabled ? aws_lambda_function.handler["chat-agent-invoke"].function_name : ""
66
+ chat_latency_finalize_fn = local.chat_latency_enabled ? aws_lambda_function.handler["chat-agent-finalize"].function_name : ""
67
+ chat_latency_graphql_fn = local.chat_latency_enabled ? aws_lambda_function.handler["graphql-http"].function_name : ""
68
+
69
+ # The four Lambdas a chat turn passes through, in turn order.
70
+ chat_latency_functions = local.chat_latency_enabled ? [
71
+ local.chat_latency_graphql_fn,
72
+ local.chat_latency_invoke_fn,
73
+ local.chat_latency_dispatch_fn,
74
+ local.chat_latency_finalize_fn,
75
+ ] : []
76
+
77
+ chat_latency_lambda_log_groups = [for fn in local.chat_latency_functions : "/aws/lambda/${fn}"]
78
+
79
+ # Constraint 4, revised after the canary.467/468 mcpherson deploys: the
80
+ # runtime log group comes ONLY from the explicit variable. The prefix
81
+ # discovery data source (aws_cloudwatch_log_groups) defers to apply in the
82
+ # customer deployment-runner flow, and the resulting unknown value first
83
+ # broke a count ("Invalid count argument") and then, once the count was
84
+ # fixed, broke the dashboard-widget conditionals ("Inconsistent conditional
85
+ # result types" — an unknown condition forces both arms of every dependent
86
+ # conditional to unify to one type, which heterogeneous widget objects
87
+ # cannot). A plan-known variable sidesteps the whole class. When it is
88
+ # empty, the runtime-sourced pieces (AgentLoopMs filter, runtime widgets,
89
+ # that leg of the saved query) are skipped; set it per stage once the
90
+ # runtime exists:
91
+ # /aws/bedrock-agentcore/runtimes/thinkwork_<stage>_pi-<runtimeId>-DEFAULT
92
+ chat_latency_runtime_log_group = var.chat_turn_runtime_log_group_name
93
+
94
+ chat_latency_has_runtime_log_group = local.chat_latency_runtime_log_group != ""
95
+
96
+ chat_latency_metric_filters_enabled = local.chat_latency_enabled && var.enable_chat_turn_latency_metric_filters
97
+ }
98
+
99
+ ################################################################################
100
+ # Metric filters
101
+ ################################################################################
102
+
103
+ # Whole-runtime-call latency: the dispatcher measures from just before
104
+ # InvokeAgentRuntime to the fully drained response, so this number INCLUDES the
105
+ # AgentCore cold start that the 52 s incident hid. Only `completed` lines feed
106
+ # it — a `failed` line's durationMs is time-to-error, not turn latency.
107
+ resource "aws_cloudwatch_log_metric_filter" "turn_runtime_invoke_ms" {
108
+ count = local.chat_latency_metric_filters_enabled ? 1 : 0
109
+
110
+ name = "thinkwork-${var.stage}-turn-runtime-invoke-ms"
111
+ log_group_name = "/aws/lambda/${local.chat_latency_dispatch_fn}"
112
+ pattern = "{ $.event = \"agentcore_phase\" && $.phase = \"api.runtime_dispatch.invoke\" && $.status = \"completed\" && $.durationMs = * }"
113
+
114
+ metric_transformation {
115
+ name = "TurnRuntimeInvokeMs"
116
+ namespace = local.chat_latency_namespace
117
+ value = "$.durationMs"
118
+ unit = "Milliseconds"
119
+ # No default_value: a period with no turns must stay a gap, not a 0 ms turn
120
+ # that would drag the percentiles down and mask a regression.
121
+ }
122
+ }
123
+
124
+ # Model-loop latency from inside the container. Subtracting this from
125
+ # TurnRuntimeInvokeMs is the cold-start / harness-overhead signal (see the
126
+ # dashboard's "runtime overhead" widget).
127
+ #
128
+ # count gates on VARIABLES ONLY — never on the prefix discovery. In the
129
+ # customer deployment-runner flow the aws_cloudwatch_log_groups data source
130
+ # defers to apply, so a count that reads local.chat_latency_has_runtime_log_group
131
+ # fails the whole plan with "Invalid count argument" (canary.467 mcpherson
132
+ # deploy, 2026-08-20). The runtime-side filter therefore requires the explicit
133
+ # chat_turn_runtime_log_group_name override; discovery still feeds the
134
+ # dashboard widgets and saved query, whose bodies tolerate unknown values.
135
+ resource "aws_cloudwatch_log_metric_filter" "agent_loop_ms" {
136
+ count = local.chat_latency_metric_filters_enabled && var.chat_turn_runtime_log_group_name != "" ? 1 : 0
137
+
138
+ name = "thinkwork-${var.stage}-agent-loop-ms"
139
+ log_group_name = var.chat_turn_runtime_log_group_name
140
+ pattern = "{ $.event = \"agentcore_phase\" && $.phase = \"runtime.agent_loop\" && $.status = \"completed\" && $.durationMs = * }"
141
+
142
+ metric_transformation {
143
+ name = "AgentLoopMs"
144
+ namespace = local.chat_latency_namespace
145
+ value = "$.durationMs"
146
+ unit = "Milliseconds"
147
+ }
148
+ }
149
+
150
+ # THINK-946: pre-dispatch latency — the Lambda async-queue leg between the
151
+ # GraphQL resolver's Event invoke and chat-agent-invoke's first line. Measured
152
+ # live at 4.5-7 s with a 30 s outlier class, and previously invisible: no phase
153
+ # spanned it, so the turn timeline simply began late. `detail` carries the
154
+ # caller-side mutation leg (`queue=Nms;mutation=Nms`) for Logs Insights; the
155
+ # metric is the queue leg alone, which is the part no code change can shorten
156
+ # and which spikes when a spillover invoke misses the single provisioned
157
+ # instance on chat-agent-invoke's `live` alias.
158
+ resource "aws_cloudwatch_log_metric_filter" "pre_dispatch_queue_ms" {
159
+ count = local.chat_latency_metric_filters_enabled ? 1 : 0
160
+
161
+ name = "thinkwork-${var.stage}-pre-dispatch-queue-ms"
162
+ log_group_name = "/aws/lambda/${local.chat_latency_invoke_fn}"
163
+ pattern = "{ $.event = \"agentcore_phase\" && $.phase = \"api.invoke.queue_delay\" && $.status = \"completed\" && $.durationMs = * }"
164
+
165
+ metric_transformation {
166
+ name = "PreDispatchQueueMs"
167
+ namespace = local.chat_latency_namespace
168
+ value = "$.durationMs"
169
+ unit = "Milliseconds"
170
+ }
171
+ }
172
+
173
+ ################################################################################
174
+ # Alarms
175
+ #
176
+ # alarm_actions default to [] — consistent with memory-alarms.tf and
177
+ # capability-search-alarms.tf. The only SNS topic in this module
178
+ # (aws_sns_topic.cost_alerts) is the cost-alert channel and wiring latency
179
+ # pages into it would misroute them. Pass `chat_turn_latency_alarm_actions`
180
+ # (and `..._ok_actions`) once a stage has a topic worth paging.
181
+ ################################################################################
182
+
183
+ resource "aws_cloudwatch_metric_alarm" "chat_turn_runtime_invoke_p95" {
184
+ count = local.chat_latency_metric_filters_enabled ? 1 : 0
185
+
186
+ alarm_name = "thinkwork-${var.stage}-chat-turn-runtime-invoke-p95"
187
+ alarm_description = "p95 of api.runtime_dispatch.invoke (the whole AgentCore runtime call, cold start included) exceeded ${var.chat_turn_p95_alarm_threshold_ms} ms in 2 of the last 3 five-minute periods. This is the regression guard for the 2026-08 incident where a 52 s turn hid a ~20-24 s cold start."
188
+
189
+ namespace = local.chat_latency_namespace
190
+ metric_name = "TurnRuntimeInvokeMs"
191
+ extended_statistic = "p95"
192
+ period = 300
193
+ evaluation_periods = 3
194
+ datapoints_to_alarm = 2
195
+ threshold = var.chat_turn_p95_alarm_threshold_ms
196
+ comparison_operator = "GreaterThanThreshold"
197
+ # Idle stages emit no turns at all; missing data is not a latency problem.
198
+ treat_missing_data = "notBreaching"
199
+
200
+ alarm_actions = var.chat_turn_latency_alarm_actions
201
+ ok_actions = var.chat_turn_latency_alarm_actions
202
+ }
203
+
204
+ # Log-group-independent companion: the dispatcher's whole job is the runtime
205
+ # call, so its Lambda Duration tracks TurnRuntimeInvokeMs closely. This alarm
206
+ # needs no metric filter, so it protects stages that have not (or cannot yet)
207
+ # turn the filters on.
208
+ resource "aws_cloudwatch_metric_alarm" "chat_dispatch_duration_p95" {
209
+ count = local.chat_latency_enabled ? 1 : 0
210
+
211
+ alarm_name = "thinkwork-${var.stage}-chat-dispatch-duration-p95"
212
+ alarm_description = "p95 agentcore-runtime-dispatch Lambda Duration exceeded ${var.chat_turn_p95_alarm_threshold_ms} ms in 2 of the last 3 five-minute periods. Native-metric stand-in for TurnRuntimeInvokeMs (the dispatcher does nothing but the runtime call), so it fires even where the log metric filters are off."
213
+
214
+ namespace = "AWS/Lambda"
215
+ metric_name = "Duration"
216
+ extended_statistic = "p95"
217
+ period = 300
218
+ evaluation_periods = 3
219
+ datapoints_to_alarm = 2
220
+ threshold = var.chat_turn_p95_alarm_threshold_ms
221
+ comparison_operator = "GreaterThanThreshold"
222
+ treat_missing_data = "notBreaching"
223
+
224
+ alarm_actions = var.chat_turn_latency_alarm_actions
225
+ ok_actions = var.chat_turn_latency_alarm_actions
226
+
227
+ dimensions = {
228
+ FunctionName = local.chat_latency_dispatch_fn
229
+ }
230
+ }
231
+
232
+ ################################################################################
233
+ # Dashboard
234
+ ################################################################################
235
+
236
+ locals {
237
+ chat_latency_dashboard_region = var.region
238
+
239
+ # No conditional wrapper here: the arms of `enabled ? concat(...) : []`
240
+ # cannot type-unify (a tuple of heterogeneous widget objects vs an empty
241
+ # tuple) and Terraform 1.9 fails the apply with "Inconsistent conditional
242
+ # result types" (canary.469 mcpherson deploy). The only consumer is the
243
+ # dashboard resource, whose count already gates on chat_latency_enabled, so
244
+ # this local is never evaluated when disabled. The runtime-sourced widget
245
+ # uses a for/if filter for the same reason.
246
+ chat_latency_widgets = concat(
247
+ [
248
+ {
249
+ type = "text"
250
+ x = 0
251
+ y = 0
252
+ width = 24
253
+ height = 3
254
+ properties = {
255
+ markdown = join("\n", [
256
+ "## Chat turn latency — ${var.stage}",
257
+ "",
258
+ "Turn path: **graphql-http** (sendMessage) → **chat-agent-invoke** (setup) → **agentcore-runtime-dispatch** → **AgentCore Runtime (Pi)** → **chat-agent-finalize**.",
259
+ "",
260
+ local.chat_latency_metric_filters_enabled ? "Phase metrics come from `agentcore_phase` log metric filters in `${local.chat_latency_namespace}`." : "**Phase metrics are OFF for this stage** (`enable_chat_turn_latency_metric_filters = false`) — the phase widgets stay empty. Lambda-native widgets below still populate.",
261
+ local.chat_latency_has_runtime_log_group ? "Runtime log group: `${local.chat_latency_runtime_log_group}`." : "**AgentCore runtime log group not configured** — set `chat_turn_runtime_log_group_name` to light up `AgentLoopMs` and the runtime-overhead widget.",
262
+ "",
263
+ "Timeline for a single turn: Logs Insights → saved query **thinkwork-${var.stage}-turn-timeline** (paste a `threadTurnId`).",
264
+ ])
265
+ }
266
+ },
267
+ {
268
+ type = "metric"
269
+ x = 0
270
+ y = 3
271
+ width = 12
272
+ height = 6
273
+ properties = {
274
+ title = "Whole runtime call — api.runtime_dispatch.invoke (ms)"
275
+ region = local.chat_latency_dashboard_region
276
+ view = "timeSeries"
277
+ stat = "p50"
278
+ period = 300
279
+ metrics = [
280
+ [local.chat_latency_namespace, "TurnRuntimeInvokeMs", { stat = "p50", label = "p50" }],
281
+ ["...", { stat = "p95", label = "p95" }],
282
+ ]
283
+ yAxis = { left = { label = "ms", showUnits = false } }
284
+ annotations = {
285
+ horizontal = [{
286
+ label = "p95 alarm threshold"
287
+ value = var.chat_turn_p95_alarm_threshold_ms
288
+ }]
289
+ }
290
+ }
291
+ },
292
+ {
293
+ type = "metric"
294
+ x = 12
295
+ y = 3
296
+ width = 12
297
+ height = 6
298
+ properties = {
299
+ title = "Model loop — runtime.agent_loop (ms)"
300
+ region = local.chat_latency_dashboard_region
301
+ view = "timeSeries"
302
+ stat = "p50"
303
+ period = 300
304
+ metrics = [
305
+ [local.chat_latency_namespace, "AgentLoopMs", { stat = "p50", label = "p50" }],
306
+ ["...", { stat = "p95", label = "p95" }],
307
+ ]
308
+ yAxis = { left = { label = "ms", showUnits = false } }
309
+ }
310
+ },
311
+ {
312
+ type = "metric"
313
+ x = 0
314
+ y = 9
315
+ width = 12
316
+ height = 6
317
+ properties = {
318
+ title = "Runtime overhead p95 (invoke − agent_loop) — cold start lives here"
319
+ region = local.chat_latency_dashboard_region
320
+ view = "timeSeries"
321
+ period = 300
322
+ metrics = [
323
+ [{ expression = "invoke - loop", label = "overhead p95 (ms)", id = "overhead" }],
324
+ [local.chat_latency_namespace, "TurnRuntimeInvokeMs", { stat = "p95", id = "invoke", visible = false }],
325
+ [local.chat_latency_namespace, "AgentLoopMs", { stat = "p95", id = "loop", visible = false }],
326
+ ]
327
+ yAxis = { left = { label = "ms", showUnits = false } }
328
+ }
329
+ },
330
+ {
331
+ type = "metric"
332
+ x = 12
333
+ y = 9
334
+ width = 12
335
+ height = 6
336
+ properties = {
337
+ title = "agentcore-runtime-dispatch Lambda Duration p95 (ms)"
338
+ region = local.chat_latency_dashboard_region
339
+ view = "timeSeries"
340
+ period = 300
341
+ metrics = [
342
+ ["AWS/Lambda", "Duration", "FunctionName", local.chat_latency_dispatch_fn, { stat = "p95", label = "p95" }],
343
+ ["...", { stat = "Maximum", label = "max" }],
344
+ ]
345
+ yAxis = { left = { label = "ms", showUnits = false } }
346
+ annotations = {
347
+ horizontal = [{
348
+ label = "p95 alarm threshold"
349
+ value = var.chat_turn_p95_alarm_threshold_ms
350
+ }]
351
+ }
352
+ }
353
+ },
354
+ {
355
+ type = "metric"
356
+ x = 0
357
+ y = 15
358
+ width = 12
359
+ height = 6
360
+ properties = {
361
+ title = "Invocations (Sum) — turn path Lambdas"
362
+ region = local.chat_latency_dashboard_region
363
+ view = "timeSeries"
364
+ stat = "Sum"
365
+ period = 300
366
+ metrics = [
367
+ for fn in local.chat_latency_functions :
368
+ ["AWS/Lambda", "Invocations", "FunctionName", fn, { label = fn }]
369
+ ]
370
+ }
371
+ },
372
+ {
373
+ type = "metric"
374
+ x = 12
375
+ y = 15
376
+ width = 12
377
+ height = 6
378
+ properties = {
379
+ title = "Errors (Sum) — turn path Lambdas"
380
+ region = local.chat_latency_dashboard_region
381
+ view = "timeSeries"
382
+ stat = "Sum"
383
+ period = 300
384
+ metrics = [
385
+ for fn in local.chat_latency_functions :
386
+ ["AWS/Lambda", "Errors", "FunctionName", fn, { label = fn }]
387
+ ]
388
+ }
389
+ },
390
+ {
391
+ type = "metric"
392
+ x = 0
393
+ y = 21
394
+ width = 24
395
+ height = 6
396
+ properties = {
397
+ title = "Pre-dispatch queue — api.invoke.queue_delay (ms)"
398
+ region = local.chat_latency_dashboard_region
399
+ view = "timeSeries"
400
+ period = 300
401
+ metrics = [
402
+ [local.chat_latency_namespace, "PreDispatchQueueMs", { stat = "p50", label = "p50" }],
403
+ ["...", { stat = "p95", label = "p95" }],
404
+ ["...", { stat = "Maximum", label = "max (outlier class)" }],
405
+ ]
406
+ yAxis = { left = { label = "ms", showUnits = false } }
407
+ }
408
+ },
409
+ ],
410
+ [for w in [
411
+ {
412
+ type = "log"
413
+ x = 0
414
+ y = 27
415
+ width = 24
416
+ height = 6
417
+ properties = {
418
+ title = "Slowest turns (last 3h) — runtime call vs model loop"
419
+ region = local.chat_latency_dashboard_region
420
+ view = "table"
421
+ query = <<-EOT
422
+ SOURCE '${local.chat_latency_runtime_log_group}'
423
+ | fields ts, phase, durationMs, sessionId, threadTurnId
424
+ | filter event = "agentcore_phase" and status = "completed" and phase = "runtime.agent_loop"
425
+ | sort durationMs desc
426
+ | limit 20
427
+ EOT
428
+ }
429
+ },
430
+ ] : w if local.chat_latency_has_runtime_log_group],
431
+ )
432
+ }
433
+
434
+ resource "aws_cloudwatch_dashboard" "chat_turn_latency" {
435
+ count = local.chat_latency_enabled ? 1 : 0
436
+
437
+ dashboard_name = "thinkwork-${var.stage}-chat-turn-latency"
438
+ dashboard_body = jsonencode({ widgets = local.chat_latency_widgets })
439
+ }
440
+
441
+ ################################################################################
442
+ # Saved Logs Insights query — one turn's timeline across every log group
443
+ #
444
+ # Usage: open the query, replace REPLACE_WITH_THREAD_TURN_ID, run. `sessionId`
445
+ # is matched alongside `threadTurnId` because the container sets sessionId =
446
+ # threadTurnId and some early-turn API lines carry only one of the two.
447
+ ################################################################################
448
+
449
+ resource "aws_cloudwatch_query_definition" "turn_timeline" {
450
+ count = local.chat_latency_enabled ? 1 : 0
451
+
452
+ name = "thinkwork-${var.stage}-turn-timeline"
453
+
454
+ log_group_names = local.chat_latency_has_runtime_log_group ? concat(
455
+ local.chat_latency_lambda_log_groups,
456
+ [local.chat_latency_runtime_log_group],
457
+ ) : local.chat_latency_lambda_log_groups
458
+
459
+ query_string = <<-EOT
460
+ fields ts, @log, source, phase, status, durationMs, detail, errorType, threadTurnId, sessionId
461
+ | filter event = "agentcore_phase"
462
+ | filter threadTurnId = "REPLACE_WITH_THREAD_TURN_ID" or sessionId = "REPLACE_WITH_THREAD_TURN_ID"
463
+ | sort ts asc
464
+ | limit 500
465
+ EOT
466
+ }
@@ -0,0 +1,55 @@
1
+ # ---------------------------------------------------------------------------
2
+ # AgentCore runtime dispatch DLQ + redrive (THINK-585 U6, KTD2/R5/R19)
3
+ # ---------------------------------------------------------------------------
4
+ # The dispatcher Lambda runs Event-mode with retries=0 (the agent loop is not
5
+ # idempotent). An invoke that dies before the handler can mark its turn
6
+ # failed (crash, OOM, 900 s timeout) lands in this queue via the on-failure
7
+ # destination; the redrive consumer marks the enveloped thread_turn failed
8
+ # idempotently and emits the dispatch_dlq_redrive metric line for the U8
9
+ # dashboard. R19 hardening: SSE-KMS (AWS-managed key), ≤24 h retention.
10
+
11
+ resource "aws_sqs_queue" "agentcore_dispatch_dlq" {
12
+ count = local.deploy_lambda_handlers ? 1 : 0
13
+
14
+ name = "thinkwork-${var.stage}-agentcore-dispatch-dlq"
15
+ message_retention_seconds = 86400 # 24 h (R19)
16
+ # Must be ≥ the redrive consumer's 60 s timeout or CreateEventSourceMapping
17
+ # rejects the mapping; 6x is the AWS-recommended margin.
18
+ visibility_timeout_seconds = 360
19
+ kms_master_key_id = "alias/aws/sqs"
20
+ kms_data_key_reuse_period_seconds = 300
21
+
22
+ tags = {
23
+ Name = "thinkwork-${var.stage}-agentcore-dispatch-dlq"
24
+ Stage = var.stage
25
+ }
26
+ }
27
+
28
+ # Retries pinned to 0 (KTD2): a retried dispatch would re-burn Bedrock
29
+ # tokens and could overwrite the first attempt's deliverables. Failures go
30
+ # straight to the DLQ for the redrive consumer.
31
+ resource "aws_lambda_function_event_invoke_config" "agentcore_runtime_dispatch" {
32
+ count = local.deploy_lambda_handlers ? 1 : 0
33
+ function_name = aws_lambda_function.handler["agentcore-runtime-dispatch"].function_name
34
+ maximum_retry_attempts = 0
35
+ maximum_event_age_in_seconds = 3600
36
+
37
+ destination_config {
38
+ on_failure {
39
+ destination = aws_sqs_queue.agentcore_dispatch_dlq[0].arn
40
+ }
41
+ }
42
+ }
43
+
44
+ resource "aws_lambda_event_source_mapping" "agentcore_dispatch_dlq_redrive" {
45
+ count = local.deploy_lambda_handlers ? 1 : 0
46
+
47
+ event_source_arn = aws_sqs_queue.agentcore_dispatch_dlq[0].arn
48
+ function_name = aws_lambda_function.handler["agentcore-dispatch-dlq-redrive"].function_name
49
+ batch_size = 10
50
+ # Single consumer is plenty — the queue should be empty in steady state
51
+ # (non-empty is a soak-gate failure per R18).
52
+ scaling_config {
53
+ maximum_concurrency = 2
54
+ }
55
+ }