thinkwork-cli 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli.js +311 -54
  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,190 @@
1
+ ################################################################################
2
+ # Greenfield Example
3
+ #
4
+ # Creates everything from scratch in a fresh AWS account.
5
+ # Copy this directory to start a new Thinkwork deployment.
6
+ #
7
+ # Usage:
8
+ # cd terraform/examples/greenfield
9
+ # cp terraform.tfvars.example terraform.tfvars # edit with your values
10
+ # terraform init
11
+ # terraform workspace new dev # or your stage name
12
+ # terraform plan -var-file=terraform.tfvars
13
+ # terraform apply -var-file=terraform.tfvars
14
+ ################################################################################
15
+
16
+ terraform {
17
+ required_version = ">= 1.5"
18
+
19
+ required_providers {
20
+ aws = {
21
+ source = "hashicorp/aws"
22
+ version = "~> 5.0"
23
+ }
24
+ archive = {
25
+ source = "hashicorp/archive"
26
+ version = "~> 2.0"
27
+ }
28
+ null = {
29
+ source = "hashicorp/null"
30
+ version = "~> 3.0"
31
+ }
32
+ }
33
+
34
+ # For the example, use local state. Production deployments should
35
+ # use S3 + DynamoDB backend — see the docs for configuration.
36
+ # backend "s3" { ... }
37
+ }
38
+
39
+ provider "aws" {
40
+ region = var.region
41
+ }
42
+
43
+ variable "stage" {
44
+ description = "Deployment stage — must match the Terraform workspace name"
45
+ type = string
46
+ }
47
+
48
+ variable "region" {
49
+ description = "AWS region"
50
+ type = string
51
+ default = "us-east-1"
52
+ }
53
+
54
+ variable "account_id" {
55
+ description = "AWS account ID"
56
+ type = string
57
+ }
58
+
59
+ variable "db_password" {
60
+ description = "Master password for the Aurora cluster"
61
+ type = string
62
+ sensitive = true
63
+ }
64
+
65
+ variable "database_engine" {
66
+ description = "Database engine: 'aurora-serverless' (production) or 'rds-postgres' (dev/test, cheaper)"
67
+ type = string
68
+ default = "aurora-serverless"
69
+ }
70
+
71
+ variable "memory_engine" {
72
+ description = "Memory engine: 'managed' (AgentCore built-in, default) or 'hindsight' (ECS+ALB, opt-in)"
73
+ type = string
74
+ default = "managed"
75
+ }
76
+
77
+ variable "google_oauth_client_id" {
78
+ description = "Google OAuth client ID (optional — leave empty to skip Google login)"
79
+ type = string
80
+ default = ""
81
+ }
82
+
83
+ variable "google_oauth_client_secret" {
84
+ description = "Google OAuth client secret"
85
+ type = string
86
+ sensitive = true
87
+ default = ""
88
+ }
89
+
90
+ variable "pre_signup_lambda_zip" {
91
+ description = "Path to the Cognito pre-signup Lambda zip"
92
+ type = string
93
+ default = ""
94
+ }
95
+
96
+ variable "lambda_zips_dir" {
97
+ description = "Local directory containing Lambda zip artifacts (from pnpm build:lambdas)"
98
+ type = string
99
+ default = ""
100
+ }
101
+
102
+ variable "api_auth_secret" {
103
+ description = "Shared secret for inter-service API authentication"
104
+ type = string
105
+ sensitive = true
106
+ default = ""
107
+ }
108
+
109
+ module "thinkwork" {
110
+ source = "../../modules/thinkwork"
111
+
112
+ stage = var.stage
113
+ region = var.region
114
+ account_id = var.account_id
115
+
116
+ db_password = var.db_password
117
+ database_engine = var.database_engine
118
+ memory_engine = var.memory_engine
119
+ google_oauth_client_id = var.google_oauth_client_id
120
+ google_oauth_client_secret = var.google_oauth_client_secret
121
+ pre_signup_lambda_zip = var.pre_signup_lambda_zip
122
+ lambda_zips_dir = var.lambda_zips_dir
123
+ api_auth_secret = var.api_auth_secret
124
+
125
+ # Greenfield: create everything (all defaults are true)
126
+ }
127
+
128
+ ################################################################################
129
+ # Outputs
130
+ ################################################################################
131
+
132
+ output "api_endpoint" {
133
+ description = "API Gateway endpoint URL"
134
+ value = module.thinkwork.api_endpoint
135
+ }
136
+
137
+ output "appsync_realtime_url" {
138
+ description = "AppSync realtime WebSocket URL (for frontend subscription clients)"
139
+ value = module.thinkwork.appsync_realtime_url
140
+ }
141
+
142
+ output "user_pool_id" {
143
+ description = "Cognito user pool ID"
144
+ value = module.thinkwork.user_pool_id
145
+ }
146
+
147
+ output "admin_client_id" {
148
+ description = "Cognito app client ID for web admin"
149
+ value = module.thinkwork.admin_client_id
150
+ }
151
+
152
+ output "mobile_client_id" {
153
+ description = "Cognito app client ID for mobile"
154
+ value = module.thinkwork.mobile_client_id
155
+ }
156
+
157
+ output "ecr_repository_url" {
158
+ description = "ECR repository URL for the AgentCore container"
159
+ value = module.thinkwork.ecr_repository_url
160
+ }
161
+
162
+ output "bucket_name" {
163
+ description = "Primary S3 bucket"
164
+ value = module.thinkwork.bucket_name
165
+ }
166
+
167
+ output "db_cluster_endpoint" {
168
+ description = "Aurora cluster endpoint"
169
+ value = module.thinkwork.db_cluster_endpoint
170
+ }
171
+
172
+ output "db_secret_arn" {
173
+ description = "Secrets Manager ARN for database credentials"
174
+ value = module.thinkwork.db_secret_arn
175
+ }
176
+
177
+ output "database_name" {
178
+ description = "Database name"
179
+ value = module.thinkwork.database_name
180
+ }
181
+
182
+ output "memory_engine" {
183
+ description = "Active memory engine (managed or hindsight)"
184
+ value = module.thinkwork.memory_engine
185
+ }
186
+
187
+ output "hindsight_endpoint" {
188
+ description = "Hindsight API endpoint (null when memory_engine = managed)"
189
+ value = module.thinkwork.hindsight_endpoint
190
+ }
@@ -0,0 +1,28 @@
1
+ # Thinkwork Greenfield Deployment
2
+ #
3
+ # Copy this file to terraform.tfvars and fill in your values.
4
+ # DO NOT commit terraform.tfvars to version control.
5
+
6
+ stage = "dev"
7
+ region = "us-east-1"
8
+ account_id = "123456789012" # your AWS account ID
9
+
10
+ # Database engine:
11
+ # "aurora-serverless" — Aurora Serverless v2 (production, auto-scaling, deletion protection on)
12
+ # "rds-postgres" — Standard RDS PostgreSQL (dev/test, cheaper, deletion protection off)
13
+ database_engine = "rds-postgres"
14
+
15
+ # Memory engine:
16
+ # "managed" — AgentCore built-in long-term memory (default, no extra infra)
17
+ # "hindsight" — Hindsight ECS+ALB service (opt-in, adds retain/recall/reflect tools)
18
+ memory_engine = "managed"
19
+
20
+ # Database master password — use a strong password
21
+ db_password = "CHANGE_ME_strong_password_here"
22
+
23
+ # Google OAuth (optional — leave empty to skip Google social login)
24
+ # google_oauth_client_id = ""
25
+ # google_oauth_client_secret = ""
26
+
27
+ # Pre-signup Lambda (optional — leave empty if not using custom pre-signup logic)
28
+ # pre_signup_lambda_zip = "./lambdas/pre-signup.zip"
@@ -0,0 +1,29 @@
1
+ ################################################################################
2
+ # Workspace Guard
3
+ #
4
+ # Prevents applying the wrong var file to the wrong Terraform workspace.
5
+ # Every tier (foundation, data, app) should consume this module.
6
+ #
7
+ # History: on 2026-04-05, running `terraform apply -var-file=prod.tfvars` in
8
+ # the dev workspace destroyed dev infrastructure. This guard prevents that
9
+ # class of incident by failing the plan before any damage occurs.
10
+ ################################################################################
11
+
12
+ variable "stage" {
13
+ description = "The deployment stage (must match the Terraform workspace name)"
14
+ type = string
15
+ }
16
+
17
+ resource "null_resource" "workspace_guard" {
18
+ triggers = {
19
+ stage = var.stage
20
+ workspace = terraform.workspace
21
+ }
22
+
23
+ lifecycle {
24
+ precondition {
25
+ condition = var.stage == terraform.workspace
26
+ error_message = "SAFETY: stage '${var.stage}' does not match workspace '${terraform.workspace}'. You are applying the wrong var file!"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,217 @@
1
+ ################################################################################
2
+ # AgentCore Runtime — App Module
3
+ #
4
+ # Creates ECR repository, IAM roles, and container build infrastructure
5
+ # for the Strands-based agent runtime. Full implementation ported in Phase 3.
6
+ # Phase 1 creates the ECR repo and IAM scaffolding only.
7
+ ################################################################################
8
+
9
+ variable "stage" {
10
+ description = "Deployment stage"
11
+ type = string
12
+ }
13
+
14
+ variable "account_id" {
15
+ description = "AWS account ID"
16
+ type = string
17
+ }
18
+
19
+ variable "region" {
20
+ description = "AWS region"
21
+ type = string
22
+ }
23
+
24
+ variable "bucket_name" {
25
+ description = "Primary S3 bucket for skills and workspace files"
26
+ type = string
27
+ }
28
+
29
+ variable "memory_engine" {
30
+ description = "Memory engine: 'managed' or 'hindsight'. Passed as MEMORY_ENGINE env var to the container."
31
+ type = string
32
+ default = "managed"
33
+ }
34
+
35
+ variable "hindsight_endpoint" {
36
+ description = "Hindsight API endpoint (only used when memory_engine = 'hindsight')"
37
+ type = string
38
+ default = ""
39
+ }
40
+
41
+ variable "agentcore_memory_id" {
42
+ description = "AgentCore Memory resource ID (only used when memory_engine = 'managed')"
43
+ type = string
44
+ default = ""
45
+ }
46
+
47
+ ################################################################################
48
+ # ECR Repository
49
+ ################################################################################
50
+
51
+ resource "aws_ecr_repository" "agentcore" {
52
+ name = "thinkwork-${var.stage}-agentcore"
53
+ image_tag_mutability = "MUTABLE"
54
+ force_delete = true
55
+
56
+ image_scanning_configuration {
57
+ scan_on_push = true
58
+ }
59
+
60
+ tags = {
61
+ Name = "thinkwork-${var.stage}-agentcore"
62
+ }
63
+ }
64
+
65
+ resource "aws_ecr_lifecycle_policy" "agentcore" {
66
+ repository = aws_ecr_repository.agentcore.name
67
+
68
+ policy = jsonencode({
69
+ rules = [{
70
+ rulePriority = 1
71
+ description = "Keep last 10 images"
72
+ selection = {
73
+ tagStatus = "any"
74
+ countType = "imageCountMoreThan"
75
+ countNumber = 10
76
+ }
77
+ action = {
78
+ type = "expire"
79
+ }
80
+ }]
81
+ })
82
+ }
83
+
84
+ ################################################################################
85
+ # Execution Role
86
+ ################################################################################
87
+
88
+ resource "aws_iam_role" "agentcore" {
89
+ name = "thinkwork-${var.stage}-agentcore-role"
90
+
91
+ assume_role_policy = jsonencode({
92
+ Version = "2012-10-17"
93
+ Statement = [{
94
+ Effect = "Allow"
95
+ Principal = { Service = ["ecs-tasks.amazonaws.com", "lambda.amazonaws.com"] }
96
+ Action = "sts:AssumeRole"
97
+ }]
98
+ })
99
+ }
100
+
101
+ resource "aws_iam_role_policy" "agentcore" {
102
+ name = "agentcore-permissions"
103
+ role = aws_iam_role.agentcore.id
104
+
105
+ policy = jsonencode({
106
+ Version = "2012-10-17"
107
+ Statement = [
108
+ {
109
+ Sid = "S3Access"
110
+ Effect = "Allow"
111
+ Action = ["s3:GetObject", "s3:PutObject", "s3:ListBucket"]
112
+ Resource = [
113
+ "arn:aws:s3:::${var.bucket_name}",
114
+ "arn:aws:s3:::${var.bucket_name}/*",
115
+ ]
116
+ },
117
+ {
118
+ Sid = "BedrockInvoke"
119
+ Effect = "Allow"
120
+ Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream", "bedrock:InvokeAgent"]
121
+ Resource = "*"
122
+ },
123
+ {
124
+ Sid = "CloudWatchLogs"
125
+ Effect = "Allow"
126
+ Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]
127
+ Resource = "arn:aws:logs:${var.region}:${var.account_id}:*"
128
+ },
129
+ {
130
+ Sid = "ECRPull"
131
+ Effect = "Allow"
132
+ Action = ["ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:GetAuthorizationToken"]
133
+ Resource = "*"
134
+ },
135
+ {
136
+ Sid = "SSMParameterAccess"
137
+ Effect = "Allow"
138
+ Action = ["ssm:GetParameter", "ssm:PutParameter"]
139
+ Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/agentcore/*"
140
+ },
141
+ ]
142
+ })
143
+ }
144
+
145
+ ################################################################################
146
+ # CloudWatch Log Group
147
+ ################################################################################
148
+
149
+ resource "aws_cloudwatch_log_group" "agentcore" {
150
+ name = "/thinkwork/${var.stage}/agentcore"
151
+ retention_in_days = 30
152
+
153
+ tags = {
154
+ Name = "thinkwork-${var.stage}-agentcore-logs"
155
+ }
156
+ }
157
+
158
+ ################################################################################
159
+ # Lambda Container Image
160
+ ################################################################################
161
+
162
+ resource "aws_lambda_function" "agentcore" {
163
+ function_name = "thinkwork-${var.stage}-agentcore"
164
+ role = aws_iam_role.agentcore.arn
165
+ package_type = "Image"
166
+ image_uri = "${aws_ecr_repository.agentcore.repository_url}:latest"
167
+ timeout = 900
168
+ memory_size = 2048
169
+
170
+ environment {
171
+ variables = {
172
+ PORT = "8080"
173
+ AWS_LWA_PORT = "8080"
174
+ MEMORY_ENGINE = var.memory_engine
175
+ AGENTCORE_MEMORY_ID = var.agentcore_memory_id
176
+ AGENTCORE_FILES_BUCKET = var.bucket_name
177
+ }
178
+ }
179
+
180
+ logging_config {
181
+ log_group = aws_cloudwatch_log_group.agentcore.name
182
+ log_format = "Text"
183
+ }
184
+
185
+ tags = {
186
+ Name = "thinkwork-${var.stage}-agentcore"
187
+ }
188
+ }
189
+
190
+ resource "aws_lambda_function_url" "agentcore" {
191
+ function_name = aws_lambda_function.agentcore.function_name
192
+ authorization_type = "NONE"
193
+ }
194
+
195
+ ################################################################################
196
+ # Outputs
197
+ ################################################################################
198
+
199
+ output "ecr_repository_url" {
200
+ description = "ECR repository URL for the AgentCore container"
201
+ value = aws_ecr_repository.agentcore.repository_url
202
+ }
203
+
204
+ output "execution_role_arn" {
205
+ description = "IAM role ARN for AgentCore execution"
206
+ value = aws_iam_role.agentcore.arn
207
+ }
208
+
209
+ output "agentcore_invoke_url" {
210
+ description = "Lambda Function URL for the AgentCore container"
211
+ value = aws_lambda_function_url.agentcore.function_url
212
+ }
213
+
214
+ output "agentcore_function_name" {
215
+ description = "AgentCore Lambda function name (for direct SDK invoke)"
216
+ value = aws_lambda_function.agentcore.function_name
217
+ }
@@ -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
+ }