thinkwork-cli 0.1.1 → 0.2.0

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 (38) hide show
  1. package/dist/cli.js +132 -11
  2. package/dist/terraform/examples/greenfield/main.tf +190 -0
  3. package/dist/terraform/examples/greenfield/terraform.tfvars.example +28 -0
  4. package/dist/terraform/modules/_internal/workspace-guard/main.tf +29 -0
  5. package/dist/terraform/modules/app/agentcore-runtime/main.tf +217 -0
  6. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +122 -0
  7. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +20 -0
  8. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +31 -0
  9. package/dist/terraform/modules/app/crons/main.tf +55 -0
  10. package/dist/terraform/modules/app/hindsight-memory/README.md +66 -0
  11. package/dist/terraform/modules/app/hindsight-memory/main.tf +331 -0
  12. package/dist/terraform/modules/app/job-triggers/main.tf +70 -0
  13. package/dist/terraform/modules/app/lambda-api/.build/placeholder.zip +0 -0
  14. package/dist/terraform/modules/app/lambda-api/handlers.tf +311 -0
  15. package/dist/terraform/modules/app/lambda-api/main.tf +245 -0
  16. package/dist/terraform/modules/app/lambda-api/outputs.tf +24 -0
  17. package/dist/terraform/modules/app/lambda-api/variables.tf +153 -0
  18. package/dist/terraform/modules/app/ses-email/main.tf +51 -0
  19. package/dist/terraform/modules/app/static-site/main.tf +176 -0
  20. package/dist/terraform/modules/data/aurora-postgres/README.md +92 -0
  21. package/dist/terraform/modules/data/aurora-postgres/main.tf +185 -0
  22. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +30 -0
  23. package/dist/terraform/modules/data/aurora-postgres/variables.tf +114 -0
  24. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +102 -0
  25. package/dist/terraform/modules/data/s3-buckets/main.tf +91 -0
  26. package/dist/terraform/modules/foundation/cognito/main.tf +377 -0
  27. package/dist/terraform/modules/foundation/cognito/outputs.tf +29 -0
  28. package/dist/terraform/modules/foundation/cognito/variables.tf +124 -0
  29. package/dist/terraform/modules/foundation/dns/main.tf +49 -0
  30. package/dist/terraform/modules/foundation/kms/main.tf +49 -0
  31. package/dist/terraform/modules/foundation/vpc/main.tf +137 -0
  32. package/dist/terraform/modules/foundation/vpc/outputs.tf +14 -0
  33. package/dist/terraform/modules/foundation/vpc/variables.tf +40 -0
  34. package/dist/terraform/modules/thinkwork/main.tf +212 -0
  35. package/dist/terraform/modules/thinkwork/outputs.tf +87 -0
  36. package/dist/terraform/modules/thinkwork/variables.tf +241 -0
  37. package/dist/terraform/schema.graphql +199 -0
  38. package/package.json +2 -2
@@ -0,0 +1,122 @@
1
+ ################################################################################
2
+ # AppSync Subscriptions — App Module
3
+ #
4
+ # AppSync exists ONLY as a thin realtime/event layer for subscription fan-out.
5
+ # Queries and mutations go through API Gateway V2 → Lambda, NOT through AppSync.
6
+ #
7
+ # Per Decision 9: the unused RDS data source is removed. Only the NONE
8
+ # passthrough data source remains for notification mutations. The schema is
9
+ # a subscription-only fragment, not the full product schema.
10
+ #
11
+ # Post-launch consideration: replace with standard graphql-ws over API Gateway
12
+ # V2 WebSocket API. Not v0.1 scope.
13
+ ################################################################################
14
+
15
+ ################################################################################
16
+ # GraphQL API
17
+ ################################################################################
18
+
19
+ resource "aws_appsync_graphql_api" "subscriptions" {
20
+ name = "thinkwork-${var.stage}-subscriptions"
21
+ authentication_type = "API_KEY"
22
+ xray_enabled = false
23
+
24
+ schema = var.subscription_schema
25
+
26
+ additional_authentication_provider {
27
+ authentication_type = "AWS_IAM"
28
+ }
29
+
30
+ additional_authentication_provider {
31
+ authentication_type = "AMAZON_COGNITO_USER_POOLS"
32
+
33
+ user_pool_config {
34
+ user_pool_id = var.user_pool_id
35
+ }
36
+ }
37
+
38
+ tags = {
39
+ Name = "thinkwork-${var.stage}-subscriptions"
40
+ }
41
+ }
42
+
43
+ ################################################################################
44
+ # API Key — 365 day expiry
45
+ ################################################################################
46
+
47
+ resource "aws_appsync_api_key" "main" {
48
+ api_id = aws_appsync_graphql_api.subscriptions.id
49
+ expires = timeadd(timestamp(), "8760h")
50
+
51
+ lifecycle {
52
+ ignore_changes = [expires]
53
+ }
54
+ }
55
+
56
+ ################################################################################
57
+ # NONE Data Source (passthrough for notification mutations)
58
+ ################################################################################
59
+
60
+ resource "aws_appsync_datasource" "none" {
61
+ api_id = aws_appsync_graphql_api.subscriptions.id
62
+ name = "NonePassthrough"
63
+ type = "NONE"
64
+ }
65
+
66
+ ################################################################################
67
+ # Notification Mutation Resolvers
68
+ #
69
+ # v1 events only — deferred events (onEvalRunUpdated, onCostRecorded) are cut.
70
+ ################################################################################
71
+
72
+ locals {
73
+ notification_mutations = [
74
+ "notifyAgentStatus",
75
+ "notifyNewMessage",
76
+ "notifyHeartbeatActivity",
77
+ "notifyThreadUpdate",
78
+ "notifyInboxItemUpdate",
79
+ "notifyThreadTurnUpdate",
80
+ "notifyOrgUpdate",
81
+ ]
82
+ }
83
+
84
+ resource "aws_appsync_resolver" "notifications" {
85
+ for_each = toset(local.notification_mutations)
86
+
87
+ api_id = aws_appsync_graphql_api.subscriptions.id
88
+ type = "Mutation"
89
+ field = each.value
90
+ data_source = aws_appsync_datasource.none.name
91
+
92
+ request_template = <<-EOF
93
+ {"version":"2017-02-28","payload":$util.toJson($context.arguments)}
94
+ EOF
95
+
96
+ response_template = <<-EOF
97
+ #set($result = $context.result)
98
+ #if(!$result.updatedAt)
99
+ #set($result.updatedAt = $util.time.nowISO8601())
100
+ #end
101
+ #if(!$result.createdAt)
102
+ #set($result.createdAt = $util.time.nowISO8601())
103
+ #end
104
+ $util.toJson($result)
105
+ EOF
106
+ }
107
+
108
+ ################################################################################
109
+ # Custom Domain (optional)
110
+ ################################################################################
111
+
112
+ resource "aws_appsync_domain_name" "main" {
113
+ count = var.custom_domain != "" ? 1 : 0
114
+ domain_name = var.custom_domain
115
+ certificate_arn = var.certificate_arn
116
+ }
117
+
118
+ resource "aws_appsync_domain_name_api_association" "main" {
119
+ count = var.custom_domain != "" ? 1 : 0
120
+ api_id = aws_appsync_graphql_api.subscriptions.id
121
+ domain_name = aws_appsync_domain_name.main[0].domain_name
122
+ }
@@ -0,0 +1,20 @@
1
+ output "graphql_api_id" {
2
+ description = "AppSync GraphQL API ID"
3
+ value = aws_appsync_graphql_api.subscriptions.id
4
+ }
5
+
6
+ output "graphql_api_url" {
7
+ description = "AppSync GraphQL endpoint URL (used by backend to push notifications)"
8
+ value = aws_appsync_graphql_api.subscriptions.uris["GRAPHQL"]
9
+ }
10
+
11
+ output "graphql_realtime_url" {
12
+ description = "AppSync realtime WebSocket URL (used by frontend subscription clients)"
13
+ value = aws_appsync_graphql_api.subscriptions.uris["REALTIME"]
14
+ }
15
+
16
+ output "graphql_api_key" {
17
+ description = "AppSync API key"
18
+ value = aws_appsync_api_key.main.key
19
+ sensitive = true
20
+ }
@@ -0,0 +1,31 @@
1
+ variable "stage" {
2
+ description = "Deployment stage"
3
+ type = string
4
+ }
5
+
6
+ variable "region" {
7
+ description = "AWS region"
8
+ type = string
9
+ }
10
+
11
+ variable "user_pool_id" {
12
+ description = "Cognito user pool ID for authentication"
13
+ type = string
14
+ }
15
+
16
+ variable "subscription_schema" {
17
+ description = "GraphQL schema containing only Subscription + notification Mutation types. This is a subscription-only fragment, NOT the full product schema."
18
+ type = string
19
+ }
20
+
21
+ variable "custom_domain" {
22
+ description = "Custom domain for the AppSync API (e.g. subscriptions.thinkwork.ai). Leave empty to skip."
23
+ type = string
24
+ default = ""
25
+ }
26
+
27
+ variable "certificate_arn" {
28
+ description = "ACM certificate ARN for the custom domain (required when custom_domain is set)"
29
+ type = string
30
+ default = ""
31
+ }
@@ -0,0 +1,55 @@
1
+ ################################################################################
2
+ # Crons — App Module
3
+ #
4
+ # Scheduled jobs and event-driven processors. Full implementation ported
5
+ # in Phase 4. Phase 1 creates the IAM role and a placeholder cron only.
6
+ #
7
+ # v1 scope: standard crons + wakeup processor.
8
+ # Cut: kg_extract_fanout, kg_extract_worker, span_enrichment (PRD-41B).
9
+ ################################################################################
10
+
11
+ variable "stage" {
12
+ description = "Deployment stage"
13
+ type = string
14
+ }
15
+
16
+ variable "account_id" {
17
+ description = "AWS account ID"
18
+ type = string
19
+ }
20
+
21
+ variable "region" {
22
+ description = "AWS region"
23
+ type = string
24
+ }
25
+
26
+ ################################################################################
27
+ # Shared IAM Role for Cron Lambdas
28
+ ################################################################################
29
+
30
+ resource "aws_iam_role" "cron_lambda" {
31
+ name = "thinkwork-${var.stage}-cron-lambda-role"
32
+
33
+ assume_role_policy = jsonencode({
34
+ Version = "2012-10-17"
35
+ Statement = [{
36
+ Effect = "Allow"
37
+ Principal = { Service = "lambda.amazonaws.com" }
38
+ Action = "sts:AssumeRole"
39
+ }]
40
+ })
41
+ }
42
+
43
+ resource "aws_iam_role_policy_attachment" "cron_basic" {
44
+ role = aws_iam_role.cron_lambda.name
45
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
46
+ }
47
+
48
+ ################################################################################
49
+ # Outputs
50
+ ################################################################################
51
+
52
+ output "cron_lambda_role_arn" {
53
+ description = "IAM role ARN for cron Lambda functions"
54
+ value = aws_iam_role.cron_lambda.arn
55
+ }
@@ -0,0 +1,66 @@
1
+ # Memory Engine Module
2
+
3
+ Thinkwork supports pluggable long-term memory for agents. Choose the engine that fits your stage and requirements.
4
+
5
+ ## Engines
6
+
7
+ | Engine | `memory_engine` value | Best for | Extra infra | Cost |
8
+ |--------|----------------------|----------|-------------|------|
9
+ | **AgentCore Managed** | `managed` (default) | All stages. Production-ready, zero-config. | None | $0 additional |
10
+ | **Hindsight** | `hindsight` | Advanced recall/reflect with entity graph, cross-encoder reranking. | ECS Fargate + ALB | ~$75/mo (ARM64) |
11
+
12
+ Both engines provide agents with memory tools. The Strands runtime reads `MEMORY_ENGINE` and loads the right tool set:
13
+
14
+ - **Managed**: `remember()`, `recall()`, `forget()` — backed by AgentCore Memory API with 4 strategies (semantic facts, user preferences, session summaries, episodic).
15
+ - **Hindsight**: `hindsight_retain()`, `hindsight_recall()`, `hindsight_reflect()` — backed by the Hindsight service with semantic + BM25 + entity graph + temporal retrieval and cross-encoder reranking.
16
+
17
+ ## What's pluggable
18
+
19
+ Only **Layer 3 (long-term cross-thread memory)** is pluggable. The other memory layers are core infrastructure:
20
+
21
+ - **Layer 1** — Workspace files on S3 (per-agent scratchpad). Always available.
22
+ - **Layer 2** — Thread history in Aurora (conversation memory). Always available.
23
+ - **Layer 3** — Long-term cross-thread recall. **This is what `memory_engine` controls.**
24
+
25
+ ## Usage
26
+
27
+ ### Managed (default — no extra config needed)
28
+
29
+ ```hcl
30
+ module "thinkwork" {
31
+ source = "thinkwork-ai/thinkwork/aws"
32
+
33
+ stage = "prod"
34
+ # memory_engine defaults to "managed" — nothing to set
35
+ }
36
+ ```
37
+
38
+ ### Hindsight (opt-in)
39
+
40
+ ```hcl
41
+ module "thinkwork" {
42
+ source = "thinkwork-ai/thinkwork/aws"
43
+
44
+ stage = "prod"
45
+ memory_engine = "hindsight"
46
+
47
+ # Optional: pin the Hindsight image version
48
+ # hindsight_image_tag = "0.4.22"
49
+ }
50
+ ```
51
+
52
+ When `memory_engine = "hindsight"`, Terraform creates:
53
+ - ECS Fargate cluster + service (ARM64, 2 vCPU, 4 GB)
54
+ - Application Load Balancer
55
+ - Security groups (ALB → Hindsight → Aurora ingress)
56
+ - CloudWatch log group
57
+
58
+ When `memory_engine = "managed"`, none of the above is created.
59
+
60
+ ## Switching engines
61
+
62
+ Changing `memory_engine` and running `thinkwork deploy` will:
63
+ - **managed → hindsight**: Create the ECS/ALB infrastructure. Agents start using Hindsight tools on next invoke.
64
+ - **hindsight → managed**: Destroy the ECS/ALB infrastructure. Agents fall back to AgentCore memory tools.
65
+
66
+ Memory data is not migrated between engines. Each engine has its own storage backend.
@@ -0,0 +1,331 @@
1
+ ################################################################################
2
+ # Hindsight Memory Engine — App Module
3
+ #
4
+ # Optional ECS Fargate + ALB service for Hindsight long-term memory.
5
+ # Only created when memory_engine = "hindsight". When memory_engine = "managed",
6
+ # this module creates nothing and AgentCore's built-in memory is used instead.
7
+ #
8
+ # Hindsight provides retain/recall/reflect tools for cross-thread,
9
+ # cross-session organizational memory. It runs local embeddings + reranker
10
+ # and uses the shared Aurora PostgreSQL instance with pgvector.
11
+ ################################################################################
12
+
13
+ variable "stage" {
14
+ type = string
15
+ }
16
+
17
+ variable "vpc_id" {
18
+ type = string
19
+ }
20
+
21
+ variable "subnet_ids" {
22
+ type = list(string)
23
+ }
24
+
25
+ variable "db_security_group_id" {
26
+ type = string
27
+ }
28
+
29
+ variable "database_url" {
30
+ type = string
31
+ sensitive = true
32
+ }
33
+
34
+ variable "image_tag" {
35
+ description = "Hindsight Docker image tag (ghcr.io/vectorize-io/hindsight:<tag>)"
36
+ type = string
37
+ default = "0.4.22"
38
+ }
39
+
40
+ data "aws_region" "current" {}
41
+
42
+ ################################################################################
43
+ # ECS Cluster
44
+ ################################################################################
45
+
46
+ resource "aws_ecs_cluster" "main" {
47
+ name = "thinkwork-${var.stage}-cluster"
48
+
49
+ setting {
50
+ name = "containerInsights"
51
+ value = "enabled"
52
+ }
53
+
54
+ tags = { Name = "thinkwork-${var.stage}-cluster" }
55
+ }
56
+
57
+ ################################################################################
58
+ # IAM
59
+ ################################################################################
60
+
61
+ resource "aws_iam_role" "ecs_execution" {
62
+ name = "thinkwork-${var.stage}-hindsight-execution"
63
+
64
+ assume_role_policy = jsonencode({
65
+ Version = "2012-10-17"
66
+ Statement = [{
67
+ Action = "sts:AssumeRole"
68
+ Effect = "Allow"
69
+ Principal = { Service = "ecs-tasks.amazonaws.com" }
70
+ }]
71
+ })
72
+ }
73
+
74
+ resource "aws_iam_role_policy_attachment" "ecs_execution" {
75
+ role = aws_iam_role.ecs_execution.name
76
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
77
+ }
78
+
79
+ resource "aws_iam_role" "ecs_task" {
80
+ name = "thinkwork-${var.stage}-hindsight-task"
81
+
82
+ assume_role_policy = jsonencode({
83
+ Version = "2012-10-17"
84
+ Statement = [{
85
+ Action = "sts:AssumeRole"
86
+ Effect = "Allow"
87
+ Principal = { Service = "ecs-tasks.amazonaws.com" }
88
+ }]
89
+ })
90
+ }
91
+
92
+ resource "aws_iam_role_policy" "bedrock_access" {
93
+ name = "bedrock-access"
94
+ role = aws_iam_role.ecs_task.id
95
+
96
+ policy = jsonencode({
97
+ Version = "2012-10-17"
98
+ Statement = [{
99
+ Effect = "Allow"
100
+ Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
101
+ Resource = "*"
102
+ }]
103
+ })
104
+ }
105
+
106
+ ################################################################################
107
+ # Security Groups
108
+ ################################################################################
109
+
110
+ resource "aws_security_group" "hindsight" {
111
+ name_prefix = "thinkwork-${var.stage}-hindsight-"
112
+ description = "Hindsight ECS task"
113
+ vpc_id = var.vpc_id
114
+
115
+ ingress {
116
+ description = "ALB to Hindsight API"
117
+ from_port = 8888
118
+ to_port = 8888
119
+ protocol = "tcp"
120
+ security_groups = [aws_security_group.alb.id]
121
+ }
122
+
123
+ egress {
124
+ from_port = 0
125
+ to_port = 0
126
+ protocol = "-1"
127
+ cidr_blocks = ["0.0.0.0/0"]
128
+ }
129
+
130
+ tags = { Name = "thinkwork-${var.stage}-hindsight-sg" }
131
+ lifecycle { create_before_destroy = true }
132
+ }
133
+
134
+ resource "aws_security_group" "alb" {
135
+ name_prefix = "thinkwork-${var.stage}-hindsight-alb-"
136
+ description = "Hindsight ALB"
137
+ vpc_id = var.vpc_id
138
+
139
+ ingress {
140
+ from_port = 80
141
+ to_port = 80
142
+ protocol = "tcp"
143
+ cidr_blocks = ["0.0.0.0/0"]
144
+ }
145
+
146
+ egress {
147
+ from_port = 0
148
+ to_port = 0
149
+ protocol = "-1"
150
+ cidr_blocks = ["0.0.0.0/0"]
151
+ }
152
+
153
+ tags = { Name = "thinkwork-${var.stage}-hindsight-alb-sg" }
154
+ lifecycle { create_before_destroy = true }
155
+ }
156
+
157
+ resource "aws_security_group_rule" "aurora_from_hindsight" {
158
+ type = "ingress"
159
+ from_port = 5432
160
+ to_port = 5432
161
+ protocol = "tcp"
162
+ source_security_group_id = aws_security_group.hindsight.id
163
+ security_group_id = var.db_security_group_id
164
+ }
165
+
166
+ ################################################################################
167
+ # ALB
168
+ ################################################################################
169
+
170
+ resource "aws_lb" "hindsight" {
171
+ name = "tw-${var.stage}-hindsight"
172
+ internal = false
173
+ load_balancer_type = "application"
174
+ security_groups = [aws_security_group.alb.id]
175
+ subnets = var.subnet_ids
176
+
177
+ tags = { Name = "thinkwork-${var.stage}-hindsight-alb" }
178
+ }
179
+
180
+ resource "aws_lb_target_group" "hindsight" {
181
+ name = "tw-${var.stage}-hindsight"
182
+ port = 8888
183
+ protocol = "HTTP"
184
+ vpc_id = var.vpc_id
185
+ target_type = "ip"
186
+
187
+ health_check {
188
+ path = "/health"
189
+ port = "8888"
190
+ protocol = "HTTP"
191
+ healthy_threshold = 2
192
+ unhealthy_threshold = 3
193
+ timeout = 10
194
+ interval = 30
195
+ matcher = "200-399"
196
+ }
197
+
198
+ tags = { Name = "thinkwork-${var.stage}-hindsight-tg" }
199
+ }
200
+
201
+ resource "aws_lb_listener" "hindsight" {
202
+ load_balancer_arn = aws_lb.hindsight.arn
203
+ port = 80
204
+ protocol = "HTTP"
205
+
206
+ default_action {
207
+ type = "forward"
208
+ target_group_arn = aws_lb_target_group.hindsight.arn
209
+ }
210
+ }
211
+
212
+ ################################################################################
213
+ # CloudWatch Logs
214
+ ################################################################################
215
+
216
+ resource "aws_cloudwatch_log_group" "hindsight" {
217
+ name = "/thinkwork/${var.stage}/hindsight"
218
+ retention_in_days = 14
219
+
220
+ tags = { Name = "thinkwork-${var.stage}-hindsight-logs" }
221
+ }
222
+
223
+ ################################################################################
224
+ # ECS Task Definition
225
+ ################################################################################
226
+
227
+ resource "aws_ecs_task_definition" "hindsight" {
228
+ family = "thinkwork-${var.stage}-hindsight"
229
+ requires_compatibilities = ["FARGATE"]
230
+ network_mode = "awsvpc"
231
+ cpu = 2048
232
+ memory = 4096
233
+ execution_role_arn = aws_iam_role.ecs_execution.arn
234
+ task_role_arn = aws_iam_role.ecs_task.arn
235
+
236
+ runtime_platform {
237
+ operating_system_family = "LINUX"
238
+ cpu_architecture = "ARM64"
239
+ }
240
+
241
+ container_definitions = jsonencode([{
242
+ name = "hindsight"
243
+ image = "ghcr.io/vectorize-io/hindsight:${var.image_tag}"
244
+ essential = true
245
+
246
+ portMappings = [{
247
+ containerPort = 8888
248
+ protocol = "tcp"
249
+ }]
250
+
251
+ environment = [
252
+ { name = "HINDSIGHT_API_DATABASE_URL", value = var.database_url },
253
+ { name = "HINDSIGHT_API_DATABASE_SCHEMA", value = "hindsight" },
254
+ { name = "HINDSIGHT_API_VECTOR_EXTENSION", value = "pgvector" },
255
+ { name = "HINDSIGHT_API_TEXT_SEARCH_EXTENSION", value = "native" },
256
+ { name = "HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP", value = "true" },
257
+ { name = "HINDSIGHT_API_LLM_PROVIDER", value = "bedrock" },
258
+ { name = "HINDSIGHT_API_LLM_MODEL", value = "openai.gpt-oss-20b-1:0" },
259
+ { name = "AWS_REGION_NAME", value = data.aws_region.current.name },
260
+ { name = "AWS_DEFAULT_REGION", value = data.aws_region.current.name },
261
+ { name = "HINDSIGHT_API_RETAIN_LLM_PROVIDER", value = "bedrock" },
262
+ { name = "HINDSIGHT_API_RETAIN_LLM_MODEL", value = "openai.gpt-oss-20b-1:0" },
263
+ { name = "HINDSIGHT_API_REFLECT_LLM_PROVIDER", value = "bedrock" },
264
+ { name = "HINDSIGHT_API_REFLECT_LLM_MODEL", value = "openai.gpt-oss-120b-1:0" },
265
+ { name = "HINDSIGHT_API_EMBEDDINGS_PROVIDER", value = "local" },
266
+ { name = "HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL", value = "BAAI/bge-small-en-v1.5" },
267
+ { name = "HINDSIGHT_API_RERANKER_PROVIDER", value = "local" },
268
+ ]
269
+
270
+ logConfiguration = {
271
+ logDriver = "awslogs"
272
+ options = {
273
+ "awslogs-group" = aws_cloudwatch_log_group.hindsight.name
274
+ "awslogs-region" = data.aws_region.current.name
275
+ "awslogs-stream-prefix" = "hindsight"
276
+ }
277
+ }
278
+
279
+ healthCheck = {
280
+ command = ["CMD-SHELL", "curl -f http://localhost:8888/health || exit 1"]
281
+ interval = 30
282
+ timeout = 10
283
+ retries = 3
284
+ startPeriod = 300
285
+ }
286
+ }])
287
+
288
+ tags = { Name = "thinkwork-${var.stage}-hindsight" }
289
+ }
290
+
291
+ ################################################################################
292
+ # ECS Service
293
+ ################################################################################
294
+
295
+ resource "aws_ecs_service" "hindsight" {
296
+ name = "thinkwork-${var.stage}-hindsight"
297
+ cluster = aws_ecs_cluster.main.id
298
+ task_definition = aws_ecs_task_definition.hindsight.arn
299
+ desired_count = 1
300
+ launch_type = "FARGATE"
301
+
302
+ network_configuration {
303
+ subnets = var.subnet_ids
304
+ security_groups = [aws_security_group.hindsight.id]
305
+ assign_public_ip = true
306
+ }
307
+
308
+ load_balancer {
309
+ target_group_arn = aws_lb_target_group.hindsight.arn
310
+ container_name = "hindsight"
311
+ container_port = 8888
312
+ }
313
+
314
+ depends_on = [aws_lb_listener.hindsight]
315
+
316
+ tags = { Name = "thinkwork-${var.stage}-hindsight" }
317
+ }
318
+
319
+ ################################################################################
320
+ # Outputs
321
+ ################################################################################
322
+
323
+ output "hindsight_endpoint" {
324
+ description = "Hindsight API endpoint (ALB URL)"
325
+ value = "http://${aws_lb.hindsight.dns_name}"
326
+ }
327
+
328
+ output "ecs_cluster_arn" {
329
+ description = "ECS cluster ARN"
330
+ value = aws_ecs_cluster.main.arn
331
+ }
@@ -0,0 +1,70 @@
1
+ ################################################################################
2
+ # Job Triggers — App Module
3
+ #
4
+ # EventBridge Scheduler + Lambda for routine/scheduled job execution.
5
+ # Full implementation ported in Phase 4. Phase 1 creates IAM scaffolding.
6
+ ################################################################################
7
+
8
+ variable "stage" {
9
+ description = "Deployment stage"
10
+ type = string
11
+ }
12
+
13
+ variable "account_id" {
14
+ description = "AWS account ID"
15
+ type = string
16
+ }
17
+
18
+ variable "region" {
19
+ description = "AWS region"
20
+ type = string
21
+ }
22
+
23
+ ################################################################################
24
+ # IAM Roles
25
+ ################################################################################
26
+
27
+ resource "aws_iam_role" "job_trigger_lambda" {
28
+ name = "thinkwork-${var.stage}-job-trigger-lambda-role"
29
+
30
+ assume_role_policy = jsonencode({
31
+ Version = "2012-10-17"
32
+ Statement = [{
33
+ Effect = "Allow"
34
+ Principal = { Service = "lambda.amazonaws.com" }
35
+ Action = "sts:AssumeRole"
36
+ }]
37
+ })
38
+ }
39
+
40
+ resource "aws_iam_role_policy_attachment" "job_trigger_basic" {
41
+ role = aws_iam_role.job_trigger_lambda.name
42
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
43
+ }
44
+
45
+ resource "aws_iam_role" "job_scheduler" {
46
+ name = "thinkwork-${var.stage}-job-scheduler-role"
47
+
48
+ assume_role_policy = jsonencode({
49
+ Version = "2012-10-17"
50
+ Statement = [{
51
+ Effect = "Allow"
52
+ Principal = { Service = "scheduler.amazonaws.com" }
53
+ Action = "sts:AssumeRole"
54
+ }]
55
+ })
56
+ }
57
+
58
+ ################################################################################
59
+ # Outputs
60
+ ################################################################################
61
+
62
+ output "job_trigger_lambda_role_arn" {
63
+ description = "IAM role ARN for job trigger Lambda"
64
+ value = aws_iam_role.job_trigger_lambda.arn
65
+ }
66
+
67
+ output "job_scheduler_role_arn" {
68
+ description = "IAM role ARN for EventBridge Scheduler"
69
+ value = aws_iam_role.job_scheduler.arn
70
+ }