thinkwork-cli 0.13.1 → 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 (130) hide show
  1. package/dist/{api-client-4DZ266J5.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-Y2WHJECR.js → chunk-Z2F6RJFR.js} +1 -39
  15. package/dist/cli.js +43080 -13150
  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 +6 -0
  38. package/dist/drizzle/0230_analyst_rls.sql +9 -0
  39. package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
  40. package/dist/drizzle/0251_brain_stragglers.sql +21 -0
  41. package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
  42. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  43. package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
  44. package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
  45. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  46. package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
  47. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  48. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  49. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  50. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
  51. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  52. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  53. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  54. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  55. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  56. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  57. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  58. package/dist/event-streams-7JNLJEEL.js +894 -0
  59. package/dist/event-streams-KDF5RWR6.js +1515 -0
  60. package/dist/loadSso-DJVB7BXE.js +576 -0
  61. package/dist/loadSso-JAMNA3WY.js +583 -0
  62. package/dist/plugins/catalog/package.json +1 -2
  63. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  64. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
  65. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  66. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  67. package/dist/plugins/catalog/src/contracts.ts +2 -179
  68. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  69. package/dist/signin-UGDWTGS4.js +821 -0
  70. package/dist/sso-oidc-Q4VXANXY.js +837 -0
  71. package/dist/sts-CWLSE3GH.js +1241 -0
  72. package/dist/sts-DNZLNVOV.js +1245 -0
  73. package/dist/terraform/examples/greenfield/main.tf +223 -66
  74. package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
  75. package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
  76. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
  77. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
  78. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
  79. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
  80. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
  81. package/dist/terraform/modules/app/agentcore-harness/main.tf +237 -8
  82. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +63 -0
  83. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
  84. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
  85. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
  86. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
  87. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
  88. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
  89. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
  90. package/dist/terraform/modules/app/agentcore-harness/variables.tf +72 -1
  91. package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
  92. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  93. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
  94. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
  95. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  96. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
  97. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  98. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
  99. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
  100. package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
  101. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  102. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  103. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  104. package/dist/terraform/modules/app/deployment-control-plane/runner.py +966 -27
  105. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +581 -22
  106. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
  107. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
  108. package/dist/terraform/modules/app/lambda-api/handlers.tf +298 -74
  109. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +99 -54
  110. package/dist/terraform/modules/app/lambda-api/main.tf +24 -0
  111. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
  112. package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
  113. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  114. package/dist/terraform/modules/app/lambda-api/variables.tf +109 -16
  115. package/dist/terraform/modules/foundation/cognito/main.tf +294 -72
  116. package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
  117. package/dist/terraform/modules/foundation/cognito/variables.tf +114 -4
  118. package/dist/terraform/modules/thinkwork/main.tf +269 -46
  119. package/dist/terraform/modules/thinkwork/outputs.tf +136 -8
  120. package/dist/terraform/modules/thinkwork/variables.tf +124 -17
  121. package/dist/terraform/schema.graphql +69 -36
  122. package/dist/workspace-defaults/files/USER.md +1 -0
  123. package/package.json +3 -2
  124. package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
  125. package/dist/plugins/workos-auth/package.json +0 -22
  126. package/dist/plugins/workos-auth/src/index.ts +0 -30
  127. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  128. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  129. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  130. package/dist/plugins/workos-auth/tsconfig.json +0 -8
@@ -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
@@ -37,6 +43,15 @@ locals {
37
43
  "msteams-install-complete",
38
44
  "msteams-account-link-complete",
39
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
+ ],
40
55
  )
41
56
 
42
57
  # Config-class configuration shared by all API handlers. As of plan
@@ -47,15 +62,19 @@ locals {
47
62
  # The reader-coverage fixture test in apps/cli fails CI if a key in this
48
63
  # map still has a direct process.env reader. Identity values (STAGE,
49
64
  # AWS_ACCOUNT_ID, NODE_OPTIONS) and secrets (DATABASE_URL,
50
- # API_AUTH_SECRET, APPSYNC_API_KEY) stay out of this map — identity stays
65
+ # API_AUTH_SECRET) stay out of this map — identity stays
51
66
  # env forever, secrets live in Secrets Manager (R4), never in the String
52
67
  # document.
53
68
  config_env = merge({
54
69
  # THINK-173: PUBLIC verification key (not a secret) + the NAME of the
55
70
  # private-key secret (resolved via runtime-config's secret loader —
56
71
  # the PEM itself never enters the env or the String document).
57
- CAPABILITY_SIGNING_PUBLIC_KEY = var.capability_signing_public_key
58
- 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
59
78
  # THINK-229 U3/KTD5 — the analyst policy-source enforcement flip.
60
79
  # "row" (default) keeps sidecar policy shadow-only; "sidecar" makes the
61
80
  # signed sidecar block authoritative (budgets/policyClaims flow). Flip
@@ -89,7 +108,7 @@ locals {
89
108
  CAPABILITY_EXTERNAL_SEARCH_ENABLED = var.enable_capability_broker ? "true" : "false"
90
109
  ENABLE_CAPABILITY_BROKER = var.enable_capability_broker ? "true" : "false"
91
110
  # BUCKET_NAME and USER_POOL_ID were duplicate aliases of WORKSPACE_BUCKET
92
- # 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;
93
112
  # THINKWORK_API_SECRET and EMAIL_HMAC_SECRET duplicated API_AUTH_SECRET
94
113
  # (~310 serialized bytes total). graphql-http sits at Lambda's hard 4KB
95
114
  # env ceiling (#2375) — every reader falls back to the canonical name.
@@ -231,18 +250,35 @@ locals {
231
250
  # window (R8). Config-class keys live ONLY in the SSM runtime-config
232
251
  # document now — adding a key here is guarded by the identity-allowlist
233
252
  # fixture test in apps/cli (R10). Follow-up release: DATABASE_URL,
234
- # APPSYNC_API_KEY, and API_AUTH_SECRET drop too (readers already resolve
253
+ # API_AUTH_SECRET drops too (readers already resolve
235
254
  # via Secrets Manager prefetch when the env copies are absent), bringing
236
255
  # every handler under the ≤1KB R1 target.
237
256
  common_env = {
238
257
  STAGE = var.stage
239
258
  DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
240
- APPSYNC_API_KEY = var.appsync_api_key
241
259
  API_AUTH_SECRET = var.api_auth_secret
242
260
  AWS_ACCOUNT_ID = var.account_id
243
261
  NODE_OPTIONS = "--enable-source-maps"
244
262
  }
245
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
+
246
282
  # Per-handler env-var overrides. ARNs are constructed from the naming
247
283
  # pattern (same trick as the api-cross-function-invoke statement in
248
284
  # iam-grouped.tf) so we don't introduce a self-referential dependency
@@ -256,6 +292,85 @@ locals {
256
292
  }
257
293
 
258
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
+ }
259
374
  # Analyst query broker (THINK-228 U3). Reader role + caller credential
260
375
  # secrets, and the workspace bucket's analyst-staging/ prefix for
261
376
  # large-result CSVs (lifecycle TTL lives on the bucket module). The
@@ -406,7 +521,15 @@ locals {
406
521
  # knowledge_graph_search tool; the per-agent tool policy gates on top.
407
522
  "chat-agent-invoke" = {
408
523
  KNOWLEDGE_GRAPH_TOOL_ENABLED = tostring(var.knowledge_graph_tool_enabled)
409
- }
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" = {}
410
533
  # 240s: sync Hindsight retain (LLM extraction + auto-consolidation) can
411
534
  # exceed 60s; the client timeout must stay below the Lambda timeout (300s)
412
535
  # and below the Hindsight ALB idle_timeout (300s) so failures classify as
@@ -417,12 +540,6 @@ locals {
417
540
  "brain-dream-state" = {
418
541
  BRAIN_DREAM_STATE_ENABLED = tostring(var.brain_dream_state_enabled)
419
542
  }
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
- }
426
543
  # Bedrock KB provisioning. Per-handler (not common_env) so these don't bloat
427
544
  # the already-near-4KB graphql-http env. Bedrock's RDS-backed KB needs the
428
545
  # cluster ARN + the KB service role (passed at CreateKnowledgeBase time).
@@ -626,6 +743,23 @@ resource "aws_lambda_function" "handler" {
626
743
  # Bearer API_AUTH_SECRET. Idempotent on thread_turns.finalized_at
627
744
  # (migration 0123). Plan: 2026-05-22-006.
628
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",
629
763
  # canvas-refresh — headless Living Artifacts data-refresh (THINK-145 U6).
630
764
  # Invoked RequestResponse by the refreshCanvasData mutation (graphql-http)
631
765
  # and by job-trigger's canvas_refresh branch (U7). Re-runs the saved
@@ -659,7 +793,13 @@ resource "aws_lambda_function" "handler" {
659
793
  "stripe-subscription",
660
794
  "deployment-sessions",
661
795
  "auth-me",
796
+ "auth-revoke",
797
+ "auth-enrollment",
798
+ "auth-subscription-ticket",
799
+ "appsync-subscription-authorizer",
800
+ "subscription-invalidation",
662
801
  "public-auth-options",
802
+ "auth-provider-reconcile",
663
803
  "workos-auth",
664
804
  # Public artifact share links (THINK-208): GET /share/{token}, token
665
805
  # verified in handler code (no gateway auth, uniform 404 on any miss).
@@ -695,10 +835,6 @@ resource "aws_lambda_function" "handler" {
695
835
  # dispatch can withhold a failing/stale connection loudly.
696
836
  "analyst-connection-reconciler",
697
837
  "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",
702
838
  "webhook-crm-opportunity",
703
839
  "webhook-task-event",
704
840
  "workspace-files",
@@ -929,7 +1065,7 @@ resource "aws_lambda_function" "handler" {
929
1065
  # so its destructive evidence-snapshot S3 capability (version enumeration +
930
1066
  # bulk version deletion) never leaks into the 90+ handlers on the shared
931
1067
  # 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
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
933
1069
  handler = "index.handler"
934
1070
  runtime = local.runtime
935
1071
  # Parameters and Secrets extension: container-local cache for the SSM
@@ -942,7 +1078,6 @@ resource "aws_lambda_function" "handler" {
942
1078
  depends_on = [
943
1079
  aws_ssm_parameter.runtime_config,
944
1080
  aws_secretsmanager_secret_version.api_auth,
945
- aws_secretsmanager_secret_version.appsync_api_key,
946
1081
  ]
947
1082
  # eval-runner walks every test case sequentially, invoking an agent +
948
1083
  # waiting up to 2 min for spans to propagate per test, so a 10-test run
@@ -957,8 +1092,11 @@ resource "aws_lambda_function" "handler" {
957
1092
  # validates the agent, builds the AgentCore invoke payload, dispatches
958
1093
  # Event-mode, and returns. Setup is ~5s in practice; 60s gives 12×
959
1094
  # headroom for transient slowness.
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
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
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
962
1100
 
963
1101
  filename = local.use_local_zips ? "${var.lambda_zips_dir}/${each.key}.zip" : null
964
1102
  source_code_hash = local.use_local_zips ? filebase64sha256("${var.lambda_zips_dir}/${each.key}.zip") : null
@@ -987,7 +1125,10 @@ resource "aws_lambda_function" "handler" {
987
1125
 
988
1126
  environment {
989
1127
  variables = merge(
990
- 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,
991
1132
  { FUNCTION_NAME = each.key },
992
1133
  lookup(local.handler_extra_env, each.key, {}),
993
1134
  )
@@ -1084,6 +1225,20 @@ resource "aws_lambda_function_event_invoke_config" "chat_agent_invoke" {
1084
1225
  maximum_event_age_in_seconds = 3600
1085
1226
  }
1086
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
+
1087
1242
  resource "aws_lambda_function_event_invoke_config" "wiki_compile" {
1088
1243
  count = local.deploy_lambda_handlers ? 1 : 0
1089
1244
  function_name = aws_lambda_function.handler["wiki-compile"].function_name
@@ -1454,8 +1609,13 @@ locals {
1454
1609
  # Public login capabilities. Unauthenticated by design; the handler
1455
1610
  # resolves tenant-scoped OAuth options only from trusted API Gateway
1456
1611
  # domain metadata and fails closed for unknown/shared hosts.
1457
- "GET /api/auth/options" = "public-auth-options"
1458
- "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.
1459
1619
  "GET /api/auth/workos/authorize" = "workos-auth"
1460
1620
  "OPTIONS /api/auth/workos/authorize" = "workos-auth"
1461
1621
  "GET /api/auth/workos/callback" = "workos-auth"
@@ -1555,14 +1715,38 @@ locals {
1555
1715
  "GET /.well-known/oauth-authorization-server" = "mcp-oauth"
1556
1716
  "GET /.well-known/openid-configuration" = "mcp-oauth"
1557
1717
  "GET /mcp/oauth/jwks" = "mcp-oauth"
1558
- "POST /mcp/oauth/register" = "mcp-oauth"
1559
- "GET /mcp/oauth/authorize" = "mcp-oauth"
1560
- "GET /mcp/oauth/callback" = "mcp-oauth"
1561
- "POST /mcp/oauth/token" = "mcp-oauth"
1562
- "POST /mcp/oauth/revoke" = "mcp-oauth"
1563
- "ANY /mcp/user-memory" = "mcp-user-memory"
1564
- "ANY /mcp/context-engine" = "mcp-context-engine"
1565
- "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"
1566
1750
  # THINK-280 U8: scoped external capability search facade. The handler is
1567
1751
  # inert unless CAPABILITY_EXTERNAL_SEARCH_ENABLED (gated on
1568
1752
  # enable_capability_broker) is on — routing it while disabled returns an
@@ -1607,6 +1791,18 @@ locals {
1607
1791
  "OPTIONS /api/deployment-sessions/{sessionId}/teardown" = "deployment-sessions"
1608
1792
  "GET /api/auth/me" = "auth-me"
1609
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"
1610
1806
  "ANY /api/extensions/{extensionId}" = "extension-proxy"
1611
1807
  "ANY /api/extensions/{extensionId}/{proxy+}" = "extension-proxy"
1612
1808
 
@@ -1821,7 +2017,9 @@ locals {
1821
2017
  "GET /api/runtime/capability-catalog" = "capability-catalog-list"
1822
2018
  "OPTIONS /api/runtime/capability-catalog" = "capability-catalog-list"
1823
2019
  } : route_key => handler_name
1824
- 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
+ )
1825
2023
  } : {}
1826
2024
  }
1827
2025
 
@@ -1842,6 +2040,16 @@ resource "aws_apigatewayv2_route" "handler" {
1842
2040
  target = "integrations/${aws_apigatewayv2_integration.handler[each.key].id}"
1843
2041
  }
1844
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
+
1845
2053
  resource "aws_lambda_permission" "handler_apigw" {
1846
2054
  for_each = local.deploy_lambda_handlers ? toset(distinct(values(local.api_routes))) : toset([])
1847
2055
 
@@ -1917,6 +2125,32 @@ resource "aws_scheduler_schedule" "wakeup_processor" {
1917
2125
  }
1918
2126
  }
1919
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
+
1920
2154
  # ---------------------------------------------------------------------------
1921
2155
  # webhook_deliveries retention cron — daily delete of rows older than 90 days
1922
2156
  # ---------------------------------------------------------------------------
@@ -2482,47 +2716,6 @@ resource "aws_scheduler_schedule" "stall_monitor" {
2482
2716
  }
2483
2717
  }
2484
2718
 
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
-
2526
2719
 
2527
2720
  # ---------------------------------------------------------------------------
2528
2721
  # Compounding Memory — nightly hygiene + export
@@ -2580,6 +2773,37 @@ resource "aws_scheduler_schedule" "knowledge_graph_observations_ingest" {
2580
2773
  }
2581
2774
  }
2582
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
+
2583
2807
  resource "aws_scheduler_schedule" "wiki_lint" {
2584
2808
  count = local.deploy_lambda_handlers ? 1 : 0
2585
2809