thinkwork-cli 0.12.15 → 0.12.17

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 (152) hide show
  1. package/dist/{api-client-4VT3FWOH.js → api-client-JUBCQQDF.js} +1 -1
  2. package/dist/{chunk-H7AB42ES.js → chunk-STY44FQO.js} +13 -0
  3. package/dist/cli.js +1107 -10225
  4. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
  5. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
  6. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
  7. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
  8. package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
  9. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
  10. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
  11. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
  12. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
  13. package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
  14. package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
  15. package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
  16. package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
  17. package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
  18. package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
  19. package/dist/drizzle/0197_rcm_context_columns.sql +28 -0
  20. package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
  21. package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
  22. package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
  23. package/dist/drizzle/0203_threads_mode_override.sql +61 -0
  24. package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
  25. package/dist/drizzle/0205_routines_git_python.sql +185 -0
  26. package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
  27. package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
  28. package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
  29. package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
  30. package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
  31. package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
  32. package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
  33. package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
  34. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
  35. package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
  36. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
  37. package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
  38. package/dist/drizzle/0218_document_plates.sql +30 -0
  39. package/dist/drizzle/0219_document_section_waivers.sql +29 -0
  40. package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
  41. package/dist/drizzle/0221_artifact_shares.sql +29 -0
  42. package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
  43. package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
  44. package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
  45. package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
  46. package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
  47. package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
  48. package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
  49. package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
  50. package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
  51. package/dist/drizzle/0230_analyst_rls.sql +1613 -0
  52. package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
  53. package/dist/plugins/catalog/package.json +0 -1
  54. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  55. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
  56. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
  57. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  58. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  59. package/dist/plugins/catalog/src/contracts.ts +112 -0
  60. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  61. package/dist/plugins/n8n/README.md +4 -36
  62. package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
  63. package/dist/plugins/n8n/src/manifest.ts +0 -5
  64. package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
  65. package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
  66. package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
  67. package/dist/plugins/n8n/test/manifest.test.ts +0 -1
  68. package/dist/plugins/twenty/package.json +5 -0
  69. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
  70. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
  71. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
  72. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
  73. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
  74. package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
  75. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
  76. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
  77. package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
  78. package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
  79. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
  80. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
  81. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
  82. package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
  83. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
  84. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
  85. package/dist/plugins/twenty/src/manifest.ts +99 -0
  86. package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
  87. package/dist/plugins/twenty/test/manifest.test.ts +12 -2
  88. package/dist/terraform/examples/greenfield/main.tf +90 -380
  89. package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
  90. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  91. package/dist/terraform/modules/app/agentcore-pi/main.tf +36 -32
  92. package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
  93. package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
  94. package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
  95. package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
  96. package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
  97. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
  98. package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
  99. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
  100. package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
  101. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
  102. package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
  103. package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
  104. package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
  105. package/dist/terraform/modules/app/ses-email/main.tf +14 -2
  106. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
  107. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
  108. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
  109. package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
  110. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
  111. package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
  112. package/dist/terraform/modules/thinkwork/README.md +0 -1
  113. package/dist/terraform/modules/thinkwork/main.tf +167 -446
  114. package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
  115. package/dist/terraform/modules/thinkwork/variables.tf +55 -309
  116. package/dist/terraform/schema.graphql +4 -0
  117. package/dist/workspace-defaults/files/AGENTS.md +45 -31
  118. package/dist/workspace-defaults/files/CONTEXT.md +48 -4
  119. package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
  120. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
  121. package/dist/workspace-defaults/files/SPACE.md +34 -19
  122. package/dist/workspace-defaults/files/TOOLS.md +30 -0
  123. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
  124. package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
  125. package/package.json +2 -1
  126. package/dist/plugins/company-brain/README.md +0 -37
  127. package/dist/plugins/company-brain/package.json +0 -33
  128. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
  129. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
  130. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
  131. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
  132. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
  133. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
  134. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
  135. package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
  136. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
  137. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
  138. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
  139. package/dist/plugins/company-brain/src/api/migration.ts +0 -665
  140. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
  141. package/dist/plugins/company-brain/src/index.ts +0 -62
  142. package/dist/plugins/company-brain/src/manifest.ts +0 -112
  143. package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
  144. package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
  145. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
  146. package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
  147. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
  148. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
  149. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
  150. package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
  151. package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
  152. package/dist/plugins/company-brain/tsconfig.json +0 -8
@@ -0,0 +1,493 @@
1
+ ################################################################################
2
+ # Workflow Interpreter Step Functions — App Module (stage-level)
3
+ #
4
+ # ONE static Step Functions Standard state machine per stage interprets every
5
+ # ThinkWork workflow definition (THINK-219). Unlike the routines substrate —
6
+ # which mints a state machine per routine at runtime — the interpreter is a
7
+ # fixed loop that lives entirely in this module:
8
+ #
9
+ # LoadNextStep (Lambda) -> Choice on directive -> dispatch
10
+ # (waitForTaskToken for agent/approval, Wait with TimestampPath for wait)
11
+ # -> RecordAdvance -> Choice (loop / rollover / terminal).
12
+ #
13
+ # SFN state carries ONLY a cursor {workflowRunId, tenantId, stepPointer,
14
+ # iteration, loopCycleCount}; all step results live in Postgres, so the 256KB
15
+ # payload cap is a non-issue by construction (KTD1). Rollover self-restarts the
16
+ # machine at loop-cycle thresholds via a fire-and-forget states:startExecution
17
+ # so the ~1,400-event history ceiling is never hit (KTD6).
18
+ #
19
+ # **Fresh module name.** This deliberately does NOT reclaim the empty
20
+ # `system-workflows-stepfunctions/` directory — that is the tombstone of the
21
+ # System Workflows subsystem destroyed 2026-05-06 (#871); re-attaching the
22
+ # retired name would confuse state history.
23
+ #
24
+ # **Tenant isolation is application-layer for the shared role (KTD9).** Matching
25
+ # the routines substrate reality, one shared execution role serves the single
26
+ # machine; the step-dispatch Lambda re-asserts tenant_id from the run row on
27
+ # every invocation. Accepted v1 posture — no per-tenant tags, no ABAC.
28
+ ################################################################################
29
+
30
+ terraform {
31
+ required_providers {
32
+ aws = {
33
+ source = "hashicorp/aws"
34
+ version = ">= 5.0"
35
+ }
36
+ }
37
+ }
38
+
39
+ locals {
40
+ state_machine_name = "thinkwork-${var.stage}-workflow-interpreter"
41
+ log_group_name = "/aws/vendedlogs/states/thinkwork-${var.stage}-workflow-interpreter"
42
+ role_name = "thinkwork-${var.stage}-workflow-interpreter-execution-role"
43
+
44
+ # Constructed (not resource-referenced) to avoid a cycle: the state machine's
45
+ # ARN is deterministic from name/region/account, so the execution role can
46
+ # grant states:StartExecution on its OWN machine without a self-reference.
47
+ state_machine_arn = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:${local.state_machine_name}"
48
+
49
+ # Single Lambda serves all interpreter phases (load_next, dispatch_agent,
50
+ # await_approval, record_approval, record_advance). Constructed by string to
51
+ # avoid a chicken-and-egg with the lambda-api module.
52
+ step_dispatch_lambda_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-workflow-step-dispatch"
53
+
54
+ # Retry applied to every synchronous lambda:invoke Task (transient
55
+ # service/SDK faults only — application errors surface, they do not retry).
56
+ lambda_invoke_retry = [{
57
+ ErrorEquals = [
58
+ "Lambda.ServiceException",
59
+ "Lambda.AWSLambdaException",
60
+ "Lambda.SdkClientException",
61
+ "Lambda.TooManyRequestsException",
62
+ ]
63
+ IntervalSeconds = 2
64
+ MaxAttempts = 3
65
+ BackoffRate = 2
66
+ }]
67
+
68
+ ssm_state_machine_arn_param = "/thinkwork/${var.stage}/workflow-interpreter/state-machine-arn"
69
+ }
70
+
71
+ ################################################################################
72
+ # CloudWatch Log Group — vendedlogs prefix dodges the resource-policy size cap.
73
+ ################################################################################
74
+
75
+ resource "aws_cloudwatch_log_group" "interpreter" {
76
+ name = local.log_group_name
77
+ retention_in_days = var.log_retention_days
78
+
79
+ tags = {
80
+ Name = local.log_group_name
81
+ Stage = var.stage
82
+ Purpose = "step-functions-workflow-interpreter"
83
+ }
84
+ }
85
+
86
+ ################################################################################
87
+ # Step Functions Execution Role — single shared role for the one machine.
88
+ # Trust: states.amazonaws.com, scoped by aws:SourceAccount.
89
+ ################################################################################
90
+
91
+ resource "aws_iam_role" "execution" {
92
+ name = local.role_name
93
+
94
+ assume_role_policy = jsonencode({
95
+ Version = "2012-10-17"
96
+ Statement = [{
97
+ Effect = "Allow"
98
+ Principal = { Service = "states.amazonaws.com" }
99
+ Action = "sts:AssumeRole"
100
+ Condition = {
101
+ StringEquals = {
102
+ "aws:SourceAccount" = var.account_id
103
+ }
104
+ }
105
+ }]
106
+ })
107
+
108
+ tags = {
109
+ Name = local.role_name
110
+ Stage = var.stage
111
+ Purpose = "step-functions-workflow-interpreter-execution"
112
+ }
113
+ }
114
+
115
+ # CloudWatch Logs delivery + X-Ray. Step Functions requires these to populate
116
+ # execution history into the vendedlogs prefix.
117
+ resource "aws_iam_role_policy" "execution_logs" {
118
+ name = "logs-and-xray"
119
+ role = aws_iam_role.execution.id
120
+
121
+ policy = jsonencode({
122
+ Version = "2012-10-17"
123
+ Statement = [
124
+ {
125
+ Effect = "Allow"
126
+ Action = [
127
+ "logs:CreateLogDelivery",
128
+ "logs:GetLogDelivery",
129
+ "logs:UpdateLogDelivery",
130
+ "logs:DeleteLogDelivery",
131
+ "logs:ListLogDeliveries",
132
+ "logs:PutResourcePolicy",
133
+ "logs:DescribeResourcePolicies",
134
+ "logs:DescribeLogGroups",
135
+ ]
136
+ Resource = "*"
137
+ },
138
+ {
139
+ Effect = "Allow"
140
+ Action = [
141
+ "logs:CreateLogStream",
142
+ "logs:PutLogEvents",
143
+ ]
144
+ Resource = "${aws_cloudwatch_log_group.interpreter.arn}:*"
145
+ },
146
+ {
147
+ Effect = "Allow"
148
+ Action = [
149
+ "xray:PutTraceSegments",
150
+ "xray:PutTelemetryRecords",
151
+ "xray:GetSamplingRules",
152
+ "xray:GetSamplingTargets",
153
+ ]
154
+ Resource = "*"
155
+ },
156
+ ]
157
+ })
158
+ }
159
+
160
+ # Lambda invocation — scoped to the single interpreter step-dispatch Lambda,
161
+ # which serves every ASL phase. The Lambda itself re-asserts tenant_id from the
162
+ # run row, so no ABAC is layered here.
163
+ resource "aws_iam_role_policy" "execution_lambda_invoke" {
164
+ name = "lambda-invoke-step-dispatch"
165
+ role = aws_iam_role.execution.id
166
+
167
+ policy = jsonencode({
168
+ Version = "2012-10-17"
169
+ Statement = [{
170
+ Effect = "Allow"
171
+ Action = ["lambda:InvokeFunction"]
172
+ Resource = [local.step_dispatch_lambda_arn]
173
+ }]
174
+ })
175
+ }
176
+
177
+ # Step Functions self-invocation (rollover continue-as-new via RolloverStart's
178
+ # fire-and-forget states:startExecution) plus task-token completion. The
179
+ # self-reference uses the constructed ARN (local.state_machine_arn) rather than
180
+ # the resource attribute to avoid a definition<->role dependency cycle.
181
+ #
182
+ # Application-layer tenant isolation (KTD9): the shared role has no tenantId
183
+ # principal tag; tenant scoping lives in the step-dispatch Lambda + the run row.
184
+ resource "aws_iam_role_policy" "execution_states" {
185
+ name = "states-self-invoke"
186
+ role = aws_iam_role.execution.id
187
+
188
+ policy = jsonencode({
189
+ Version = "2012-10-17"
190
+ Statement = [
191
+ {
192
+ Effect = "Allow"
193
+ Action = ["states:StartExecution"]
194
+ Resource = local.state_machine_arn
195
+ },
196
+ {
197
+ Effect = "Allow"
198
+ Action = [
199
+ "states:SendTaskSuccess",
200
+ "states:SendTaskFailure",
201
+ "states:SendTaskHeartbeat",
202
+ ]
203
+ Resource = "*"
204
+ },
205
+ ]
206
+ })
207
+ }
208
+
209
+ ################################################################################
210
+ # The one static interpreter state machine (Standard).
211
+ #
212
+ # See KTD1 for the loop shape. Directives are the FROZEN protocol the
213
+ # step-dispatch Lambda emits: dispatch_agent, wait_until, execute_step,
214
+ # await_approval, continue, rollover, terminal_success, terminal_failure,
215
+ # terminal_canceled.
216
+ ################################################################################
217
+
218
+ resource "aws_sfn_state_machine" "interpreter" {
219
+ name = local.state_machine_name
220
+ role_arn = aws_iam_role.execution.arn
221
+
222
+ logging_configuration {
223
+ include_execution_data = false
224
+ level = "ALL"
225
+ log_destination = "${aws_cloudwatch_log_group.interpreter.arn}:*"
226
+ }
227
+
228
+ definition = jsonencode({
229
+ Comment = "ThinkWork Workflow Interpreter — one static loop per stage. DB is source of truth; SFN state carries only a cursor."
230
+ StartAt = "LoadNextStep"
231
+ States = {
232
+ # ---- Load the next step for the current cursor -------------------------
233
+ LoadNextStep = {
234
+ Type = "Task"
235
+ Resource = "arn:aws:states:::lambda:invoke"
236
+ Parameters = {
237
+ FunctionName = local.step_dispatch_lambda_arn
238
+ Payload = {
239
+ phase = "load_next"
240
+ "cursor.$" = "$.cursor"
241
+ "executionArn.$" = "$$.Execution.Id"
242
+ }
243
+ }
244
+ OutputPath = "$.Payload"
245
+ Retry = local.lambda_invoke_retry
246
+ Next = "DirectiveChoice"
247
+ }
248
+
249
+ # ---- Branch on the directive the Lambda emitted ------------------------
250
+ DirectiveChoice = {
251
+ Type = "Choice"
252
+ Choices = [
253
+ { Variable = "$.directive", StringEquals = "dispatch_agent", Next = "AgentStep" },
254
+ { Variable = "$.directive", StringEquals = "wait_until", Next = "WaitState" },
255
+ { Variable = "$.directive", StringEquals = "execute_step", Next = "ExecuteStep" },
256
+ { Variable = "$.directive", StringEquals = "await_approval", Next = "ApprovalStep" },
257
+ { Variable = "$.directive", StringEquals = "continue", Next = "LoadNextStep" },
258
+ { Variable = "$.directive", StringEquals = "rollover", Next = "RolloverStart" },
259
+ { Variable = "$.directive", StringEquals = "terminal_success", Next = "SucceedState" },
260
+ { Variable = "$.directive", StringEquals = "terminal_canceled", Next = "SucceedState" },
261
+ ]
262
+ # terminal_failure and any unknown directive fail the run.
263
+ Default = "FailState"
264
+ }
265
+
266
+ # ---- Agent step: dispatch a Pi goal turn, park on the task token -------
267
+ AgentStep = {
268
+ Type = "Task"
269
+ Resource = "arn:aws:states:::lambda:invoke.waitForTaskToken"
270
+ Parameters = {
271
+ FunctionName = local.step_dispatch_lambda_arn
272
+ Payload = {
273
+ phase = "dispatch_agent"
274
+ "cursor.$" = "$.cursor"
275
+ "taskToken.$" = "$$.Task.Token"
276
+ }
277
+ }
278
+ ResultPath = "$.stepResult"
279
+ HeartbeatSeconds = 3600
280
+ TimeoutSeconds = 172800
281
+ # States.ALL must stand alone; it subsumes States.Timeout/States.TaskFailed.
282
+ # Any agent-step failure routes to RecordAdvance so the Lambda records a
283
+ # step-failed event rather than stranding the run.
284
+ Catch = [{
285
+ ErrorEquals = ["States.ALL"]
286
+ ResultPath = "$.stepResult"
287
+ Next = "RecordAdvance"
288
+ }]
289
+ Next = "RecordAdvance"
290
+ }
291
+
292
+ # ---- Executable step: routine / http / emit_event run synchronously in
293
+ # the Lambda (THINK-215). The phase executes the step, records outcome +
294
+ # output evidence, evaluates advance/policy itself, and returns the next
295
+ # directive — so its output feeds DirectiveChoice directly. Application
296
+ # failures are recorded in the ledger by the Lambda; only an infra-level
297
+ # crash reaches Catch, where the EventBridge execution-callback
298
+ # terminalizes the run.
299
+ ExecuteStep = {
300
+ Type = "Task"
301
+ Resource = "arn:aws:states:::lambda:invoke"
302
+ Parameters = {
303
+ FunctionName = local.step_dispatch_lambda_arn
304
+ Payload = {
305
+ phase = "execute_step"
306
+ "cursor.$" = "$.cursor"
307
+ }
308
+ }
309
+ OutputPath = "$.Payload"
310
+ TimeoutSeconds = 900
311
+ Retry = local.lambda_invoke_retry
312
+ Catch = [{
313
+ ErrorEquals = ["States.ALL"]
314
+ Next = "FailState"
315
+ }]
316
+ Next = "DirectiveChoice"
317
+ }
318
+
319
+ # ---- Wait step: park until the ISO timestamp the Lambda emitted --------
320
+ WaitState = {
321
+ Type = "Wait"
322
+ TimestampPath = "$.until"
323
+ Next = "InjectWaitResult"
324
+ }
325
+
326
+ # Wait carries no stepResult; inject a marker so RecordAdvance's frozen
327
+ # contract (top-level cursor + stepResult) holds uniformly.
328
+ InjectWaitResult = {
329
+ Type = "Pass"
330
+ Result = { waitCompleted = true }
331
+ ResultPath = "$.stepResult"
332
+ Next = "RecordAdvance"
333
+ }
334
+
335
+ # ---- Approval step: suspend as waiting_for_human on a task token -------
336
+ ApprovalStep = {
337
+ Type = "Task"
338
+ Resource = "arn:aws:states:::lambda:invoke.waitForTaskToken"
339
+ Parameters = {
340
+ FunctionName = local.step_dispatch_lambda_arn
341
+ Payload = {
342
+ phase = "await_approval"
343
+ "cursor.$" = "$.cursor"
344
+ "taskToken.$" = "$$.Task.Token"
345
+ }
346
+ }
347
+ ResultPath = "$.approval"
348
+ TimeoutSeconds = 604800
349
+ Catch = [{
350
+ ErrorEquals = ["States.Timeout"]
351
+ Next = "FailState"
352
+ }]
353
+ Next = "RecordApproval"
354
+ }
355
+
356
+ RecordApproval = {
357
+ Type = "Task"
358
+ Resource = "arn:aws:states:::lambda:invoke"
359
+ Parameters = {
360
+ FunctionName = local.step_dispatch_lambda_arn
361
+ Payload = {
362
+ phase = "record_approval"
363
+ "cursor.$" = "$.cursor"
364
+ "approval.$" = "$.approval"
365
+ }
366
+ }
367
+ OutputPath = "$.Payload"
368
+ Retry = local.lambda_invoke_retry
369
+ Next = "DirectiveChoice"
370
+ }
371
+
372
+ # ---- Record the step outcome, evaluate policy, emit the next directive -
373
+ RecordAdvance = {
374
+ Type = "Task"
375
+ Resource = "arn:aws:states:::lambda:invoke"
376
+ Parameters = {
377
+ FunctionName = local.step_dispatch_lambda_arn
378
+ Payload = {
379
+ phase = "record_advance"
380
+ "cursor.$" = "$.cursor"
381
+ "stepResult.$" = "$.stepResult"
382
+ }
383
+ }
384
+ OutputPath = "$.Payload"
385
+ Retry = local.lambda_invoke_retry
386
+ Next = "DirectiveChoice"
387
+ }
388
+
389
+ # ---- Rollover: continue-as-new self-restart (fire-and-forget) ----------
390
+ RolloverStart = {
391
+ Type = "Task"
392
+ Resource = "arn:aws:states:::states:startExecution"
393
+ Parameters = {
394
+ "StateMachineArn.$" = "$$.StateMachine.Id"
395
+ Input = {
396
+ "cursor.$" = "$.cursor"
397
+ "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id"
398
+ }
399
+ }
400
+ Next = "SucceedState"
401
+ }
402
+
403
+ SucceedState = { Type = "Succeed" }
404
+
405
+ FailState = {
406
+ Type = "Fail"
407
+ Error = "WorkflowFailed"
408
+ Cause = "see ThinkWork run ledger"
409
+ }
410
+ }
411
+ })
412
+
413
+ tags = {
414
+ Name = local.state_machine_name
415
+ Stage = var.stage
416
+ Purpose = "workflow-interpreter"
417
+ }
418
+ }
419
+
420
+ ################################################################################
421
+ # SSM parameter — interpreter machine ARN.
422
+ #
423
+ # graphql-http (manual triggerWorkflowRun) and job-trigger (workflow_schedule)
424
+ # resolve the machine ARN from SSM rather than a Lambda env var, dodging the
425
+ # graphql-http 4KB env ceiling (plan Risks). The shared API lambda role already
426
+ # reads /thinkwork/${stage}/* via the api_data_plane grant.
427
+ ################################################################################
428
+
429
+ resource "aws_ssm_parameter" "state_machine_arn" {
430
+ name = local.ssm_state_machine_arn_param
431
+ description = "ARN of the shared workflow-interpreter Step Functions state machine."
432
+ type = "String"
433
+ value = aws_sfn_state_machine.interpreter.arn
434
+
435
+ tags = {
436
+ Stage = var.stage
437
+ Purpose = "workflow-interpreter"
438
+ }
439
+ }
440
+
441
+ ################################################################################
442
+ # EventBridge — Step Functions execution-state-change -> workflow-execution-callback
443
+ #
444
+ # AWS fires `Step Functions Execution Status Change` on every transition. The
445
+ # callback Lambda projects terminal status onto workflow_runs (with the
446
+ # current-ARN guard for rolled-over executions, KTD6). Gated on the callback
447
+ # ARN being wired so this substrate module applies before lambda-api defines
448
+ # the function; the composite module passes the ARN once both apply.
449
+ ################################################################################
450
+
451
+ resource "aws_cloudwatch_event_rule" "sfn_state_change" {
452
+ count = var.enable_execution_callback ? 1 : 0
453
+ name = "thinkwork-${var.stage}-workflow-interpreter-sfn-state-change"
454
+ description = "Forward interpreter Step Functions execution-state-change events to workflow-execution-callback so workflow_runs tracks lifecycle status."
455
+
456
+ event_pattern = jsonencode({
457
+ source = ["aws.states"]
458
+ "detail-type" = ["Step Functions Execution Status Change"]
459
+ detail = {
460
+ stateMachineArn = [
461
+ {
462
+ prefix = local.state_machine_arn
463
+ },
464
+ ]
465
+ # SUCCEEDED is intentionally excluded: success is always projected onto
466
+ # workflow_runs by the step-dispatch Lambda BEFORE the execution ends,
467
+ # and a rolled-over execution's SUCCEEDED racing the repoint could
468
+ # otherwise terminalize a live looping run.
469
+ status = ["FAILED", "TIMED_OUT", "ABORTED"]
470
+ }
471
+ })
472
+
473
+ tags = {
474
+ Name = "thinkwork-${var.stage}-workflow-interpreter-sfn-state-change"
475
+ Stage = var.stage
476
+ }
477
+ }
478
+
479
+ resource "aws_cloudwatch_event_target" "sfn_state_change" {
480
+ count = var.enable_execution_callback ? 1 : 0
481
+ rule = aws_cloudwatch_event_rule.sfn_state_change[0].name
482
+ target_id = "workflow-execution-callback"
483
+ arn = var.execution_callback_lambda_arn
484
+ }
485
+
486
+ resource "aws_lambda_permission" "sfn_state_change" {
487
+ count = var.enable_execution_callback ? 1 : 0
488
+ statement_id = "AllowEventBridgeInvokeWorkflowExecutionCallback"
489
+ action = "lambda:InvokeFunction"
490
+ function_name = var.execution_callback_lambda_arn
491
+ principal = "events.amazonaws.com"
492
+ source_arn = aws_cloudwatch_event_rule.sfn_state_change[0].arn
493
+ }
@@ -0,0 +1,34 @@
1
+ output "state_machine_arn" {
2
+ description = "ARN of the shared workflow-interpreter state machine."
3
+ value = aws_sfn_state_machine.interpreter.arn
4
+ }
5
+
6
+ output "state_machine_name" {
7
+ description = "Name of the shared workflow-interpreter state machine."
8
+ value = aws_sfn_state_machine.interpreter.name
9
+ }
10
+
11
+ output "state_machine_arn_ssm_parameter_name" {
12
+ description = "SSM parameter name holding the interpreter machine ARN (read by graphql-http + job-trigger)."
13
+ value = aws_ssm_parameter.state_machine_arn.name
14
+ }
15
+
16
+ output "execution_role_arn" {
17
+ description = "ARN of the Step Functions execution role assumed by the interpreter machine."
18
+ value = aws_iam_role.execution.arn
19
+ }
20
+
21
+ output "execution_role_name" {
22
+ description = "Name of the Step Functions execution role."
23
+ value = aws_iam_role.execution.name
24
+ }
25
+
26
+ output "log_group_arn" {
27
+ description = "CloudWatch log group ARN for interpreter execution histories."
28
+ value = aws_cloudwatch_log_group.interpreter.arn
29
+ }
30
+
31
+ output "log_group_name" {
32
+ description = "CloudWatch log group name."
33
+ value = aws_cloudwatch_log_group.interpreter.name
34
+ }
@@ -0,0 +1,32 @@
1
+ variable "stage" {
2
+ description = "Deployment stage (dev, prod, etc.)."
3
+ type = string
4
+ }
5
+
6
+ variable "region" {
7
+ description = "AWS region."
8
+ type = string
9
+ }
10
+
11
+ variable "account_id" {
12
+ description = "AWS account ID (used to construct IAM + Lambda + state-machine ARNs)."
13
+ type = string
14
+ }
15
+
16
+ variable "log_retention_days" {
17
+ description = "CloudWatch log retention for interpreter state machine executions."
18
+ type = number
19
+ default = 30
20
+ }
21
+
22
+ variable "execution_callback_lambda_arn" {
23
+ description = "ARN of the workflow-execution-callback Lambda. EventBridge sends SFN execution-state-change events here so workflow_runs lifecycle status mirrors the SFN-side reality. Only consumed when enable_execution_callback is true."
24
+ type = string
25
+ default = ""
26
+ }
27
+
28
+ variable "enable_execution_callback" {
29
+ description = "Provision the EventBridge SFN-state-change rule targeting the execution-callback Lambda. Must be a STATIC value (never derived from a computed resource attribute): it gates resource count, and Terraform cannot plan a count that depends on attributes only known after apply — the ARN itself is fine to consume inside the resources."
30
+ type = bool
31
+ default = false
32
+ }
@@ -117,6 +117,18 @@ resource "aws_rds_cluster" "main" {
117
117
  deletion_protection = local.deletion_protection
118
118
  storage_encrypted = true
119
119
 
120
+ # THINK-229 U1 (R1): the analyst-query-broker Lambda authenticates as
121
+ # analyst_reader with per-connect RDS IAM auth tokens instead of a
122
+ # stored password. Cluster-level flag; applies without downtime.
123
+ # apply_immediately is required: the provider default defers cluster
124
+ # modifications to the maintenance window, and if the GRANT rds_iam
125
+ # migration (0229) landed inside that pending window the role would
126
+ # refuse BOTH password (grant applied) and IAM (flag still pending)
127
+ # login. Deploys on this platform are CLI/CI-driven and expect
128
+ # immediate semantics.
129
+ iam_database_authentication_enabled = true
130
+ apply_immediately = true
131
+
120
132
  serverlessv2_scaling_configuration {
121
133
  min_capacity = var.min_capacity
122
134
  max_capacity = var.max_capacity
@@ -180,6 +192,13 @@ resource "aws_db_instance" "main" {
180
192
  skip_final_snapshot = true
181
193
  deletion_protection = local.deletion_protection
182
194
 
195
+ # THINK-229 U1 (R1): keep the rds-postgres dev/test engine at parity
196
+ # with the Aurora cluster — analyst_reader logs in with IAM tokens.
197
+ # apply_immediately for the same both-paths-stranded reason as the
198
+ # Aurora cluster block above.
199
+ iam_database_authentication_enabled = true
200
+ apply_immediately = true
201
+
183
202
  tags = {
184
203
  Name = "thinkwork-${var.stage}-db"
185
204
  }
@@ -358,3 +377,39 @@ resource "aws_secretsmanager_secret" "compliance_reader" {
358
377
  Role = "compliance_reader"
359
378
  }
360
379
  }
380
+
381
+ ################################################################################
382
+ # Secrets Manager — Analyst Reader Role Credentials (THINK-228 U2)
383
+ #
384
+ # Container for the analyst_reader Aurora role used by the analyst
385
+ # query-broker Lambda. Terraform owns the SECRET CONTAINER; the operator
386
+ # owns the SECRET VALUE (populated by scripts/bootstrap-analyst-roles.sh,
387
+ # which also applies drizzle/0227_analyst_reader_role.sql to create the
388
+ # matching hardened read-only role).
389
+ ################################################################################
390
+
391
+ resource "aws_secretsmanager_secret" "analyst_reader" {
392
+ count = local.create ? 1 : 0
393
+ name = "thinkwork/${var.stage}/analyst/reader-credentials"
394
+
395
+ tags = {
396
+ Name = "thinkwork-${var.stage}-analyst-reader-credentials"
397
+ Role = "analyst_reader"
398
+ }
399
+ }
400
+
401
+ # Broker caller credential for the analyst query broker (THINK-228 U3).
402
+ # The connector row (tenant_mcp_servers, seeded by
403
+ # scripts/provision-analyst-connector.mts) references this ARN as its
404
+ # auth_config.secretRef; the broker Lambda validates incoming Bearer
405
+ # tokens against it. Value shape: JSON {token, tenantId} — populated by
406
+ # the provisioning script, never by Terraform.
407
+ resource "aws_secretsmanager_secret" "analyst_broker" {
408
+ count = local.create ? 1 : 0
409
+ name = "thinkwork/${var.stage}/analyst/broker-credential"
410
+
411
+ tags = {
412
+ Name = "thinkwork-${var.stage}-analyst-broker-credential"
413
+ Role = "analyst_query_broker"
414
+ }
415
+ }
@@ -8,6 +8,11 @@ output "db_cluster_arn" {
8
8
  value = local.db_cluster_arn
9
9
  }
10
10
 
11
+ output "cluster_resource_id" {
12
+ description = "Immutable RDS cluster resource ID (cluster-XXXX / db-XXXX), the identifier IAM rds-db:connect policies key on — NOT the ARN (THINK-229 U1). Empty string when create_database = false."
13
+ value = local.use_aurora ? aws_rds_cluster.main[0].cluster_resource_id : (local.use_rds ? aws_db_instance.main[0].resource_id : "")
14
+ }
15
+
11
16
  output "graphql_db_secret_arn" {
12
17
  description = "Secrets Manager ARN for DB credentials (created or existing)"
13
18
  value = local.graphql_db_secret_arn
@@ -62,3 +67,13 @@ output "compliance_reader_secret_arn" {
62
67
  description = "Secrets Manager ARN for the compliance_reader Aurora role (Phase 3 U2). Empty string when create_database = false."
63
68
  value = local.create ? aws_secretsmanager_secret.compliance_reader[0].arn : ""
64
69
  }
70
+
71
+ output "analyst_reader_secret_arn" {
72
+ description = "Secrets Manager ARN for the analyst_reader Aurora role (THINK-228 U2). Empty string when create_database = false."
73
+ value = local.create ? aws_secretsmanager_secret.analyst_reader[0].arn : ""
74
+ }
75
+
76
+ output "analyst_broker_secret_arn" {
77
+ description = "Secrets Manager ARN for the analyst query-broker caller credential (THINK-228 U3). Empty string when create_database = false."
78
+ value = local.create ? aws_secretsmanager_secret.analyst_broker[0].arn : ""
79
+ }
@@ -108,3 +108,29 @@ output "bucket_arn" {
108
108
  description = "ARN of the S3 bucket"
109
109
  value = aws_s3_bucket.main.arn
110
110
  }
111
+
112
+ # Analyst query-broker result staging (THINK-228 U3). Results larger than
113
+ # the inline envelope cap land as CSVs under analyst-staging/<tenantId>/
114
+ # with SSE; they are scratch data consumed by the agent sandbox within the
115
+ # same delegation, so a short TTL bounds both storage cost and the window
116
+ # in which a leaked object key is useful.
117
+ resource "aws_s3_bucket_lifecycle_configuration" "analyst_staging" {
118
+ bucket = aws_s3_bucket.main.id
119
+
120
+ rule {
121
+ id = "analyst-staging-ttl"
122
+ status = "Enabled"
123
+
124
+ filter {
125
+ prefix = "analyst-staging/"
126
+ }
127
+
128
+ expiration {
129
+ days = 3
130
+ }
131
+
132
+ abort_incomplete_multipart_upload {
133
+ days_after_initiation = 1
134
+ }
135
+ }
136
+ }