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
@@ -6,25 +6,25 @@
6
6
  # 10,240-byte aggregate hard cap for inline policies (#2378 failed the dev
7
7
  # apply with LimitExceeded; #2379 is this consolidation). Every grant that was
8
8
  # inline on this role — plus the one-off standalone managed policies that had
9
- # accreted as cap workarounds — now lives in exactly four grouped
9
+ # accreted as cap workarounds — now lives in five grouped
10
10
  # customer-managed policies:
11
11
  #
12
12
  # thinkwork-<stage>-api-data-plane — RDS Data API, Secrets Manager, S3,
13
13
  # DynamoDB, Cognito, SSM reads, KMS,
14
14
  # SQS (here for size balance, see
15
15
  # the locals note)
16
- # thinkwork-<stage>-api-orchestration — lambda:InvokeFunction, Scheduler,
17
- # Step Functions, SES
18
- # thinkwork-<stage>-api-ai — Bedrock invoke, Knowledge Bases,
16
+ # thinkwork-<stage>-api-orchestration — Scheduler, Step Functions, SES
17
+ # thinkwork-<stage>-api-invocation — internal lambda:InvokeFunction
18
+ # thinkwork-<stage>-api-ai — Bedrock invoke,
19
19
  # AgentCore memory/eval/code-interp
20
20
  # thinkwork-<stage>-api-observability — CloudWatch Logs reads, ECS/ALB
21
21
  # health reads
22
22
  #
23
- # STANDING RULE: new grants for aws_iam_role.lambda go into one of these four
23
+ # STANDING RULE: new grants for aws_iam_role.lambda go into one of these five
24
24
  # grouped policies — never a new inline aws_iam_role_policy and never a new
25
25
  # standalone managed-policy attachment. (Managed-policy attachments have a
26
26
  # default quota of 10 per role; the steady state here is
27
- # AWSLambdaBasicExecutionRole + these four, plus the conditional AWS-managed
27
+ # AWSLambdaBasicExecutionRole + these five, plus the conditional AWS-managed
28
28
  # VPC-access policy when OKF EFS wiring is enabled.)
29
29
  #
30
30
  # Each managed policy document caps at 6,144 characters (JSON minus
@@ -59,16 +59,6 @@ locals {
59
59
  Action = ["secretsmanager:GetSecretValue"]
60
60
  Resource = var.graphql_db_secret_arn
61
61
  },
62
- # THINK-239 — analystInternalClusters enumerates the environment's own
63
- # RDS clusters (filtered to thinkwork-<stage>-* in code) so operators can
64
- # register a database with zero credential entry. Describe-only; the
65
- # admin credential read is covered by graphql_db_secret_arn above.
66
- {
67
- Sid = "AnalystInternalClustersDescribe"
68
- Effect = "Allow"
69
- Action = ["rds:DescribeDBClusters"]
70
- Resource = "*"
71
- },
72
62
  {
73
63
  Effect = "Allow"
74
64
  Action = [
@@ -76,14 +66,6 @@ locals {
76
66
  "secretsmanager:UpdateSecret",
77
67
  "secretsmanager:DeleteSecret",
78
68
  "secretsmanager:GetSecretValue",
79
- # THINK-230 — provisionAnalystConnector writes the broker credential
80
- # secret value (thinkwork/<stage>/analyst/broker-credential, under
81
- # this thinkwork/* prefix) via ensureAnalystBrokerSecret's
82
- # PutSecretValue. GetSecretValue above already covers the read; this
83
- # role already holds Create/Update/Delete on the same prefix, so
84
- # PutSecretValue grants no capability beyond the existing delete +
85
- # recreate. Kept on the existing statement to stay under the 6,144
86
- # rendered-char managed-policy cap.
87
69
  "secretsmanager:PutSecretValue"
88
70
  ]
89
71
  Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/*"
@@ -119,13 +101,6 @@ locals {
119
101
  "${var.bucket_arn}/*",
120
102
  ]
121
103
  },
122
- # (was inline policy "wiki-exports-s3" in handlers.tf) — wiki-export
123
- # writes markdown vault bundles to the per-stage wiki-exports bucket.
124
- {
125
- Effect = "Allow"
126
- Action = ["s3:PutObject", "s3:AbortMultipartUpload"]
127
- Resource = "${aws_s3_bucket.wiki_exports.arn}/*"
128
- },
129
104
  # Canonical ThinkWork Brain artifacts: durable source artifacts,
130
105
  # ingestion manifests, migration snapshots, vault projections, and
131
106
  # exports, and OKF Wiki Navigator bundles/current manifests.
@@ -324,17 +299,25 @@ locals {
324
299
  Resource = var.plugin_catalog_github_token_secret_arn
325
300
  },
326
301
  ] : [],
327
- # THINK-229 U1 analyst-query-broker mints RDS IAM auth tokens for the
328
- # analyst_reader role. The rds-db ARN keys on the immutable cluster
329
- # RESOURCE ID (cluster-XXXX), never the cluster ARN, and the dbuser
330
- # segment is the exact-case Postgres role name. aws:SourceIp /
331
- # aws:SourceVpc conditions are unsupported on rds-db:connect, so the
332
- # narrow Resource IS the control surface.
333
- var.analyst_db_cluster_resource_id != "" ? [
302
+ # Company Brain machine lane (THINK-628): MCP config build mints Cognito
303
+ # client-credentials bearers from this one secret instead of reading a
304
+ # stored tkt_ token. Read-only, one exact ARN, count-gated: an account
305
+ # that never sets the var plans to zero changes.
306
+ var.brain_m2m_platform_agent_secret_arn != "" ? [
334
307
  {
335
308
  Effect = "Allow"
336
- Action = ["rds-db:connect"]
337
- Resource = "arn:aws:rds-db:${var.region}:${var.account_id}:dbuser:${var.analyst_db_cluster_resource_id}/analyst_reader"
309
+ Action = ["secretsmanager:GetSecretValue"]
310
+ Resource = var.brain_m2m_platform_agent_secret_arn
311
+ },
312
+ ] : [],
313
+ # Brain ops-api agent identity (THINK-781): flagThreadToBrain mints a
314
+ # bearer from this secret to POST /flags. Same posture as the machine
315
+ # lane above — read-only, one exact ARN, count-gated.
316
+ var.brain_ops_m2m_secret_arn != "" ? [
317
+ {
318
+ Effect = "Allow"
319
+ Action = ["secretsmanager:GetSecretValue"]
320
+ Resource = var.brain_ops_m2m_secret_arn
338
321
  },
339
322
  ] : [],
340
323
  # (was standalone managed policy "lambda_deployment_evidence_read")
@@ -397,33 +380,33 @@ locals {
397
380
  )
398
381
  },
399
382
  ] : [],
400
- var.brain_artifacts_kms_key_arn != "" ? [
383
+ # THINK-585 U6: the dispatcher's on-failure destination (Lambda sends
384
+ # with the function's execution role) + the redrive consumer's ESM
385
+ # polling, both on the agentcore-dispatch DLQ.
386
+ local.deploy_lambda_handlers ? [
401
387
  {
402
388
  Effect = "Allow"
403
389
  Action = [
404
- "kms:Decrypt",
405
- "kms:GenerateDataKey",
390
+ "sqs:SendMessage",
391
+ "sqs:ReceiveMessage",
392
+ "sqs:DeleteMessage",
393
+ "sqs:GetQueueAttributes",
394
+ "sqs:ChangeMessageVisibility",
406
395
  ]
407
- Resource = var.brain_artifacts_kms_key_arn
408
- Condition = {
409
- StringEquals = {
410
- "kms:ViaService" = "s3.${var.region}.amazonaws.com"
411
- }
412
- }
396
+ Resource = [for q in aws_sqs_queue.agentcore_dispatch_dlq : q.arn]
413
397
  },
414
398
  ] : [],
415
- var.okf_efs_file_system_arn != "" && var.okf_efs_refresh_access_point_arn != "" ? [
399
+ var.brain_artifacts_kms_key_arn != "" ? [
416
400
  {
417
- Sid = "OkfEfsHydratorMountWrite"
418
401
  Effect = "Allow"
419
402
  Action = [
420
- "elasticfilesystem:ClientMount",
421
- "elasticfilesystem:ClientWrite",
403
+ "kms:Decrypt",
404
+ "kms:GenerateDataKey",
422
405
  ]
423
- Resource = var.okf_efs_file_system_arn
406
+ Resource = var.brain_artifacts_kms_key_arn
424
407
  Condition = {
425
408
  StringEquals = {
426
- "elasticfilesystem:AccessPointArn" = var.okf_efs_refresh_access_point_arn
409
+ "kms:ViaService" = "s3.${var.region}.amazonaws.com"
427
410
  }
428
411
  }
429
412
  },
@@ -444,13 +427,36 @@ locals {
444
427
  Resource = var.capability_broker_session_table_arn
445
428
  },
446
429
  ] : [],
430
+ # THINK-643 — auth-subscription-ticket mints tickets and bumps the connect
431
+ # counter; appsync-subscription-authorizer reads and conditionally consumes
432
+ # them. Both run on this shared role. Empty (→ no grant) while
433
+ # auth_state_store is "postgres", so the grant appears with the table.
434
+ #
435
+ # THINK-644 adds the `webhooks` handler (idempotency receipts) as a third
436
+ # consumer. It needs no separate statement: every handler in the
437
+ # aws_lambda_function.api for_each runs on aws_iam_role.lambda — the sole
438
+ # exception is memory-retraction-drainer, which does not touch this table.
439
+ # A future consumer on a sibling role would need its own grant.
440
+ local.auth_state_enabled ? [
441
+ {
442
+ Effect = "Allow"
443
+ Action = [
444
+ "dynamodb:GetItem",
445
+ "dynamodb:PutItem",
446
+ "dynamodb:UpdateItem",
447
+ "dynamodb:DeleteItem",
448
+ "dynamodb:Query",
449
+ ]
450
+ Resource = aws_dynamodb_table.auth_state[0].arn
451
+ },
452
+ ] : [],
447
453
  )
448
454
 
449
455
  # ---------------------------------------------------------------------------
450
456
  # Group 2: orchestration — cross-function invokes, Scheduler, Step
451
457
  # Functions, SQS, SES.
452
458
  # ---------------------------------------------------------------------------
453
- api_orchestration_statements = concat(
459
+ api_orchestration_candidate_statements = concat(
454
460
  [
455
461
  # (was inline policy "ses-send")
456
462
  # SES send permissions for the email-send handler. Scoped to any
@@ -500,39 +506,30 @@ locals {
500
506
  # (was inline policy "api-cross-function-invoke")
501
507
  # Allow API handler Lambdas to invoke each other directly. sendMessage
502
508
  # dispatches to chat-agent-invoke for instant chat response; the memory
503
- # resolvers reach knowledge-base-manager and job-schedule-manager for
504
- # admin-driven operations. The agentcore-invoke statement below covers
509
+ # resolvers reach job-schedule-manager for
510
+ # admin-driven operations. The Pi-invoke statement below covers
505
511
  # the Pi runtime Lambda only — this one covers internal api-to-api
506
512
  # calls. ARNs are constructed deterministically from the handler naming
507
513
  # pattern so we don't create a dependency cycle with the handler
508
514
  # resource.
509
515
  {
516
+ Sid = "ApiCrossFunctionInvoke"
510
517
  Effect = "Allow"
511
518
  Action = ["lambda:InvokeFunction"]
512
519
  Resource = [
513
520
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke",
514
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-knowledge-base-manager",
521
+ # THINK-583 U3: invokers address the `live` alias (provisioned
522
+ # concurrency only serves alias-qualified invokes); the alias ARN
523
+ # is a distinct IAM resource from the unqualified function ARN.
524
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke:live",
515
525
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-schedule-manager",
516
526
  # eval-runner: graphql-http's startEvalRun mutation Event-invokes
517
527
  # this asynchronously after inserting the eval_runs row.
518
528
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-eval-runner",
519
- # wiki-compile: memory-retain Event-invokes this after a successful
520
- # retainTurn when the tenant's wiki_compile_enabled flag is on.
521
- # compileWikiNow admin mutation also Event-invokes.
522
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-wiki-compile",
523
- # knowledge-graph-observations-ingest: graphql-http's
524
- # startKnowledgeGraphObservationsIngest mutation invokes this with
525
- # RequestResponse, and memory-stage-worker's graph stage (THINK-193
526
- # U4 stitch) RequestResponse-invokes it with a targeted bankIds
527
- # payload — the Bedrock classifier/extraction model config lives on
528
- # the ingest Lambda's own env, so the worker never runs the ingest
529
- # in-process. (The worker's self-invoke drain is retired — AWS
530
- # recursive-loop detection terminated those chains; backlog drains
531
- # in-process now.)
532
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-knowledge-graph-observations-ingest",
533
- # wiki-bootstrap-import: bootstrapJournalImport admin mutation
534
- # Event-invokes this for the long-running ingest path.
535
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-wiki-bootstrap-import",
529
+ # identity-graph-projector (Company Brain U5): identity mutations
530
+ # Event-invoke this as a post-commit nudge; the rebuild command is
531
+ # a RequestResponse invoke. Cursor makes missed nudges harmless.
532
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-identity-graph-projector",
536
533
  # harness-runner: chat-agent-invoke Event-invokes this for chat
537
534
  # turns routed to the AWS AgentCore runtime (THINK-311 trial).
538
535
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-harness-runner",
@@ -544,19 +541,10 @@ locals {
544
541
  # signer re-derives the actor/tenant/thread tuple from the running
545
542
  # turn and alone holds KMS Sign; the shared runner role never does.
546
543
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-turn-assertion-mint",
547
- # ontology-scan: startOntologySuggestionScan Event-invokes this
548
- # after inserting a durable scan job row.
549
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-ontology-scan",
550
- # ontology-reprocess: approveOntologyChangeSet Event-invokes this
551
- # after inserting a durable reprocess job row.
552
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-ontology-reprocess",
553
- # okf-materialize / okf-efs-refresh: the OKF distribution chain
554
- # (THINK-200). wiki-compile Event-invokes okf-materialize after a
555
- # successful compile; okf-materialize Event-invokes okf-efs-refresh
556
- # after publishing bundles, keeping the Pi wiki navigator's EFS
557
- # view current without manual invocation.
558
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-okf-materialize",
559
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-okf-efs-refresh",
544
+ # identity-match (THINK-321 U7): startIdentityMatchJob Event-invokes
545
+ # this after inserting a durable match job row; identity-match also
546
+ # self-invokes for the continuation chain (same shared role).
547
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-identity-match",
560
548
  # routine-resume: routine-approval-bridge (Phase B U8) invokes
561
549
  # this with RequestResponse after a HITL decideInboxItem
562
550
  # decision. Calls SendTaskSuccess/SendTaskFailure on the SFN
@@ -568,7 +556,9 @@ locals {
568
556
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-files-efs",
569
557
  # workspace-renderer: chat-agent-invoke invokes this synchronously
570
558
  # before AgentCore so Pi can opt into the rendered
571
- # per-(agent, Space, user) workspace prefix.
559
+ # per-(agent, Space, user) workspace prefix. THINK-583 U3: the
560
+ # `:live` alias ARN is covered by the dedicated workspace-renderer
561
+ # statement below (`...workspace-renderer:*`).
572
562
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-renderer",
573
563
  # skill-trust-runner: graphql-http's publishSkillDraft mutation
574
564
  # invokes this with RequestResponse so SkillSpector completion is a
@@ -614,6 +604,9 @@ locals {
614
604
  Resource = [
615
605
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-renderer",
616
606
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-renderer:*",
607
+ # THINK-585 U6: chat-agent-invoke Event-invokes the runtime
608
+ # dispatcher at the flag-on dispatch seam.
609
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-agentcore-runtime-dispatch",
617
610
  ]
618
611
  },
619
612
  # (was standalone managed policy "thread_idle_memory_learning_invoke")
@@ -781,11 +774,35 @@ locals {
781
774
  ] : [],
782
775
  )
783
776
 
777
+ # Internal Lambda invocation is intentionally partitioned from the remaining
778
+ # orchestration grants. The generated resource list grows with API features
779
+ # and can otherwise push the shared orchestration managed policy beyond
780
+ # IAM's hard 6,144-character document limit. Both partitions remain attached
781
+ # to the same shared Lambda role and are covered by the size assertion below.
782
+ api_orchestration_statements = [
783
+ for statement in local.api_orchestration_candidate_statements : statement
784
+ if !contains(try(statement.Action, []), "lambda:InvokeFunction")
785
+ ]
786
+ api_invocation_statements = [
787
+ for statement in local.api_orchestration_candidate_statements : statement
788
+ if contains(try(statement.Action, []), "lambda:InvokeFunction")
789
+ ]
790
+
784
791
  # ---------------------------------------------------------------------------
785
- # Group 3: AI — Bedrock model invocation, Knowledge Bases, AgentCore
792
+ # Group 3: AI — Bedrock model invocation, AgentCore
786
793
  # memory / evaluations / code interpreter.
787
794
  # ---------------------------------------------------------------------------
788
795
  api_ai_statements = concat([
796
+ # Signed-turn identity (THINK-324 C18): dispatch Lambdas mint with
797
+ # kms:Sign; verifiers fetch the public key once per container with
798
+ # kms:GetPublicKey. Scoped to the turn-assertion keys only; the role is
799
+ # shared, so which handler may mint is code-enforced.
800
+ {
801
+ Sid = "TurnAssertionSigning"
802
+ Effect = "Allow"
803
+ Action = ["kms:Sign", "kms:GetPublicKey"]
804
+ Resource = local.turn_assertion_key_arns
805
+ },
789
806
  # (was inline policy "bedrock-invoke")
790
807
  # Cross-region inference profiles (us.anthropic.claude-*) require
791
808
  # `bedrock:InvokeModel` on the *inference-profile* ARN AND on the
@@ -801,62 +818,17 @@ locals {
801
818
  "arn:aws:bedrock:*:${var.account_id}:inference-profile/*",
802
819
  ]
803
820
  },
804
- # (was standalone managed policy "lambda_bedrock_knowledge_base")
805
- # The knowledge-base-manager Lambda provisions and manages Bedrock
806
- # Knowledge Bases (control plane: create/sync/rechunk/delete), and
807
- # graphql-http's testKnowledgeBaseRetrieval query calls Retrieve. Without
808
- # these the manager fails with "not authorized to perform:
809
- # bedrock:CreateKnowledgeBase" and every KB is stuck in `failed`.
810
- # CreateKnowledgeBase / CreateDataSource also take a roleArn that Bedrock
811
- # assumes, so the caller needs iam:PassRole on the KB service role.
812
- {
813
- Effect = "Allow"
814
- Action = [
815
- "bedrock:CreateKnowledgeBase",
816
- "bedrock:GetKnowledgeBase",
817
- "bedrock:UpdateKnowledgeBase",
818
- "bedrock:DeleteKnowledgeBase",
819
- "bedrock:ListKnowledgeBases",
820
- "bedrock:CreateDataSource",
821
- "bedrock:GetDataSource",
822
- "bedrock:UpdateDataSource",
823
- "bedrock:DeleteDataSource",
824
- "bedrock:ListDataSources",
825
- "bedrock:StartIngestionJob",
826
- "bedrock:GetIngestionJob",
827
- "bedrock:ListIngestionJobs",
828
- "bedrock:Retrieve",
829
- # THINK-193 U7: knowledge-base-manager reconciles the per-document
830
- # manifest after successful syncs (List/Get) and gates deletion
831
- # settlement on Get + the scoped Retrieve above. Ingest/Delete cover
832
- # the explicit direct-delete path for S3 RETAIN'd documents — the
833
- # manager never races them with StartIngestionJob. The workspace-
834
- # bucket S3 read the memory-stage-worker needs for document text is
835
- # already granted by the shared role's workspace "s3-access"
836
- # statement (data-plane group).
837
- "bedrock:ListKnowledgeBaseDocuments",
838
- "bedrock:GetKnowledgeBaseDocuments",
839
- "bedrock:IngestKnowledgeBaseDocuments",
840
- "bedrock:DeleteKnowledgeBaseDocuments",
841
- ]
842
- # CreateKnowledgeBase/CreateDataSource have no resource ARN at create
843
- # time and don't support resource-level scoping, so a knowledge-base/*
844
- # ARN makes the grant not match. Control-plane "*" for this internal
845
- # manager Lambda; PassRole below stays scoped to the KB service role.
846
- Resource = "*"
847
- },
848
- {
849
- Effect = "Allow"
850
- Action = ["iam:PassRole"]
851
- Resource = var.kb_service_role_arn != "" ? var.kb_service_role_arn : "*"
852
- },
853
821
  # (was inline policy "agentcore-memory-rw")
854
- # AgentCore Memory read access for the GraphQL memory resolvers.
855
- # memoryRecords / memorySearch call ListMemoryRecordsCommand to fetch
856
- # records across the tenant's agents.
822
+ # AgentCore Memory access for the GraphQL memory resolvers and the
823
+ # memory-retain handler. memoryRecords / memorySearch call
824
+ # ListMemoryRecordsCommand to fetch records across the tenant's agents;
825
+ # memory-retain calls CreateEventCommand to land the turn in the managed
826
+ # memory (before the agentcore engine flip that write went to Hindsight
827
+ # over HTTP, so no IAM action existed for it).
857
828
  {
858
829
  Effect = "Allow"
859
830
  Action = [
831
+ "bedrock-agentcore:CreateEvent",
860
832
  "bedrock-agentcore:ListMemoryRecords",
861
833
  "bedrock-agentcore:RetrieveMemoryRecords",
862
834
  "bedrock-agentcore:GetMemoryRecord",
@@ -907,29 +879,15 @@ locals {
907
879
  Resource = "*"
908
880
  },
909
881
  ],
910
- # THINK-316 U2 request path receives data-plane invocation only.
911
- # Provisioning/version/endpoint/PassRole rights belong to Terraform's
912
- # dedicated Harness lifecycle and never reach a chat Lambda.
913
- #
914
- # CRITICAL: never grant `bedrock-agentcore:InvokeAgentRuntimeCommand`
915
- # anywhere it bypasses Harness allowedTools enforcement. Invocation goes
916
- # through InvokeHarness / InvokeAgentRuntime only.
917
- var.enable_agentcore_harness_invocation ? [
918
- {
919
- # Data plane — invoke a Harness (and its runtime endpoints) directly.
920
- Sid = "AgentCoreHarnessInvoke"
921
- Effect = "Allow"
922
- Action = [
923
- "bedrock-agentcore:InvokeHarness",
924
- "bedrock-agentcore:InvokeAgentRuntime",
925
- ]
926
- Resource = ["arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:harness/*"]
927
- },
882
+ # THINK-324the managed-harness invoke grant is retired. The AgentCore
883
+ # Identity chain below survives UNCONDITIONALLY: the live Twenty
884
+ # connector's per-user 3LO (handlers/skills.ts via
885
+ # lib/agentcore-identity/agentcore-user-oauth.ts) mints workload tokens
886
+ # against the shared workload identity and reads the twenty-crm
887
+ # credential provider from the token vault.
888
+ [
928
889
  {
929
- # The deterministic report adapter uses the same AgentCore Identity
930
- # chain as Harness Gateway outbound auth: turn JWT -> workload token
931
- # -> OBO Gateway token. No provider client secret enters Lambda.
932
- Sid = "AgentCoreHarnessGatewayIdentity"
890
+ Sid = "AgentCoreTwentyIdentity"
933
891
  Effect = "Allow"
934
892
  Action = [
935
893
  "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
@@ -941,21 +899,18 @@ locals {
941
899
  "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:workload-identity-directory/default",
942
900
  "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:workload-identity-directory/default/workload-identity/thinkwork-${var.stage}-multiplayer-proof",
943
901
  "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:token-vault/default",
944
- "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:token-vault/default/oauth2credentialprovider/thinkwork-${var.stage}-proof-oauth",
945
902
  "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:token-vault/default/oauth2credentialprovider/thinkwork-${var.stage}-twenty-crm",
946
903
  ]
947
904
  },
948
- ] : [],
905
+ ],
949
906
  )
950
907
 
951
908
  # ---------------------------------------------------------------------------
952
909
  # Group 4: observability — CloudWatch Logs reads, ECS/ALB health reads.
953
910
  # ---------------------------------------------------------------------------
954
- # Handler-gated SQS grants moved HERE from the data-plane group
955
- # (THINK-229 U5 follow-up): data-plane hit IAM's 6,144-char rendered cap
956
- # when the analyst-connection-reconciler DLQ grant landed (live size was
957
- # 6,018 before it). Queue plumbing is operational surface; observability
958
- # has ~5k headroom. Same statements, different envelope.
911
+ # Handler-gated SQS grants live HERE rather than in the data-plane group:
912
+ # data-plane sits near IAM's 6,144-char rendered cap. Queue plumbing is
913
+ # operational surface; observability has ~5k headroom.
959
914
  api_observability_sqs_statements = concat(
960
915
  # SQS grants live here rather than in the orchestration group purely for
961
916
  # size balance: with every conditional on, orchestration's rendered JSON
@@ -963,26 +918,12 @@ locals {
963
918
  # Handler-gated SQS grants. Each statement was a count-gated inline
964
919
  # policy whose queue exists only when local.deploy_lambda_handlers.
965
920
  local.deploy_lambda_handlers ? [
966
- # (was inline policy "thinkwork-${stage}-wiki-compile-dlq-send")
967
- {
968
- Sid = "WikiCompileDlqSend"
969
- Effect = "Allow"
970
- Action = ["sqs:SendMessage"]
971
- Resource = aws_sqs_queue.wiki_compile_dlq[0].arn
972
- },
973
- # (was inline policy "thinkwork-${stage}-ontology-scan-dlq-send")
974
- {
975
- Sid = "OntologyScanDlqSend"
976
- Effect = "Allow"
977
- Action = ["sqs:SendMessage"]
978
- Resource = aws_sqs_queue.ontology_scan_dlq[0].arn
979
- },
980
- # (was inline policy "thinkwork-${stage}-ontology-reprocess-dlq-send")
921
+ # THINK-321 U7 identity-match async-failure DLQ.
981
922
  {
982
- Sid = "OntologyReprocessDlqSend"
923
+ Sid = "IdentityMatchDlqSend"
983
924
  Effect = "Allow"
984
925
  Action = ["sqs:SendMessage"]
985
- Resource = aws_sqs_queue.ontology_reprocess_dlq[0].arn
926
+ Resource = aws_sqs_queue.identity_match_dlq[0].arn
986
927
  },
987
928
  # (was inline policy "compliance-drainer-dlq-send")
988
929
  {
@@ -991,13 +932,6 @@ locals {
991
932
  Action = ["sqs:SendMessage"]
992
933
  Resource = aws_sqs_queue.compliance_drainer_dlq[0].arn
993
934
  },
994
- # THINK-229 U5 — analyst-connection-reconciler async on_failure DLQ.
995
- {
996
- Sid = "AnalystConnectionReconcilerDlqSend"
997
- Effect = "Allow"
998
- Action = ["sqs:SendMessage"]
999
- Resource = aws_sqs_queue.analyst_connection_reconciler_dlq[0].arn
1000
- },
1001
935
  # (was inline policy "compliance-exports-send")
1002
936
  # graphql-http needs sqs:SendMessage on the exports queue to dispatch
1003
937
  # jobIds from the createComplianceExport mutation. Attached to the
@@ -1039,6 +973,72 @@ locals {
1039
973
  Resource = aws_sqs_queue.eval_fanout_dlq[0].arn
1040
974
  },
1041
975
  ] : [],
976
+ # Company Brain twin (plan 2026-07-21-001 U5): Neptune data access for
977
+ # the identity-graph-projector (and the wiki-compile soft-layer writer)
978
+ # on the SHARED role. The twin-query read Lambda and its dedicated
979
+ # read-only role retired to the platform service (THINK-339 U15) — no
980
+ # caller-supplied query path remains product-side.
981
+ # Lives in the ai envelope for size balance (data-plane sits ~430 chars
982
+ # under IAM's 6,144 cap). Grouped — not inline — so the no-terraform
983
+ # targeted deploy path (which applies exactly these grouped policies)
984
+ # self-heals the grant; the original inline resource never survived a
985
+ # superseded full apply. Data actions are condition-pinned to openCypher,
986
+ # mirroring the etl-side policies.
987
+ # (Two segments, not one: the data statement carries a Condition the
988
+ # status statement can't — mixed shapes in one conditional tuple fail
989
+ # Terraform's type unification.)
990
+ var.neptune_cluster_resource_id == "" ? [] : [
991
+ {
992
+ Sid = "TwinNeptuneData"
993
+ Effect = "Allow"
994
+ Action = [
995
+ "neptune-db:ReadDataViaQuery",
996
+ "neptune-db:WriteDataViaQuery",
997
+ "neptune-db:DeleteDataViaQuery",
998
+ ]
999
+ Resource = "arn:aws:neptune-db:${var.region}:${var.account_id}:${var.neptune_cluster_resource_id}/*"
1000
+ Condition = {
1001
+ StringEquals = { "neptune-db:QueryLanguage" = "OpenCypher" }
1002
+ }
1003
+ },
1004
+ ],
1005
+ var.neptune_cluster_resource_id == "" ? [] : [
1006
+ {
1007
+ Sid = "TwinEngineStatus"
1008
+ Effect = "Allow"
1009
+ Action = ["neptune-db:GetEngineStatus", "neptune-db:GetGraphSummary"]
1010
+ Resource = "arn:aws:neptune-db:${var.region}:${var.account_id}:${var.neptune_cluster_resource_id}/*"
1011
+ },
1012
+ ],
1013
+ # Bulk-rebuild lane (THINK-331 U4): the projector stages openCypher CSVs
1014
+ # under the tenant-scoped thinkwork-identity/ prefix of the etl-platform
1015
+ # load bucket (Put for upload, Delete for the terminal-state cleanup)
1016
+ # and drives the loader job API. The CSVs are streamed as S3 multipart
1017
+ # uploads for large tenants (THINK-409): CreateMultipartUpload /
1018
+ # UploadPart / CompleteMultipartUpload all authorize as s3:PutObject, and
1019
+ # the failure path's AbortMultipartUpload is the action below — no
1020
+ # s3:ListMultipartUploadParts needed, the stager tracks its own parts
1021
+ # (this policy is near IAM's 6,144-char cap; see below). A NEW statement
1022
+ # group — the existing
1023
+ # TwinNeptuneData statement is condition-pinned to QueryLanguage=
1024
+ # OpenCypher, which the loader actions don't satisfy. Gated on the
1025
+ # bulk-loader variables so stages without the twin stack ship inert
1026
+ # (KTD-9). No iam:PassRole needed: the loader role is assumed by the
1027
+ # Neptune service via its cluster association, not passed by this role.
1028
+ var.neptune_load_bucket == "" || var.neptune_loader_role_arn == "" || var.neptune_cluster_resource_id == "" ? [] : [
1029
+ {
1030
+ Sid = "TwinBulkLoaderStage"
1031
+ Effect = "Allow"
1032
+ Action = ["s3:PutObject", "s3:AbortMultipartUpload", "s3:DeleteObject"]
1033
+ Resource = "arn:aws:s3:::${var.neptune_load_bucket}/thinkwork-identity/*"
1034
+ },
1035
+ {
1036
+ Sid = "TwinBulkLoaderJobs"
1037
+ Effect = "Allow"
1038
+ Action = ["neptune-db:StartLoaderJob", "neptune-db:GetLoaderJobStatus", "neptune-db:CancelLoaderJob"]
1039
+ Resource = "arn:aws:neptune-db:${var.region}:${var.account_id}:${var.neptune_cluster_resource_id}/*"
1040
+ },
1041
+ ],
1042
1042
  )
1043
1043
 
1044
1044
  api_observability_statements = concat(local.api_observability_sqs_statements, [
@@ -1122,6 +1122,10 @@ locals {
1122
1122
  Version = "2012-10-17"
1123
1123
  Statement = local.api_orchestration_statements
1124
1124
  })
1125
+ invocation = jsonencode({
1126
+ Version = "2012-10-17"
1127
+ Statement = local.api_invocation_statements
1128
+ })
1125
1129
  ai = jsonencode({
1126
1130
  Version = "2012-10-17"
1127
1131
  Statement = local.api_ai_statements
@@ -1147,6 +1151,13 @@ resource "aws_iam_policy" "api_data_plane" {
1147
1151
  description = "Grouped data-plane grants (RDS Data API, Secrets Manager, S3, DynamoDB, Cognito, SSM, KMS-via-SSM, SQS) for the shared api Lambda role"
1148
1152
 
1149
1153
  policy = local.api_grouped_policy_documents.data_plane
1154
+
1155
+ lifecycle {
1156
+ precondition {
1157
+ condition = length(local.api_grouped_policy_documents.data_plane) <= 6144
1158
+ error_message = "The grouped API data-plane managed-policy document exceeds AWS IAM's 6,144-character limit."
1159
+ }
1160
+ }
1150
1161
  }
1151
1162
 
1152
1163
  resource "aws_iam_role_policy_attachment" "api_data_plane" {
@@ -1156,9 +1167,27 @@ resource "aws_iam_role_policy_attachment" "api_data_plane" {
1156
1167
 
1157
1168
  resource "aws_iam_policy" "api_orchestration" {
1158
1169
  name = "thinkwork-${var.stage}-api-orchestration"
1159
- description = "Grouped orchestration grants (Lambda invoke, EventBridge Scheduler, Step Functions, SES) for the shared api Lambda role"
1170
+ description = "Grouped orchestration grants (EventBridge Scheduler, Step Functions, SES) for the shared api Lambda role"
1160
1171
 
1161
1172
  policy = local.api_grouped_policy_documents.orchestration
1173
+
1174
+ # Attach the replacement invoke grant before removing it from this policy.
1175
+ # This preserves Lambda-to-Lambda access if an apply stops between updates.
1176
+ depends_on = [aws_iam_role_policy_attachment.api_invocation]
1177
+
1178
+ lifecycle {
1179
+ # Existing environments may retain the historical description that also
1180
+ # mentioned Lambda invocation. Description changes replace IAM managed
1181
+ # policies, which would destroy the old orchestration grant before the new
1182
+ # invocation attachment is ready. Keep the policy identity stable so the
1183
+ # depends_on edge above orders an in-place document update after attach.
1184
+ ignore_changes = [description]
1185
+
1186
+ precondition {
1187
+ condition = length(local.api_grouped_policy_documents.orchestration) <= 6144
1188
+ error_message = "The grouped API orchestration managed-policy document exceeds AWS IAM's 6,144-character limit."
1189
+ }
1190
+ }
1162
1191
  }
1163
1192
 
1164
1193
  resource "aws_iam_role_policy_attachment" "api_orchestration" {
@@ -1166,11 +1195,37 @@ resource "aws_iam_role_policy_attachment" "api_orchestration" {
1166
1195
  policy_arn = aws_iam_policy.api_orchestration.arn
1167
1196
  }
1168
1197
 
1198
+ resource "aws_iam_policy" "api_invocation" {
1199
+ name = "thinkwork-${var.stage}-api-invocation"
1200
+ description = "Internal Lambda invocation grants for the shared api Lambda role"
1201
+
1202
+ policy = local.api_grouped_policy_documents.invocation
1203
+
1204
+ lifecycle {
1205
+ precondition {
1206
+ condition = length(local.api_grouped_policy_documents.invocation) <= 6144
1207
+ error_message = "The grouped API invocation managed-policy document exceeds AWS IAM's 6,144-character limit."
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ resource "aws_iam_role_policy_attachment" "api_invocation" {
1213
+ role = aws_iam_role.lambda.name
1214
+ policy_arn = aws_iam_policy.api_invocation.arn
1215
+ }
1216
+
1169
1217
  resource "aws_iam_policy" "api_ai" {
1170
1218
  name = "thinkwork-${var.stage}-api-ai"
1171
- description = "Grouped AI grants (Bedrock invoke, Knowledge Bases, AgentCore memory/eval/code-interpreter) for the shared api Lambda role"
1219
+ description = "Grouped AI grants (Bedrock invoke, AgentCore memory/eval/code-interpreter) for the shared api Lambda role"
1172
1220
 
1173
1221
  policy = local.api_grouped_policy_documents.ai
1222
+
1223
+ lifecycle {
1224
+ precondition {
1225
+ condition = length(local.api_grouped_policy_documents.ai) <= 6144
1226
+ error_message = "The grouped API AI managed-policy document exceeds AWS IAM's 6,144-character limit."
1227
+ }
1228
+ }
1174
1229
  }
1175
1230
 
1176
1231
  resource "aws_iam_role_policy_attachment" "api_ai" {
@@ -1183,6 +1238,13 @@ resource "aws_iam_policy" "api_observability" {
1183
1238
  description = "Grouped observability grants (CloudWatch Logs reads, ECS/ALB health reads) for the shared api Lambda role"
1184
1239
 
1185
1240
  policy = local.api_grouped_policy_documents.observability
1241
+
1242
+ lifecycle {
1243
+ precondition {
1244
+ condition = length(local.api_grouped_policy_documents.observability) <= 6144
1245
+ error_message = "The grouped API observability managed-policy document exceeds AWS IAM's 6,144-character limit."
1246
+ }
1247
+ }
1186
1248
  }
1187
1249
 
1188
1250
  resource "aws_iam_role_policy_attachment" "api_observability" {
@@ -1221,3 +1283,5 @@ moved {
1221
1283
  from = aws_iam_role_policy_attachment.lambda_bedrock_knowledge_base
1222
1284
  to = aws_iam_role_policy_attachment.api_ai
1223
1285
  }
1286
+
1287
+