thinkwork-cli 0.13.0 → 0.13.1

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 (74) hide show
  1. package/dist/{api-client-JUBCQQDF.js → api-client-4DZ266J5.js} +1 -1
  2. package/dist/{chunk-STY44FQO.js → chunk-Y2WHJECR.js} +44 -12
  3. package/dist/cli.js +36715 -12965
  4. package/dist/drizzle/0227_analyst_reader_role.sql +100 -38
  5. package/dist/drizzle/0230_analyst_rls.sql +183 -66
  6. package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
  7. package/dist/drizzle/0232_message_source_event_id.sql +9 -0
  8. package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
  9. package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
  10. package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
  11. package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
  12. package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
  13. package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
  14. package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
  15. package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
  16. package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
  17. package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
  18. package/dist/drizzle/0242_search_queries.sql +28 -0
  19. package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
  20. package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
  21. package/dist/drizzle/0245_wiki_page_render.sql +21 -0
  22. package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
  23. package/dist/drizzle/0247_capability_runtime.sql +402 -0
  24. package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
  25. package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
  26. package/dist/drizzle/0250_kg_schema_extraction.sql +549 -0
  27. package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
  28. package/dist/drizzle/0251_brain_stragglers.sql +242 -0
  29. package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
  30. package/dist/drizzle/0253_drop_brain_substrate.sql +91 -0
  31. package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
  32. package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
  33. package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
  34. package/dist/drizzle/0257_capability_approvals.sql +53 -0
  35. package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
  36. package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
  37. package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
  38. package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +11 -0
  39. package/dist/terraform/examples/greenfield/main.tf +40 -0
  40. package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
  41. package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
  42. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
  43. package/dist/terraform/modules/app/agentcore-harness/main.tf +101 -0
  44. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +9 -0
  45. package/dist/terraform/modules/app/agentcore-harness/variables.tf +25 -0
  46. package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
  47. package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
  48. package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
  49. package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
  50. package/dist/terraform/modules/app/deployment-control-plane/runner.py +46 -5
  51. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +94 -0
  52. package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
  53. package/dist/terraform/modules/app/lambda-api/handlers.tf +361 -16
  54. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +131 -5
  55. package/dist/terraform/modules/app/lambda-api/main.tf +11 -0
  56. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
  57. package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
  58. package/dist/terraform/modules/app/lambda-api/variables.tf +92 -0
  59. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
  60. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
  61. package/dist/terraform/modules/foundation/cognito/main.tf +32 -8
  62. package/dist/terraform/modules/foundation/cognito/variables.tf +12 -0
  63. package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
  64. package/dist/terraform/modules/thinkwork/main.tf +152 -5
  65. package/dist/terraform/modules/thinkwork/outputs.tf +55 -0
  66. package/dist/terraform/modules/thinkwork/variables.tf +42 -0
  67. package/dist/workspace-defaults/files/TOOLS.md +39 -0
  68. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
  69. package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
  70. package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
  71. package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
  72. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
  73. package/package.json +4 -2
  74. /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
@@ -146,6 +146,31 @@ locals {
146
146
  Action = ["s3:ListBucket"]
147
147
  Resource = aws_s3_bucket.brain_artifacts.arn
148
148
  },
149
+ # THINK-193 U2 (Codex S2 + round-6 P1): bulk/prefix destruction of
150
+ # evidence snapshots lives EXCLUSIVELY on the memory-retraction-drainer's
151
+ # dedicated role (handlers.tf). The SHARED role gets only the narrow
152
+ # erase-fence COMPENSATION capability: a stage worker that loses the
153
+ # fence mid-PutObject must delete the EXACT object version it just
154
+ # wrote (VersionId from its own PutObject response) and verify the key
155
+ # is gone. Without ListBucketVersions-driven enumeration + bulk delete
156
+ # this cannot destroy anything the caller did not itself write —
157
+ # version ids are unguessable. ListBucketVersions is granted read-only
158
+ # for the verification step, prefix-conditioned.
159
+ {
160
+ Effect = "Allow"
161
+ Action = ["s3:DeleteObjectVersion"]
162
+ Resource = "${aws_s3_bucket.brain_artifacts.arn}/evidence-snapshots/*"
163
+ },
164
+ {
165
+ Effect = "Allow"
166
+ Action = ["s3:ListBucketVersions"]
167
+ Resource = aws_s3_bucket.brain_artifacts.arn
168
+ Condition = {
169
+ StringLike = {
170
+ "s3:prefix" = "evidence-snapshots/*"
171
+ }
172
+ }
173
+ },
149
174
  # (was inline policy "cognito-access")
150
175
  {
151
176
  Effect = "Allow"
@@ -394,6 +419,22 @@ locals {
394
419
  }
395
420
  },
396
421
  ] : [],
422
+ # THINK-280 — the headless capability executor (routine-exec-git) mints and
423
+ # advances the broker PoP session by conditional reads/writes on the broker's
424
+ # DynamoDB session table. Empty (→ no grant) when the broker is disabled.
425
+ var.capability_broker_session_table_arn != "" ? [
426
+ {
427
+ Effect = "Allow"
428
+ Action = [
429
+ "dynamodb:GetItem",
430
+ "dynamodb:PutItem",
431
+ "dynamodb:UpdateItem",
432
+ "dynamodb:DeleteItem",
433
+ "dynamodb:Query",
434
+ ]
435
+ Resource = var.capability_broker_session_table_arn
436
+ },
437
+ ] : [],
397
438
  )
398
439
 
399
440
  # ---------------------------------------------------------------------------
@@ -472,9 +513,13 @@ locals {
472
513
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-wiki-compile",
473
514
  # knowledge-graph-observations-ingest: graphql-http's
474
515
  # startKnowledgeGraphObservationsIngest mutation invokes this with
475
- # RequestResponse. (The worker's self-invoke drain is retired —
476
- # AWS recursive-loop detection terminated those chains; backlog
477
- # drains in-process now.)
516
+ # RequestResponse, and memory-stage-worker's graph stage (THINK-193
517
+ # U4 stitch) RequestResponse-invokes it with a targeted bankIds
518
+ # payload the Bedrock classifier/extraction model config lives on
519
+ # the ingest Lambda's own env, so the worker never runs the ingest
520
+ # in-process. (The worker's self-invoke drain is retired — AWS
521
+ # recursive-loop detection terminated those chains; backlog drains
522
+ # in-process now.)
478
523
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-knowledge-graph-observations-ingest",
479
524
  # wiki-bootstrap-import: bootstrapJournalImport admin mutation
480
525
  # Event-invokes this for the long-running ingest path.
@@ -518,6 +563,11 @@ locals {
518
563
  # step RequestResponse-invokes its workflow-delivery mode to email
519
564
  # the maintained report to the operator-configured recipient list.
520
565
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-artifact-deliver",
566
+ # memory-stage-worker (external-memory-compounding U1):
567
+ # workflow-step-dispatch's memory_stage step Event-invokes it after
568
+ # parking on the task token; the worker resumes the token via
569
+ # SendTaskSuccess when the pipeline stage ends.
570
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-memory-stage-worker",
521
571
  # job-trigger self-target (plan 2026-07-03-004 U5, KTD-3): the
522
572
  # manual GraphQL trigger Event-invokes job-trigger with the
523
573
  # agent_loop_continue_dispatch event so routine actions never run
@@ -528,6 +578,12 @@ locals {
528
578
  # triggers), and job-trigger's canvas_refresh branch RequestResponse-
529
579
  # invokes it (scheduled trigger). Both run on this shared role.
530
580
  "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-canvas-refresh",
581
+ # agentcore-admin (THINK-280): graphql-http's createTenant
582
+ # RequestResponse-invokes it to provision per-tenant sandbox
583
+ # resources; routine-exec-git (same shared role) reaches it for the
584
+ # capability-private provisioning path. Deployed by the dedicated
585
+ # agentcore-admin module with its own least-privilege role.
586
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-agentcore-admin",
531
587
  ]
532
588
  },
533
589
  # (was standalone managed policy "workspace_renderer_invoke")
@@ -709,7 +765,7 @@ locals {
709
765
  # Group 3: AI — Bedrock model invocation, Knowledge Bases, AgentCore
710
766
  # memory / evaluations / code interpreter.
711
767
  # ---------------------------------------------------------------------------
712
- api_ai_statements = [
768
+ api_ai_statements = concat([
713
769
  # (was inline policy "bedrock-invoke")
714
770
  # Cross-region inference profiles (us.anthropic.claude-*) require
715
771
  # `bedrock:InvokeModel` on the *inference-profile* ARN AND on the
@@ -750,6 +806,18 @@ locals {
750
806
  "bedrock:GetIngestionJob",
751
807
  "bedrock:ListIngestionJobs",
752
808
  "bedrock:Retrieve",
809
+ # THINK-193 U7: knowledge-base-manager reconciles the per-document
810
+ # manifest after successful syncs (List/Get) and gates deletion
811
+ # settlement on Get + the scoped Retrieve above. Ingest/Delete cover
812
+ # the explicit direct-delete path for S3 RETAIN'd documents — the
813
+ # manager never races them with StartIngestionJob. The workspace-
814
+ # bucket S3 read the memory-stage-worker needs for document text is
815
+ # already granted by the shared role's workspace "s3-access"
816
+ # statement (data-plane group).
817
+ "bedrock:ListKnowledgeBaseDocuments",
818
+ "bedrock:GetKnowledgeBaseDocuments",
819
+ "bedrock:IngestKnowledgeBaseDocuments",
820
+ "bedrock:DeleteKnowledgeBaseDocuments",
753
821
  ]
754
822
  # CreateKnowledgeBase/CreateDataSource have no resource ARN at create
755
823
  # time and don't support resource-level scoping, so a knowledge-base/*
@@ -818,7 +886,65 @@ locals {
818
886
  ]
819
887
  Resource = "*"
820
888
  },
821
- ]
889
+ ],
890
+ # THINK-311 U4 — AgentCore Harness trial invoker grants, present only when
891
+ # the agentcore-harness module is enabled (empty role ARN = module off).
892
+ # Ship-inert: no handler calls Harness until U5's harness-runner lands.
893
+ #
894
+ # CRITICAL: never grant `bedrock-agentcore:InvokeAgentRuntimeCommand`
895
+ # anywhere — it bypasses Harness allowedTools enforcement. Invocation goes
896
+ # through InvokeHarness / InvokeAgentRuntime only.
897
+ var.agentcore_harness_execution_role_arn != "" ? [
898
+ {
899
+ # Data plane — invoke a Harness (and its runtime endpoints) directly.
900
+ Sid = "AgentCoreHarnessInvoke"
901
+ Effect = "Allow"
902
+ Action = [
903
+ "bedrock-agentcore:InvokeHarness",
904
+ "bedrock-agentcore:InvokeAgentRuntime",
905
+ ]
906
+ Resource = "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:harness/*"
907
+ },
908
+ {
909
+ # Control plane — CreateHarness has no resource ARN at create time
910
+ # (same non-scopable shape as bedrock:CreateKnowledgeBase above), so
911
+ # this statement is "*"; PassRole below stays scoped to the single
912
+ # Harness execution role.
913
+ Sid = "AgentCoreHarnessControl"
914
+ Effect = "Allow"
915
+ Action = [
916
+ "bedrock-agentcore:CreateHarness",
917
+ "bedrock-agentcore:GetHarness",
918
+ "bedrock-agentcore:UpdateHarness",
919
+ "bedrock-agentcore:DeleteHarness",
920
+ "bedrock-agentcore:CreateHarnessEndpoint",
921
+ "bedrock-agentcore:GetHarnessEndpoint",
922
+ "bedrock-agentcore:ListHarnessEndpoints",
923
+ "bedrock-agentcore:ListHarnesses",
924
+ "bedrock-agentcore:ListHarnessVersions",
925
+ ]
926
+ Resource = "*"
927
+ },
928
+ ] : [],
929
+ # (Separate conditional from the two statements above only because the
930
+ # Condition attribute gives this object a different Terraform type —
931
+ # same gate, same feature.)
932
+ var.agentcore_harness_execution_role_arn != "" ? [
933
+ {
934
+ # CreateHarness takes executionRoleArn; the caller must be able to
935
+ # pass exactly that role, and only to the bedrock-agentcore service.
936
+ Sid = "AgentCoreHarnessPassExecutionRole"
937
+ Effect = "Allow"
938
+ Action = ["iam:PassRole"]
939
+ Resource = var.agentcore_harness_execution_role_arn
940
+ Condition = {
941
+ StringEquals = {
942
+ "iam:PassedToService" = "bedrock-agentcore.amazonaws.com"
943
+ }
944
+ }
945
+ },
946
+ ] : [],
947
+ )
822
948
 
823
949
  # ---------------------------------------------------------------------------
824
950
  # Group 4: observability — CloudWatch Logs reads, ECS/ALB health reads.
@@ -20,6 +20,17 @@ data "aws_caller_identity" "current" {}
20
20
 
21
21
  locals {
22
22
  okf_efs_vpc_enabled = length(var.okf_efs_subnet_ids) > 0 && length(var.okf_efs_security_group_ids) > 0
23
+ # Handlers that open direct Postgres connections to registered analyst data
24
+ # sources. VPC-attaching them (analyst_egress_subnet_ids) gives their egress
25
+ # the NAT gateway's stable EIP, which external databases behind an IP
26
+ # allowlist can admit — a non-VPC Lambda egresses from the shared AWS pool
27
+ # and can never satisfy such an allowlist.
28
+ analyst_vpc_enabled = length(var.analyst_egress_subnet_ids) > 0 && length(var.analyst_egress_security_group_ids) > 0
29
+ analyst_vpc_handlers = toset([
30
+ "graphql-http",
31
+ "analyst-query-broker",
32
+ "analyst-connection-reconciler",
33
+ ])
23
34
  # Constructed rather than read from aws_apigatewayv2_stage.default.invoke_url
24
35
  # (the two are identical for an HTTP API's $default stage). Reading it off
25
36
  # the stage made every Lambda's env depend on the stage, which forbids the
@@ -0,0 +1,92 @@
1
+ # THINK-193 U8 — external-memory compounding observability.
2
+ #
3
+ # Alarms here fire from metrics that EXIST without any log-group management:
4
+ # - AWS/Lambda Errors for the memory-stage worker and the retraction drainer
5
+ # (handler crashes, including ones that never reach a log line);
6
+ # - the wiki-compile DLQ depth (the only memory-path Lambda with an
7
+ # on_failure destination).
8
+ #
9
+ # DELIBERATELY NOT HERE — log-metric filters over the drainer's structured
10
+ # summary line and the worker's "status=failed" line. They require the
11
+ # /aws/lambda/<function> log groups, which only exist after a function's first
12
+ # invocation: PutMetricFilter failed with ResourceNotFoundException on customer
13
+ # stages taking a fresh release (dev passed only because its groups already
14
+ # existed). Managing the groups in Terraform instead needs a coordinated state
15
+ # import on every stage that already has the implicit group, so it is a
16
+ # follow-up. Until then, drainer errors / dead-letters and stage failures are
17
+ # observable through the structured logs and
18
+ # packages/api/scripts/external-memory-readiness.ts (which reports due,
19
+ # failed, and dead-lettered attempts directly from the ledger).
20
+ #
21
+ # Also runbook-only (no metric exists): source checkpoint age, graph-to-wiki
22
+ # lag, evidence lag, deferred identity count/age.
23
+ #
24
+ # alarm_actions are empty: the only SNS topic in this module
25
+ # (aws_sns_topic.cost_alerts) is the cost-alert channel — wiring memory alarms
26
+ # into it would misroute pages. Operators subscribe a topic when the rollout
27
+ # needs paging.
28
+
29
+
30
+ resource "aws_cloudwatch_metric_alarm" "memory_stage_worker_lambda_errors" {
31
+ count = local.deploy_lambda_handlers ? 1 : 0
32
+
33
+ alarm_name = "thinkwork-${var.stage}-memory-stage-worker-lambda-errors"
34
+ alarm_description = "memory-stage-worker Lambda crashed (unhandled error, retries pinned to 0, no failure destination) — the parked workflow run will need a redrive."
35
+ namespace = "AWS/Lambda"
36
+ metric_name = "Errors"
37
+ statistic = "Sum"
38
+ period = 300
39
+ evaluation_periods = 1
40
+ threshold = 1
41
+ comparison_operator = "GreaterThanOrEqualToThreshold"
42
+ treat_missing_data = "notBreaching"
43
+ alarm_actions = []
44
+
45
+ dimensions = {
46
+ FunctionName = aws_lambda_function.handler["memory-stage-worker"].function_name
47
+ }
48
+ }
49
+
50
+ resource "aws_cloudwatch_metric_alarm" "memory_retraction_drainer_lambda_errors" {
51
+ count = local.deploy_lambda_handlers ? 1 : 0
52
+
53
+ alarm_name = "thinkwork-${var.stage}-memory-retraction-drainer-lambda-errors"
54
+ alarm_description = "memory-retraction-drainer Lambda crashed before emitting its summary line — due retraction work is stalled until the next scheduled tick succeeds."
55
+ namespace = "AWS/Lambda"
56
+ metric_name = "Errors"
57
+ statistic = "Sum"
58
+ period = 300
59
+ evaluation_periods = 1
60
+ threshold = 1
61
+ comparison_operator = "GreaterThanOrEqualToThreshold"
62
+ treat_missing_data = "notBreaching"
63
+ alarm_actions = []
64
+
65
+ dimensions = {
66
+ FunctionName = aws_lambda_function.handler["memory-retraction-drainer"].function_name
67
+ }
68
+ }
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # wiki-compile DLQ depth (graph → wiki handoff failures)
72
+ # ---------------------------------------------------------------------------
73
+
74
+ resource "aws_cloudwatch_metric_alarm" "wiki_compile_dlq_depth" {
75
+ count = local.deploy_lambda_handlers ? 1 : 0
76
+
77
+ alarm_name = "thinkwork-${var.stage}-wiki-compile-dlq-depth"
78
+ alarm_description = "wiki-compile DLQ has messages — a compile invocation crashed before the job ledger recorded an outcome; canonical pages are stale until replayed."
79
+ namespace = "AWS/SQS"
80
+ metric_name = "ApproximateNumberOfMessagesVisible"
81
+ statistic = "Maximum"
82
+ period = 60
83
+ evaluation_periods = 1
84
+ threshold = 1
85
+ comparison_operator = "GreaterThanOrEqualToThreshold"
86
+ treat_missing_data = "notBreaching"
87
+ alarm_actions = []
88
+
89
+ dimensions = {
90
+ QueueName = aws_sqs_queue.wiki_compile_dlq[0].name
91
+ }
92
+ }
@@ -0,0 +1,46 @@
1
+ ################################################################################
2
+ # Microsoft Teams app credentials
3
+ #
4
+ # Stored in Secrets Manager as a JSON blob with two fields:
5
+ #
6
+ # {
7
+ # "app_id": "Teams (Entra) app registration / bot app id",
8
+ # "client_secret": "Entra app client secret"
9
+ # }
10
+ #
11
+ # The Teams install + account-link handlers receive only this secret ARN in
12
+ # Lambda configuration. The client_secret doubles as the HMAC signing key for
13
+ # install state and account-link tokens — transport-only material that must
14
+ # never appear in logs or responses. The shared Lambda role already has
15
+ # access to the `thinkwork/*` prefix, so no additional IAM attachment is
16
+ # needed (same as the Slack app credentials secret).
17
+ #
18
+ # Operators populate the real value out-of-band. Terraform creates an initial
19
+ # empty version so Lambdas fail with a clear missing-field error before setup,
20
+ # and lifecycle.ignore_changes prevents later applies from overwriting rotated
21
+ # credentials.
22
+ ################################################################################
23
+
24
+ resource "aws_secretsmanager_secret" "msteams_app_credentials" {
25
+ count = var.enable_msteams_app ? 1 : 0
26
+ name = "thinkwork/${var.stage}/msteams/app"
27
+ description = "Microsoft Teams app credentials (app_id, client_secret). Populate via Secrets Manager; never via tfvars."
28
+ tags = {
29
+ Name = "thinkwork-${var.stage}-msteams-app"
30
+ Stage = var.stage
31
+ Provider = "msteams"
32
+ }
33
+ }
34
+
35
+ resource "aws_secretsmanager_secret_version" "msteams_app_credentials_initial" {
36
+ count = var.enable_msteams_app ? 1 : 0
37
+ secret_id = aws_secretsmanager_secret.msteams_app_credentials[0].id
38
+ secret_string = jsonencode({
39
+ app_id = ""
40
+ client_secret = ""
41
+ })
42
+
43
+ lifecycle {
44
+ ignore_changes = [secret_string]
45
+ }
46
+ }
@@ -146,6 +146,12 @@ variable "kb_service_role_arn" {
146
146
  type = string
147
147
  }
148
148
 
149
+ variable "agentcore_harness_execution_role_arn" {
150
+ description = "AgentCore Harness execution role ARN (THINK-311 U4). Empty string (harness module disabled) omits the Harness invoker grants from the grouped ai policy."
151
+ type = string
152
+ default = ""
153
+ }
154
+
149
155
  variable "certificate_arn" {
150
156
  description = "ACM certificate ARN for custom API domain"
151
157
  type = string
@@ -238,6 +244,18 @@ variable "brain_dream_state_schedule_expression" {
238
244
  default = "cron(0 5 * * ? *)"
239
245
  }
240
246
 
247
+ variable "retry_dispatcher_enabled" {
248
+ description = "Enable the recurring retry-dispatcher schedule (THINK-307). Never enable on a stage before THINK-305, THINK-308, and THINK-309 are live there (parent plan deploy ordering); manual invokes work regardless."
249
+ type = bool
250
+ default = false
251
+ }
252
+
253
+ variable "stall_threshold_minutes" {
254
+ description = "Minutes of turn inactivity before the stall monitor marks a running turn timed_out; the retry dispatcher reads the same knob as its origin-freshness guard (THINK-306/THINK-307 — one knob, two consumers)."
255
+ type = number
256
+ default = 5
257
+ }
258
+
241
259
  variable "extension_proxy_signing_secret" {
242
260
  description = "Shared HMAC secret used by the generic Admin extension proxy to sign actor context for extension backends."
243
261
  type = string
@@ -273,6 +291,18 @@ variable "memory_engine" {
273
291
  }
274
292
  }
275
293
 
294
+ variable "analyst_egress_subnet_ids" {
295
+ description = "Private (NAT-routed) subnet IDs for VPC-attaching the analyst data-path handlers (graphql-http, analyst-query-broker, analyst-connection-reconciler) so external Postgres sources can allowlist the stack's stable NAT egress IP. Leave empty to keep the handlers outside any VPC."
296
+ type = list(string)
297
+ default = []
298
+ }
299
+
300
+ variable "analyst_egress_security_group_ids" {
301
+ description = "Security group IDs for the VPC-attached analyst data-path handlers. Leave empty to keep the handlers outside any VPC."
302
+ type = list(string)
303
+ default = []
304
+ }
305
+
276
306
  variable "okf_efs_subnet_ids" {
277
307
  description = "Subnet IDs for the OKF EFS hydrator Lambda VPC attachment. Leave empty to deploy the hydrator without an EFS mount."
278
308
  type = list(string)
@@ -435,6 +465,12 @@ variable "enable_slack_workspace_app" {
435
465
  default = true
436
466
  }
437
467
 
468
+ variable "enable_msteams_app" {
469
+ description = "Provision Microsoft Teams app Lambdas, API routes, and credentials placeholder secret."
470
+ type = bool
471
+ default = false
472
+ }
473
+
438
474
  variable "appsync_realtime_url" {
439
475
  description = "AppSync realtime/WebSocket endpoint URL"
440
476
  type = string
@@ -790,6 +826,12 @@ variable "analyst_policy_source" {
790
826
  }
791
827
  }
792
828
 
829
+ variable "capability_self_extension_tenants" {
830
+ description = "Governed autonomy opt-in: comma/space-separated tenant ids allowed to autonomously self-extend capabilities (self_admit_connection / self_approve_routine). Empty (default) = NO tenant enabled; self-extension ships inert and fail-closed. Read via getConfig() in capability-control-service."
831
+ type = string
832
+ default = ""
833
+ }
834
+
793
835
  variable "analyst_db_cluster_resource_id" {
794
836
  description = "Immutable RDS cluster resource ID (cluster-XXXX / db-XXXX) for the stage database (THINK-229 U1). Wired from `module.database.cluster_resource_id`. Keys the broker's `rds-db:connect` IAM grant — the rds-db ARN format requires the resource ID, not the cluster ARN — and gates the IAM connect env on the broker Lambda: when empty, the broker stays on the password path."
795
837
  type = string
@@ -801,3 +843,53 @@ variable "artifact_delivery_from_email" {
801
843
  type = string
802
844
  default = ""
803
845
  }
846
+
847
+ variable "enable_capability_broker" {
848
+ description = "THINK-280 U8 rollout gate (default off). When true, the governed capability runtime's external MCP search facade (/mcp/capabilities) and the Inspector governed-runtime operation layer are enabled for the stage. The external-search leg is the LAST rollout stage (shadow → internal search → private broker dogfood → promotion/tracer → external search): keep this false until the earlier stages are proven for the stage. When false every U8 surface is inert and returns no data — the handler and route still deploy, but read nothing."
849
+ type = bool
850
+ default = false
851
+ }
852
+
853
+ # THINK-280 — sandbox provisioning + capability broker wiring. -----------------
854
+ variable "agentcore_admin_lambda_arn" {
855
+ description = "ARN of the agentcore-admin Lambda (AGENTCORE_ADMIN_LAMBDA_ARN on graphql-http). createTenant RequestResponse-invokes it. Empty when the Lambda is not deployed."
856
+ type = string
857
+ default = ""
858
+ }
859
+
860
+ variable "agentcore_admin_token" {
861
+ description = "Bearer token graphql-http presents when invoking agentcore-admin (AGENTCORE_ADMIN_TOKEN). Must match the token the admin Lambda validates. Empty when not deployed."
862
+ type = string
863
+ sensitive = true
864
+ default = ""
865
+ }
866
+
867
+ variable "capability_broker_session_table" {
868
+ description = "DynamoDB broker session table name (CAPABILITY_BROKER_SESSION_TABLE). Empty when the broker is disabled."
869
+ type = string
870
+ default = ""
871
+ }
872
+
873
+ variable "capability_broker_session_table_arn" {
874
+ description = "ARN of the broker session table — grants routine-exec-git DynamoDB access to mint/advance PoP sessions. Empty when the broker is disabled (no grant)."
875
+ type = string
876
+ default = ""
877
+ }
878
+
879
+ variable "capability_broker_audience" {
880
+ description = "Capability broker PoP audience (CAPABILITY_BROKER_AUDIENCE). Empty when the broker is disabled."
881
+ type = string
882
+ default = ""
883
+ }
884
+
885
+ variable "capability_broker_api_id" {
886
+ description = "Capability broker private REST API id (CAPABILITY_BROKER_API_ID). Empty when the broker is disabled."
887
+ type = string
888
+ default = ""
889
+ }
890
+
891
+ variable "capability_broker_vpce_dns" {
892
+ description = "Capability broker execute-api VPCE DNS name (CAPABILITY_BROKER_VPCE_DNS). Empty when the broker is disabled."
893
+ type = string
894
+ default = ""
895
+ }
@@ -0,0 +1,47 @@
1
+ # THINK-193 U8 — workflow-interpreter execution failure alarms.
2
+ #
3
+ # Terminal FAILED/TIMED_OUT/ABORTED executions are projected onto the run
4
+ # row by workflow-execution-callback (DB, not a parseable log line), so the
5
+ # reliable infrastructure signal is the Step Functions service metrics on
6
+ # the interpreter state machine itself. Memory workflows (personal +
7
+ # shared) execute through this machine — an execution failure here is the
8
+ # "workflow-interpreter execution failures" rollout gate signal.
9
+ #
10
+ # alarm_actions are empty: no paging SNS topic exists in this module.
11
+ # Operators subscribe one when the rollout needs paging.
12
+
13
+ resource "aws_cloudwatch_metric_alarm" "interpreter_executions_failed" {
14
+ alarm_name = "thinkwork-${var.stage}-workflow-interpreter-executions-failed"
15
+ alarm_description = "Workflow-interpreter Step Functions executions FAILED — check the run's timeline (workflow_step_failed events) and the execution history."
16
+ namespace = "AWS/States"
17
+ metric_name = "ExecutionsFailed"
18
+ statistic = "Sum"
19
+ period = 300
20
+ evaluation_periods = 1
21
+ threshold = 1
22
+ comparison_operator = "GreaterThanOrEqualToThreshold"
23
+ treat_missing_data = "notBreaching"
24
+ alarm_actions = []
25
+
26
+ dimensions = {
27
+ StateMachineArn = aws_sfn_state_machine.interpreter.arn
28
+ }
29
+ }
30
+
31
+ resource "aws_cloudwatch_metric_alarm" "interpreter_executions_timed_out" {
32
+ alarm_name = "thinkwork-${var.stage}-workflow-interpreter-executions-timed-out"
33
+ alarm_description = "Workflow-interpreter Step Functions executions TIMED_OUT — a parked task token (approval / memory stage) was never resolved within the execution timeout."
34
+ namespace = "AWS/States"
35
+ metric_name = "ExecutionsTimedOut"
36
+ statistic = "Sum"
37
+ period = 300
38
+ evaluation_periods = 1
39
+ threshold = 1
40
+ comparison_operator = "GreaterThanOrEqualToThreshold"
41
+ treat_missing_data = "notBreaching"
42
+ alarm_actions = []
43
+
44
+ dimensions = {
45
+ StateMachineArn = aws_sfn_state_machine.interpreter.arn
46
+ }
47
+ }
@@ -211,8 +211,8 @@ resource "aws_iam_role_policy" "execution_states" {
211
211
  #
212
212
  # See KTD1 for the loop shape. Directives are the FROZEN protocol the
213
213
  # step-dispatch Lambda emits: dispatch_agent, wait_until, execute_step,
214
- # await_approval, continue, rollover, terminal_success, terminal_failure,
215
- # terminal_canceled.
214
+ # await_approval, await_memory_stage, continue, rollover, terminal_success,
215
+ # terminal_failure, terminal_canceled.
216
216
  ################################################################################
217
217
 
218
218
  resource "aws_sfn_state_machine" "interpreter" {
@@ -254,6 +254,7 @@ resource "aws_sfn_state_machine" "interpreter" {
254
254
  { Variable = "$.directive", StringEquals = "wait_until", Next = "WaitState" },
255
255
  { Variable = "$.directive", StringEquals = "execute_step", Next = "ExecuteStep" },
256
256
  { Variable = "$.directive", StringEquals = "await_approval", Next = "ApprovalStep" },
257
+ { Variable = "$.directive", StringEquals = "await_memory_stage", Next = "MemoryStageStep" },
257
258
  { Variable = "$.directive", StringEquals = "continue", Next = "LoadNextStep" },
258
259
  { Variable = "$.directive", StringEquals = "rollover", Next = "RolloverStart" },
259
260
  { Variable = "$.directive", StringEquals = "terminal_success", Next = "SucceedState" },
@@ -369,6 +370,46 @@ resource "aws_sfn_state_machine" "interpreter" {
369
370
  Next = "DirectiveChoice"
370
371
  }
371
372
 
373
+ # ---- Memory-stage step: park on a task token while the async memory-
374
+ # stage worker runs one pipeline stage; the await phase Event-invokes the
375
+ # worker, which resumes the token via SendTaskSuccess with a result JSON.
376
+ MemoryStageStep = {
377
+ Type = "Task"
378
+ Resource = "arn:aws:states:::lambda:invoke.waitForTaskToken"
379
+ Parameters = {
380
+ FunctionName = local.step_dispatch_lambda_arn
381
+ Payload = {
382
+ phase = "await_memory_stage"
383
+ "cursor.$" = "$.cursor"
384
+ "taskToken.$" = "$$.Task.Token"
385
+ }
386
+ }
387
+ ResultPath = "$.result"
388
+ TimeoutSeconds = 7200
389
+ HeartbeatSeconds = 3600
390
+ Catch = [{
391
+ ErrorEquals = ["States.Timeout"]
392
+ Next = "FailState"
393
+ }]
394
+ Next = "RecordMemoryStage"
395
+ }
396
+
397
+ RecordMemoryStage = {
398
+ Type = "Task"
399
+ Resource = "arn:aws:states:::lambda:invoke"
400
+ Parameters = {
401
+ FunctionName = local.step_dispatch_lambda_arn
402
+ Payload = {
403
+ phase = "record_memory_stage"
404
+ "cursor.$" = "$.cursor"
405
+ "result.$" = "$.result"
406
+ }
407
+ }
408
+ OutputPath = "$.Payload"
409
+ Retry = local.lambda_invoke_retry
410
+ Next = "DirectiveChoice"
411
+ }
412
+
372
413
  # ---- Record the step outcome, evaluate policy, emit the next directive -
373
414
  RecordAdvance = {
374
415
  Type = "Task"
@@ -7,9 +7,12 @@
7
7
  ################################################################################
8
8
 
9
9
  locals {
10
- create = var.create_cognito
11
- create_pre_signup = local.create && var.pre_signup_lambda_zip != ""
12
- create_custom_auth = local.create && var.custom_auth_lambda_zip != ""
10
+ create = var.create_cognito
11
+ create_pre_signup = local.create && var.pre_signup_lambda_zip != ""
12
+ use_local_custom_auth_artifact = trimspace(var.custom_auth_lambda_zip) != ""
13
+ use_remote_custom_auth_artifact = trimspace(var.custom_auth_lambda_s3_bucket) != ""
14
+ custom_auth_artifact_count = (local.use_local_custom_auth_artifact ? 1 : 0) + (local.use_remote_custom_auth_artifact ? 1 : 0)
15
+ create_custom_auth = local.create && local.custom_auth_artifact_count == 1
13
16
 
14
17
  user_pool_id = local.create ? aws_cognito_user_pool.main[0].id : var.existing_user_pool_id
15
18
  user_pool_arn = local.create ? aws_cognito_user_pool.main[0].arn : var.existing_user_pool_arn
@@ -24,6 +27,22 @@ locals {
24
27
  }
25
28
  }
26
29
 
30
+ resource "terraform_data" "custom_auth_artifact_validation" {
31
+ input = local.custom_auth_artifact_count
32
+
33
+ lifecycle {
34
+ precondition {
35
+ condition = local.custom_auth_artifact_count <= 1
36
+ error_message = "Set only one Cognito custom-auth Lambda artifact source: custom_auth_lambda_zip or custom_auth_lambda_s3_bucket/custom_auth_lambda_s3_key."
37
+ }
38
+
39
+ precondition {
40
+ condition = !local.use_remote_custom_auth_artifact || trimspace(var.custom_auth_lambda_s3_key) != ""
41
+ error_message = "custom_auth_lambda_s3_key must be set when custom_auth_lambda_s3_bucket is set."
42
+ }
43
+ }
44
+ }
45
+
27
46
  data "aws_caller_identity" "current" {}
28
47
 
29
48
  ################################################################################
@@ -97,7 +116,10 @@ resource "aws_lambda_permission" "cognito_pre_signup" {
97
116
 
98
117
  resource "aws_iam_role" "custom_auth" {
99
118
  count = local.create_custom_auth ? 1 : 0
100
- name = "thinkwork-${var.stage}-cognito-custom-auth-role"
119
+ # Release deployments use a distinct managed name so the first convergent
120
+ # apply does not collide with custom-auth Lambdas created during the legacy
121
+ # manual rollout. Local source deployments retain the original name.
122
+ name = local.use_remote_custom_auth_artifact ? "thinkwork-${var.stage}-cognito-custom-auth-release-role" : "thinkwork-${var.stage}-cognito-custom-auth-role"
101
123
 
102
124
  assume_role_policy = jsonencode({
103
125
  Version = "2012-10-17"
@@ -117,8 +139,10 @@ resource "aws_iam_role_policy_attachment" "custom_auth_basic" {
117
139
 
118
140
  resource "aws_lambda_function" "custom_auth" {
119
141
  count = local.create_custom_auth ? 1 : 0
120
- function_name = "thinkwork-${var.stage}-cognito-custom-auth"
121
- filename = var.custom_auth_lambda_zip
142
+ function_name = local.use_remote_custom_auth_artifact ? "thinkwork-${var.stage}-cognito-custom-auth-release" : "thinkwork-${var.stage}-cognito-custom-auth"
143
+ filename = local.use_local_custom_auth_artifact ? var.custom_auth_lambda_zip : null
144
+ s3_bucket = local.use_remote_custom_auth_artifact ? var.custom_auth_lambda_s3_bucket : null
145
+ s3_key = local.use_remote_custom_auth_artifact ? var.custom_auth_lambda_s3_key : null
122
146
  handler = "index.handler"
123
147
  runtime = "nodejs20.x"
124
148
  timeout = 10
@@ -130,12 +154,12 @@ resource "aws_lambda_function" "custom_auth" {
130
154
  }
131
155
  }
132
156
 
133
- source_code_hash = filebase64sha256(var.custom_auth_lambda_zip)
157
+ source_code_hash = local.use_local_custom_auth_artifact ? filebase64sha256(var.custom_auth_lambda_zip) : null
134
158
 
135
159
  lifecycle {
136
160
  precondition {
137
161
  condition = var.api_auth_secret != ""
138
- error_message = "custom_auth_lambda_zip requires api_auth_secret so Cognito custom-auth challenges can be verified."
162
+ error_message = "A Cognito custom-auth Lambda artifact requires api_auth_secret so custom-auth challenges can be verified."
139
163
  }
140
164
  }
141
165
  }