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
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${AWS_REGION:?AWS_REGION is required}"
5
+ : "${TWENTY_OAUTH_ISSUER:?TWENTY_OAUTH_ISSUER is required}"
6
+ : "${TWENTY_CLIENT_SECRET_ARN:?TWENTY_CLIENT_SECRET_ARN is required}"
7
+ : "${TWENTY_CREDENTIAL_PROVIDER_NAME:?TWENTY_CREDENTIAL_PROVIDER_NAME is required}"
8
+
9
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ issuer="${TWENTY_OAUTH_ISSUER%/}"
11
+
12
+ read_client_record() {
13
+ aws secretsmanager get-secret-value \
14
+ --region "$AWS_REGION" \
15
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
16
+ --query SecretString \
17
+ --output text 2>/dev/null || printf '{}'
18
+ }
19
+
20
+ write_client_record() {
21
+ aws secretsmanager put-secret-value \
22
+ --region "$AWS_REGION" \
23
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
24
+ --secret-string file:///dev/stdin >/dev/null
25
+ }
26
+
27
+ # Twenty's authorize endpoint is "$issuer/authorize" (NOT /oauth/authorize —
28
+ # observed live on tei-e2e: the previously hard-coded path 404s). Fetch the
29
+ # issuer's discovery document once and take both provider endpoints from it,
30
+ # falling back to the documented paths when discovery is unavailable.
31
+ discovery="$(curl --silent --fail --max-time 15 \
32
+ "$issuer/.well-known/oauth-authorization-server" 2>/dev/null || printf '')"
33
+ provider_endpoint() {
34
+ local field="$1" fallback="$2"
35
+ local discovered
36
+ discovered="$(jq -r --arg f "$field" '.[$f] // empty' <<<"$discovery" 2>/dev/null)"
37
+ if [[ -n "$discovered" ]]; then
38
+ printf '%s' "$discovered"
39
+ else
40
+ printf '%s' "$fallback"
41
+ fi
42
+ }
43
+
44
+ reconcile_provider() {
45
+ local client_id="$1"
46
+ local runtime_script="$script_dir/reconcile_twenty_provider.mjs"
47
+ if [[ -n "${THINKWORK_AGENTCORE_CONTROL_RUNTIME_DIR:-}" ]]; then
48
+ runtime_script="$THINKWORK_AGENTCORE_CONTROL_RUNTIME_DIR/reconcile_twenty_provider.js"
49
+ if [[ ! -f "$runtime_script" ]]; then
50
+ printf '%s\n' 'Managed AgentCore control runtime is missing reconcile_twenty_provider.js' >&2
51
+ return 66
52
+ fi
53
+ fi
54
+ jq -n \
55
+ --arg region "$AWS_REGION" \
56
+ --arg name "$TWENTY_CREDENTIAL_PROVIDER_NAME" \
57
+ --arg issuer "$issuer" \
58
+ --arg authorizationEndpoint "$(provider_endpoint authorization_endpoint "$issuer/authorize")" \
59
+ --arg tokenEndpoint "$(provider_endpoint token_endpoint "$issuer/oauth/token")" \
60
+ --arg clientId "$client_id" \
61
+ --arg secretArn "$TWENTY_CLIENT_SECRET_ARN" \
62
+ '{
63
+ region: $region,
64
+ name: $name,
65
+ issuer: $issuer,
66
+ authorizationEndpoint: $authorizationEndpoint,
67
+ tokenEndpoint: $tokenEndpoint,
68
+ clientId: $clientId,
69
+ secretArn: $secretArn
70
+ }' | node "$runtime_script"
71
+ }
72
+
73
+ verify_provider() {
74
+ local response="$1"
75
+ local expected_client_id="$2"
76
+ local expected_callback="$3"
77
+ jq -e \
78
+ --arg name "$TWENTY_CREDENTIAL_PROVIDER_NAME" \
79
+ --arg client_id "$expected_client_id" \
80
+ --arg callback "$expected_callback" \
81
+ --arg secret_arn "$TWENTY_CLIENT_SECRET_ARN" \
82
+ '.name == $name and
83
+ .clientId == $client_id and
84
+ .callbackUrl == $callback and
85
+ .clientSecretArn == $secret_arn and
86
+ .clientSecretJsonKey == "client_secret" and
87
+ .clientSecretSource == "EXTERNAL" and
88
+ .status == "READY"' <<<"$response" >/dev/null
89
+ }
90
+
91
+ client_record="$(read_client_record)"
92
+ if ! jq -e 'type == "object"' <<<"$client_record" >/dev/null 2>&1; then
93
+ printf '%s\n' 'Twenty client secret must contain a JSON object' >&2
94
+ exit 1
95
+ fi
96
+
97
+ client_id="$(jq -r '.client_id // empty' <<<"$client_record")"
98
+ client_secret_ready=false
99
+ if [[ -n "$client_id" ]] &&
100
+ jq -e '.client_secret | type == "string" and length > 0' \
101
+ <<<"$client_record" >/dev/null 2>&1 &&
102
+ [[ "$(jq -r '.bootstrap_state // empty' <<<"$client_record")" == "ready" ]]; then
103
+ client_secret_ready=true
104
+ else
105
+ placeholder_id="thinkwork-bootstrap-$(openssl rand -hex 12)"
106
+ placeholder_secret="$(openssl rand -base64 48 | tr -d '\n')"
107
+ client_record="$(jq -n \
108
+ --arg client_id "$placeholder_id" \
109
+ --arg client_secret "$placeholder_secret" \
110
+ '{
111
+ bootstrap_state: "placeholder",
112
+ client_id: $client_id,
113
+ client_secret: $client_secret,
114
+ token_endpoint_auth_method: "client_secret_post"
115
+ }')"
116
+ printf '%s' "$client_record" | write_client_record
117
+ client_id="$placeholder_id"
118
+ fi
119
+
120
+ # AgentCore issues a provider-specific callback only after the provider exists.
121
+ # The placeholder is never used for a user grant; it only lets AgentCore return
122
+ # the callback that Twenty must bind to the confidential client registration.
123
+ provider_response="$(reconcile_provider "$client_id")"
124
+ callback_url="$(jq -r '.callbackUrl // empty' <<<"$provider_response")"
125
+ if [[ -z "$callback_url" ]]; then
126
+ printf '%s\n' 'AgentCore Identity did not return a provider callback URL' >&2
127
+ exit 1
128
+ fi
129
+
130
+ registered_callback_matches=false
131
+ if $client_secret_ready &&
132
+ jq -e --arg callback "$callback_url" \
133
+ '.redirect_uris | type == "array" and index($callback) != null' \
134
+ <<<"$client_record" >/dev/null 2>&1 &&
135
+ [[ "$(jq -r '.token_endpoint_auth_method // empty' <<<"$client_record")" == \
136
+ "client_secret_post" ]]; then
137
+ registered_callback_matches=true
138
+ fi
139
+
140
+ if $registered_callback_matches; then
141
+ verify_provider "$provider_response" "$client_id" "$callback_url"
142
+ printf 'Twenty confidential OAuth client reused: provider=%s callback=%s\n' \
143
+ "$TWENTY_CREDENTIAL_PROVIDER_NAME" "$callback_url" >&2
144
+ printf '%s' "$provider_response"
145
+ exit 0
146
+ fi
147
+
148
+ if [[ -z "$discovery" ]]; then
149
+ printf '%s\n' 'Twenty discovery document is unavailable' >&2
150
+ exit 1
151
+ fi
152
+ registration_endpoint="$(jq -r '.registration_endpoint // empty' <<<"$discovery")"
153
+ if ! python3 - "$issuer" "$registration_endpoint" <<'PY'
154
+ import sys
155
+ from urllib.parse import urlsplit
156
+
157
+
158
+ def origin(url: str) -> tuple[str, str, int]:
159
+ parsed = urlsplit(url)
160
+ if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
161
+ raise ValueError("HTTPS origin without userinfo required")
162
+ return parsed.scheme, parsed.hostname.lower(), parsed.port or 443
163
+
164
+
165
+ try:
166
+ issuer_origin = origin(sys.argv[1])
167
+ registration_origin = origin(sys.argv[2])
168
+ except (ValueError, IndexError):
169
+ raise SystemExit(1)
170
+ raise SystemExit(0 if issuer_origin == registration_origin else 1)
171
+ PY
172
+ then
173
+ printf '%s\n' 'Twenty registration endpoint is not on the exact issuer origin' >&2
174
+ exit 1
175
+ fi
176
+ if ! jq -e \
177
+ --arg issuer "$issuer" \
178
+ '.issuer == $issuer and
179
+ (.grant_types_supported | index("authorization_code") != null) and
180
+ (.token_endpoint_auth_methods_supported | index("client_secret_post") != null) and
181
+ (.scopes_supported | index("api") != null) and
182
+ (.scopes_supported | index("profile") != null)' \
183
+ <<<"$discovery" >/dev/null; then
184
+ printf '%s\n' \
185
+ 'Twenty discovery does not advertise same-origin confidential authorization-code DCR' >&2
186
+ exit 1
187
+ fi
188
+
189
+ registration_payload="$(jq -n \
190
+ --arg callback "$callback_url" \
191
+ '{
192
+ client_name: "ThinkWork AgentCore Identity Twenty CRM",
193
+ redirect_uris: [$callback],
194
+ grant_types: ["authorization_code"],
195
+ response_types: ["code"],
196
+ token_endpoint_auth_method: "client_secret_post",
197
+ scope: "api profile"
198
+ }')"
199
+ registration_response="$(curl --silent --show-error --fail-with-body \
200
+ -H 'content-type: application/json' \
201
+ --data-binary "$registration_payload" \
202
+ "$registration_endpoint")"
203
+
204
+ client_record="$(jq -e \
205
+ --arg callback "$callback_url" \
206
+ '{
207
+ bootstrap_state: "ready",
208
+ client_id: .client_id,
209
+ client_secret: .client_secret,
210
+ client_id_issued_at: .client_id_issued_at,
211
+ client_secret_expires_at: .client_secret_expires_at,
212
+ client_name: .client_name,
213
+ redirect_uris: .redirect_uris,
214
+ grant_types: .grant_types,
215
+ response_types: .response_types,
216
+ token_endpoint_auth_method: .token_endpoint_auth_method,
217
+ scope: (.scope // "api profile"),
218
+ registration_access_token: .registration_access_token,
219
+ registration_client_uri: .registration_client_uri
220
+ } | select(
221
+ (.client_id | type == "string" and length > 0) and
222
+ (.client_secret | type == "string" and length > 0) and
223
+ (.redirect_uris | type == "array" and index($callback) != null) and
224
+ .token_endpoint_auth_method == "client_secret_post"
225
+ )' <<<"$registration_response")" || {
226
+ printf '%s\n' 'Twenty DCR did not return a confidential callback-bound client' >&2
227
+ exit 1
228
+ }
229
+
230
+ # A Secrets Manager version write is atomic. The plaintext client secret never
231
+ # enters Terraform state or process arguments and is not printed to logs.
232
+ printf '%s' "$client_record" | write_client_record
233
+ client_id="$(jq -r '.client_id' <<<"$client_record")"
234
+ provider_response="$(reconcile_provider "$client_id")"
235
+ verify_provider "$provider_response" "$client_id" "$callback_url"
236
+
237
+ printf 'Twenty confidential OAuth client registered: provider=%s callback=%s\n' \
238
+ "$TWENTY_CREDENTIAL_PROVIDER_NAME" "$callback_url" >&2
239
+ printf '%s' "$provider_response"
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${AWS_REGION:?AWS_REGION is required}"
5
+ : "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
6
+ : "${CREDENTIAL_PROVIDER_NAME:?CREDENTIAL_PROVIDER_NAME is required}"
7
+
8
+ if aws bedrock-agentcore-control get-oauth2-credential-provider \
9
+ --region "$AWS_REGION" \
10
+ --name "$CREDENTIAL_PROVIDER_NAME" >/dev/null 2>&1; then
11
+ aws bedrock-agentcore-control delete-oauth2-credential-provider \
12
+ --region "$AWS_REGION" \
13
+ --name "$CREDENTIAL_PROVIDER_NAME" >/dev/null
14
+ fi
15
+
16
+ # THINK-324: the workload identity is SHARED with the live Twenty connector
17
+ # (per-user grants live under it). Its deletion is owned by
18
+ # workload_identity_cleanup_owner / delete_workload_identity.sh — retiring
19
+ # the proof plane must never delete it. See main.tf for the proof-only-stage
20
+ # leak trade-off.
21
+ printf 'AgentCore Identity proof provider deleted (workload identity retained): workload=%s provider=%s\n' \
22
+ "$WORKLOAD_IDENTITY_NAME" "$CREDENTIAL_PROVIDER_NAME"
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${AWS_REGION:?AWS_REGION is required}"
5
+ : "${TWENTY_CREDENTIAL_PROVIDER_NAME:?TWENTY_CREDENTIAL_PROVIDER_NAME is required}"
6
+
7
+ if aws bedrock-agentcore-control get-oauth2-credential-provider \
8
+ --region "$AWS_REGION" \
9
+ --name "$TWENTY_CREDENTIAL_PROVIDER_NAME" >/dev/null 2>&1; then
10
+ aws bedrock-agentcore-control delete-oauth2-credential-provider \
11
+ --region "$AWS_REGION" \
12
+ --name "$TWENTY_CREDENTIAL_PROVIDER_NAME" >/dev/null
13
+ fi
14
+
15
+ printf 'AgentCore Twenty provider deleted: provider=%s\n' \
16
+ "$TWENTY_CREDENTIAL_PROVIDER_NAME"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # THINK-324 — final deletion of the SHARED workload identity, owned by the
5
+ # Twenty identity half (workload_identity_cleanup_owner). Runs only when the
6
+ # Twenty user-federation identity is itself being removed.
7
+
8
+ : "${AWS_REGION:?AWS_REGION is required}"
9
+ : "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
10
+
11
+ if aws bedrock-agentcore-control get-workload-identity \
12
+ --region "$AWS_REGION" \
13
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null 2>&1; then
14
+ aws bedrock-agentcore-control delete-workload-identity \
15
+ --region "$AWS_REGION" \
16
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null
17
+ fi
18
+
19
+ printf 'AgentCore workload identity deleted: %s\n' "$WORKLOAD_IDENTITY_NAME"
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # THINK-324 — Twenty-side workload-identity guarantee. Create-if-missing
5
+ # ONLY: when the identity already exists (created by the proof lifecycle or a
6
+ # prior run) this script must not touch it — reconcile_twenty_identity.sh
7
+ # owns the return-url allowlist and always runs afterwards.
8
+
9
+ : "${AWS_REGION:?AWS_REGION is required}"
10
+ : "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
11
+ : "${OAUTH_RETURN_URLS_JSON:?OAUTH_RETURN_URLS_JSON is required}"
12
+
13
+ jq -e 'type == "array" and length > 0 and all(.[]; type == "string" and length > 0)' \
14
+ <<<"$OAUTH_RETURN_URLS_JSON" >/dev/null
15
+
16
+ if aws bedrock-agentcore-control get-workload-identity \
17
+ --region "$AWS_REGION" \
18
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null 2>&1; then
19
+ printf 'AgentCore workload identity already present: %s\n' "$WORKLOAD_IDENTITY_NAME"
20
+ exit 0
21
+ fi
22
+
23
+ aws bedrock-agentcore-control create-workload-identity \
24
+ --region "$AWS_REGION" \
25
+ --name "$WORKLOAD_IDENTITY_NAME" \
26
+ --allowed-resource-oauth2-return-urls "$OAUTH_RETURN_URLS_JSON" \
27
+ --tags '{"purpose":"twenty-user-federation","managed-by":"terraform"}' >/dev/null
28
+
29
+ printf 'AgentCore workload identity created: %s\n' "$WORKLOAD_IDENTITY_NAME"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+ region="$(jq -r '.region // empty' <<<"$input")"
6
+ workload_name="$(jq -r '.workload_identity_name // empty' <<<"$input")"
7
+ provider_name="$(jq -r '.credential_provider_name // empty' <<<"$input")"
8
+ twenty_provider_name="$(jq -r '.twenty_credential_provider_name // empty' <<<"$input")"
9
+ # THINK-324: an empty provider name means that half is disabled (proof
10
+ # retired, or Twenty absent on a proof-only stage) — skip its reads and
11
+ # return empty fields instead of failing.
12
+ if [[ -z "$region" || -z "$workload_name" ]]; then
13
+ printf '%s\n' '{"error":"region and workload_identity_name are required"}' >&2
14
+ exit 1
15
+ fi
16
+
17
+ workload_json="$(aws bedrock-agentcore-control get-workload-identity \
18
+ --region "$region" --name "$workload_name" --output json)"
19
+
20
+ provider_json='{}'
21
+ if [[ -n "$provider_name" ]]; then
22
+ provider_json="$(aws bedrock-agentcore-control get-oauth2-credential-provider \
23
+ --region "$region" --name "$provider_name" --output json)"
24
+ fi
25
+
26
+ twenty_provider_json='{}'
27
+ if [[ -n "$twenty_provider_name" ]]; then
28
+ twenty_provider_json="$(aws bedrock-agentcore-control get-oauth2-credential-provider \
29
+ --region "$region" --name "$twenty_provider_name" --output json)"
30
+ fi
31
+
32
+ jq -n \
33
+ --arg proof_enabled "$provider_name" \
34
+ --arg twenty_enabled "$twenty_provider_name" \
35
+ --arg workload_identity_arn "$(jq -r '.workloadIdentityArn // empty' <<<"$workload_json")" \
36
+ --arg credential_provider_arn "$(jq -r '.credentialProviderArn // empty' <<<"$provider_json")" \
37
+ --arg credential_secret_arn "$(jq -r '
38
+ if (.clientSecretArn | type) == "object"
39
+ then .clientSecretArn.secretArn // empty
40
+ else .clientSecretArn // empty
41
+ end
42
+ ' <<<"$provider_json")" \
43
+ --arg twenty_credential_provider_arn "$(jq -r '.credentialProviderArn // empty' <<<"$twenty_provider_json")" \
44
+ --arg twenty_oauth_callback_url "$(jq -r '.callbackUrl // empty' <<<"$twenty_provider_json")" \
45
+ 'if ($workload_identity_arn == ""
46
+ or ($proof_enabled != "" and ($credential_provider_arn == "" or $credential_secret_arn == ""))
47
+ or ($twenty_enabled != "" and ($twenty_credential_provider_arn == "" or $twenty_oauth_callback_url == "")))
48
+ then error("AgentCore Identity readback omitted required ARNs")
49
+ else {
50
+ workload_identity_arn: $workload_identity_arn,
51
+ credential_provider_arn: $credential_provider_arn,
52
+ credential_secret_arn: $credential_secret_arn,
53
+ twenty_credential_provider_arn: $twenty_credential_provider_arn,
54
+ twenty_oauth_callback_url: $twenty_oauth_callback_url
55
+ } end'
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${AWS_REGION:?AWS_REGION is required}"
5
+ : "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
6
+ : "${CREDENTIAL_PROVIDER_NAME:?CREDENTIAL_PROVIDER_NAME is required}"
7
+ : "${OAUTH_ISSUER:?OAUTH_ISSUER is required}"
8
+ : "${OAUTH_CLIENT_ID:?OAUTH_CLIENT_ID is required}"
9
+ : "${OAUTH_CLIENT_SECRET:?OAUTH_CLIENT_SECRET is required}"
10
+ : "${OAUTH_RETURN_URL:?OAUTH_RETURN_URL is required}"
11
+
12
+ if aws bedrock-agentcore-control get-workload-identity \
13
+ --region "$AWS_REGION" \
14
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null 2>&1; then
15
+ aws bedrock-agentcore-control update-workload-identity \
16
+ --region "$AWS_REGION" \
17
+ --name "$WORKLOAD_IDENTITY_NAME" \
18
+ --allowed-resource-oauth2-return-urls "$OAUTH_RETURN_URL" >/dev/null
19
+ else
20
+ aws bedrock-agentcore-control create-workload-identity \
21
+ --region "$AWS_REGION" \
22
+ --name "$WORKLOAD_IDENTITY_NAME" \
23
+ --allowed-resource-oauth2-return-urls "$OAUTH_RETURN_URL" \
24
+ --tags '{"purpose":"think-316-proof","managed-by":"terraform"}' >/dev/null
25
+ fi
26
+
27
+ payload_file="$(mktemp)"
28
+ trap 'rm -f "$payload_file"' EXIT
29
+ jq -n \
30
+ --arg name "$CREDENTIAL_PROVIDER_NAME" \
31
+ --arg issuer "$OAUTH_ISSUER" \
32
+ --arg authorizationEndpoint "$OAUTH_ISSUER/authorize" \
33
+ --arg tokenEndpoint "$OAUTH_ISSUER/token" \
34
+ --arg clientId "$OAUTH_CLIENT_ID" \
35
+ --arg clientSecret "$OAUTH_CLIENT_SECRET" \
36
+ '{
37
+ name: $name,
38
+ credentialProviderVendor: "CustomOauth2",
39
+ oauth2ProviderConfigInput: {
40
+ customOauth2ProviderConfig: {
41
+ oauthDiscovery: {
42
+ authorizationServerMetadata: {
43
+ issuer: $issuer,
44
+ authorizationEndpoint: $authorizationEndpoint,
45
+ tokenEndpoint: $tokenEndpoint,
46
+ responseTypes: ["code"]
47
+ }
48
+ },
49
+ clientId: $clientId,
50
+ clientSecret: $clientSecret,
51
+ clientAuthenticationMethod: "CLIENT_SECRET_BASIC",
52
+ onBehalfOfTokenExchangeConfig: {
53
+ grantType: "TOKEN_EXCHANGE",
54
+ tokenExchangeGrantTypeConfig: {actorTokenContent: "NONE"}
55
+ }
56
+ }
57
+ }
58
+ }' >"$payload_file"
59
+
60
+ if aws bedrock-agentcore-control get-oauth2-credential-provider \
61
+ --region "$AWS_REGION" \
62
+ --name "$CREDENTIAL_PROVIDER_NAME" >/dev/null 2>&1; then
63
+ aws bedrock-agentcore-control update-oauth2-credential-provider \
64
+ --region "$AWS_REGION" \
65
+ --cli-input-json "file://$payload_file" >/dev/null
66
+ else
67
+ aws bedrock-agentcore-control create-oauth2-credential-provider \
68
+ --region "$AWS_REGION" \
69
+ --cli-input-json "file://$payload_file" >/dev/null
70
+ fi
71
+
72
+ printf 'AgentCore Identity proof resources reconciled: workload=%s provider=%s\n' \
73
+ "$WORKLOAD_IDENTITY_NAME" "$CREDENTIAL_PROVIDER_NAME"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${AWS_REGION:?AWS_REGION is required}"
5
+ : "${WORKLOAD_IDENTITY_NAME:?WORKLOAD_IDENTITY_NAME is required}"
6
+ : "${OAUTH_RETURN_URLS_JSON:?OAUTH_RETURN_URLS_JSON is required}"
7
+ : "${TWENTY_CREDENTIAL_PROVIDER_NAME:?TWENTY_CREDENTIAL_PROVIDER_NAME is required}"
8
+ : "${TWENTY_CLIENT_SECRET_ARN:?TWENTY_CLIENT_SECRET_ARN is required}"
9
+ : "${TWENTY_OAUTH_ISSUER:?TWENTY_OAUTH_ISSUER is required}"
10
+ : "${TWENTY_OAUTH_RESOURCE:?TWENTY_OAUTH_RESOURCE is required}"
11
+
12
+ jq -e 'type == "array" and length > 0 and all(.[]; type == "string" and length > 0)' \
13
+ <<<"$OAUTH_RETURN_URLS_JSON" >/dev/null
14
+
15
+ # The core lifecycle owns this workload. Extend its allowlist in place; never
16
+ # delete or recreate it while adding a downstream user-federation provider.
17
+ aws bedrock-agentcore-control update-workload-identity \
18
+ --region "$AWS_REGION" \
19
+ --name "$WORKLOAD_IDENTITY_NAME" \
20
+ --allowed-resource-oauth2-return-urls "$OAUTH_RETURN_URLS_JSON" >/dev/null
21
+
22
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ provider_response="$(bash "$script_dir/bootstrap_twenty_oauth_client.sh")"
24
+ callback_url="$(jq -r '.callbackUrl // empty' <<<"$provider_response")"
25
+ if [[ -z "$callback_url" || "$callback_url" == "None" ]]; then
26
+ printf '%s\n' 'AgentCore Identity did not return the Twenty callback URL' >&2
27
+ exit 1
28
+ fi
29
+
30
+ client_record="$(aws secretsmanager get-secret-value \
31
+ --region "$AWS_REGION" \
32
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
33
+ --query SecretString \
34
+ --output text)"
35
+ jq -e --arg callback "$callback_url" \
36
+ '.bootstrap_state == "ready" and
37
+ (.client_id | type == "string" and length > 0) and
38
+ (.client_secret | type == "string" and length > 0) and
39
+ (.redirect_uris | type == "array" and index($callback) != null) and
40
+ .token_endpoint_auth_method == "client_secret_post"' \
41
+ <<<"$client_record" >/dev/null
42
+
43
+ printf 'AgentCore Twenty provider reconciled: workload=%s provider=%s resource=%s callback=%s\n' \
44
+ "$WORKLOAD_IDENTITY_NAME" "$TWENTY_CREDENTIAL_PROVIDER_NAME" \
45
+ "$TWENTY_OAUTH_RESOURCE" "$callback_url"
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ BedrockAgentCoreControlClient,
5
+ CreateOauth2CredentialProviderCommand,
6
+ GetOauth2CredentialProviderCommand,
7
+ ResourceNotFoundException,
8
+ UpdateOauth2CredentialProviderCommand,
9
+ } from "@aws-sdk/client-bedrock-agentcore-control";
10
+
11
+ if (process.argv[2] === "--runtime-preflight") {
12
+ process.stdout.write(
13
+ JSON.stringify({
14
+ entrypoint: "reconcile_twenty_provider",
15
+ sdkImportReady:
16
+ typeof BedrockAgentCoreControlClient === "function" &&
17
+ typeof CreateOauth2CredentialProviderCommand === "function" &&
18
+ typeof GetOauth2CredentialProviderCommand === "function" &&
19
+ typeof UpdateOauth2CredentialProviderCommand === "function",
20
+ }),
21
+ );
22
+ process.exit(0);
23
+ }
24
+
25
+ const chunks = [];
26
+ for await (const chunk of process.stdin) chunks.push(chunk);
27
+ const input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
28
+
29
+ const client = new BedrockAgentCoreControlClient({ region: input.region });
30
+ const providerInput = {
31
+ name: input.name,
32
+ credentialProviderVendor: "CustomOauth2",
33
+ oauth2ProviderConfigInput: {
34
+ customOauth2ProviderConfig: {
35
+ oauthDiscovery: {
36
+ authorizationServerMetadata: {
37
+ issuer: input.issuer,
38
+ authorizationEndpoint: input.authorizationEndpoint,
39
+ tokenEndpoint: input.tokenEndpoint,
40
+ responseTypes: ["code"],
41
+ },
42
+ },
43
+ clientId: input.clientId,
44
+ clientSecretConfig: {
45
+ secretId: input.secretArn,
46
+ jsonKey: "client_secret",
47
+ },
48
+ clientSecretSource: "EXTERNAL",
49
+ clientAuthenticationMethod: "CLIENT_SECRET_POST",
50
+ },
51
+ },
52
+ };
53
+
54
+ let exists = false;
55
+ try {
56
+ await client.send(
57
+ new GetOauth2CredentialProviderCommand({ name: input.name }),
58
+ );
59
+ exists = true;
60
+ } catch (error) {
61
+ if (!(error instanceof ResourceNotFoundException)) throw error;
62
+ }
63
+
64
+ if (exists) {
65
+ await client.send(new UpdateOauth2CredentialProviderCommand(providerInput));
66
+ } else {
67
+ await client.send(new CreateOauth2CredentialProviderCommand(providerInput));
68
+ }
69
+
70
+ const sleep = (milliseconds) =>
71
+ new Promise((resolve) => setTimeout(resolve, milliseconds));
72
+
73
+ let response;
74
+ for (let attempt = 0; attempt < 30; attempt += 1) {
75
+ response = await client.send(
76
+ new GetOauth2CredentialProviderCommand({ name: input.name }),
77
+ );
78
+ const status = response.status;
79
+ const provider =
80
+ response.oauth2ProviderConfigOutput?.customOauth2ProviderConfig;
81
+ const secretArn =
82
+ typeof response.clientSecretArn === "string"
83
+ ? response.clientSecretArn
84
+ : response.clientSecretArn?.secretArn;
85
+ const ready =
86
+ response.callbackUrl &&
87
+ response.credentialProviderArn &&
88
+ provider?.clientId === input.clientId &&
89
+ secretArn === input.secretArn &&
90
+ response.clientSecretJsonKey === "client_secret" &&
91
+ response.clientSecretSource === "EXTERNAL" &&
92
+ status === "READY";
93
+ if (ready) break;
94
+ if (status?.endsWith("_FAILED")) {
95
+ throw new Error(`AgentCore OAuth provider ${input.name} entered ${status}`);
96
+ }
97
+ if (attempt === 29) {
98
+ throw new Error(
99
+ `AgentCore OAuth provider ${input.name} did not converge to the requested client and external secret`,
100
+ );
101
+ }
102
+ await sleep(2000);
103
+ }
104
+
105
+ const secretArn =
106
+ typeof response.clientSecretArn === "string"
107
+ ? response.clientSecretArn
108
+ : response.clientSecretArn?.secretArn;
109
+ const provider =
110
+ response.oauth2ProviderConfigOutput?.customOauth2ProviderConfig;
111
+
112
+ process.stdout.write(
113
+ JSON.stringify({
114
+ name: response.name,
115
+ callbackUrl: response.callbackUrl,
116
+ credentialProviderArn: response.credentialProviderArn,
117
+ clientId: provider?.clientId,
118
+ clientSecretArn: secretArn,
119
+ clientSecretJsonKey: response.clientSecretJsonKey,
120
+ clientSecretSource: response.clientSecretSource,
121
+ status: response.status,
122
+ }),
123
+ );
@@ -0,0 +1,15 @@
1
+ terraform {
2
+ required_version = "= 1.8.5"
3
+ }
4
+
5
+ removed {
6
+ from = module.thinkwork.module.agentcore_proof_identity.terraform_data.twenty_identity_lifecycle
7
+
8
+ lifecycle {
9
+ destroy = false
10
+ }
11
+ }
12
+
13
+ module "thinkwork" {
14
+ source = "./thinkwork"
15
+ }
@@ -0,0 +1,7 @@
1
+ terraform {
2
+ required_version = "= 1.8.5"
3
+ }
4
+
5
+ module "thinkwork" {
6
+ source = "./thinkwork"
7
+ }
@@ -0,0 +1,8 @@
1
+ resource "terraform_data" "twenty_identity_reconciliation" {
2
+ count = 1
3
+ input = "new-idempotent-reconciliation-marker"
4
+
5
+ provisioner "local-exec" {
6
+ command = "printf '%s\\n' reconciled > '${path.root}/reconciled.log'"
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ resource "terraform_data" "twenty_identity_lifecycle" {
2
+ count = 1
3
+ input = "legacy-state-only-marker"
4
+
5
+ provisioner "local-exec" {
6
+ when = destroy
7
+ command = "printf '%s\\n' destroyed > '${path.root}/destroyed.log'"
8
+ }
9
+ }