thinkwork-cli 0.12.17 → 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
@@ -30,10 +30,13 @@ locals {
30
30
  "oauth-authorize",
31
31
  "oauth-callback",
32
32
  "slack-events",
33
- "slack-slash-command",
34
- "slack-interactivity",
35
33
  "slack-oauth-install",
36
34
  ],
35
+ var.enable_msteams_app ? [] : [
36
+ "msteams-install-start",
37
+ "msteams-install-complete",
38
+ "msteams-account-link-complete",
39
+ ],
37
40
  )
38
41
 
39
42
  # Config-class configuration shared by all API handlers. As of plan
@@ -58,6 +61,12 @@ locals {
58
61
  # signed sidecar block authoritative (budgets/policyClaims flow). Flip
59
62
  # ONLY after clean shadow parity on live traffic.
60
63
  ANALYST_POLICY_SOURCE = var.analyst_policy_source
64
+ # Governed autonomy — per-tenant opt-in allowlist for autonomous capability
65
+ # self-extension (the two self_admit_connection / self_approve_routine
66
+ # actions on capability-control-service). Comma/space-separated tenant ids;
67
+ # empty (default) = NO tenant enabled, so self-extension ships inert and
68
+ # fail-closed. Read via getConfig() (env-wins) in isSelfExtensionEnabled.
69
+ CAPABILITY_SELF_EXTENSION_TENANTS = var.capability_self_extension_tenants
61
70
  # THINK-230 — the operator-facing provisionAnalystConnector mutation runs
62
71
  # the analyst connector provisioning ceremony inside graphql-http, so the
63
72
  # shared api handlers read the same broker-secret ARN + rds_iam connect
@@ -74,6 +83,11 @@ locals {
74
83
  DATABASE_SECRET_ARN = var.graphql_db_secret_arn
75
84
  DATABASE_HOST = var.db_cluster_endpoint
76
85
  DATABASE_NAME = var.database_name
86
+ # THINK-280 U8: the LAST rollout gate. External capability search + the
87
+ # Inspector governed-runtime operation layer stay inert until the broker is
88
+ # enabled for the stage. Off by default — read via getConfig() (env-wins).
89
+ CAPABILITY_EXTERNAL_SEARCH_ENABLED = var.enable_capability_broker ? "true" : "false"
90
+ ENABLE_CAPABILITY_BROKER = var.enable_capability_broker ? "true" : "false"
77
91
  # BUCKET_NAME and USER_POOL_ID were duplicate aliases of WORKSPACE_BUCKET
78
92
  # and COGNITO_USER_POOL_ID; GRAPHQL_API_KEY duplicated APPSYNC_API_KEY;
79
93
  # THINKWORK_API_SECRET and EMAIL_HMAC_SECRET duplicated API_AUTH_SECRET
@@ -237,6 +251,10 @@ locals {
237
251
  SLACK_APP_CREDENTIALS_SECRET_ARN = var.enable_slack_workspace_app ? aws_secretsmanager_secret.slack_app_credentials[0].arn : ""
238
252
  }
239
253
 
254
+ msteams_handler_env = {
255
+ MSTEAMS_APP_CREDENTIALS_SECRET_ARN = var.enable_msteams_app ? aws_secretsmanager_secret.msteams_app_credentials[0].arn : ""
256
+ }
257
+
240
258
  handler_extra_env = {
241
259
  # Analyst query broker (THINK-228 U3). Reader role + caller credential
242
260
  # secrets, and the workspace bucket's analyst-staging/ prefix for
@@ -302,6 +320,17 @@ locals {
302
320
  THINKWORK_RELEASE_VERSION = var.deployment_release_version
303
321
  THINKWORK_RELEASE_MANIFEST_URL = var.deployment_release_manifest_url
304
322
  THINKWORK_RELEASE_MANIFEST_SHA256 = var.deployment_release_manifest_sha256
323
+ # THINK-280 — createTenant RequestResponse-invokes agentcore-admin to
324
+ # provision the per-tenant sandbox. sandbox-provisioning.ts reads both
325
+ # from process.env directly (the token is a secret → env, never the SSM
326
+ # String doc). Broker-session build reads the three broker values below
327
+ # (broker-session.ts); all EMPTY when the broker is disabled → inert.
328
+ AGENTCORE_ADMIN_LAMBDA_ARN = var.agentcore_admin_lambda_arn
329
+ AGENTCORE_ADMIN_TOKEN = var.agentcore_admin_token
330
+ CAPABILITY_BROKER_AUDIENCE = var.capability_broker_audience
331
+ CAPABILITY_BROKER_API_ID = var.capability_broker_api_id
332
+ CAPABILITY_BROKER_VPCE_DNS = var.capability_broker_vpce_dns
333
+ CAPABILITY_BROKER_SESSION_TABLE = var.capability_broker_session_table
305
334
  }
306
335
  # Compounding Memory compile Lambda. Any Converse-compatible Bedrock
307
336
  # model works; the planner + section-writer cap themselves at ~500
@@ -343,12 +372,25 @@ locals {
343
372
  BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
344
373
  OKF_EFS_ROOT = var.okf_efs_mount_path
345
374
  }
346
- "oauth-authorize" = local.slack_handler_env
347
- "oauth-callback" = local.slack_handler_env
348
- "slack-events" = local.slack_handler_env
349
- "slack-slash-command" = local.slack_handler_env
350
- "slack-interactivity" = local.slack_handler_env
351
- "slack-oauth-install" = local.slack_handler_env
375
+ # THINK-193 U1 (Codex F6): normalized evidence snapshots live in the
376
+ # encrypted brain-artifacts bucket under evidence-snapshots/ — Postgres
377
+ # keeps only the s3:// ref + hash. 30-day lifecycle expiration below.
378
+ "memory-stage-worker" = {
379
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
380
+ }
381
+ # THINK-193 U2 (Codex S1/S2): the retraction drainer performs the
382
+ # versioned evidence-snapshot deletion for source erases under its
383
+ # DEDICATED role (below) — the only place bulk destructive S3 runs.
384
+ "memory-retraction-drainer" = {
385
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
386
+ }
387
+ "oauth-authorize" = local.slack_handler_env
388
+ "oauth-callback" = local.slack_handler_env
389
+ "slack-events" = local.slack_handler_env
390
+ "slack-oauth-install" = local.slack_handler_env
391
+ "msteams-install-start" = local.msteams_handler_env
392
+ "msteams-install-complete" = local.msteams_handler_env
393
+ "msteams-account-link-complete" = local.msteams_handler_env
352
394
  "thread-attachments-finalize" = {
353
395
  REQUESTER_IDLE_MEMORY_LEARNING_ENABLED = tostring(var.requester_idle_memory_learning_enabled)
354
396
  }
@@ -375,6 +417,12 @@ locals {
375
417
  "brain-dream-state" = {
376
418
  BRAIN_DREAM_STATE_ENABLED = tostring(var.brain_dream_state_enabled)
377
419
  }
420
+ # THINK-307 KTD2: the origin-freshness guard reads the same stall
421
+ # threshold as the stall monitor — one operational knob, two consumers,
422
+ # no drift between "stalled" and "recovered".
423
+ "cron-retry-dispatcher" = {
424
+ STALL_THRESHOLD_MINUTES = tostring(var.stall_threshold_minutes)
425
+ }
378
426
  # Bedrock KB provisioning. Per-handler (not common_env) so these don't bloat
379
427
  # the already-near-4KB graphql-http env. Bedrock's RDS-backed KB needs the
380
428
  # cluster ARN + the KB service role (passed at CreateKnowledgeBase time).
@@ -395,6 +443,15 @@ locals {
395
443
  # invocation (never Lambda self-invoke — AWS recursive-loop
396
444
  # detection terminates worker-to-self Event chains).
397
445
  KG_OBS_MAX_CANDIDATES_PER_RUN = var.kg_obs_max_candidates_per_run
446
+ # THINK-193 U4 dead-handoff fix: a successful observations ingest
447
+ # enqueues the tenant graph wiki-compile job inside its own commit
448
+ # and Event-invokes wiki-compile post-commit. WIKI_SOURCE must match
449
+ # the wiki-compile handler's dispatch flag (stage-global); the
450
+ # per-tenant kill switch stays tenants.wiki_compile_enabled. The
451
+ # invoke rides the shared lambda role's existing wiki-compile
452
+ # lambda:InvokeFunction grant (iam-grouped.tf); the function name is
453
+ # derived from STAGE (common_env) at call time.
454
+ WIKI_SOURCE = var.wiki_source
398
455
  }
399
456
  # routine-task-python (Phase B U6) needs the AgentCore code-interpreter
400
457
  # id + the per-stage S3 routine-output bucket. The interpreter id is
@@ -414,6 +471,14 @@ locals {
414
471
  "routine-exec-git" = {
415
472
  SANDBOX_INTERPRETER_ID = var.agentcore_code_interpreter_id
416
473
  ROUTINE_OUTPUT_BUCKET = "thinkwork-${var.stage}-routine-output"
474
+ # THINK-280 U7 — the headless capability executor runs inside
475
+ # routine-exec-git and mints broker sessions. routine-exec-git.ts +
476
+ # capability-headless-executor.ts read all four from process.env; all
477
+ # EMPTY when the broker is disabled → the executor stays inert.
478
+ CAPABILITY_BROKER_SESSION_TABLE = var.capability_broker_session_table
479
+ CAPABILITY_BROKER_AUDIENCE = var.capability_broker_audience
480
+ CAPABILITY_BROKER_API_ID = var.capability_broker_api_id
481
+ CAPABILITY_BROKER_VPCE_DNS = var.capability_broker_vpce_dns
417
482
  }
418
483
  # graphql-http hosts the createRoutine / publishRoutineVersion / etc.
419
484
  # resolvers (Phase B U7) AND the routine-approval-bridge (Phase B
@@ -608,6 +673,8 @@ resource "aws_lambda_function" "handler" {
608
673
  "mcp-user-memory",
609
674
  "mcp-context-engine",
610
675
  "mcp-open-engine",
676
+ # THINK-280 U8: scoped external capability search MCP facade.
677
+ "mcp-capability-search",
611
678
  "activity",
612
679
  "routines",
613
680
  "budgets",
@@ -628,6 +695,10 @@ resource "aws_lambda_function" "handler" {
628
695
  # dispatch can withhold a failing/stale connection loudly.
629
696
  "analyst-connection-reconciler",
630
697
  "cron-stall-monitor",
698
+ # THINK-307: drains retry_queue rows the stall monitor enqueues. Built by
699
+ # scripts/build-lambdas.sh since PRD-09 but first registered here; its
700
+ # schedule ships DISABLED (var.retry_dispatcher_enabled default false).
701
+ "cron-retry-dispatcher",
631
702
  "webhook-crm-opportunity",
632
703
  "webhook-task-event",
633
704
  "workspace-files",
@@ -642,13 +713,28 @@ resource "aws_lambda_function" "handler" {
642
713
  "email-provider-webhook",
643
714
  "email-readiness-probe",
644
715
  "slack-events",
645
- "slack-slash-command",
646
- "slack-interactivity",
647
716
  "slack-oauth-install",
717
+ "msteams-install-start",
718
+ "msteams-install-complete",
719
+ "msteams-account-link-complete",
648
720
  "github-app",
649
721
  "memory",
650
722
  "memory-retain",
651
723
  "brain-dream-state",
724
+ "memory-stage-worker",
725
+ # THINK-193 U2 (Codex P1 #3): scheduled retry drainer for the retraction
726
+ # saga ledger. Claims due/stale memory_retraction_attempts rows with the
727
+ # locked_by + lock_generation fence, processes each via the saga, sweeps
728
+ # exhausted rows to dead_lettered, and emits structured-log metrics.
729
+ # rate(5 minutes) EventBridge Scheduler + MaxRetryAttempts=0 (dedicated
730
+ # resources below) — the next tick IS the retry.
731
+ "memory-retraction-drainer",
732
+ # THINK-193 U3: stalled memory_stage token sweeper — re-invokes the
733
+ # memory-stage-worker for pending/executing tokens past their lease and
734
+ # redrives consumed-but-unsent results; unrecoverable parks get a
735
+ # terminal step event + SendTaskFailure. rate(15 minutes) schedule
736
+ # (dedicated resource below).
737
+ "memory-stage-sweeper",
652
738
  "wiki-compile",
653
739
  "knowledge-graph-observations-ingest",
654
740
  "ontology-scan",
@@ -791,6 +877,14 @@ resource "aws_lambda_function" "handler" {
791
877
  # POSTs one row per agent-session-start. Shared
792
878
  # API_AUTH_SECRET bearer (runtime→API; no tenant OAuth).
793
879
  "manifest-log",
880
+ # Governed capability runtime control-plane service (THINK-280 U2).
881
+ # NO HTTP route: the Pi container invokes it directly (RequestResponse)
882
+ # via lambda:InvokeFunction over the approved in-account path — public
883
+ # execute-api is not reliable from Pi's private VPC. Identity comes
884
+ # exclusively from the Ed25519-signed capability caller context (the
885
+ # CAPABILITY_SIGNING_PUBLIC_KEY in the shared runtime-config document
886
+ # verifies it); plaintext payload fields are never trusted.
887
+ "capability-control-service",
794
888
  # SI-7 catalog-list read endpoint (plan §U15 pt 3/3). The runtime
795
889
  # fetches the allowed builtin-tool slug set once per
796
890
  # session-start + feature-flag-gated enforcement filter drops
@@ -831,9 +925,13 @@ resource "aws_lambda_function" "handler" {
831
925
  ]), toset(local.optional_integration_handler_names)) : toset([])
832
926
 
833
927
  function_name = "thinkwork-${var.stage}-api-${each.key}"
834
- role = aws_iam_role.lambda.arn
835
- handler = "index.handler"
836
- runtime = local.runtime
928
+ # S2 (THINK-193 U2): memory-retraction-drainer runs under a DEDICATED role
929
+ # so its destructive evidence-snapshot S3 capability (version enumeration +
930
+ # bulk version deletion) never leaks into the 90+ handlers on the shared
931
+ # role. Everything else keeps the shared role.
932
+ role = each.key == "memory-retraction-drainer" ? aws_iam_role.memory_retraction_drainer.arn : aws_iam_role.lambda.arn
933
+ handler = "index.handler"
934
+ runtime = local.runtime
837
935
  # Parameters and Secrets extension: container-local cache for the SSM
838
936
  # runtime-config document + Secrets Manager reads (runtime-config.tf).
839
937
  layers = local.api_handler_layers
@@ -859,7 +957,7 @@ resource "aws_lambda_function" "handler" {
859
957
  # validates the agent, builds the AgentCore invoke payload, dispatches
860
958
  # Event-mode, and returns. Setup is ~5s in practice; 60s gives 12×
861
959
  # headroom for transient slowness.
862
- timeout = each.key == "wakeup-processor" ? 300 : each.key == "chat-agent-invoke" ? 60 : each.key == "chat-agent-finalize" ? 60 : each.key == "workspace-event-dispatcher" ? 60 : each.key == "eval-runner" ? 900 : each.key == "eval-worker" ? 240 : each.key == "wiki-compile" ? 480 : each.key == "knowledge-graph-observations-ingest" ? 480 : each.key == "requester-memory-dreaming" ? 300 : each.key == "ontology-scan" ? 300 : each.key == "ontology-reprocess" ? 300 : each.key == "wiki-lint" ? 300 : each.key == "wiki-export" ? 600 : each.key == "okf-materialize" ? 600 : each.key == "okf-efs-refresh" ? 600 : each.key == "wiki-bootstrap-import" ? 900 : each.key == "folder-bundle-import" ? 300 : each.key == "routine-task-python" ? 360 : each.key == "routine-exec-git" ? 360 : each.key == "job-trigger" ? 600 : each.key == "model-converse" ? 60 : each.key == "memory-retain" ? 300 : each.key == "brain-dream-state" ? 900 : each.key == "canvas-refresh" ? 120 : each.key == "document-conformance-judge" ? 300 : each.key == "workflow-step-dispatch" ? 600 : each.key == "workflow-execution-callback" ? 60 : each.key == "workflow-resume" ? 60 : 30
960
+ timeout = each.key == "wakeup-processor" ? 300 : each.key == "chat-agent-invoke" ? 60 : each.key == "chat-agent-finalize" ? 60 : each.key == "workspace-event-dispatcher" ? 60 : each.key == "eval-runner" ? 900 : each.key == "eval-worker" ? 240 : each.key == "wiki-compile" ? 480 : each.key == "knowledge-graph-observations-ingest" ? 480 : each.key == "requester-memory-dreaming" ? 300 : each.key == "ontology-scan" ? 300 : each.key == "ontology-reprocess" ? 300 : each.key == "wiki-lint" ? 300 : each.key == "wiki-export" ? 600 : each.key == "okf-materialize" ? 600 : each.key == "okf-efs-refresh" ? 600 : each.key == "wiki-bootstrap-import" ? 900 : each.key == "folder-bundle-import" ? 300 : each.key == "routine-task-python" ? 360 : each.key == "routine-exec-git" ? 360 : each.key == "job-trigger" ? 600 : each.key == "model-converse" ? 60 : each.key == "memory-retain" ? 300 : each.key == "brain-dream-state" ? 900 : each.key == "memory-stage-worker" ? 900 : each.key == "memory-stage-sweeper" ? 120 : each.key == "memory-retraction-drainer" ? 300 : each.key == "canvas-refresh" ? 120 : each.key == "document-conformance-judge" ? 300 : each.key == "workflow-step-dispatch" ? 600 : each.key == "workflow-execution-callback" ? 60 : each.key == "workflow-resume" ? 60 : 30
863
961
  memory_size = each.key == "graphql-http" ? 512 : each.key == "wakeup-processor" ? 512 : each.key == "workspace-event-dispatcher" ? 512 : each.key == "eval-runner" ? 512 : each.key == "eval-worker" ? 512 : each.key == "wiki-compile" ? 1024 : each.key == "knowledge-graph-observations-ingest" ? 1024 : each.key == "requester-memory-dreaming" ? 512 : each.key == "ontology-scan" ? 512 : each.key == "wiki-export" ? 1024 : each.key == "okf-materialize" ? 1024 : each.key == "okf-efs-refresh" ? 1024 : each.key == "wiki-bootstrap-import" ? 1024 : each.key == "folder-bundle-import" ? 1024 : 256
864
962
 
865
963
  filename = local.use_local_zips ? "${var.lambda_zips_dir}/${each.key}.zip" : null
@@ -904,6 +1002,19 @@ resource "aws_lambda_function" "handler" {
904
1002
  }
905
1003
  }
906
1004
 
1005
+ # Analyst data-path handlers get a stable NAT egress IP when
1006
+ # analyst_egress_subnet_ids is set — external Postgres sources behind an IP
1007
+ # allowlist admit that one EIP. Disjoint from the okf-efs-refresh block
1008
+ # above (a function takes at most one vpc_config).
1009
+ dynamic "vpc_config" {
1010
+ for_each = contains(local.analyst_vpc_handlers, each.key) && local.analyst_vpc_enabled ? [1] : []
1011
+
1012
+ content {
1013
+ subnet_ids = var.analyst_egress_subnet_ids
1014
+ security_group_ids = var.analyst_egress_security_group_ids
1015
+ }
1016
+ }
1017
+
907
1018
  dynamic "file_system_config" {
908
1019
  for_each = each.key == "okf-efs-refresh" && local.okf_efs_vpc_enabled ? [1] : []
909
1020
 
@@ -1061,6 +1172,21 @@ resource "aws_lambda_function_event_invoke_config" "routine_approval_callback" {
1061
1172
  # retain-cost path (Bedrock tokens charged in adapter.retainConversation)
1062
1173
  # is NOT idempotent — retries multiply LLM cost. Per
1063
1174
  # project_async_retry_idempotency_lessons.
1175
+ # memory-stage-worker (THINK-193 U1): Event-invoked by workflow-step-dispatch
1176
+ # AFTER the memory_stage task token is stored. Lambda async retries are
1177
+ # disabled like its memory siblings — the worker's stages are idempotent, but
1178
+ # an automatic re-execution would race the original's token CAS and turn a
1179
+ # completed stage into a spurious failure. A worker that dies without
1180
+ # resuming the token is bounded by the state machine's HeartbeatSeconds
1181
+ # (3600s): the parked step times out and the run fails visibly, and a
1182
+ # re-triggered run self-heals from the durable checkpoint/evidence ledger.
1183
+ resource "aws_lambda_function_event_invoke_config" "memory_stage_worker" {
1184
+ count = local.deploy_lambda_handlers ? 1 : 0
1185
+ function_name = aws_lambda_function.handler["memory-stage-worker"].function_name
1186
+ maximum_retry_attempts = 0
1187
+ maximum_event_age_in_seconds = 3600
1188
+ }
1189
+
1064
1190
  resource "aws_lambda_function_event_invoke_config" "memory_retain" {
1065
1191
  count = local.deploy_lambda_handlers ? 1 : 0
1066
1192
  function_name = aws_lambda_function.handler["memory-retain"].function_name
@@ -1068,6 +1194,132 @@ resource "aws_lambda_function_event_invoke_config" "memory_retain" {
1068
1194
  maximum_event_age_in_seconds = 3600
1069
1195
  }
1070
1196
 
1197
+ # ---------------------------------------------------------------------------
1198
+ # THINK-193 U2 (Codex S2): dedicated IAM role for memory-retraction-drainer.
1199
+ #
1200
+ # The drainer is the ONLY principal allowed to enumerate and bulk-delete
1201
+ # evidence-snapshot object VERSIONS (S1: the brain-artifacts bucket is
1202
+ # versioned; erase completeness requires deleting every noncurrent version
1203
+ # and delete marker under the source prefix). Granting that on the shared
1204
+ # api-lambda role would let every unrelated handler destroy any tenant's
1205
+ # evidence — hence the sibling-role pattern (same as
1206
+ # compliance-anchor-watchdog). The role otherwise carries only what the
1207
+ # drainer actually uses: logs (managed basic policy), Secrets Manager +
1208
+ # SSM runtime-config reads, and conditional KMS for the encrypted bucket.
1209
+ # ---------------------------------------------------------------------------
1210
+
1211
+ resource "aws_iam_role" "memory_retraction_drainer" {
1212
+ name = "thinkwork-${var.stage}-memory-retraction-drainer-role"
1213
+
1214
+ assume_role_policy = jsonencode({
1215
+ Version = "2012-10-17"
1216
+ Statement = [{
1217
+ Effect = "Allow"
1218
+ Principal = { Service = "lambda.amazonaws.com" }
1219
+ Action = "sts:AssumeRole"
1220
+ }]
1221
+ })
1222
+ }
1223
+
1224
+ resource "aws_iam_role_policy_attachment" "memory_retraction_drainer_basic" {
1225
+ role = aws_iam_role.memory_retraction_drainer.name
1226
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
1227
+ }
1228
+
1229
+ resource "aws_iam_role_policy" "memory_retraction_drainer" {
1230
+ name = "memory-retraction-drainer"
1231
+ role = aws_iam_role.memory_retraction_drainer.id
1232
+
1233
+ policy = jsonencode({
1234
+ Version = "2012-10-17"
1235
+ Statement = concat(
1236
+ [
1237
+ # Versioned evidence-snapshot destruction, evidence-snapshots/ only.
1238
+ {
1239
+ Effect = "Allow"
1240
+ Action = [
1241
+ "s3:DeleteObject",
1242
+ "s3:DeleteObjectVersion",
1243
+ ]
1244
+ Resource = "${aws_s3_bucket.brain_artifacts.arn}/evidence-snapshots/*"
1245
+ },
1246
+ {
1247
+ Effect = "Allow"
1248
+ Action = [
1249
+ "s3:ListBucket",
1250
+ "s3:ListBucketVersions",
1251
+ ]
1252
+ Resource = aws_s3_bucket.brain_artifacts.arn
1253
+ Condition = {
1254
+ StringLike = {
1255
+ "s3:prefix" = "evidence-snapshots/*"
1256
+ }
1257
+ }
1258
+ },
1259
+ # Runtime-config document + platform secrets (DATABASE_URL rides the
1260
+ # env during the R8 transition window; the loader prefetches the
1261
+ # api-auth/appsync secrets at cold start).
1262
+ {
1263
+ Effect = "Allow"
1264
+ Action = ["ssm:GetParameter", "ssm:GetParameters"]
1265
+ Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/*"
1266
+ },
1267
+ {
1268
+ Effect = "Allow"
1269
+ Action = ["secretsmanager:GetSecretValue"]
1270
+ Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/*"
1271
+ },
1272
+ ],
1273
+ var.brain_artifacts_kms_key_arn != "" ? [
1274
+ {
1275
+ Effect = "Allow"
1276
+ Action = [
1277
+ "kms:Decrypt",
1278
+ "kms:GenerateDataKey",
1279
+ ]
1280
+ Resource = var.brain_artifacts_kms_key_arn
1281
+ },
1282
+ ] : [],
1283
+ )
1284
+ })
1285
+ }
1286
+
1287
+ # memory-retraction-drainer (THINK-193 U2, Codex P1 #3): the retraction
1288
+ # saga's product-owned retry path. Lambda async retries are disabled — every
1289
+ # transition on memory_retraction_attempts is a fenced CAS, so a duplicate
1290
+ # invocation is harmless but pointless; the rate(5 minutes) schedule below
1291
+ # is the retry. Attempts exceeding max_attempts are swept to dead_lettered
1292
+ # by the drainer itself instead of retrying forever.
1293
+ resource "aws_lambda_function_event_invoke_config" "memory_retraction_drainer" {
1294
+ count = local.deploy_lambda_handlers ? 1 : 0
1295
+ function_name = aws_lambda_function.handler["memory-retraction-drainer"].function_name
1296
+ maximum_retry_attempts = 0
1297
+ maximum_event_age_in_seconds = 3600
1298
+ }
1299
+
1300
+ resource "aws_scheduler_schedule" "memory_retraction_drainer" {
1301
+ count = local.deploy_lambda_handlers ? 1 : 0
1302
+
1303
+ name = "thinkwork-${var.stage}-memory-retraction-drainer"
1304
+ group_name = "default"
1305
+ schedule_expression = "rate(5 minutes)"
1306
+ state = "ENABLED"
1307
+
1308
+ flexible_time_window {
1309
+ mode = "OFF"
1310
+ }
1311
+
1312
+ target {
1313
+ arn = aws_lambda_function.handler["memory-retraction-drainer"].arn
1314
+ role_arn = aws_iam_role.scheduler.arn
1315
+ input = jsonencode({ limit = 25 })
1316
+
1317
+ retry_policy {
1318
+ maximum_retry_attempts = 0
1319
+ }
1320
+ }
1321
+ }
1322
+
1071
1323
  # Product-owned retry path for memory-retain. Lambda async retries remain
1072
1324
  # disabled above; this scheduled drain claims due rows from
1073
1325
  # memory_retain_attempts and replays bounded retry payloads idempotently.
@@ -1311,6 +1563,11 @@ locals {
1311
1563
  "ANY /mcp/user-memory" = "mcp-user-memory"
1312
1564
  "ANY /mcp/context-engine" = "mcp-context-engine"
1313
1565
  "ANY /mcp/open-engine" = "mcp-open-engine"
1566
+ # THINK-280 U8: scoped external capability search facade. The handler is
1567
+ # inert unless CAPABILITY_EXTERNAL_SEARCH_ENABLED (gated on
1568
+ # enable_capability_broker) is on — routing it while disabled returns an
1569
+ # empty, no-data projection.
1570
+ "ANY /mcp/capabilities" = "mcp-capability-search"
1314
1571
 
1315
1572
  # Brain v0 service-auth writeback.
1316
1573
  "POST /api/brain/agent-write" = "brain-agent-write"
@@ -1419,11 +1676,18 @@ locals {
1419
1676
  # Slack workspace app ingress. These unauthenticated public endpoints
1420
1677
  # verify Slack signatures in handler code before any tenant work happens.
1421
1678
  "POST /slack/events" = "slack-events"
1422
- "POST /slack/slash-command" = "slack-slash-command"
1423
- "POST /slack/interactivity" = "slack-interactivity"
1424
1679
  "GET /slack/oauth/install" = "slack-oauth-install"
1425
1680
  "POST /slack/oauth/install" = "slack-oauth-install"
1426
1681
 
1682
+ # Microsoft Teams install + account-link ingress. Like the Slack block
1683
+ # above, these routes have no gateway authorizer: install/start and
1684
+ # account-link/complete verify the Cognito JWT in-handler, and
1685
+ # install/complete (the Microsoft admin-consent redirect) verifies the
1686
+ # HMAC-signed install state in-handler before any tenant work happens.
1687
+ "POST /msteams/install/start" = "msteams-install-start"
1688
+ "GET /msteams/install/complete" = "msteams-install-complete"
1689
+ "POST /msteams/account-link/complete" = "msteams-account-link-complete"
1690
+
1427
1691
  # Memory
1428
1692
  "ANY /api/memory/{proxy+}" = "memory"
1429
1693
 
@@ -1740,6 +2004,25 @@ resource "aws_scheduler_schedule" "requester_memory_dreaming" {
1740
2004
  # the audit log.
1741
2005
  # ---------------------------------------------------------------------------
1742
2006
 
2007
+ # THINK-193 U3: memory_stage stall recovery — every 15 minutes, bounded batch.
2008
+ resource "aws_scheduler_schedule" "memory_stage_sweeper" {
2009
+ count = local.deploy_lambda_handlers ? 1 : 0
2010
+
2011
+ name = "thinkwork-${var.stage}-memory-stage-sweeper"
2012
+ group_name = "default"
2013
+ schedule_expression = "rate(15 minutes)"
2014
+ state = "ENABLED"
2015
+
2016
+ flexible_time_window {
2017
+ mode = "OFF"
2018
+ }
2019
+
2020
+ target {
2021
+ arn = aws_lambda_function.handler["memory-stage-sweeper"].arn
2022
+ role_arn = aws_iam_role.scheduler.arn
2023
+ }
2024
+ }
2025
+
1743
2026
  resource "aws_scheduler_schedule" "mcp_approval_sweeper" {
1744
2027
  count = local.deploy_lambda_handlers ? 1 : 0
1745
2028
 
@@ -2199,6 +2482,47 @@ resource "aws_scheduler_schedule" "stall_monitor" {
2199
2482
  }
2200
2483
  }
2201
2484
 
2485
+ # ---------------------------------------------------------------------------
2486
+ # Retry dispatcher — drains retry_queue rows the stall monitor enqueues and
2487
+ # re-runs genuinely stalled turns (THINK-307). First-ever scheduling of this
2488
+ # handler; state is var-driven and defaults DISABLED — never enable on a
2489
+ # stage before THINK-305/308/309 are live there (parent plan deploy ordering).
2490
+ # The 1-minute rate is fixed by design: the queue's own scheduled_at does the
2491
+ # pacing. Scheduler retries stay at 0 — the next 1-minute tick IS the retry.
2492
+ # ---------------------------------------------------------------------------
2493
+
2494
+ # Lambda async retries are disabled (mirrors memory-retraction-drainer): the
2495
+ # claim is a CAS UPDATE so a duplicate invocation only picks up new rows, and
2496
+ # the next 1-minute tick is the retry.
2497
+ resource "aws_lambda_function_event_invoke_config" "retry_dispatcher" {
2498
+ count = local.deploy_lambda_handlers ? 1 : 0
2499
+ function_name = aws_lambda_function.handler["cron-retry-dispatcher"].function_name
2500
+ maximum_retry_attempts = 0
2501
+ maximum_event_age_in_seconds = 3600
2502
+ }
2503
+
2504
+ resource "aws_scheduler_schedule" "retry_dispatcher" {
2505
+ count = local.deploy_lambda_handlers ? 1 : 0
2506
+
2507
+ name = "thinkwork-${var.stage}-retry-dispatcher"
2508
+ group_name = "default"
2509
+ schedule_expression = "rate(1 minutes)"
2510
+ state = var.retry_dispatcher_enabled ? "ENABLED" : "DISABLED"
2511
+
2512
+ flexible_time_window {
2513
+ mode = "OFF"
2514
+ }
2515
+
2516
+ target {
2517
+ arn = aws_lambda_function.handler["cron-retry-dispatcher"].arn
2518
+ role_arn = aws_iam_role.scheduler.arn
2519
+
2520
+ retry_policy {
2521
+ maximum_retry_attempts = 0
2522
+ }
2523
+ }
2524
+ }
2525
+
2202
2526
 
2203
2527
  # ---------------------------------------------------------------------------
2204
2528
  # Compounding Memory — nightly hygiene + export
@@ -2447,6 +2771,27 @@ resource "aws_s3_bucket_lifecycle_configuration" "brain_artifacts" {
2447
2771
  }
2448
2772
  }
2449
2773
 
2774
+ # THINK-193 U1 (Codex F6): normalized evidence snapshots are short-lived
2775
+ # working copies — the durable record is the evidence row (hash + ref) and
2776
+ # the Hindsight projection. Expire content after 30 days; the app mirrors
2777
+ # this via memory_evidence_items.snapshot_expires_at.
2778
+ rule {
2779
+ id = "expire-evidence-snapshots"
2780
+ status = "Enabled"
2781
+
2782
+ filter {
2783
+ prefix = "evidence-snapshots/"
2784
+ }
2785
+
2786
+ expiration {
2787
+ days = 30
2788
+ }
2789
+
2790
+ noncurrent_version_expiration {
2791
+ noncurrent_days = 30
2792
+ }
2793
+ }
2794
+
2450
2795
  rule {
2451
2796
  id = "exports-expiration"
2452
2797
  status = "Enabled"