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.
- package/dist/cli.js +311 -54
- package/dist/terraform/examples/greenfield/main.tf +190 -0
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +28 -0
- package/dist/terraform/modules/_internal/workspace-guard/main.tf +29 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +217 -0
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +122 -0
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +20 -0
- package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +31 -0
- package/dist/terraform/modules/app/crons/main.tf +55 -0
- package/dist/terraform/modules/app/hindsight-memory/README.md +66 -0
- package/dist/terraform/modules/app/hindsight-memory/main.tf +331 -0
- package/dist/terraform/modules/app/job-triggers/main.tf +70 -0
- package/dist/terraform/modules/app/lambda-api/.build/placeholder.zip +0 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +311 -0
- package/dist/terraform/modules/app/lambda-api/main.tf +245 -0
- package/dist/terraform/modules/app/lambda-api/outputs.tf +24 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +153 -0
- package/dist/terraform/modules/app/ses-email/main.tf +51 -0
- package/dist/terraform/modules/app/static-site/main.tf +176 -0
- package/dist/terraform/modules/data/aurora-postgres/README.md +92 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +185 -0
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +30 -0
- package/dist/terraform/modules/data/aurora-postgres/variables.tf +114 -0
- package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +102 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +91 -0
- package/dist/terraform/modules/foundation/cognito/main.tf +377 -0
- package/dist/terraform/modules/foundation/cognito/outputs.tf +29 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +124 -0
- package/dist/terraform/modules/foundation/dns/main.tf +49 -0
- package/dist/terraform/modules/foundation/kms/main.tf +49 -0
- package/dist/terraform/modules/foundation/vpc/main.tf +137 -0
- package/dist/terraform/modules/foundation/vpc/outputs.tf +14 -0
- package/dist/terraform/modules/foundation/vpc/variables.tf +40 -0
- package/dist/terraform/modules/thinkwork/main.tf +212 -0
- package/dist/terraform/modules/thinkwork/outputs.tf +87 -0
- package/dist/terraform/modules/thinkwork/variables.tf +241 -0
- package/dist/terraform/schema.graphql +199 -0
- 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
|
+
}
|