thinkwork-cli 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/{api-client-JUBCQQDF.js → api-client-JYZXYQJR.js} +3 -1
  2. package/dist/bowser-6Z6JFAIR.js +2822 -0
  3. package/dist/chunk-DV3MRBR5.js +43 -0
  4. package/dist/chunk-DVPBI5HM.js +1728 -0
  5. package/dist/chunk-EI5ZX7CN.js +522 -0
  6. package/dist/chunk-EUJ7X6YD.js +126 -0
  7. package/dist/chunk-GG5WKBOX.js +436 -0
  8. package/dist/chunk-I64EZSU5.js +358 -0
  9. package/dist/chunk-ILZKG5WV.js +6151 -0
  10. package/dist/chunk-KMIJ5KGT.js +128 -0
  11. package/dist/chunk-OCPHU34C.js +996 -0
  12. package/dist/chunk-QOERT44A.js +3286 -0
  13. package/dist/chunk-XYM7PE2A.js +2264 -0
  14. package/dist/{chunk-STY44FQO.js → chunk-Z2F6RJFR.js} +12 -18
  15. package/dist/cli.js +72564 -18884
  16. package/dist/dist-es-CRV46D5B.js +315 -0
  17. package/dist/dist-es-DCY4IC3Y.js +2000 -0
  18. package/dist/dist-es-ENBUC3JP.js +14726 -0
  19. package/dist/dist-es-G54LBMRN.js +48 -0
  20. package/dist/dist-es-G7PTG75M.js +176 -0
  21. package/dist/dist-es-GKDBE7E6.js +88 -0
  22. package/dist/dist-es-HY3535OI.js +70 -0
  23. package/dist/dist-es-KL7Y5NB2.js +377 -0
  24. package/dist/dist-es-LDE437AM.js +170 -0
  25. package/dist/dist-es-P26FWFLT.js +485 -0
  26. package/dist/dist-es-Q57SZXZ7.js +70 -0
  27. package/dist/dist-es-R7FGL2IB.js +88 -0
  28. package/dist/dist-es-USORBXZ5.js +318 -0
  29. package/dist/dist-es-ZKC2QKD2.js +377 -0
  30. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
  31. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
  32. package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
  33. package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
  34. package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
  35. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
  36. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
  37. package/dist/drizzle/0227_analyst_reader_role.sql +106 -38
  38. package/dist/drizzle/0230_analyst_rls.sql +192 -66
  39. package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
  40. package/dist/drizzle/0232_message_source_event_id.sql +9 -0
  41. package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
  42. package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
  43. package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
  44. package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
  45. package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
  46. package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
  47. package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
  48. package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
  49. package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
  50. package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
  51. package/dist/drizzle/0242_search_queries.sql +28 -0
  52. package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
  53. package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
  54. package/dist/drizzle/0245_wiki_page_render.sql +21 -0
  55. package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
  56. package/dist/drizzle/0247_capability_runtime.sql +402 -0
  57. package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
  58. package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
  59. package/dist/drizzle/0250_kg_schema_extraction.sql +576 -0
  60. package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
  61. package/dist/drizzle/0251_brain_stragglers.sql +263 -0
  62. package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
  63. package/dist/drizzle/0253_drop_brain_substrate.sql +93 -0
  64. package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
  65. package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
  66. package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
  67. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  68. package/dist/drizzle/0257_capability_approvals.sql +53 -0
  69. package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
  70. package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
  71. package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
  72. package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
  73. package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
  74. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  75. package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
  76. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  77. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  78. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  79. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
  80. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  81. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  82. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  83. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  84. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  85. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  86. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  87. package/dist/event-streams-7JNLJEEL.js +894 -0
  88. package/dist/event-streams-KDF5RWR6.js +1515 -0
  89. package/dist/loadSso-DJVB7BXE.js +576 -0
  90. package/dist/loadSso-JAMNA3WY.js +583 -0
  91. package/dist/plugins/catalog/package.json +1 -2
  92. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  93. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
  94. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  95. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  96. package/dist/plugins/catalog/src/contracts.ts +2 -179
  97. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  98. package/dist/signin-UGDWTGS4.js +821 -0
  99. package/dist/sso-oidc-Q4VXANXY.js +837 -0
  100. package/dist/sts-CWLSE3GH.js +1241 -0
  101. package/dist/sts-DNZLNVOV.js +1245 -0
  102. package/dist/terraform/examples/greenfield/main.tf +247 -50
  103. package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
  104. package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
  105. package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
  106. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
  107. package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
  108. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
  109. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
  110. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
  111. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
  112. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
  113. package/dist/terraform/modules/app/agentcore-harness/main.tf +330 -0
  114. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +72 -0
  115. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
  116. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
  117. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
  118. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
  119. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
  120. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
  121. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
  122. package/dist/terraform/modules/app/agentcore-harness/variables.tf +96 -0
  123. package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
  124. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  125. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
  126. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
  127. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  128. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
  129. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  130. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
  131. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
  132. package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
  133. package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
  134. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  135. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  136. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  137. package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
  138. package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
  139. package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
  140. package/dist/terraform/modules/app/deployment-control-plane/runner.py +1011 -31
  141. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +655 -2
  142. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
  143. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
  144. package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
  145. package/dist/terraform/modules/app/lambda-api/handlers.tf +606 -37
  146. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +192 -21
  147. package/dist/terraform/modules/app/lambda-api/main.tf +35 -0
  148. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
  149. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
  150. package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
  151. package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
  152. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  153. package/dist/terraform/modules/app/lambda-api/variables.tf +188 -3
  154. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
  155. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
  156. package/dist/terraform/modules/foundation/cognito/main.tf +319 -73
  157. package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
  158. package/dist/terraform/modules/foundation/cognito/variables.tf +124 -2
  159. package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
  160. package/dist/terraform/modules/thinkwork/main.tf +396 -26
  161. package/dist/terraform/modules/thinkwork/outputs.tf +188 -5
  162. package/dist/terraform/modules/thinkwork/variables.tf +154 -5
  163. package/dist/terraform/schema.graphql +69 -36
  164. package/dist/workspace-defaults/files/TOOLS.md +39 -0
  165. package/dist/workspace-defaults/files/USER.md +1 -0
  166. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
  167. package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
  168. package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
  169. package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
  170. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
  171. package/package.json +6 -3
  172. package/dist/plugins/workos-auth/package.json +0 -22
  173. package/dist/plugins/workos-auth/src/index.ts +0 -30
  174. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  175. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  176. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  177. package/dist/plugins/workos-auth/tsconfig.json +0 -8
  178. /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
@@ -14,6 +14,12 @@ locals {
14
14
  # deployment jobs — plan 2026-06-12-001 U10); the TWENTY config key is
15
15
  # retired.
16
16
  optional_integration_handler_names = concat(
17
+ var.auth_retirement_phase == "retired" ? [
18
+ # The native login UI never publishes this handler. It remains reachable
19
+ # only as an operator-controlled rollback seam through coexistence and
20
+ # cutover/soak, then Terraform removes the Lambda and routes atomically.
21
+ "workos-auth",
22
+ ] : [],
17
23
  var.deployment_control_plane_enabled ? [] : [
18
24
  # Host-only onboarding/deployment API. Customer foundations disable the
19
25
  # deployment control plane, so release-based customer installs must not
@@ -30,10 +36,22 @@ locals {
30
36
  "oauth-authorize",
31
37
  "oauth-callback",
32
38
  "slack-events",
33
- "slack-slash-command",
34
- "slack-interactivity",
35
39
  "slack-oauth-install",
36
40
  ],
41
+ var.enable_msteams_app ? [] : [
42
+ "msteams-install-start",
43
+ "msteams-install-complete",
44
+ "msteams-account-link-complete",
45
+ ],
46
+ var.enable_agentcore_multiplayer_proof ? [] : [
47
+ "turn-assertion-mint",
48
+ "agentcore-proof-oauth-provider",
49
+ "agentcore-identity-boundary-target",
50
+ "harness-capability-mcp",
51
+ "harness-code-interpreter-target",
52
+ "harness-builtin-tools-target",
53
+ "harness-platform-tools-target",
54
+ ],
37
55
  )
38
56
 
39
57
  # Config-class configuration shared by all API handlers. As of plan
@@ -44,20 +62,30 @@ locals {
44
62
  # The reader-coverage fixture test in apps/cli fails CI if a key in this
45
63
  # map still has a direct process.env reader. Identity values (STAGE,
46
64
  # AWS_ACCOUNT_ID, NODE_OPTIONS) and secrets (DATABASE_URL,
47
- # API_AUTH_SECRET, APPSYNC_API_KEY) stay out of this map — identity stays
65
+ # API_AUTH_SECRET) stay out of this map — identity stays
48
66
  # env forever, secrets live in Secrets Manager (R4), never in the String
49
67
  # document.
50
68
  config_env = merge({
51
69
  # THINK-173: PUBLIC verification key (not a secret) + the NAME of the
52
70
  # private-key secret (resolved via runtime-config's secret loader —
53
71
  # the PEM itself never enters the env or the String document).
54
- CAPABILITY_SIGNING_PUBLIC_KEY = var.capability_signing_public_key
55
- CAPABILITY_SIGNING_PRIVATE_KEY_SECRET = var.capability_signing_private_key_secret
72
+ CAPABILITY_SIGNING_PUBLIC_KEY = var.capability_signing_public_key
73
+ CAPABILITY_SIGNING_PRIVATE_KEY_SECRET = var.capability_signing_private_key_secret
74
+ SUBSCRIPTION_TICKET_SIGNING_KEY_ID = var.subscription_ticket_signing_key_id
75
+ SUBSCRIPTION_TICKET_PUBLIC_KEYS = var.subscription_ticket_public_keys
76
+ SUBSCRIPTION_TICKET_PRIVATE_KEY_SECRET = var.subscription_ticket_private_key_secret
77
+ APPSYNC_API_ID = var.appsync_api_id
56
78
  # THINK-229 U3/KTD5 — the analyst policy-source enforcement flip.
57
79
  # "row" (default) keeps sidecar policy shadow-only; "sidecar" makes the
58
80
  # signed sidecar block authoritative (budgets/policyClaims flow). Flip
59
81
  # ONLY after clean shadow parity on live traffic.
60
82
  ANALYST_POLICY_SOURCE = var.analyst_policy_source
83
+ # Governed autonomy — per-tenant opt-in allowlist for autonomous capability
84
+ # self-extension (the two self_admit_connection / self_approve_routine
85
+ # actions on capability-control-service). Comma/space-separated tenant ids;
86
+ # empty (default) = NO tenant enabled, so self-extension ships inert and
87
+ # fail-closed. Read via getConfig() (env-wins) in isSelfExtensionEnabled.
88
+ CAPABILITY_SELF_EXTENSION_TENANTS = var.capability_self_extension_tenants
61
89
  # THINK-230 — the operator-facing provisionAnalystConnector mutation runs
62
90
  # the analyst connector provisioning ceremony inside graphql-http, so the
63
91
  # shared api handlers read the same broker-secret ARN + rds_iam connect
@@ -74,8 +102,13 @@ locals {
74
102
  DATABASE_SECRET_ARN = var.graphql_db_secret_arn
75
103
  DATABASE_HOST = var.db_cluster_endpoint
76
104
  DATABASE_NAME = var.database_name
105
+ # THINK-280 U8: the LAST rollout gate. External capability search + the
106
+ # Inspector governed-runtime operation layer stay inert until the broker is
107
+ # enabled for the stage. Off by default — read via getConfig() (env-wins).
108
+ CAPABILITY_EXTERNAL_SEARCH_ENABLED = var.enable_capability_broker ? "true" : "false"
109
+ ENABLE_CAPABILITY_BROKER = var.enable_capability_broker ? "true" : "false"
77
110
  # BUCKET_NAME and USER_POOL_ID were duplicate aliases of WORKSPACE_BUCKET
78
- # and COGNITO_USER_POOL_ID; GRAPHQL_API_KEY duplicated APPSYNC_API_KEY;
111
+ # and COGNITO_USER_POOL_ID; the legacy GRAPHQL_API_KEY alias is retired;
79
112
  # THINKWORK_API_SECRET and EMAIL_HMAC_SECRET duplicated API_AUTH_SECRET
80
113
  # (~310 serialized bytes total). graphql-http sits at Lambda's hard 4KB
81
114
  # env ceiling (#2375) — every reader falls back to the canonical name.
@@ -217,18 +250,35 @@ locals {
217
250
  # window (R8). Config-class keys live ONLY in the SSM runtime-config
218
251
  # document now — adding a key here is guarded by the identity-allowlist
219
252
  # fixture test in apps/cli (R10). Follow-up release: DATABASE_URL,
220
- # APPSYNC_API_KEY, and API_AUTH_SECRET drop too (readers already resolve
253
+ # API_AUTH_SECRET drops too (readers already resolve
221
254
  # via Secrets Manager prefetch when the env copies are absent), bringing
222
255
  # every handler under the ≤1KB R1 target.
223
256
  common_env = {
224
257
  STAGE = var.stage
225
258
  DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
226
- APPSYNC_API_KEY = var.appsync_api_key
227
259
  API_AUTH_SECRET = var.api_auth_secret
228
260
  AWS_ACCOUNT_ID = var.account_id
229
261
  NODE_OPTIONS = "--enable-source-maps"
230
262
  }
231
263
 
264
+ # THINK-316 U1: the assertion mint runs on a sibling role and receives only
265
+ # the database connection plus immutable stage identity. In particular it
266
+ # does not inherit API_AUTH_SECRET or APPSYNC_API_KEY from common_env.
267
+ turn_assertion_mint_env = {
268
+ STAGE = var.stage
269
+ DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
270
+ AWS_ACCOUNT_ID = var.account_id
271
+ NODE_OPTIONS = "--enable-source-maps"
272
+ }
273
+
274
+ # THINK-316 U1: the synthetic provider/target are proof-only public
275
+ # boundaries. They receive no database URL, platform bearer, or AppSync key.
276
+ agentcore_proof_boundary_env = {
277
+ STAGE = var.stage
278
+ AWS_ACCOUNT_ID = var.account_id
279
+ NODE_OPTIONS = "--enable-source-maps"
280
+ }
281
+
232
282
  # Per-handler env-var overrides. ARNs are constructed from the naming
233
283
  # pattern (same trick as the api-cross-function-invoke statement in
234
284
  # iam-grouped.tf) so we don't introduce a self-referential dependency
@@ -237,7 +287,90 @@ locals {
237
287
  SLACK_APP_CREDENTIALS_SECRET_ARN = var.enable_slack_workspace_app ? aws_secretsmanager_secret.slack_app_credentials[0].arn : ""
238
288
  }
239
289
 
290
+ msteams_handler_env = {
291
+ MSTEAMS_APP_CREDENTIALS_SECRET_ARN = var.enable_msteams_app ? aws_secretsmanager_secret.msteams_app_credentials[0].arn : ""
292
+ }
293
+
240
294
  handler_extra_env = {
295
+ # THINK-316 U1: short-lived, server-minted identity assertions for
296
+ # AgentCore Harness and Gateway. The public mcp-oauth handler receives the
297
+ # key id only to publish GetPublicKey-derived JWKS; its role cannot Sign.
298
+ "mcp-oauth" = {
299
+ AGENTCORE_TURN_ASSERTION_ISSUER = local.agentcore_turn_assertion_issuer
300
+ AGENTCORE_TURN_ASSERTION_JWKS_KEYS = jsonencode([
301
+ for key in values(local.agentcore_turn_assertion_keys) : {
302
+ keyId = key.key_id
303
+ kid = key.kid
304
+ }
305
+ ])
306
+ }
307
+ "skills" = {
308
+ AGENTCORE_IDENTITY_WORKLOAD_NAME = "thinkwork-${var.stage}-multiplayer-proof"
309
+ AGENTCORE_TWENTY_CREDENTIAL_PROVIDER_NAME = "thinkwork-${var.stage}-twenty-crm"
310
+ AGENTCORE_USER_OAUTH_RETURN_URL = "${local.mcp_oauth_api_base_url}/api/skills/mcp-oauth/agentcore/complete"
311
+ AGENTCORE_TWENTY_OAUTH_RESOURCE = "https://crm.thinkwork.ai/mcp"
312
+ }
313
+ "turn-assertion-mint" = {
314
+ AGENTCORE_TURN_ASSERTION_ISSUER = local.agentcore_turn_assertion_issuer
315
+ AGENTCORE_HARNESS_AUDIENCE = local.agentcore_harness_audience
316
+ AGENTCORE_GATEWAY_AUDIENCE = local.agentcore_gateway_audience
317
+ AGENTCORE_TURN_ASSERTION_KMS_KEY_ID = local.agentcore_turn_assertion_active_key.key_id
318
+ AGENTCORE_TURN_ASSERTION_KID = local.agentcore_turn_assertion_active_key.kid
319
+ }
320
+ "agentcore-proof-oauth-provider" = {
321
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
322
+ AGENTCORE_PROOF_OAUTH_CLIENT_ID = var.agentcore_proof_oauth_client_id
323
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
324
+ AGENTCORE_ASSERTION_ISSUER = local.agentcore_turn_assertion_issuer
325
+ AGENTCORE_HARNESS_AUDIENCE = local.agentcore_harness_audience
326
+ AGENTCORE_GATEWAY_AUDIENCE = local.agentcore_gateway_audience
327
+ AGENTCORE_TURN_ASSERTION_KMS_KEY_ID = local.agentcore_turn_assertion_active_key.key_id
328
+ AGENTCORE_TURN_ASSERTION_KID = local.agentcore_turn_assertion_active_key.kid
329
+ AGENTCORE_PROOF_OWNER_ALLOWLIST = var.agentcore_proof_owner_allowlist
330
+ }
331
+ "agentcore-identity-boundary-target" = {
332
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
333
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
334
+ AGENTCORE_PROOF_OWNER_ALLOWLIST = var.agentcore_proof_owner_allowlist
335
+ }
336
+ "harness-capability-mcp" = {
337
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
338
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
339
+ AGENTCORE_GATEWAY_POLICY_REVISION = "mcp-list-call-v2-tenant-admission"
340
+ AGENTCORE_IDENTITY_WORKLOAD_NAME = "thinkwork-${var.stage}-multiplayer-proof"
341
+ AGENTCORE_TWENTY_CREDENTIAL_PROVIDER_NAME = "thinkwork-${var.stage}-twenty-crm"
342
+ AGENTCORE_TWENTY_OAUTH_RESOURCE = "https://crm.thinkwork.ai/mcp"
343
+ }
344
+ "harness-code-interpreter-target" = {
345
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
346
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
347
+ AGENTCORE_GATEWAY_POLICY_REVISION = "sandbox-execute-v1-tenant-admission"
348
+ }
349
+ "harness-builtin-tools-target" = {
350
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
351
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
352
+ AGENTCORE_GATEWAY_POLICY_REVISION = "builtin-web-v1-tenant-admission"
353
+ }
354
+ "harness-platform-tools-target" = {
355
+ AGENTCORE_PROOF_OAUTH_ISSUER = "${local.mcp_oauth_api_base_url}/agentcore-proof/oauth"
356
+ AGENTCORE_PROOF_OAUTH_CLIENT_SECRET = var.agentcore_proof_oauth_client_secret
357
+ AGENTCORE_GATEWAY_POLICY_REVISION = "platform-tools-v4-user-questions"
358
+ }
359
+ "workos-auth" = {
360
+ # During cutover/soak the callback, bridge, and logout paths remain for
361
+ # already-issued state, but the handler independently refuses every new
362
+ # WorkOS authorize start. Terraform removes the handler in retired.
363
+ AUTH_RETIREMENT_PHASE = var.auth_retirement_phase
364
+ }
365
+ "public-auth-options" = {
366
+ AUTH_RETIREMENT_PHASE = var.auth_retirement_phase
367
+ }
368
+ "auth-me" = {
369
+ AUTH_MIGRATION_RECOVERY_DEADLINE = var.auth_migration_recovery_deadline
370
+ }
371
+ "auth-enrollment" = {
372
+ AUTH_MIGRATION_RECOVERY_DEADLINE = var.auth_migration_recovery_deadline
373
+ }
241
374
  # Analyst query broker (THINK-228 U3). Reader role + caller credential
242
375
  # secrets, and the workspace bucket's analyst-staging/ prefix for
243
376
  # large-result CSVs (lifecycle TTL lives on the bucket module). The
@@ -302,6 +435,17 @@ locals {
302
435
  THINKWORK_RELEASE_VERSION = var.deployment_release_version
303
436
  THINKWORK_RELEASE_MANIFEST_URL = var.deployment_release_manifest_url
304
437
  THINKWORK_RELEASE_MANIFEST_SHA256 = var.deployment_release_manifest_sha256
438
+ # THINK-280 — createTenant RequestResponse-invokes agentcore-admin to
439
+ # provision the per-tenant sandbox. sandbox-provisioning.ts reads both
440
+ # from process.env directly (the token is a secret → env, never the SSM
441
+ # String doc). Broker-session build reads the three broker values below
442
+ # (broker-session.ts); all EMPTY when the broker is disabled → inert.
443
+ AGENTCORE_ADMIN_LAMBDA_ARN = var.agentcore_admin_lambda_arn
444
+ AGENTCORE_ADMIN_TOKEN = var.agentcore_admin_token
445
+ CAPABILITY_BROKER_AUDIENCE = var.capability_broker_audience
446
+ CAPABILITY_BROKER_API_ID = var.capability_broker_api_id
447
+ CAPABILITY_BROKER_VPCE_DNS = var.capability_broker_vpce_dns
448
+ CAPABILITY_BROKER_SESSION_TABLE = var.capability_broker_session_table
305
449
  }
306
450
  # Compounding Memory compile Lambda. Any Converse-compatible Bedrock
307
451
  # model works; the planner + section-writer cap themselves at ~500
@@ -343,12 +487,25 @@ locals {
343
487
  BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
344
488
  OKF_EFS_ROOT = var.okf_efs_mount_path
345
489
  }
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
490
+ # THINK-193 U1 (Codex F6): normalized evidence snapshots live in the
491
+ # encrypted brain-artifacts bucket under evidence-snapshots/ — Postgres
492
+ # keeps only the s3:// ref + hash. 30-day lifecycle expiration below.
493
+ "memory-stage-worker" = {
494
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
495
+ }
496
+ # THINK-193 U2 (Codex S1/S2): the retraction drainer performs the
497
+ # versioned evidence-snapshot deletion for source erases under its
498
+ # DEDICATED role (below) — the only place bulk destructive S3 runs.
499
+ "memory-retraction-drainer" = {
500
+ BRAIN_ARTIFACTS_BUCKET = aws_s3_bucket.brain_artifacts.bucket
501
+ }
502
+ "oauth-authorize" = local.slack_handler_env
503
+ "oauth-callback" = local.slack_handler_env
504
+ "slack-events" = local.slack_handler_env
505
+ "slack-oauth-install" = local.slack_handler_env
506
+ "msteams-install-start" = local.msteams_handler_env
507
+ "msteams-install-complete" = local.msteams_handler_env
508
+ "msteams-account-link-complete" = local.msteams_handler_env
352
509
  "thread-attachments-finalize" = {
353
510
  REQUESTER_IDLE_MEMORY_LEARNING_ENABLED = tostring(var.requester_idle_memory_learning_enabled)
354
511
  }
@@ -364,7 +521,15 @@ locals {
364
521
  # knowledge_graph_search tool; the per-agent tool policy gates on top.
365
522
  "chat-agent-invoke" = {
366
523
  KNOWLEDGE_GRAPH_TOOL_ENABLED = tostring(var.knowledge_graph_tool_enabled)
367
- }
524
+ # THINK-311 U5: no HARNESS_RUNNER_FUNCTION_NAME env — derivable
525
+ # thinkwork-<stage>-api-* names never ride env (R1/R10 identity-only
526
+ # rule); resolveRuntimeFunctionName derives it from STAGE at call
527
+ # time, exactly like workspace-renderer.
528
+ }
529
+ # THINK-316 U5: the runner reads the server-only attested profile and
530
+ # invokes its named endpoint with a purpose-bound CUSTOM_JWT. It receives
531
+ # no Harness control-plane inputs or permissions.
532
+ "harness-runner" = {}
368
533
  # 240s: sync Hindsight retain (LLM extraction + auto-consolidation) can
369
534
  # exceed 60s; the client timeout must stay below the Lambda timeout (300s)
370
535
  # and below the Hindsight ALB idle_timeout (300s) so failures classify as
@@ -395,6 +560,15 @@ locals {
395
560
  # invocation (never Lambda self-invoke — AWS recursive-loop
396
561
  # detection terminates worker-to-self Event chains).
397
562
  KG_OBS_MAX_CANDIDATES_PER_RUN = var.kg_obs_max_candidates_per_run
563
+ # THINK-193 U4 dead-handoff fix: a successful observations ingest
564
+ # enqueues the tenant graph wiki-compile job inside its own commit
565
+ # and Event-invokes wiki-compile post-commit. WIKI_SOURCE must match
566
+ # the wiki-compile handler's dispatch flag (stage-global); the
567
+ # per-tenant kill switch stays tenants.wiki_compile_enabled. The
568
+ # invoke rides the shared lambda role's existing wiki-compile
569
+ # lambda:InvokeFunction grant (iam-grouped.tf); the function name is
570
+ # derived from STAGE (common_env) at call time.
571
+ WIKI_SOURCE = var.wiki_source
398
572
  }
399
573
  # routine-task-python (Phase B U6) needs the AgentCore code-interpreter
400
574
  # id + the per-stage S3 routine-output bucket. The interpreter id is
@@ -414,6 +588,14 @@ locals {
414
588
  "routine-exec-git" = {
415
589
  SANDBOX_INTERPRETER_ID = var.agentcore_code_interpreter_id
416
590
  ROUTINE_OUTPUT_BUCKET = "thinkwork-${var.stage}-routine-output"
591
+ # THINK-280 U7 — the headless capability executor runs inside
592
+ # routine-exec-git and mints broker sessions. routine-exec-git.ts +
593
+ # capability-headless-executor.ts read all four from process.env; all
594
+ # EMPTY when the broker is disabled → the executor stays inert.
595
+ CAPABILITY_BROKER_SESSION_TABLE = var.capability_broker_session_table
596
+ CAPABILITY_BROKER_AUDIENCE = var.capability_broker_audience
597
+ CAPABILITY_BROKER_API_ID = var.capability_broker_api_id
598
+ CAPABILITY_BROKER_VPCE_DNS = var.capability_broker_vpce_dns
417
599
  }
418
600
  # graphql-http hosts the createRoutine / publishRoutineVersion / etc.
419
601
  # resolvers (Phase B U7) AND the routine-approval-bridge (Phase B
@@ -561,6 +743,23 @@ resource "aws_lambda_function" "handler" {
561
743
  # Bearer API_AUTH_SECRET. Idempotent on thread_turns.finalized_at
562
744
  # (migration 0123). Plan: 2026-05-22-006.
563
745
  "chat-agent-finalize",
746
+ # harness-runner — THINK-311 U5: Event-mode target for harness-flagged
747
+ # agents. The chat dispatch selector (resolveRuntimeFunctionName) routes
748
+ # a trial agent's turn here instead of the Pi container Lambda; the
749
+ # runner projects the payload into an AWS AgentCore Harness config,
750
+ # drives InvokeHarness, fulfills emit_document, and finalizes the turn
751
+ # via processFinalize. No API route — direct Event invoke only.
752
+ "harness-runner",
753
+ # THINK-316 U1: direct-invoke-only assertion mint. It derives all identity
754
+ # claims from the persisted turn tuple and signs with a dedicated KMS key;
755
+ # there is intentionally no public API Gateway route.
756
+ "turn-assertion-mint",
757
+ "agentcore-proof-oauth-provider",
758
+ "agentcore-identity-boundary-target",
759
+ "harness-capability-mcp",
760
+ "harness-code-interpreter-target",
761
+ "harness-builtin-tools-target",
762
+ "harness-platform-tools-target",
564
763
  # canvas-refresh — headless Living Artifacts data-refresh (THINK-145 U6).
565
764
  # Invoked RequestResponse by the refreshCanvasData mutation (graphql-http)
566
765
  # and by job-trigger's canvas_refresh branch (U7). Re-runs the saved
@@ -594,7 +793,13 @@ resource "aws_lambda_function" "handler" {
594
793
  "stripe-subscription",
595
794
  "deployment-sessions",
596
795
  "auth-me",
796
+ "auth-revoke",
797
+ "auth-enrollment",
798
+ "auth-subscription-ticket",
799
+ "appsync-subscription-authorizer",
800
+ "subscription-invalidation",
597
801
  "public-auth-options",
802
+ "auth-provider-reconcile",
598
803
  "workos-auth",
599
804
  # Public artifact share links (THINK-208): GET /share/{token}, token
600
805
  # verified in handler code (no gateway auth, uniform 404 on any miss).
@@ -608,6 +813,8 @@ resource "aws_lambda_function" "handler" {
608
813
  "mcp-user-memory",
609
814
  "mcp-context-engine",
610
815
  "mcp-open-engine",
816
+ # THINK-280 U8: scoped external capability search MCP facade.
817
+ "mcp-capability-search",
611
818
  "activity",
612
819
  "routines",
613
820
  "budgets",
@@ -642,13 +849,28 @@ resource "aws_lambda_function" "handler" {
642
849
  "email-provider-webhook",
643
850
  "email-readiness-probe",
644
851
  "slack-events",
645
- "slack-slash-command",
646
- "slack-interactivity",
647
852
  "slack-oauth-install",
853
+ "msteams-install-start",
854
+ "msteams-install-complete",
855
+ "msteams-account-link-complete",
648
856
  "github-app",
649
857
  "memory",
650
858
  "memory-retain",
651
859
  "brain-dream-state",
860
+ "memory-stage-worker",
861
+ # THINK-193 U2 (Codex P1 #3): scheduled retry drainer for the retraction
862
+ # saga ledger. Claims due/stale memory_retraction_attempts rows with the
863
+ # locked_by + lock_generation fence, processes each via the saga, sweeps
864
+ # exhausted rows to dead_lettered, and emits structured-log metrics.
865
+ # rate(5 minutes) EventBridge Scheduler + MaxRetryAttempts=0 (dedicated
866
+ # resources below) — the next tick IS the retry.
867
+ "memory-retraction-drainer",
868
+ # THINK-193 U3: stalled memory_stage token sweeper — re-invokes the
869
+ # memory-stage-worker for pending/executing tokens past their lease and
870
+ # redrives consumed-but-unsent results; unrecoverable parks get a
871
+ # terminal step event + SendTaskFailure. rate(15 minutes) schedule
872
+ # (dedicated resource below).
873
+ "memory-stage-sweeper",
652
874
  "wiki-compile",
653
875
  "knowledge-graph-observations-ingest",
654
876
  "ontology-scan",
@@ -791,6 +1013,14 @@ resource "aws_lambda_function" "handler" {
791
1013
  # POSTs one row per agent-session-start. Shared
792
1014
  # API_AUTH_SECRET bearer (runtime→API; no tenant OAuth).
793
1015
  "manifest-log",
1016
+ # Governed capability runtime control-plane service (THINK-280 U2).
1017
+ # NO HTTP route: the Pi container invokes it directly (RequestResponse)
1018
+ # via lambda:InvokeFunction over the approved in-account path — public
1019
+ # execute-api is not reliable from Pi's private VPC. Identity comes
1020
+ # exclusively from the Ed25519-signed capability caller context (the
1021
+ # CAPABILITY_SIGNING_PUBLIC_KEY in the shared runtime-config document
1022
+ # verifies it); plaintext payload fields are never trusted.
1023
+ "capability-control-service",
794
1024
  # SI-7 catalog-list read endpoint (plan §U15 pt 3/3). The runtime
795
1025
  # fetches the allowed builtin-tool slug set once per
796
1026
  # session-start + feature-flag-gated enforcement filter drops
@@ -831,9 +1061,13 @@ resource "aws_lambda_function" "handler" {
831
1061
  ]), toset(local.optional_integration_handler_names)) : toset([])
832
1062
 
833
1063
  function_name = "thinkwork-${var.stage}-api-${each.key}"
834
- role = aws_iam_role.lambda.arn
835
- handler = "index.handler"
836
- runtime = local.runtime
1064
+ # S2 (THINK-193 U2): memory-retraction-drainer runs under a DEDICATED role
1065
+ # so its destructive evidence-snapshot S3 capability (version enumeration +
1066
+ # bulk version deletion) never leaks into the 90+ handlers on the shared
1067
+ # role. Everything else keeps the shared role.
1068
+ role = each.key == "memory-retraction-drainer" ? aws_iam_role.memory_retraction_drainer.arn : each.key == "turn-assertion-mint" ? aws_iam_role.turn_assertion_mint[0].arn : each.key == "agentcore-proof-oauth-provider" ? aws_iam_role.agentcore_proof_provider[0].arn : each.key == "agentcore-identity-boundary-target" ? aws_iam_role.agentcore_proof_boundary[0].arn : aws_iam_role.lambda.arn
1069
+ handler = "index.handler"
1070
+ runtime = local.runtime
837
1071
  # Parameters and Secrets extension: container-local cache for the SSM
838
1072
  # runtime-config document + Secrets Manager reads (runtime-config.tf).
839
1073
  layers = local.api_handler_layers
@@ -844,7 +1078,6 @@ resource "aws_lambda_function" "handler" {
844
1078
  depends_on = [
845
1079
  aws_ssm_parameter.runtime_config,
846
1080
  aws_secretsmanager_secret_version.api_auth,
847
- aws_secretsmanager_secret_version.appsync_api_key,
848
1081
  ]
849
1082
  # eval-runner walks every test case sequentially, invoking an agent +
850
1083
  # waiting up to 2 min for spans to propagate per test, so a 10-test run
@@ -859,8 +1092,11 @@ resource "aws_lambda_function" "handler" {
859
1092
  # validates the agent, builds the AgentCore invoke payload, dispatches
860
1093
  # Event-mode, and returns. Setup is ~5s in practice; 60s gives 12×
861
1094
  # 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
863
- 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
1095
+ # harness-runner holds the Lambda open for the full Harness turn (the
1096
+ # reference QBR run was ~2 min; 900s is the ceiling a longer run is a
1097
+ # legitimate trial limitation, recorded, not engineered around).
1098
+ timeout = each.key == "harness-runner" ? 900 : 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
1099
+ memory_size = each.key == "harness-runner" ? 512 : 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
1100
 
865
1101
  filename = local.use_local_zips ? "${var.lambda_zips_dir}/${each.key}.zip" : null
866
1102
  source_code_hash = local.use_local_zips ? filebase64sha256("${var.lambda_zips_dir}/${each.key}.zip") : null
@@ -889,7 +1125,10 @@ resource "aws_lambda_function" "handler" {
889
1125
 
890
1126
  environment {
891
1127
  variables = merge(
892
- local.common_env,
1128
+ each.key == "turn-assertion-mint" ? local.turn_assertion_mint_env : contains([
1129
+ "agentcore-proof-oauth-provider",
1130
+ "agentcore-identity-boundary-target",
1131
+ ], each.key) ? local.agentcore_proof_boundary_env : local.common_env,
893
1132
  { FUNCTION_NAME = each.key },
894
1133
  lookup(local.handler_extra_env, each.key, {}),
895
1134
  )
@@ -904,6 +1143,19 @@ resource "aws_lambda_function" "handler" {
904
1143
  }
905
1144
  }
906
1145
 
1146
+ # Analyst data-path handlers get a stable NAT egress IP when
1147
+ # analyst_egress_subnet_ids is set — external Postgres sources behind an IP
1148
+ # allowlist admit that one EIP. Disjoint from the okf-efs-refresh block
1149
+ # above (a function takes at most one vpc_config).
1150
+ dynamic "vpc_config" {
1151
+ for_each = contains(local.analyst_vpc_handlers, each.key) && local.analyst_vpc_enabled ? [1] : []
1152
+
1153
+ content {
1154
+ subnet_ids = var.analyst_egress_subnet_ids
1155
+ security_group_ids = var.analyst_egress_security_group_ids
1156
+ }
1157
+ }
1158
+
907
1159
  dynamic "file_system_config" {
908
1160
  for_each = each.key == "okf-efs-refresh" && local.okf_efs_vpc_enabled ? [1] : []
909
1161
 
@@ -973,6 +1225,20 @@ resource "aws_lambda_function_event_invoke_config" "chat_agent_invoke" {
973
1225
  maximum_event_age_in_seconds = 3600
974
1226
  }
975
1227
 
1228
+ # harness-runner retry-0 (THINK-311 U5, async-retry idempotency): an AWS
1229
+ # async retry would re-execute a whole agent turn. Failures finalize the
1230
+ # turn in-process (finalize-through-failure); a hard crash is reconciled
1231
+ # by the stall monitor (timed_out + checkout release, no retry enqueue
1232
+ # for harness turns). No DLQ destination for the same reason as
1233
+ # chat-agent-invoke above: the shared role's inline policy budget is at
1234
+ # the AWS cap, and the stall monitor is the operational surface.
1235
+ resource "aws_lambda_function_event_invoke_config" "harness_runner" {
1236
+ count = local.deploy_lambda_handlers ? 1 : 0
1237
+ function_name = aws_lambda_function.handler["harness-runner"].function_name
1238
+ maximum_retry_attempts = 0
1239
+ maximum_event_age_in_seconds = 3600
1240
+ }
1241
+
976
1242
  resource "aws_lambda_function_event_invoke_config" "wiki_compile" {
977
1243
  count = local.deploy_lambda_handlers ? 1 : 0
978
1244
  function_name = aws_lambda_function.handler["wiki-compile"].function_name
@@ -1061,6 +1327,21 @@ resource "aws_lambda_function_event_invoke_config" "routine_approval_callback" {
1061
1327
  # retain-cost path (Bedrock tokens charged in adapter.retainConversation)
1062
1328
  # is NOT idempotent — retries multiply LLM cost. Per
1063
1329
  # project_async_retry_idempotency_lessons.
1330
+ # memory-stage-worker (THINK-193 U1): Event-invoked by workflow-step-dispatch
1331
+ # AFTER the memory_stage task token is stored. Lambda async retries are
1332
+ # disabled like its memory siblings — the worker's stages are idempotent, but
1333
+ # an automatic re-execution would race the original's token CAS and turn a
1334
+ # completed stage into a spurious failure. A worker that dies without
1335
+ # resuming the token is bounded by the state machine's HeartbeatSeconds
1336
+ # (3600s): the parked step times out and the run fails visibly, and a
1337
+ # re-triggered run self-heals from the durable checkpoint/evidence ledger.
1338
+ resource "aws_lambda_function_event_invoke_config" "memory_stage_worker" {
1339
+ count = local.deploy_lambda_handlers ? 1 : 0
1340
+ function_name = aws_lambda_function.handler["memory-stage-worker"].function_name
1341
+ maximum_retry_attempts = 0
1342
+ maximum_event_age_in_seconds = 3600
1343
+ }
1344
+
1064
1345
  resource "aws_lambda_function_event_invoke_config" "memory_retain" {
1065
1346
  count = local.deploy_lambda_handlers ? 1 : 0
1066
1347
  function_name = aws_lambda_function.handler["memory-retain"].function_name
@@ -1068,6 +1349,132 @@ resource "aws_lambda_function_event_invoke_config" "memory_retain" {
1068
1349
  maximum_event_age_in_seconds = 3600
1069
1350
  }
1070
1351
 
1352
+ # ---------------------------------------------------------------------------
1353
+ # THINK-193 U2 (Codex S2): dedicated IAM role for memory-retraction-drainer.
1354
+ #
1355
+ # The drainer is the ONLY principal allowed to enumerate and bulk-delete
1356
+ # evidence-snapshot object VERSIONS (S1: the brain-artifacts bucket is
1357
+ # versioned; erase completeness requires deleting every noncurrent version
1358
+ # and delete marker under the source prefix). Granting that on the shared
1359
+ # api-lambda role would let every unrelated handler destroy any tenant's
1360
+ # evidence — hence the sibling-role pattern (same as
1361
+ # compliance-anchor-watchdog). The role otherwise carries only what the
1362
+ # drainer actually uses: logs (managed basic policy), Secrets Manager +
1363
+ # SSM runtime-config reads, and conditional KMS for the encrypted bucket.
1364
+ # ---------------------------------------------------------------------------
1365
+
1366
+ resource "aws_iam_role" "memory_retraction_drainer" {
1367
+ name = "thinkwork-${var.stage}-memory-retraction-drainer-role"
1368
+
1369
+ assume_role_policy = jsonencode({
1370
+ Version = "2012-10-17"
1371
+ Statement = [{
1372
+ Effect = "Allow"
1373
+ Principal = { Service = "lambda.amazonaws.com" }
1374
+ Action = "sts:AssumeRole"
1375
+ }]
1376
+ })
1377
+ }
1378
+
1379
+ resource "aws_iam_role_policy_attachment" "memory_retraction_drainer_basic" {
1380
+ role = aws_iam_role.memory_retraction_drainer.name
1381
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
1382
+ }
1383
+
1384
+ resource "aws_iam_role_policy" "memory_retraction_drainer" {
1385
+ name = "memory-retraction-drainer"
1386
+ role = aws_iam_role.memory_retraction_drainer.id
1387
+
1388
+ policy = jsonencode({
1389
+ Version = "2012-10-17"
1390
+ Statement = concat(
1391
+ [
1392
+ # Versioned evidence-snapshot destruction, evidence-snapshots/ only.
1393
+ {
1394
+ Effect = "Allow"
1395
+ Action = [
1396
+ "s3:DeleteObject",
1397
+ "s3:DeleteObjectVersion",
1398
+ ]
1399
+ Resource = "${aws_s3_bucket.brain_artifacts.arn}/evidence-snapshots/*"
1400
+ },
1401
+ {
1402
+ Effect = "Allow"
1403
+ Action = [
1404
+ "s3:ListBucket",
1405
+ "s3:ListBucketVersions",
1406
+ ]
1407
+ Resource = aws_s3_bucket.brain_artifacts.arn
1408
+ Condition = {
1409
+ StringLike = {
1410
+ "s3:prefix" = "evidence-snapshots/*"
1411
+ }
1412
+ }
1413
+ },
1414
+ # Runtime-config document + platform secrets (DATABASE_URL rides the
1415
+ # env during the R8 transition window; the loader prefetches the
1416
+ # api-auth/appsync secrets at cold start).
1417
+ {
1418
+ Effect = "Allow"
1419
+ Action = ["ssm:GetParameter", "ssm:GetParameters"]
1420
+ Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/*"
1421
+ },
1422
+ {
1423
+ Effect = "Allow"
1424
+ Action = ["secretsmanager:GetSecretValue"]
1425
+ Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/*"
1426
+ },
1427
+ ],
1428
+ var.brain_artifacts_kms_key_arn != "" ? [
1429
+ {
1430
+ Effect = "Allow"
1431
+ Action = [
1432
+ "kms:Decrypt",
1433
+ "kms:GenerateDataKey",
1434
+ ]
1435
+ Resource = var.brain_artifacts_kms_key_arn
1436
+ },
1437
+ ] : [],
1438
+ )
1439
+ })
1440
+ }
1441
+
1442
+ # memory-retraction-drainer (THINK-193 U2, Codex P1 #3): the retraction
1443
+ # saga's product-owned retry path. Lambda async retries are disabled — every
1444
+ # transition on memory_retraction_attempts is a fenced CAS, so a duplicate
1445
+ # invocation is harmless but pointless; the rate(5 minutes) schedule below
1446
+ # is the retry. Attempts exceeding max_attempts are swept to dead_lettered
1447
+ # by the drainer itself instead of retrying forever.
1448
+ resource "aws_lambda_function_event_invoke_config" "memory_retraction_drainer" {
1449
+ count = local.deploy_lambda_handlers ? 1 : 0
1450
+ function_name = aws_lambda_function.handler["memory-retraction-drainer"].function_name
1451
+ maximum_retry_attempts = 0
1452
+ maximum_event_age_in_seconds = 3600
1453
+ }
1454
+
1455
+ resource "aws_scheduler_schedule" "memory_retraction_drainer" {
1456
+ count = local.deploy_lambda_handlers ? 1 : 0
1457
+
1458
+ name = "thinkwork-${var.stage}-memory-retraction-drainer"
1459
+ group_name = "default"
1460
+ schedule_expression = "rate(5 minutes)"
1461
+ state = "ENABLED"
1462
+
1463
+ flexible_time_window {
1464
+ mode = "OFF"
1465
+ }
1466
+
1467
+ target {
1468
+ arn = aws_lambda_function.handler["memory-retraction-drainer"].arn
1469
+ role_arn = aws_iam_role.scheduler.arn
1470
+ input = jsonencode({ limit = 25 })
1471
+
1472
+ retry_policy {
1473
+ maximum_retry_attempts = 0
1474
+ }
1475
+ }
1476
+ }
1477
+
1071
1478
  # Product-owned retry path for memory-retain. Lambda async retries remain
1072
1479
  # disabled above; this scheduled drain claims due rows from
1073
1480
  # memory_retain_attempts and replays bounded retry payloads idempotently.
@@ -1202,8 +1609,13 @@ locals {
1202
1609
  # Public login capabilities. Unauthenticated by design; the handler
1203
1610
  # resolves tenant-scoped OAuth options only from trusted API Gateway
1204
1611
  # domain metadata and fails closed for unknown/shared hosts.
1205
- "GET /api/auth/options" = "public-auth-options"
1206
- "OPTIONS /api/auth/options" = "public-auth-options"
1612
+ "GET /api/auth/options" = "public-auth-options"
1613
+ "OPTIONS /api/auth/options" = "public-auth-options"
1614
+ # Operator/deployment-only metadata seam. The Lambda independently
1615
+ # verifies API_AUTH_SECRET and rejects raw secret values.
1616
+ "POST /api/auth/providers/reconcile" = "auth-provider-reconcile"
1617
+ # Rollback-only routes. No native client links to these, and the entire
1618
+ # handler is filtered from Lambda/API Gateway in the retired phase.
1207
1619
  "GET /api/auth/workos/authorize" = "workos-auth"
1208
1620
  "OPTIONS /api/auth/workos/authorize" = "workos-auth"
1209
1621
  "GET /api/auth/workos/callback" = "workos-auth"
@@ -1303,14 +1715,43 @@ locals {
1303
1715
  "GET /.well-known/oauth-authorization-server" = "mcp-oauth"
1304
1716
  "GET /.well-known/openid-configuration" = "mcp-oauth"
1305
1717
  "GET /mcp/oauth/jwks" = "mcp-oauth"
1306
- "POST /mcp/oauth/register" = "mcp-oauth"
1307
- "GET /mcp/oauth/authorize" = "mcp-oauth"
1308
- "GET /mcp/oauth/callback" = "mcp-oauth"
1309
- "POST /mcp/oauth/token" = "mcp-oauth"
1310
- "POST /mcp/oauth/revoke" = "mcp-oauth"
1311
- "ANY /mcp/user-memory" = "mcp-user-memory"
1312
- "ANY /mcp/context-engine" = "mcp-context-engine"
1313
- "ANY /mcp/open-engine" = "mcp-open-engine"
1718
+ # THINK-316 U1: AgentCore CUSTOM_JWT discovery is deliberately isolated
1719
+ # under /agentcore so the existing user-memory OAuth issuer is unchanged.
1720
+ "GET /agentcore/.well-known/openid-configuration" = "mcp-oauth"
1721
+ "GET /agentcore/oauth/jwks" = "mcp-oauth"
1722
+ # THINK-316 U1 proof-only provider and downstream HTTPS target. Both
1723
+ # handlers validate their own credentials; the route filter removes all
1724
+ # five when enable_agentcore_multiplayer_proof is false.
1725
+ "GET /agentcore-proof/oauth/.well-known/openid-configuration" = "agentcore-proof-oauth-provider"
1726
+ "GET /agentcore-proof/oauth/authorize" = "agentcore-proof-oauth-provider"
1727
+ "POST /agentcore-proof/oauth/token" = "agentcore-proof-oauth-provider"
1728
+ "GET /agentcore-proof/target/owner" = "agentcore-identity-boundary-target"
1729
+ "GET /agentcore-proof/target/mixed" = "agentcore-identity-boundary-target"
1730
+ "POST /agentcore/capabilities/mcp/tools/list" = "harness-capability-mcp"
1731
+ "POST /agentcore/capabilities/mcp/tools/call" = "harness-capability-mcp"
1732
+ "POST /agentcore/capabilities/sandbox/execute" = "harness-code-interpreter-target"
1733
+ "POST /agentcore/capabilities/web/search" = "harness-builtin-tools-target"
1734
+ "POST /agentcore/capabilities/web/extract" = "harness-builtin-tools-target"
1735
+ "POST /agentcore/capabilities/brain/query" = "harness-platform-tools-target"
1736
+ "POST /agentcore/capabilities/workspace/skills/list" = "harness-platform-tools-target"
1737
+ "POST /agentcore/capabilities/workspace/skills/load" = "harness-platform-tools-target"
1738
+ "POST /agentcore/capabilities/message/attachments/list" = "harness-platform-tools-target"
1739
+ "POST /agentcore/capabilities/message/attachments/read" = "harness-platform-tools-target"
1740
+ "POST /agentcore/capabilities/user/questions/ask" = "harness-platform-tools-target"
1741
+ "POST /agentcore/capabilities/email/send" = "harness-platform-tools-target"
1742
+ "POST /mcp/oauth/register" = "mcp-oauth"
1743
+ "GET /mcp/oauth/authorize" = "mcp-oauth"
1744
+ "GET /mcp/oauth/callback" = "mcp-oauth"
1745
+ "POST /mcp/oauth/token" = "mcp-oauth"
1746
+ "POST /mcp/oauth/revoke" = "mcp-oauth"
1747
+ "ANY /mcp/user-memory" = "mcp-user-memory"
1748
+ "ANY /mcp/context-engine" = "mcp-context-engine"
1749
+ "ANY /mcp/open-engine" = "mcp-open-engine"
1750
+ # THINK-280 U8: scoped external capability search facade. The handler is
1751
+ # inert unless CAPABILITY_EXTERNAL_SEARCH_ENABLED (gated on
1752
+ # enable_capability_broker) is on — routing it while disabled returns an
1753
+ # empty, no-data projection.
1754
+ "ANY /mcp/capabilities" = "mcp-capability-search"
1314
1755
 
1315
1756
  # Brain v0 service-auth writeback.
1316
1757
  "POST /api/brain/agent-write" = "brain-agent-write"
@@ -1350,6 +1791,18 @@ locals {
1350
1791
  "OPTIONS /api/deployment-sessions/{sessionId}/teardown" = "deployment-sessions"
1351
1792
  "GET /api/auth/me" = "auth-me"
1352
1793
  "OPTIONS /api/auth/me" = "auth-me"
1794
+ "POST /api/auth/revoke" = "auth-revoke"
1795
+ "OPTIONS /api/auth/revoke" = "auth-revoke"
1796
+ "POST /api/auth/subscription-ticket" = "auth-subscription-ticket"
1797
+ "OPTIONS /api/auth/subscription-ticket" = "auth-subscription-ticket"
1798
+ "POST /api/auth/enrollment/consume" = "auth-enrollment"
1799
+ "OPTIONS /api/auth/enrollment/consume" = "auth-enrollment"
1800
+ "POST /api/auth/enrollment/recover" = "auth-enrollment"
1801
+ "OPTIONS /api/auth/enrollment/recover" = "auth-enrollment"
1802
+ "POST /api/auth/enrollment/migrate" = "auth-enrollment"
1803
+ "OPTIONS /api/auth/enrollment/migrate" = "auth-enrollment"
1804
+ "POST /api/auth/enrollment/auto-link" = "auth-enrollment"
1805
+ "OPTIONS /api/auth/enrollment/auto-link" = "auth-enrollment"
1353
1806
  "ANY /api/extensions/{extensionId}" = "extension-proxy"
1354
1807
  "ANY /api/extensions/{extensionId}/{proxy+}" = "extension-proxy"
1355
1808
 
@@ -1419,11 +1872,18 @@ locals {
1419
1872
  # Slack workspace app ingress. These unauthenticated public endpoints
1420
1873
  # verify Slack signatures in handler code before any tenant work happens.
1421
1874
  "POST /slack/events" = "slack-events"
1422
- "POST /slack/slash-command" = "slack-slash-command"
1423
- "POST /slack/interactivity" = "slack-interactivity"
1424
1875
  "GET /slack/oauth/install" = "slack-oauth-install"
1425
1876
  "POST /slack/oauth/install" = "slack-oauth-install"
1426
1877
 
1878
+ # Microsoft Teams install + account-link ingress. Like the Slack block
1879
+ # above, these routes have no gateway authorizer: install/start and
1880
+ # account-link/complete verify the Cognito JWT in-handler, and
1881
+ # install/complete (the Microsoft admin-consent redirect) verifies the
1882
+ # HMAC-signed install state in-handler before any tenant work happens.
1883
+ "POST /msteams/install/start" = "msteams-install-start"
1884
+ "GET /msteams/install/complete" = "msteams-install-complete"
1885
+ "POST /msteams/account-link/complete" = "msteams-account-link-complete"
1886
+
1427
1887
  # Memory
1428
1888
  "ANY /api/memory/{proxy+}" = "memory"
1429
1889
 
@@ -1557,7 +2017,9 @@ locals {
1557
2017
  "GET /api/runtime/capability-catalog" = "capability-catalog-list"
1558
2018
  "OPTIONS /api/runtime/capability-catalog" = "capability-catalog-list"
1559
2019
  } : route_key => handler_name
1560
- if !contains(local.optional_integration_handler_names, handler_name)
2020
+ if !contains(local.optional_integration_handler_names, handler_name) && (
2021
+ var.enable_agentcore_multiplayer_proof || !startswith(route_key, "GET /agentcore/")
2022
+ )
1561
2023
  } : {}
1562
2024
  }
1563
2025
 
@@ -1578,6 +2040,16 @@ resource "aws_apigatewayv2_route" "handler" {
1578
2040
  target = "integrations/${aws_apigatewayv2_integration.handler[each.key].id}"
1579
2041
  }
1580
2042
 
2043
+ resource "aws_lambda_permission" "appsync_subscription_authorizer" {
2044
+ count = local.deploy_lambda_handlers ? 1 : 0
2045
+
2046
+ statement_id = "AllowAppSyncSubscriptionAuthorization"
2047
+ action = "lambda:InvokeFunction"
2048
+ function_name = aws_lambda_function.handler["appsync-subscription-authorizer"].function_name
2049
+ principal = "appsync.amazonaws.com"
2050
+ source_arn = "arn:aws:appsync:${var.region}:${var.account_id}:apis/${var.appsync_api_id}"
2051
+ }
2052
+
1581
2053
  resource "aws_lambda_permission" "handler_apigw" {
1582
2054
  for_each = local.deploy_lambda_handlers ? toset(distinct(values(local.api_routes))) : toset([])
1583
2055
 
@@ -1653,6 +2125,32 @@ resource "aws_scheduler_schedule" "wakeup_processor" {
1653
2125
  }
1654
2126
  }
1655
2127
 
2128
+ # Replays durable authorization-revocation invalidations. The worker owns its
2129
+ # retry state in Aurora and suppresses tenant realtime fan-out until AppSync
2130
+ # confirms every sensitive subscription class has been invalidated.
2131
+ resource "aws_scheduler_schedule" "subscription_invalidation" {
2132
+ count = local.deploy_lambda_handlers ? 1 : 0
2133
+
2134
+ name = "thinkwork-${var.stage}-subscription-invalidation"
2135
+ group_name = "default"
2136
+ schedule_expression = "rate(1 minutes)"
2137
+ state = "ENABLED"
2138
+
2139
+ flexible_time_window {
2140
+ mode = "OFF"
2141
+ }
2142
+
2143
+ target {
2144
+ arn = aws_lambda_function.handler["subscription-invalidation"].arn
2145
+ role_arn = aws_iam_role.scheduler.arn
2146
+ input = jsonencode({ limit = 25 })
2147
+
2148
+ retry_policy {
2149
+ maximum_retry_attempts = 0
2150
+ }
2151
+ }
2152
+ }
2153
+
1656
2154
  # ---------------------------------------------------------------------------
1657
2155
  # webhook_deliveries retention cron — daily delete of rows older than 90 days
1658
2156
  # ---------------------------------------------------------------------------
@@ -1740,6 +2238,25 @@ resource "aws_scheduler_schedule" "requester_memory_dreaming" {
1740
2238
  # the audit log.
1741
2239
  # ---------------------------------------------------------------------------
1742
2240
 
2241
+ # THINK-193 U3: memory_stage stall recovery — every 15 minutes, bounded batch.
2242
+ resource "aws_scheduler_schedule" "memory_stage_sweeper" {
2243
+ count = local.deploy_lambda_handlers ? 1 : 0
2244
+
2245
+ name = "thinkwork-${var.stage}-memory-stage-sweeper"
2246
+ group_name = "default"
2247
+ schedule_expression = "rate(15 minutes)"
2248
+ state = "ENABLED"
2249
+
2250
+ flexible_time_window {
2251
+ mode = "OFF"
2252
+ }
2253
+
2254
+ target {
2255
+ arn = aws_lambda_function.handler["memory-stage-sweeper"].arn
2256
+ role_arn = aws_iam_role.scheduler.arn
2257
+ }
2258
+ }
2259
+
1743
2260
  resource "aws_scheduler_schedule" "mcp_approval_sweeper" {
1744
2261
  count = local.deploy_lambda_handlers ? 1 : 0
1745
2262
 
@@ -2256,6 +2773,37 @@ resource "aws_scheduler_schedule" "knowledge_graph_observations_ingest" {
2256
2773
  }
2257
2774
  }
2258
2775
 
2776
+ # Ontology suggestion scan sweep (THINK-320 U4/KTD-3). Enumerates tenants and
2777
+ # starts a suggestion scan job per tenant; tenants with a pending/running scan
2778
+ # are skipped in the handler, and the scan-job dedupe key drops same-bucket
2779
+ # duplicate starts. Ships DISABLED — enabled per stage via
2780
+ # ontology_scan_sweep_enabled after the Living Map review surfaces land.
2781
+ resource "aws_scheduler_schedule" "ontology_scan_sweep" {
2782
+ count = local.deploy_lambda_handlers ? 1 : 0
2783
+
2784
+ name = "thinkwork-${var.stage}-ontology-scan-sweep"
2785
+ group_name = "default"
2786
+ schedule_expression = "rate(1 days)"
2787
+ state = var.ontology_scan_sweep_enabled ? "ENABLED" : "DISABLED"
2788
+
2789
+ flexible_time_window {
2790
+ mode = "OFF"
2791
+ }
2792
+
2793
+ target {
2794
+ arn = aws_lambda_function.handler["ontology-scan"].arn
2795
+ role_arn = aws_iam_role.scheduler.arn
2796
+ input = jsonencode({ sweep = true, trigger = "scheduled" })
2797
+
2798
+ # Periodic idempotent worker: the next daily tick IS the retry (mirrors
2799
+ # the knowledge_graph_observations_ingest precedent — scheduler retries
2800
+ # only stack invocations onto a self-correcting cadence).
2801
+ retry_policy {
2802
+ maximum_retry_attempts = 0
2803
+ }
2804
+ }
2805
+ }
2806
+
2259
2807
  resource "aws_scheduler_schedule" "wiki_lint" {
2260
2808
  count = local.deploy_lambda_handlers ? 1 : 0
2261
2809
 
@@ -2447,6 +2995,27 @@ resource "aws_s3_bucket_lifecycle_configuration" "brain_artifacts" {
2447
2995
  }
2448
2996
  }
2449
2997
 
2998
+ # THINK-193 U1 (Codex F6): normalized evidence snapshots are short-lived
2999
+ # working copies — the durable record is the evidence row (hash + ref) and
3000
+ # the Hindsight projection. Expire content after 30 days; the app mirrors
3001
+ # this via memory_evidence_items.snapshot_expires_at.
3002
+ rule {
3003
+ id = "expire-evidence-snapshots"
3004
+ status = "Enabled"
3005
+
3006
+ filter {
3007
+ prefix = "evidence-snapshots/"
3008
+ }
3009
+
3010
+ expiration {
3011
+ days = 30
3012
+ }
3013
+
3014
+ noncurrent_version_expiration {
3015
+ noncurrent_days = 30
3016
+ }
3017
+ }
3018
+
2450
3019
  rule {
2451
3020
  id = "exports-expiration"
2452
3021
  status = "Enabled"