thinkwork-cli 0.13.0 → 0.13.2

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 (178) hide show
  1. package/dist/{api-client-JUBCQQDF.js → api-client-JYZXYQJR.js} +3 -1
  2. package/dist/bowser-6Z6JFAIR.js +2822 -0
  3. package/dist/chunk-DV3MRBR5.js +43 -0
  4. package/dist/chunk-DVPBI5HM.js +1728 -0
  5. package/dist/chunk-EI5ZX7CN.js +522 -0
  6. package/dist/chunk-EUJ7X6YD.js +126 -0
  7. package/dist/chunk-GG5WKBOX.js +436 -0
  8. package/dist/chunk-I64EZSU5.js +358 -0
  9. package/dist/chunk-ILZKG5WV.js +6151 -0
  10. package/dist/chunk-KMIJ5KGT.js +128 -0
  11. package/dist/chunk-OCPHU34C.js +996 -0
  12. package/dist/chunk-QOERT44A.js +3286 -0
  13. package/dist/chunk-XYM7PE2A.js +2264 -0
  14. package/dist/{chunk-STY44FQO.js → chunk-Z2F6RJFR.js} +12 -18
  15. package/dist/cli.js +72564 -18884
  16. package/dist/dist-es-CRV46D5B.js +315 -0
  17. package/dist/dist-es-DCY4IC3Y.js +2000 -0
  18. package/dist/dist-es-ENBUC3JP.js +14726 -0
  19. package/dist/dist-es-G54LBMRN.js +48 -0
  20. package/dist/dist-es-G7PTG75M.js +176 -0
  21. package/dist/dist-es-GKDBE7E6.js +88 -0
  22. package/dist/dist-es-HY3535OI.js +70 -0
  23. package/dist/dist-es-KL7Y5NB2.js +377 -0
  24. package/dist/dist-es-LDE437AM.js +170 -0
  25. package/dist/dist-es-P26FWFLT.js +485 -0
  26. package/dist/dist-es-Q57SZXZ7.js +70 -0
  27. package/dist/dist-es-R7FGL2IB.js +88 -0
  28. package/dist/dist-es-USORBXZ5.js +318 -0
  29. package/dist/dist-es-ZKC2QKD2.js +377 -0
  30. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
  31. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
  32. package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
  33. package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
  34. package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
  35. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
  36. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
  37. package/dist/drizzle/0227_analyst_reader_role.sql +106 -38
  38. package/dist/drizzle/0230_analyst_rls.sql +192 -66
  39. package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
  40. package/dist/drizzle/0232_message_source_event_id.sql +9 -0
  41. package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
  42. package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
  43. package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
  44. package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
  45. package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
  46. package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
  47. package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
  48. package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
  49. package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
  50. package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
  51. package/dist/drizzle/0242_search_queries.sql +28 -0
  52. package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
  53. package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
  54. package/dist/drizzle/0245_wiki_page_render.sql +21 -0
  55. package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
  56. package/dist/drizzle/0247_capability_runtime.sql +402 -0
  57. package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
  58. package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
  59. package/dist/drizzle/0250_kg_schema_extraction.sql +576 -0
  60. package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
  61. package/dist/drizzle/0251_brain_stragglers.sql +263 -0
  62. package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
  63. package/dist/drizzle/0253_drop_brain_substrate.sql +93 -0
  64. package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
  65. package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
  66. package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
  67. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  68. package/dist/drizzle/0257_capability_approvals.sql +53 -0
  69. package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
  70. package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
  71. package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
  72. package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
  73. package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
  74. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  75. package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
  76. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  77. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  78. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  79. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
  80. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  81. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  82. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  83. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  84. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  85. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  86. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  87. package/dist/event-streams-7JNLJEEL.js +894 -0
  88. package/dist/event-streams-KDF5RWR6.js +1515 -0
  89. package/dist/loadSso-DJVB7BXE.js +576 -0
  90. package/dist/loadSso-JAMNA3WY.js +583 -0
  91. package/dist/plugins/catalog/package.json +1 -2
  92. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  93. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
  94. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  95. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  96. package/dist/plugins/catalog/src/contracts.ts +2 -179
  97. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  98. package/dist/signin-UGDWTGS4.js +821 -0
  99. package/dist/sso-oidc-Q4VXANXY.js +837 -0
  100. package/dist/sts-CWLSE3GH.js +1241 -0
  101. package/dist/sts-DNZLNVOV.js +1245 -0
  102. package/dist/terraform/examples/greenfield/main.tf +247 -50
  103. package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
  104. package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
  105. package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
  106. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
  107. package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
  108. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
  109. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
  110. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
  111. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
  112. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
  113. package/dist/terraform/modules/app/agentcore-harness/main.tf +330 -0
  114. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +72 -0
  115. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
  116. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
  117. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
  118. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
  119. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
  120. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
  121. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
  122. package/dist/terraform/modules/app/agentcore-harness/variables.tf +96 -0
  123. package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
  124. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  125. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
  126. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
  127. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  128. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
  129. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  130. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
  131. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
  132. package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
  133. package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
  134. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  135. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  136. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  137. package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
  138. package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
  139. package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
  140. package/dist/terraform/modules/app/deployment-control-plane/runner.py +1011 -31
  141. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +655 -2
  142. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
  143. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
  144. package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
  145. package/dist/terraform/modules/app/lambda-api/handlers.tf +606 -37
  146. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +192 -21
  147. package/dist/terraform/modules/app/lambda-api/main.tf +35 -0
  148. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
  149. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
  150. package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
  151. package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
  152. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  153. package/dist/terraform/modules/app/lambda-api/variables.tf +188 -3
  154. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
  155. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
  156. package/dist/terraform/modules/foundation/cognito/main.tf +319 -73
  157. package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
  158. package/dist/terraform/modules/foundation/cognito/variables.tf +124 -2
  159. package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
  160. package/dist/terraform/modules/thinkwork/main.tf +396 -26
  161. package/dist/terraform/modules/thinkwork/outputs.tf +188 -5
  162. package/dist/terraform/modules/thinkwork/variables.tf +154 -5
  163. package/dist/terraform/schema.graphql +69 -36
  164. package/dist/workspace-defaults/files/TOOLS.md +39 -0
  165. package/dist/workspace-defaults/files/USER.md +1 -0
  166. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
  167. package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
  168. package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
  169. package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
  170. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
  171. package/package.json +6 -3
  172. package/dist/plugins/workos-auth/package.json +0 -22
  173. package/dist/plugins/workos-auth/src/index.ts +0 -30
  174. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  175. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  176. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  177. package/dist/plugins/workos-auth/tsconfig.json +0 -8
  178. /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
@@ -0,0 +1,193 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const GOVERNED_TOOL_TYPES = new Map([
4
+ ["thinkwork_gateway", "agentcore_gateway"],
5
+ ["browser", "agentcore_browser"],
6
+ ["emit_document", "inline_function"],
7
+ ["goal_complete", "inline_function"],
8
+ ["submit_skill_draft", "inline_function"],
9
+ ]);
10
+
11
+ export function canonicalJson(value) {
12
+ if (Array.isArray(value)) {
13
+ return `[${value.map(canonicalJson).join(",")}]`;
14
+ }
15
+ if (value && typeof value === "object") {
16
+ return `{${Object.keys(value)
17
+ .sort()
18
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
19
+ .join(",")}}`;
20
+ }
21
+ return JSON.stringify(value);
22
+ }
23
+
24
+ export function fingerprintGovernedHarnessTools(value) {
25
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
26
+ }
27
+
28
+ export function selectHarnessEndpointsForRetention(
29
+ endpoints,
30
+ { activeEndpointName, endpointPrefix, legacyEndpointName },
31
+ ) {
32
+ const managed = endpoints.filter(
33
+ (endpoint) =>
34
+ endpoint.endpointName === legacyEndpointName ||
35
+ endpoint.endpointName?.startsWith(endpointPrefix),
36
+ );
37
+ const rollbackEndpointName = managed
38
+ .filter((endpoint) => endpoint.endpointName !== activeEndpointName)
39
+ .sort((left, right) =>
40
+ String(right.liveVersion ?? right.targetVersion ?? "").localeCompare(
41
+ String(left.liveVersion ?? left.targetVersion ?? ""),
42
+ undefined,
43
+ { numeric: true },
44
+ ),
45
+ )[0]?.endpointName;
46
+ const retainedEndpointNames = new Set(
47
+ [activeEndpointName, rollbackEndpointName].filter(
48
+ (value) => typeof value === "string",
49
+ ),
50
+ );
51
+ return {
52
+ rollbackEndpointName,
53
+ retainedEndpointNames: [...retainedEndpointNames],
54
+ deletedEndpointNames: managed
55
+ .map((endpoint) => endpoint.endpointName)
56
+ .filter(
57
+ (endpointName) =>
58
+ endpointName && !retainedEndpointNames.has(endpointName),
59
+ ),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * One canonical producer contract for both Harness reconciliation and
65
+ * immutable-version readback. Keeping the complete inline schemas here makes
66
+ * schema drift a deployment failure instead of re-attesting it into SSM.
67
+ */
68
+ export function buildGovernedHarnessTools({ gatewayArn, providerArn }) {
69
+ return [
70
+ {
71
+ type: "agentcore_gateway",
72
+ name: "thinkwork_gateway",
73
+ config: {
74
+ agentCoreGateway: {
75
+ gatewayArn,
76
+ outboundAuth: {
77
+ oauth: {
78
+ providerArn,
79
+ scopes: ["gateway:invoke"],
80
+ customParameters: {
81
+ subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
82
+ },
83
+ grantType: "TOKEN_EXCHANGE",
84
+ },
85
+ },
86
+ },
87
+ },
88
+ },
89
+ {
90
+ type: "agentcore_browser",
91
+ name: "browser",
92
+ config: { agentCoreBrowser: {} },
93
+ },
94
+ {
95
+ type: "inline_function",
96
+ name: "emit_document",
97
+ config: {
98
+ inlineFunction: {
99
+ description:
100
+ "Emit a durable ThinkWork HTML plate from markdown. Use genre report, plan, brief, or ideation. The platform compiles, validates, persists, and attaches it to the thread. On rejection, fix every diagnostic and call again with document_id when supplied.",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ genre: {
105
+ type: "string",
106
+ enum: ["report", "plan", "brief", "ideation"],
107
+ },
108
+ title: { type: "string" },
109
+ abstract: { type: "string" },
110
+ digest_markdown: {
111
+ type: "string",
112
+ description: "Complete markdown document body.",
113
+ },
114
+ status: { type: "string", enum: ["draft", "final"] },
115
+ document_id: { type: "string" },
116
+ space_id: { type: "string" },
117
+ },
118
+ required: ["genre", "title", "abstract", "digest_markdown"],
119
+ additionalProperties: false,
120
+ },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ type: "inline_function",
126
+ name: "goal_complete",
127
+ config: {
128
+ inlineFunction: {
129
+ description:
130
+ "Mark the current ThinkWork-managed Goal mode objective complete. Call exactly once only after the objective is fully satisfied.",
131
+ inputSchema: {
132
+ type: "object",
133
+ properties: {
134
+ summary: {
135
+ type: "string",
136
+ description: "Concise user-facing completion summary.",
137
+ },
138
+ completion_notes: {
139
+ type: "string",
140
+ description: "Optional bounded completion details.",
141
+ },
142
+ verification_notes: {
143
+ type: "array",
144
+ items: { type: "string" },
145
+ maxItems: 5,
146
+ description: "Concrete checks proving completion.",
147
+ },
148
+ },
149
+ required: ["summary"],
150
+ additionalProperties: false,
151
+ },
152
+ },
153
+ },
154
+ },
155
+ {
156
+ type: "inline_function",
157
+ name: "submit_skill_draft",
158
+ config: {
159
+ inlineFunction: {
160
+ description:
161
+ "Submit a complete Agent Skills draft to ThinkWork's governed review and trust queue. Available only during a trusted /skill-creator turn; this does not publish the skill.",
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ skill_markdown: {
166
+ type: "string",
167
+ description:
168
+ "Complete SKILL.md including valid name and description frontmatter.",
169
+ },
170
+ supporting_files: {
171
+ type: "array",
172
+ maxItems: 20,
173
+ items: {
174
+ type: "object",
175
+ properties: {
176
+ path: { type: "string" },
177
+ content: { type: "string" },
178
+ },
179
+ required: ["path", "content"],
180
+ additionalProperties: false,
181
+ },
182
+ description:
183
+ "Optional bounded text references, scripts, or assets required by the skill.",
184
+ },
185
+ },
186
+ required: ["skill_markdown"],
187
+ additionalProperties: false,
188
+ },
189
+ },
190
+ },
191
+ },
192
+ ];
193
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec node "$script_dir/harness-lifecycle.mjs" prune
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec node "$script_dir/harness-lifecycle.mjs" read
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec node "$script_dir/harness-lifecycle.mjs" reconcile
@@ -0,0 +1,96 @@
1
+ variable "enabled" {
2
+ description = "Create the AgentCore Harness execution IAM surface. The role is independent of whether a managed tenant/profile runtime is enabled."
3
+ type = bool
4
+ default = true
5
+ }
6
+
7
+ variable "stage" {
8
+ description = "Deployment stage (dev, prod, etc.)"
9
+ type = string
10
+ }
11
+
12
+ variable "region" {
13
+ description = "AWS region"
14
+ type = string
15
+ }
16
+
17
+ variable "account_id" {
18
+ description = "AWS account ID"
19
+ type = string
20
+ }
21
+
22
+ variable "bucket_name" {
23
+ description = "Stage workspace S3 bucket name — Harness microVMs read tenant skill sources under tenants/*"
24
+ type = string
25
+ }
26
+
27
+ variable "managed_runtime_enabled" {
28
+ description = "Reconcile the managed tenant/profile AgentCore Harness runtime. The base IAM role may remain enabled while this is false."
29
+ type = bool
30
+ default = false
31
+ }
32
+
33
+ variable "tenant_slug" {
34
+ description = "Tenant slug whose skill prefix and managed Harness profile are admitted."
35
+ type = string
36
+ default = ""
37
+
38
+ validation {
39
+ condition = var.tenant_slug == "" || can(regex("^[a-z0-9][a-z0-9-]{0,47}$", var.tenant_slug))
40
+ error_message = "tenant_slug must be a lowercase slug up to 48 characters."
41
+ }
42
+ }
43
+
44
+ variable "trust_profile" {
45
+ description = "Stable execution/trust profile. Ordinary agent/user/tool differences do not create more Harnesses."
46
+ type = string
47
+ default = "default"
48
+
49
+ validation {
50
+ condition = can(regex("^[a-z][a-z0-9-]{0,31}$", var.trust_profile))
51
+ error_message = "trust_profile must be a lowercase slug up to 32 characters."
52
+ }
53
+ }
54
+
55
+ variable "discovery_url" {
56
+ description = "OIDC discovery URL used by the Harness CUSTOM_JWT authorizer."
57
+ type = string
58
+ default = ""
59
+ }
60
+
61
+ variable "harness_audience" {
62
+ description = "Exact audience accepted by the Harness CUSTOM_JWT authorizer."
63
+ type = string
64
+ default = ""
65
+ }
66
+
67
+ variable "gateway_arn" {
68
+ description = "Exact selected AgentCore Gateway ARN exposed as the native Harness tool."
69
+ type = string
70
+ default = ""
71
+ }
72
+
73
+ variable "oauth_credential_provider_arn" {
74
+ description = "Identity provider ARN used for the Harness on-behalf-of Gateway token exchange."
75
+ type = string
76
+ default = ""
77
+ }
78
+
79
+ variable "oauth_credential_secret_arn" {
80
+ description = "AgentCore-managed Secrets Manager ARN backing the selected OAuth provider."
81
+ type = string
82
+ default = ""
83
+ sensitive = true
84
+ }
85
+
86
+ variable "oauth_return_url" {
87
+ description = "Application callback that verifies the active user and completes AgentCore Identity OAuth session binding."
88
+ type = string
89
+ default = ""
90
+ }
91
+
92
+ variable "model_id" {
93
+ description = "Safe baseline Bedrock model for the managed Harness profile. Per-turn trusted projection may narrow/replace it."
94
+ type = string
95
+ default = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
96
+ }
@@ -0,0 +1,173 @@
1
+ ################################################################################
2
+
3
+ terraform {
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = ">= 5.0"
8
+ }
9
+ external = {
10
+ source = "hashicorp/external"
11
+ version = ">= 2.3.0"
12
+ }
13
+ }
14
+ }
15
+ # THINK-316 U1 — AgentCore Identity proof substrate
16
+ #
17
+ # The AWS provider does not yet model Workload Identity or OAuth2 credential
18
+ # providers. A terraform_data lifecycle wrapper reconciles them with the
19
+ # current AWS CLI and owns destroy cleanup. The client secret is passed only in
20
+ # the local-exec environment and never printed by the scripts.
21
+ ################################################################################
22
+
23
+ locals {
24
+ workload_identity_name = "thinkwork-${var.stage}-multiplayer-proof"
25
+ credential_provider_name = "thinkwork-${var.stage}-proof-oauth"
26
+ twenty_credential_provider_name = "thinkwork-${var.stage}-twenty-crm"
27
+ oauth_issuer = trimsuffix(var.oauth_issuer, "/")
28
+ oauth_return_url = "${local.oauth_issuer}/complete"
29
+ allowed_oauth_return_urls = distinct(concat([local.oauth_return_url], var.user_federation_return_urls))
30
+ configuration_hash = nonsensitive(sha256(jsonencode({
31
+ issuer = local.oauth_issuer
32
+ client_id = var.oauth_client_id
33
+ secret_digest = sha256(var.oauth_client_secret)
34
+ return_url = local.oauth_return_url
35
+ })))
36
+ twenty_configuration_hash = nonsensitive(sha256(jsonencode({
37
+ allowed_oauth_return_urls = local.allowed_oauth_return_urls
38
+ twenty_credential_provider_name = local.twenty_credential_provider_name
39
+ twenty_oauth_issuer = var.twenty_oauth_issuer
40
+ twenty_oauth_resource = var.twenty_oauth_resource
41
+ twenty_client_secret_arn = try(aws_secretsmanager_secret.twenty_oauth_client[0].arn, "")
42
+ })))
43
+ }
44
+
45
+ # The DCR client secret is written by the reconciler, not Terraform, so its
46
+ # value never enters Terraform state. AgentCore Identity reads the JSON key
47
+ # directly and remains the sole custodian of each user's downstream grant.
48
+ resource "aws_secretsmanager_secret" "twenty_oauth_client" {
49
+ count = var.enabled ? 1 : 0
50
+
51
+ name = "thinkwork/${var.stage}/agentcore-identity/twenty-crm-oauth-client"
52
+ description = "Confidential Twenty OAuth client used only by AgentCore Identity Token Vault"
53
+ recovery_window_in_days = 7
54
+
55
+ tags = {
56
+ "managed-by" = "terraform"
57
+ "thinkwork:stage" = var.stage
58
+ "thinkwork:purpose" = "agentcore-identity-user-federation"
59
+ }
60
+ }
61
+
62
+ resource "aws_secretsmanager_secret_policy" "twenty_oauth_client" {
63
+ count = var.enabled ? 1 : 0
64
+ secret_arn = aws_secretsmanager_secret.twenty_oauth_client[0].arn
65
+ policy = jsonencode({
66
+ Version = "2012-10-17"
67
+ Statement = [{
68
+ Sid = "AgentCoreIdentityRead"
69
+ Effect = "Allow"
70
+ Principal = { Service = "bedrock-agentcore.amazonaws.com" }
71
+ Action = "secretsmanager:GetSecretValue"
72
+ Resource = aws_secretsmanager_secret.twenty_oauth_client[0].arn
73
+ Condition = {
74
+ StringEquals = { "aws:SourceAccount" = var.account_id }
75
+ }
76
+ }]
77
+ })
78
+ }
79
+
80
+ resource "terraform_data" "identity_lifecycle" {
81
+ count = var.enabled ? 1 : 0
82
+
83
+ input = {
84
+ region = var.region
85
+ workload_identity_name = local.workload_identity_name
86
+ credential_provider_name = local.credential_provider_name
87
+ }
88
+ triggers_replace = [local.configuration_hash]
89
+
90
+ provisioner "local-exec" {
91
+ command = "bash ${path.module}/scripts/reconcile_identity.sh"
92
+ environment = {
93
+ AWS_REGION = var.region
94
+ WORKLOAD_IDENTITY_NAME = local.workload_identity_name
95
+ CREDENTIAL_PROVIDER_NAME = local.credential_provider_name
96
+ OAUTH_ISSUER = local.oauth_issuer
97
+ OAUTH_CLIENT_ID = var.oauth_client_id
98
+ OAUTH_CLIENT_SECRET = var.oauth_client_secret
99
+ OAUTH_RETURN_URL = local.oauth_return_url
100
+ }
101
+ }
102
+
103
+ provisioner "local-exec" {
104
+ when = destroy
105
+ command = "bash ${path.module}/scripts/delete_identity.sh"
106
+ environment = {
107
+ AWS_REGION = self.input.region
108
+ WORKLOAD_IDENTITY_NAME = self.input.workload_identity_name
109
+ CREDENTIAL_PROVIDER_NAME = self.input.credential_provider_name
110
+ }
111
+ }
112
+ }
113
+
114
+ # Additive user-federation lifecycle. It may update the workload's callback
115
+ # allowlist in place, but it never owns or deletes the workload/Gateway provider.
116
+ # This keeps the proven exact-user admission path live while Twenty is added,
117
+ # rotated, or removed independently.
118
+ resource "terraform_data" "twenty_identity_lifecycle" {
119
+ count = var.enabled ? 1 : 0
120
+
121
+ input = {
122
+ region = var.region
123
+ workload_identity_name = local.workload_identity_name
124
+ twenty_credential_provider_name = local.twenty_credential_provider_name
125
+ }
126
+ triggers_replace = [local.twenty_configuration_hash]
127
+
128
+ provisioner "local-exec" {
129
+ command = "bash ${path.module}/scripts/reconcile_twenty_identity.sh"
130
+ environment = {
131
+ AWS_REGION = var.region
132
+ WORKLOAD_IDENTITY_NAME = local.workload_identity_name
133
+ OAUTH_RETURN_URLS_JSON = jsonencode(local.allowed_oauth_return_urls)
134
+ TWENTY_CREDENTIAL_PROVIDER_NAME = local.twenty_credential_provider_name
135
+ TWENTY_CLIENT_SECRET_ARN = aws_secretsmanager_secret.twenty_oauth_client[0].arn
136
+ TWENTY_OAUTH_ISSUER = trimsuffix(var.twenty_oauth_issuer, "/")
137
+ TWENTY_OAUTH_RESOURCE = var.twenty_oauth_resource
138
+ }
139
+ }
140
+
141
+ provisioner "local-exec" {
142
+ when = destroy
143
+ command = "bash ${path.module}/scripts/delete_twenty_identity.sh"
144
+ environment = {
145
+ AWS_REGION = self.input.region
146
+ TWENTY_CREDENTIAL_PROVIDER_NAME = self.input.twenty_credential_provider_name
147
+ }
148
+ }
149
+
150
+ depends_on = [
151
+ terraform_data.identity_lifecycle,
152
+ aws_secretsmanager_secret_policy.twenty_oauth_client,
153
+ ]
154
+ }
155
+
156
+ # Read back the service-generated ARNs only after apply-time reconciliation.
157
+ # Gateway IAM can therefore scope vault and secret access exactly rather than
158
+ # granting a token-vault or Secrets Manager wildcard.
159
+ data "external" "identity_state" {
160
+ count = var.enabled ? 1 : 0
161
+ depends_on = [
162
+ terraform_data.identity_lifecycle,
163
+ terraform_data.twenty_identity_lifecycle,
164
+ ]
165
+ program = ["bash", "${path.module}/scripts/read_identity.sh"]
166
+
167
+ query = {
168
+ region = var.region
169
+ workload_identity_name = local.workload_identity_name
170
+ credential_provider_name = local.credential_provider_name
171
+ twenty_credential_provider_name = local.twenty_credential_provider_name
172
+ }
173
+ }
@@ -0,0 +1,51 @@
1
+ output "workload_identity_name" {
2
+ description = "Name used for GetWorkloadAccessTokenForJWT. Empty when disabled."
3
+ value = var.enabled ? local.workload_identity_name : ""
4
+ }
5
+
6
+ output "credential_provider_name" {
7
+ description = "AgentCore Identity OAuth2 credential-provider name. Empty when disabled."
8
+ value = var.enabled ? local.credential_provider_name : ""
9
+ }
10
+
11
+ output "oauth_return_url" {
12
+ description = "Allowed user-federation return URL. Empty when disabled."
13
+ value = var.enabled ? local.oauth_return_url : ""
14
+ }
15
+
16
+ output "twenty_credential_provider_name" {
17
+ description = "AgentCore Identity provider name for per-user Twenty grants."
18
+ value = var.enabled ? local.twenty_credential_provider_name : ""
19
+ }
20
+
21
+ output "twenty_credential_provider_arn" {
22
+ description = "AgentCore Identity provider ARN for per-user Twenty grants."
23
+ value = var.enabled ? data.external.identity_state[0].result.twenty_credential_provider_arn : ""
24
+ }
25
+
26
+ output "twenty_oauth_callback_url" {
27
+ description = "Service-generated callback URL registered with Twenty OAuth."
28
+ value = var.enabled ? data.external.identity_state[0].result.twenty_oauth_callback_url : ""
29
+ }
30
+
31
+ output "twenty_client_secret_arn" {
32
+ description = "External Secrets Manager client record consumed by AgentCore Identity."
33
+ value = var.enabled ? aws_secretsmanager_secret.twenty_oauth_client[0].arn : ""
34
+ sensitive = true
35
+ }
36
+
37
+ output "workload_identity_arn" {
38
+ description = "Service-generated workload identity ARN. Empty when disabled."
39
+ value = var.enabled ? data.external.identity_state[0].result.workload_identity_arn : ""
40
+ }
41
+
42
+ output "credential_provider_arn" {
43
+ description = "Service-generated OAuth2 credential-provider ARN. Empty when disabled."
44
+ value = var.enabled ? data.external.identity_state[0].result.credential_provider_arn : ""
45
+ }
46
+
47
+ output "credential_secret_arn" {
48
+ description = "AgentCore-managed Secrets Manager ARN backing the provider. Empty when disabled."
49
+ value = var.enabled ? data.external.identity_state[0].result.credential_secret_arn : ""
50
+ sensitive = true
51
+ }
@@ -0,0 +1,122 @@
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_ADMIN_TOKEN_SECRET_ARN:?TWENTY_ADMIN_TOKEN_SECRET_ARN is required}"
8
+
9
+ callback_url="${TWENTY_CALLBACK_URL:-https://bedrock-agentcore.${AWS_REGION}.amazonaws.com/identities/oauth2/callback}"
10
+
11
+ if existing_secret="$(aws secretsmanager get-secret-value \
12
+ --region "$AWS_REGION" \
13
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
14
+ --query SecretString \
15
+ --output text 2>/dev/null)" &&
16
+ jq -e '.client_id | type == "string" and length > 0' <<<"$existing_secret" >/dev/null &&
17
+ jq -e '.client_secret | type == "string" and length > 0' <<<"$existing_secret" >/dev/null; then
18
+ existing_callback="$(jq -r '.redirect_uris[0] // empty' <<<"$existing_secret")"
19
+ if [[ "$existing_callback" == "$callback_url" ]]; then
20
+ printf 'Twenty confidential OAuth client already bootstrapped: secret=%s\n' \
21
+ "$TWENTY_CLIENT_SECRET_ARN"
22
+ exit 0
23
+ fi
24
+ fi
25
+
26
+ # Twenty's RFC 7591 endpoint intentionally downgrades all dynamic clients to
27
+ # public clients. Its authenticated metadata API is the supported path for a
28
+ # confidential application registration: Twenty generates and bcrypt-hashes
29
+ # the secret server-side and returns the plaintext exactly once.
30
+ admin_record="$(aws secretsmanager get-secret-value \
31
+ --region "$AWS_REGION" \
32
+ --secret-id "$TWENTY_ADMIN_TOKEN_SECRET_ARN" \
33
+ --query SecretString \
34
+ --output text)"
35
+ admin_token="$(jq -r '.access_token // empty' <<<"$admin_record")"
36
+ if [[ -z "$admin_token" ]]; then
37
+ printf '%s\n' 'Twenty administrator token secret has no access_token' >&2
38
+ exit 1
39
+ fi
40
+
41
+ if [[ -n "${existing_secret:-}" ]] &&
42
+ jq -e '.registration_id | type == "string" and length > 0' \
43
+ <<<"$existing_secret" >/dev/null; then
44
+ registration_id="$(jq -r '.registration_id' <<<"$existing_secret")"
45
+ update_payload="$(jq -n \
46
+ --arg id "$registration_id" \
47
+ --arg callback "$callback_url" \
48
+ '{
49
+ query: "mutation UpdateAgentCoreRegistration($input: UpdateApplicationRegistrationInput!) { updateApplicationRegistration(input: $input) { id oAuthRedirectUris oAuthScopes } }",
50
+ variables: {
51
+ input: {
52
+ id: $id,
53
+ update: {oAuthRedirectUris: [$callback]}
54
+ }
55
+ }
56
+ }')"
57
+ update_response="$(curl --silent --show-error --fail-with-body \
58
+ -H "authorization: Bearer $admin_token" \
59
+ -H 'content-type: application/json' \
60
+ --data-binary "$update_payload" \
61
+ "${TWENTY_OAUTH_ISSUER%/}/metadata")"
62
+ if jq -e '.errors | type == "array" and length > 0' <<<"$update_response" >/dev/null; then
63
+ jq '{errors: [.errors[] | {message, extensions: {code: .extensions.code}}]}' \
64
+ <<<"$update_response" >&2
65
+ printf '%s\n' 'Twenty OAuth callback update failed' >&2
66
+ exit 1
67
+ fi
68
+ updated_secret="$(jq --arg callback "$callback_url" \
69
+ '.redirect_uris = [$callback]' <<<"$existing_secret")"
70
+ printf '%s' "$updated_secret" | aws secretsmanager put-secret-value \
71
+ --region "$AWS_REGION" \
72
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
73
+ --secret-string file:///dev/stdin >/dev/null
74
+ printf 'Twenty confidential OAuth client callback updated: secret=%s callback=%s\n' \
75
+ "$TWENTY_CLIENT_SECRET_ARN" "$callback_url"
76
+ exit 0
77
+ fi
78
+
79
+ graphql_payload="$(jq -n \
80
+ --arg callback "$callback_url" \
81
+ '{
82
+ query: "mutation CreateAgentCoreRegistration($input: CreateApplicationRegistrationInput!) { createApplicationRegistration(input: $input) { applicationRegistration { id universalIdentifier oAuthClientId oAuthRedirectUris oAuthScopes } clientSecret } }",
83
+ variables: {
84
+ input: {
85
+ name: "ThinkWork AgentCore Identity Twenty CRM",
86
+ oAuthRedirectUris: [$callback],
87
+ oAuthScopes: ["api", "profile"]
88
+ }
89
+ }
90
+ }')"
91
+ graphql_response="$(curl --silent --show-error --fail-with-body \
92
+ -H "authorization: Bearer $admin_token" \
93
+ -H 'content-type: application/json' \
94
+ --data-binary "$graphql_payload" \
95
+ "${TWENTY_OAUTH_ISSUER%/}/metadata")"
96
+
97
+ if jq -e '.errors | type == "array" and length > 0' <<<"$graphql_response" >/dev/null; then
98
+ jq '{errors: [.errors[] | {message, extensions: {code: .extensions.code}}]}' \
99
+ <<<"$graphql_response" >&2
100
+ printf '%s\n' 'Twenty confidential OAuth client bootstrap failed' >&2
101
+ exit 1
102
+ fi
103
+
104
+ client_record="$(jq -e '{
105
+ client_id: .data.createApplicationRegistration.applicationRegistration.oAuthClientId,
106
+ client_secret: .data.createApplicationRegistration.clientSecret,
107
+ registration_id: .data.createApplicationRegistration.applicationRegistration.id,
108
+ universal_identifier: .data.createApplicationRegistration.applicationRegistration.universalIdentifier,
109
+ redirect_uris: .data.createApplicationRegistration.applicationRegistration.oAuthRedirectUris,
110
+ scopes: .data.createApplicationRegistration.applicationRegistration.oAuthScopes
111
+ } | select(
112
+ (.client_id | type == "string" and length > 0) and
113
+ (.client_secret | type == "string" and length > 0)
114
+ )' <<<"$graphql_response")"
115
+
116
+ printf '%s' "$client_record" | aws secretsmanager put-secret-value \
117
+ --region "$AWS_REGION" \
118
+ --secret-id "$TWENTY_CLIENT_SECRET_ARN" \
119
+ --secret-string file:///dev/stdin >/dev/null
120
+
121
+ printf 'Twenty confidential OAuth client bootstrapped: secret=%s callback=%s\n' \
122
+ "$TWENTY_CLIENT_SECRET_ARN" "$callback_url"
@@ -0,0 +1,25 @@
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
+ if aws bedrock-agentcore-control get-workload-identity \
17
+ --region "$AWS_REGION" \
18
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null 2>&1; then
19
+ aws bedrock-agentcore-control delete-workload-identity \
20
+ --region "$AWS_REGION" \
21
+ --name "$WORKLOAD_IDENTITY_NAME" >/dev/null
22
+ fi
23
+
24
+ printf 'AgentCore Identity proof resources deleted: workload=%s provider=%s\n' \
25
+ "$WORKLOAD_IDENTITY_NAME" "$CREDENTIAL_PROVIDER_NAME"