thinkwork-cli 0.13.1 → 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 (262) hide show
  1. package/README.md +3 -14
  2. package/dist/{api-client-4DZ266J5.js → api-client-4P4JZOBX.js} +3 -1
  3. package/dist/bowser-6Z6JFAIR.js +2822 -0
  4. package/dist/{chunk-Y2WHJECR.js → chunk-4OMDWYZQ.js} +5 -44
  5. package/dist/chunk-AD2NIWB5.js +6516 -0
  6. package/dist/chunk-DV3MRBR5.js +43 -0
  7. package/dist/chunk-EI5ZX7CN.js +522 -0
  8. package/dist/chunk-EUJ7X6YD.js +126 -0
  9. package/dist/chunk-GG5WKBOX.js +436 -0
  10. package/dist/chunk-I64EZSU5.js +358 -0
  11. package/dist/chunk-KMIJ5KGT.js +128 -0
  12. package/dist/chunk-MWQ7RO76.js +137 -0
  13. package/dist/chunk-OCPHU34C.js +996 -0
  14. package/dist/chunk-OLUQ4U2Z.js +1728 -0
  15. package/dist/chunk-XYM7PE2A.js +2264 -0
  16. package/dist/chunk-YQCYD6NE.js +3299 -0
  17. package/dist/cli.js +49156 -31257
  18. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
  19. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
  20. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
  21. package/dist/dist-es-2OTNTL4N.js +315 -0
  22. package/dist/dist-es-2XVAH4BQ.js +14726 -0
  23. package/dist/dist-es-3DPDUJAH.js +2000 -0
  24. package/dist/dist-es-BNZTBBVC.js +176 -0
  25. package/dist/dist-es-CD2RBDAC.js +88 -0
  26. package/dist/dist-es-I4FW57PS.js +88 -0
  27. package/dist/dist-es-KL7Y5NB2.js +377 -0
  28. package/dist/dist-es-KVFWJNDY.js +318 -0
  29. package/dist/dist-es-PS73DWNL.js +70 -0
  30. package/dist/dist-es-S3NZQFGT.js +170 -0
  31. package/dist/dist-es-SGCA4P32.js +48 -0
  32. package/dist/dist-es-WBM6J4RA.js +70 -0
  33. package/dist/dist-es-ZGVADYIF.js +485 -0
  34. package/dist/dist-es-ZKC2QKD2.js +377 -0
  35. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
  36. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
  37. package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
  38. package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
  39. package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
  40. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
  41. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
  42. package/dist/drizzle/0227_analyst_reader_role.sql +17 -20
  43. package/dist/drizzle/0230_analyst_rls.sql +28 -36
  44. package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
  45. package/dist/drizzle/0251_brain_stragglers.sql +21 -0
  46. package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
  47. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  48. package/dist/drizzle/0261_native_auth_control_plane.sql +332 -0
  49. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  50. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  51. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  52. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  53. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  54. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  55. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  56. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  57. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  58. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  59. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  60. package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
  61. package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
  62. package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
  63. package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
  64. package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
  65. package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
  66. package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
  67. package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
  68. package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
  69. package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
  70. package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
  71. package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
  72. package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
  73. package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
  74. package/dist/drizzle/0284_user_brain_claims.sql +69 -0
  75. package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
  76. package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
  77. package/dist/drizzle/0287_brain_subject.sql +25 -0
  78. package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
  79. package/dist/event-streams-7JNLJEEL.js +894 -0
  80. package/dist/event-streams-KDF5RWR6.js +1515 -0
  81. package/dist/loadSso-ETCXTR4I.js +576 -0
  82. package/dist/loadSso-UBD7KUHE.js +583 -0
  83. package/dist/plugins/README.md +2 -2
  84. package/dist/plugins/catalog/package.json +1 -5
  85. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -4
  86. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
  87. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -94
  88. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -8
  89. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -20
  90. package/dist/plugins/catalog/src/contracts.ts +17 -179
  91. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -24
  92. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
  93. package/dist/plugins/twenty/package.json +2 -1
  94. package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
  95. package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
  96. package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
  97. package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
  98. package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
  99. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
  100. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
  101. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
  102. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
  103. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
  104. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
  105. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
  106. package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
  107. package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
  108. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
  109. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
  110. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
  111. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
  112. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
  113. package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
  114. package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
  115. package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
  116. package/dist/plugins/twenty/test/manifest.test.ts +2 -4
  117. package/dist/scripts/post-deploy.sh +79 -1
  118. package/dist/signin-5QZNRSOO.js +821 -0
  119. package/dist/sso-oidc-XK673P54.js +837 -0
  120. package/dist/sts-OXI2UALF.js +1113 -0
  121. package/dist/sts-SQW4NBK3.js +1117 -0
  122. package/dist/terraform/examples/greenfield/main.tf +391 -176
  123. package/dist/terraform/examples/greenfield/terraform.tfvars.example +26 -8
  124. package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
  125. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
  126. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
  127. package/dist/terraform/modules/app/agentcore-identity/main.tf +253 -0
  128. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  129. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +239 -0
  130. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +22 -0
  131. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  132. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
  133. package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
  134. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +55 -0
  135. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  136. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +45 -0
  137. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +123 -0
  138. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
  139. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
  140. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
  141. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
  142. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
  143. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
  144. package/dist/terraform/modules/app/agentcore-identity/variables.tf +60 -0
  145. package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
  146. package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
  147. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
  148. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  149. package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
  150. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
  151. package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
  152. package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
  153. package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
  154. package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
  155. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  156. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  157. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  158. package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
  159. package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
  160. package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
  161. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
  162. package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
  163. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
  164. package/dist/terraform/modules/app/deployment-control-plane/runner.py +3689 -233
  165. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2579 -140
  166. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
  167. package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
  168. package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
  169. package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
  170. package/dist/terraform/modules/app/lambda-api/handlers.tf +531 -619
  171. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +350 -241
  172. package/dist/terraform/modules/app/lambda-api/main.tf +75 -13
  173. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
  174. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
  175. package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -10
  176. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  177. package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
  178. package/dist/terraform/modules/app/lambda-api/variables.tf +307 -121
  179. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
  180. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
  181. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
  182. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
  183. package/dist/terraform/modules/app/static-site/main.tf +1 -1
  184. package/dist/terraform/modules/app/www-dns/main.tf +9 -27
  185. package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
  186. package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
  187. package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
  188. package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
  189. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
  190. package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
  191. package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
  192. package/dist/terraform/modules/foundation/cognito/main.tf +367 -72
  193. package/dist/terraform/modules/foundation/cognito/outputs.tf +47 -0
  194. package/dist/terraform/modules/foundation/cognito/variables.tf +131 -9
  195. package/dist/terraform/modules/thinkwork/README.md +5 -1
  196. package/dist/terraform/modules/thinkwork/main.tf +459 -234
  197. package/dist/terraform/modules/thinkwork/outputs.tf +44 -62
  198. package/dist/terraform/modules/thinkwork/variables.tf +353 -89
  199. package/dist/terraform/schema.graphql +69 -36
  200. package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
  201. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
  202. package/dist/workspace-defaults/files/ROUTER.md +1 -1
  203. package/dist/workspace-defaults/files/TOOLS.md +9 -39
  204. package/dist/workspace-defaults/files/USER.md +1 -0
  205. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
  206. package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
  207. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
  208. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  209. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  210. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
  211. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
  212. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
  213. package/package.json +3 -2
  214. package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
  215. package/dist/plugins/company-data/README.md +0 -49
  216. package/dist/plugins/company-data/package.json +0 -21
  217. package/dist/plugins/company-data/src/index.ts +0 -30
  218. package/dist/plugins/company-data/src/manifest.ts +0 -26
  219. package/dist/plugins/company-data/test/manifest.test.ts +0 -116
  220. package/dist/plugins/company-data/tsconfig.json +0 -8
  221. package/dist/plugins/company-etl/README.md +0 -46
  222. package/dist/plugins/company-etl/package.json +0 -21
  223. package/dist/plugins/company-etl/src/index.ts +0 -29
  224. package/dist/plugins/company-etl/src/manifest.ts +0 -26
  225. package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
  226. package/dist/plugins/company-etl/tsconfig.json +0 -8
  227. package/dist/plugins/lastmile/README.md +0 -50
  228. package/dist/plugins/lastmile/package.json +0 -23
  229. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
  230. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
  231. package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
  232. package/dist/plugins/lastmile/src/index.ts +0 -46
  233. package/dist/plugins/lastmile/src/manifest.ts +0 -139
  234. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
  235. package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
  236. package/dist/plugins/lastmile/tsconfig.json +0 -8
  237. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
  238. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
  239. package/dist/plugins/workos-auth/package.json +0 -22
  240. package/dist/plugins/workos-auth/src/index.ts +0 -30
  241. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  242. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  243. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  244. package/dist/plugins/workos-auth/tsconfig.json +0 -8
  245. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
  246. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
  247. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
  248. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
  249. package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -101
  250. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -9
  251. package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -25
  252. package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
  253. package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
  254. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
  255. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
  256. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
  257. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
  258. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
  259. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
  260. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
  261. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
  262. /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.
@@ -181,6 +156,8 @@ locals {
181
156
  "cognito-idp:AdminRespondToAuthChallenge",
182
157
  "cognito-idp:AdminSetUserPassword",
183
158
  "cognito-idp:AdminUpdateUserAttributes",
159
+ "cognito-idp:DescribeIdentityProvider",
160
+ "cognito-idp:DescribeUserPoolClient",
184
161
  "cognito-idp:ListUsers",
185
162
  ]
186
163
  Resource = var.user_pool_arn
@@ -322,17 +299,25 @@ locals {
322
299
  Resource = var.plugin_catalog_github_token_secret_arn
323
300
  },
324
301
  ] : [],
325
- # THINK-229 U1 analyst-query-broker mints RDS IAM auth tokens for the
326
- # analyst_reader role. The rds-db ARN keys on the immutable cluster
327
- # RESOURCE ID (cluster-XXXX), never the cluster ARN, and the dbuser
328
- # segment is the exact-case Postgres role name. aws:SourceIp /
329
- # aws:SourceVpc conditions are unsupported on rds-db:connect, so the
330
- # narrow Resource IS the control surface.
331
- 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 != "" ? [
332
307
  {
333
308
  Effect = "Allow"
334
- Action = ["rds-db:connect"]
335
- 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
336
321
  },
337
322
  ] : [],
338
323
  # (was standalone managed policy "lambda_deployment_evidence_read")
@@ -346,6 +331,7 @@ locals {
346
331
  # refresh path cannot replace the old manual AWS CLI step.
347
332
  var.deployment_evidence_bucket != "" ? [
348
333
  {
334
+ Sid = "DeploymentEvidenceBucket"
349
335
  Effect = "Allow"
350
336
  Action = [
351
337
  "s3:GetObject",
@@ -357,6 +343,12 @@ locals {
357
343
  "arn:aws:s3:::${var.deployment_evidence_bucket}/*",
358
344
  ]
359
345
  },
346
+ {
347
+ Sid = "AgentCoreHarnessGatewayOauthSecret"
348
+ Effect = "Allow"
349
+ Action = ["secretsmanager:GetSecretValue"]
350
+ Resource = ["arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:bedrock-agentcore-identity!default/oauth2/thinkwork-${var.stage}-proof-oauth-*"]
351
+ },
360
352
  ] : [],
361
353
  var.billing_export_bucket_name != "" ? [
362
354
  {
@@ -388,33 +380,33 @@ locals {
388
380
  )
389
381
  },
390
382
  ] : [],
391
- 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 ? [
392
387
  {
393
388
  Effect = "Allow"
394
389
  Action = [
395
- "kms:Decrypt",
396
- "kms:GenerateDataKey",
390
+ "sqs:SendMessage",
391
+ "sqs:ReceiveMessage",
392
+ "sqs:DeleteMessage",
393
+ "sqs:GetQueueAttributes",
394
+ "sqs:ChangeMessageVisibility",
397
395
  ]
398
- Resource = var.brain_artifacts_kms_key_arn
399
- Condition = {
400
- StringEquals = {
401
- "kms:ViaService" = "s3.${var.region}.amazonaws.com"
402
- }
403
- }
396
+ Resource = [for q in aws_sqs_queue.agentcore_dispatch_dlq : q.arn]
404
397
  },
405
398
  ] : [],
406
- var.okf_efs_file_system_arn != "" && var.okf_efs_refresh_access_point_arn != "" ? [
399
+ var.brain_artifacts_kms_key_arn != "" ? [
407
400
  {
408
- Sid = "OkfEfsHydratorMountWrite"
409
401
  Effect = "Allow"
410
402
  Action = [
411
- "elasticfilesystem:ClientMount",
412
- "elasticfilesystem:ClientWrite",
403
+ "kms:Decrypt",
404
+ "kms:GenerateDataKey",
413
405
  ]
414
- Resource = var.okf_efs_file_system_arn
406
+ Resource = var.brain_artifacts_kms_key_arn
415
407
  Condition = {
416
408
  StringEquals = {
417
- "elasticfilesystem:AccessPointArn" = var.okf_efs_refresh_access_point_arn
409
+ "kms:ViaService" = "s3.${var.region}.amazonaws.com"
418
410
  }
419
411
  }
420
412
  },
@@ -435,13 +427,36 @@ locals {
435
427
  Resource = var.capability_broker_session_table_arn
436
428
  },
437
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
+ ] : [],
438
453
  )
439
454
 
440
455
  # ---------------------------------------------------------------------------
441
456
  # Group 2: orchestration — cross-function invokes, Scheduler, Step
442
457
  # Functions, SQS, SES.
443
458
  # ---------------------------------------------------------------------------
444
- api_orchestration_statements = concat(
459
+ api_orchestration_candidate_statements = concat(
445
460
  [
446
461
  # (was inline policy "ses-send")
447
462
  # SES send permissions for the email-send handler. Scoped to any
@@ -491,52 +506,45 @@ locals {
491
506
  # (was inline policy "api-cross-function-invoke")
492
507
  # Allow API handler Lambdas to invoke each other directly. sendMessage
493
508
  # dispatches to chat-agent-invoke for instant chat response; the memory
494
- # resolvers reach knowledge-base-manager and job-schedule-manager for
495
- # 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
496
511
  # the Pi runtime Lambda only — this one covers internal api-to-api
497
512
  # calls. ARNs are constructed deterministically from the handler naming
498
513
  # pattern so we don't create a dependency cycle with the handler
499
514
  # resource.
500
515
  {
516
+ Sid = "ApiCrossFunctionInvoke"
501
517
  Effect = "Allow"
502
518
  Action = ["lambda:InvokeFunction"]
503
519
  Resource = [
504
520
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke",
505
- "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",
506
525
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-schedule-manager",
507
526
  # eval-runner: graphql-http's startEvalRun mutation Event-invokes
508
527
  # this asynchronously after inserting the eval_runs row.
509
528
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-eval-runner",
510
- # wiki-compile: memory-retain Event-invokes this after a successful
511
- # retainTurn when the tenant's wiki_compile_enabled flag is on.
512
- # compileWikiNow admin mutation also Event-invokes.
513
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-wiki-compile",
514
- # knowledge-graph-observations-ingest: graphql-http's
515
- # startKnowledgeGraphObservationsIngest mutation invokes this with
516
- # RequestResponse, and memory-stage-worker's graph stage (THINK-193
517
- # U4 stitch) RequestResponse-invokes it with a targeted bankIds
518
- # payload the Bedrock classifier/extraction model config lives on
519
- # the ingest Lambda's own env, so the worker never runs the ingest
520
- # in-process. (The worker's self-invoke drain is retired — AWS
521
- # recursive-loop detection terminated those chains; backlog drains
522
- # in-process now.)
523
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-knowledge-graph-observations-ingest",
524
- # wiki-bootstrap-import: bootstrapJournalImport admin mutation
525
- # Event-invokes this for the long-running ingest path.
526
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-wiki-bootstrap-import",
527
- # ontology-scan: startOntologySuggestionScan Event-invokes this
528
- # after inserting a durable scan job row.
529
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-ontology-scan",
530
- # ontology-reprocess: approveOntologyChangeSet Event-invokes this
531
- # after inserting a durable reprocess job row.
532
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-ontology-reprocess",
533
- # okf-materialize / okf-efs-refresh: the OKF distribution chain
534
- # (THINK-200). wiki-compile Event-invokes okf-materialize after a
535
- # successful compile; okf-materialize Event-invokes okf-efs-refresh
536
- # after publishing bundles, keeping the Pi wiki navigator's EFS
537
- # view current without manual invocation.
538
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-okf-materialize",
539
- "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-okf-efs-refresh",
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",
533
+ # harness-runner: chat-agent-invoke Event-invokes this for chat
534
+ # turns routed to the AWS AgentCore runtime (THINK-311 trial).
535
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-harness-runner",
536
+ # harness-runner queues the same canonical post-turn memory pipeline
537
+ # as Pi after a completed AgentCore turn. The retain ledger makes the
538
+ # Event invoke idempotent on the thread-turn source key.
539
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-memory-retain",
540
+ # harness-runner RequestResponse-invokes this dedicated signer. The
541
+ # signer re-derives the actor/tenant/thread tuple from the running
542
+ # turn and alone holds KMS Sign; the shared runner role never does.
543
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-turn-assertion-mint",
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",
540
548
  # routine-resume: routine-approval-bridge (Phase B U8) invokes
541
549
  # this with RequestResponse after a HITL decideInboxItem
542
550
  # decision. Calls SendTaskSuccess/SendTaskFailure on the SFN
@@ -548,7 +556,9 @@ locals {
548
556
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-files-efs",
549
557
  # workspace-renderer: chat-agent-invoke invokes this synchronously
550
558
  # before AgentCore so Pi can opt into the rendered
551
- # 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:*`).
552
562
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-renderer",
553
563
  # skill-trust-runner: graphql-http's publishSkillDraft mutation
554
564
  # invokes this with RequestResponse so SkillSpector completion is a
@@ -594,6 +604,9 @@ locals {
594
604
  Resource = [
595
605
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workspace-renderer",
596
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",
597
610
  ]
598
611
  },
599
612
  # (was standalone managed policy "thread_idle_memory_learning_invoke")
@@ -761,11 +774,35 @@ locals {
761
774
  ] : [],
762
775
  )
763
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
+
764
791
  # ---------------------------------------------------------------------------
765
- # Group 3: AI — Bedrock model invocation, Knowledge Bases, AgentCore
792
+ # Group 3: AI — Bedrock model invocation, AgentCore
766
793
  # memory / evaluations / code interpreter.
767
794
  # ---------------------------------------------------------------------------
768
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
+ },
769
806
  # (was inline policy "bedrock-invoke")
770
807
  # Cross-region inference profiles (us.anthropic.claude-*) require
771
808
  # `bedrock:InvokeModel` on the *inference-profile* ARN AND on the
@@ -781,62 +818,17 @@ locals {
781
818
  "arn:aws:bedrock:*:${var.account_id}:inference-profile/*",
782
819
  ]
783
820
  },
784
- # (was standalone managed policy "lambda_bedrock_knowledge_base")
785
- # The knowledge-base-manager Lambda provisions and manages Bedrock
786
- # Knowledge Bases (control plane: create/sync/rechunk/delete), and
787
- # graphql-http's testKnowledgeBaseRetrieval query calls Retrieve. Without
788
- # these the manager fails with "not authorized to perform:
789
- # bedrock:CreateKnowledgeBase" and every KB is stuck in `failed`.
790
- # CreateKnowledgeBase / CreateDataSource also take a roleArn that Bedrock
791
- # assumes, so the caller needs iam:PassRole on the KB service role.
792
- {
793
- Effect = "Allow"
794
- Action = [
795
- "bedrock:CreateKnowledgeBase",
796
- "bedrock:GetKnowledgeBase",
797
- "bedrock:UpdateKnowledgeBase",
798
- "bedrock:DeleteKnowledgeBase",
799
- "bedrock:ListKnowledgeBases",
800
- "bedrock:CreateDataSource",
801
- "bedrock:GetDataSource",
802
- "bedrock:UpdateDataSource",
803
- "bedrock:DeleteDataSource",
804
- "bedrock:ListDataSources",
805
- "bedrock:StartIngestionJob",
806
- "bedrock:GetIngestionJob",
807
- "bedrock:ListIngestionJobs",
808
- "bedrock:Retrieve",
809
- # THINK-193 U7: knowledge-base-manager reconciles the per-document
810
- # manifest after successful syncs (List/Get) and gates deletion
811
- # settlement on Get + the scoped Retrieve above. Ingest/Delete cover
812
- # the explicit direct-delete path for S3 RETAIN'd documents — the
813
- # manager never races them with StartIngestionJob. The workspace-
814
- # bucket S3 read the memory-stage-worker needs for document text is
815
- # already granted by the shared role's workspace "s3-access"
816
- # statement (data-plane group).
817
- "bedrock:ListKnowledgeBaseDocuments",
818
- "bedrock:GetKnowledgeBaseDocuments",
819
- "bedrock:IngestKnowledgeBaseDocuments",
820
- "bedrock:DeleteKnowledgeBaseDocuments",
821
- ]
822
- # CreateKnowledgeBase/CreateDataSource have no resource ARN at create
823
- # time and don't support resource-level scoping, so a knowledge-base/*
824
- # ARN makes the grant not match. Control-plane "*" for this internal
825
- # manager Lambda; PassRole below stays scoped to the KB service role.
826
- Resource = "*"
827
- },
828
- {
829
- Effect = "Allow"
830
- Action = ["iam:PassRole"]
831
- Resource = var.kb_service_role_arn != "" ? var.kb_service_role_arn : "*"
832
- },
833
821
  # (was inline policy "agentcore-memory-rw")
834
- # AgentCore Memory read access for the GraphQL memory resolvers.
835
- # memoryRecords / memorySearch call ListMemoryRecordsCommand to fetch
836
- # 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).
837
828
  {
838
829
  Effect = "Allow"
839
830
  Action = [
831
+ "bedrock-agentcore:CreateEvent",
840
832
  "bedrock-agentcore:ListMemoryRecords",
841
833
  "bedrock-agentcore:RetrieveMemoryRecords",
842
834
  "bedrock-agentcore:GetMemoryRecord",
@@ -887,73 +879,38 @@ locals {
887
879
  Resource = "*"
888
880
  },
889
881
  ],
890
- # THINK-311 U4 AgentCore Harness trial invoker grants, present only when
891
- # the agentcore-harness module is enabled (empty role ARN = module off).
892
- # Ship-inert: no handler calls Harness until U5's harness-runner lands.
893
- #
894
- # CRITICAL: never grant `bedrock-agentcore:InvokeAgentRuntimeCommand`
895
- # anywhere it bypasses Harness allowedTools enforcement. Invocation goes
896
- # through InvokeHarness / InvokeAgentRuntime only.
897
- var.agentcore_harness_execution_role_arn != "" ? [
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
+ [
898
889
  {
899
- # Data plane — invoke a Harness (and its runtime endpoints) directly.
900
- Sid = "AgentCoreHarnessInvoke"
890
+ Sid = "AgentCoreTwentyIdentity"
901
891
  Effect = "Allow"
902
892
  Action = [
903
- "bedrock-agentcore:InvokeHarness",
904
- "bedrock-agentcore:InvokeAgentRuntime",
893
+ "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
894
+ "bedrock-agentcore:GetWorkloadAccessTokenForUserId",
895
+ "bedrock-agentcore:GetResourceOauth2Token",
896
+ "bedrock-agentcore:CompleteResourceTokenAuth",
905
897
  ]
906
- Resource = "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:harness/*"
907
- },
908
- {
909
- # Control plane — CreateHarness has no resource ARN at create time
910
- # (same non-scopable shape as bedrock:CreateKnowledgeBase above), so
911
- # this statement is "*"; PassRole below stays scoped to the single
912
- # Harness execution role.
913
- Sid = "AgentCoreHarnessControl"
914
- Effect = "Allow"
915
- Action = [
916
- "bedrock-agentcore:CreateHarness",
917
- "bedrock-agentcore:GetHarness",
918
- "bedrock-agentcore:UpdateHarness",
919
- "bedrock-agentcore:DeleteHarness",
920
- "bedrock-agentcore:CreateHarnessEndpoint",
921
- "bedrock-agentcore:GetHarnessEndpoint",
922
- "bedrock-agentcore:ListHarnessEndpoints",
923
- "bedrock-agentcore:ListHarnesses",
924
- "bedrock-agentcore:ListHarnessVersions",
898
+ Resource = [
899
+ "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:workload-identity-directory/default",
900
+ "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:workload-identity-directory/default/workload-identity/thinkwork-${var.stage}-multiplayer-proof",
901
+ "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:token-vault/default",
902
+ "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:token-vault/default/oauth2credentialprovider/thinkwork-${var.stage}-twenty-crm",
925
903
  ]
926
- Resource = "*"
927
904
  },
928
- ] : [],
929
- # (Separate conditional from the two statements above only because the
930
- # Condition attribute gives this object a different Terraform type —
931
- # same gate, same feature.)
932
- var.agentcore_harness_execution_role_arn != "" ? [
933
- {
934
- # CreateHarness takes executionRoleArn; the caller must be able to
935
- # pass exactly that role, and only to the bedrock-agentcore service.
936
- Sid = "AgentCoreHarnessPassExecutionRole"
937
- Effect = "Allow"
938
- Action = ["iam:PassRole"]
939
- Resource = var.agentcore_harness_execution_role_arn
940
- Condition = {
941
- StringEquals = {
942
- "iam:PassedToService" = "bedrock-agentcore.amazonaws.com"
943
- }
944
- }
945
- },
946
- ] : [],
905
+ ],
947
906
  )
948
907
 
949
908
  # ---------------------------------------------------------------------------
950
909
  # Group 4: observability — CloudWatch Logs reads, ECS/ALB health reads.
951
910
  # ---------------------------------------------------------------------------
952
- # Handler-gated SQS grants moved HERE from the data-plane group
953
- # (THINK-229 U5 follow-up): data-plane hit IAM's 6,144-char rendered cap
954
- # when the analyst-connection-reconciler DLQ grant landed (live size was
955
- # 6,018 before it). Queue plumbing is operational surface; observability
956
- # 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.
957
914
  api_observability_sqs_statements = concat(
958
915
  # SQS grants live here rather than in the orchestration group purely for
959
916
  # size balance: with every conditional on, orchestration's rendered JSON
@@ -961,26 +918,12 @@ locals {
961
918
  # Handler-gated SQS grants. Each statement was a count-gated inline
962
919
  # policy whose queue exists only when local.deploy_lambda_handlers.
963
920
  local.deploy_lambda_handlers ? [
964
- # (was inline policy "thinkwork-${stage}-wiki-compile-dlq-send")
965
- {
966
- Sid = "WikiCompileDlqSend"
967
- Effect = "Allow"
968
- Action = ["sqs:SendMessage"]
969
- Resource = aws_sqs_queue.wiki_compile_dlq[0].arn
970
- },
971
- # (was inline policy "thinkwork-${stage}-ontology-scan-dlq-send")
921
+ # THINK-321 U7 identity-match async-failure DLQ.
972
922
  {
973
- Sid = "OntologyScanDlqSend"
923
+ Sid = "IdentityMatchDlqSend"
974
924
  Effect = "Allow"
975
925
  Action = ["sqs:SendMessage"]
976
- Resource = aws_sqs_queue.ontology_scan_dlq[0].arn
977
- },
978
- # (was inline policy "thinkwork-${stage}-ontology-reprocess-dlq-send")
979
- {
980
- Sid = "OntologyReprocessDlqSend"
981
- Effect = "Allow"
982
- Action = ["sqs:SendMessage"]
983
- Resource = aws_sqs_queue.ontology_reprocess_dlq[0].arn
926
+ Resource = aws_sqs_queue.identity_match_dlq[0].arn
984
927
  },
985
928
  # (was inline policy "compliance-drainer-dlq-send")
986
929
  {
@@ -989,13 +932,6 @@ locals {
989
932
  Action = ["sqs:SendMessage"]
990
933
  Resource = aws_sqs_queue.compliance_drainer_dlq[0].arn
991
934
  },
992
- # THINK-229 U5 — analyst-connection-reconciler async on_failure DLQ.
993
- {
994
- Sid = "AnalystConnectionReconcilerDlqSend"
995
- Effect = "Allow"
996
- Action = ["sqs:SendMessage"]
997
- Resource = aws_sqs_queue.analyst_connection_reconciler_dlq[0].arn
998
- },
999
935
  # (was inline policy "compliance-exports-send")
1000
936
  # graphql-http needs sqs:SendMessage on the exports queue to dispatch
1001
937
  # jobIds from the createComplianceExport mutation. Attached to the
@@ -1037,9 +973,102 @@ locals {
1037
973
  Resource = aws_sqs_queue.eval_fanout_dlq[0].arn
1038
974
  },
1039
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
+ ],
1040
1042
  )
1041
1043
 
1042
1044
  api_observability_statements = concat(local.api_observability_sqs_statements, [
1045
+ # Backend notification publishing is IAM-only. Scope the shared API
1046
+ # execution role to the NONE-resolver mutation fields; end-user identity-
1047
+ # pool credentials have no AppSync grant. This statement lives in the
1048
+ # observability envelope for size balance: adding subscription invalidation
1049
+ # pushed the data-plane policy over IAM's 6,144-character hard cap.
1050
+ {
1051
+ Sid = "PublishAppSyncNotifications"
1052
+ Effect = "Allow"
1053
+ Action = ["appsync:GraphQL"]
1054
+ Resource = [
1055
+ for field in [
1056
+ "notifyAgentStatus",
1057
+ "notifyNewMessage",
1058
+ "notifyHeartbeatActivity",
1059
+ "notifyThreadActivity",
1060
+ "notifyThreadUpdate",
1061
+ "notifyInboxItemUpdate",
1062
+ "notifyThreadTurnUpdate",
1063
+ "notifyThreadTurnStep",
1064
+ "notifyOrgUpdate",
1065
+ "notifyCostRecorded",
1066
+ "notifyEvalRunUpdate",
1067
+ "notifyWorkspaceAccessRevoked",
1068
+ "invalidateSubscription",
1069
+ ] : "arn:aws:appsync:${var.region}:${var.account_id}:apis/${var.appsync_api_id}/types/Mutation/fields/${field}"
1070
+ ]
1071
+ },
1043
1072
  # (was inline policy "cloudwatch-logs-read")
1044
1073
  {
1045
1074
  Effect = "Allow"
@@ -1083,16 +1112,52 @@ locals {
1083
1112
  Resource = "*"
1084
1113
  },
1085
1114
  ])
1115
+
1116
+ api_grouped_policy_documents = {
1117
+ data_plane = jsonencode({
1118
+ Version = "2012-10-17"
1119
+ Statement = local.api_data_plane_statements
1120
+ })
1121
+ orchestration = jsonencode({
1122
+ Version = "2012-10-17"
1123
+ Statement = local.api_orchestration_statements
1124
+ })
1125
+ invocation = jsonencode({
1126
+ Version = "2012-10-17"
1127
+ Statement = local.api_invocation_statements
1128
+ })
1129
+ ai = jsonencode({
1130
+ Version = "2012-10-17"
1131
+ Statement = local.api_ai_statements
1132
+ })
1133
+ observability = jsonencode({
1134
+ Version = "2012-10-17"
1135
+ Statement = local.api_observability_statements
1136
+ })
1137
+ }
1138
+ }
1139
+
1140
+ check "api_grouped_managed_policy_sizes" {
1141
+ assert {
1142
+ condition = alltrue([
1143
+ for document in values(local.api_grouped_policy_documents) : length(document) <= 6144
1144
+ ])
1145
+ error_message = "Each grouped API managed-policy document must remain within AWS IAM's 6,144-character limit."
1146
+ }
1086
1147
  }
1087
1148
 
1088
1149
  resource "aws_iam_policy" "api_data_plane" {
1089
1150
  name = "thinkwork-${var.stage}-api-data-plane"
1090
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"
1091
1152
 
1092
- policy = jsonencode({
1093
- Version = "2012-10-17"
1094
- Statement = local.api_data_plane_statements
1095
- })
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
+ }
1096
1161
  }
1097
1162
 
1098
1163
  resource "aws_iam_role_policy_attachment" "api_data_plane" {
@@ -1102,12 +1167,27 @@ resource "aws_iam_role_policy_attachment" "api_data_plane" {
1102
1167
 
1103
1168
  resource "aws_iam_policy" "api_orchestration" {
1104
1169
  name = "thinkwork-${var.stage}-api-orchestration"
1105
- 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"
1171
+
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]
1106
1177
 
1107
- policy = jsonencode({
1108
- Version = "2012-10-17"
1109
- Statement = local.api_orchestration_statements
1110
- })
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
+ }
1111
1191
  }
1112
1192
 
1113
1193
  resource "aws_iam_role_policy_attachment" "api_orchestration" {
@@ -1115,14 +1195,37 @@ resource "aws_iam_role_policy_attachment" "api_orchestration" {
1115
1195
  policy_arn = aws_iam_policy.api_orchestration.arn
1116
1196
  }
1117
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
+
1118
1217
  resource "aws_iam_policy" "api_ai" {
1119
1218
  name = "thinkwork-${var.stage}-api-ai"
1120
- 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"
1121
1220
 
1122
- policy = jsonencode({
1123
- Version = "2012-10-17"
1124
- Statement = local.api_ai_statements
1125
- })
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
+ }
1126
1229
  }
1127
1230
 
1128
1231
  resource "aws_iam_role_policy_attachment" "api_ai" {
@@ -1134,10 +1237,14 @@ resource "aws_iam_policy" "api_observability" {
1134
1237
  name = "thinkwork-${var.stage}-api-observability"
1135
1238
  description = "Grouped observability grants (CloudWatch Logs reads, ECS/ALB health reads) for the shared api Lambda role"
1136
1239
 
1137
- policy = jsonencode({
1138
- Version = "2012-10-17"
1139
- Statement = local.api_observability_statements
1140
- })
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
+ }
1141
1248
  }
1142
1249
 
1143
1250
  resource "aws_iam_role_policy_attachment" "api_observability" {
@@ -1176,3 +1283,5 @@ moved {
1176
1283
  from = aws_iam_role_policy_attachment.lambda_bedrock_knowledge_base
1177
1284
  to = aws_iam_role_policy_attachment.api_ai
1178
1285
  }
1286
+
1287
+