thinkwork-cli 0.13.0 → 0.13.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/{api-client-JUBCQQDF.js → api-client-4DZ266J5.js} +1 -1
- package/dist/{chunk-STY44FQO.js → chunk-Y2WHJECR.js} +44 -12
- package/dist/cli.js +36715 -12965
- package/dist/drizzle/0227_analyst_reader_role.sql +100 -38
- package/dist/drizzle/0230_analyst_rls.sql +183 -66
- package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
- package/dist/drizzle/0232_message_source_event_id.sql +9 -0
- package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
- package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
- package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
- package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
- package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
- package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
- package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
- package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
- package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
- package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
- package/dist/drizzle/0242_search_queries.sql +28 -0
- package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
- package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
- package/dist/drizzle/0245_wiki_page_render.sql +21 -0
- package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
- package/dist/drizzle/0247_capability_runtime.sql +402 -0
- package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
- package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
- package/dist/drizzle/0250_kg_schema_extraction.sql +549 -0
- package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
- package/dist/drizzle/0251_brain_stragglers.sql +242 -0
- package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +91 -0
- package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
- package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
- package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
- package/dist/drizzle/0257_capability_approvals.sql +53 -0
- package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
- package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
- package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
- package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +11 -0
- package/dist/terraform/examples/greenfield/main.tf +40 -0
- package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
- package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
- package/dist/terraform/modules/app/agentcore-harness/main.tf +101 -0
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +9 -0
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +25 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
- package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
- package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
- package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +46 -5
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +94 -0
- package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +361 -16
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +131 -5
- package/dist/terraform/modules/app/lambda-api/main.tf +11 -0
- package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
- package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +92 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
- package/dist/terraform/modules/foundation/cognito/main.tf +32 -8
- package/dist/terraform/modules/foundation/cognito/variables.tf +12 -0
- package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
- package/dist/terraform/modules/thinkwork/main.tf +152 -5
- package/dist/terraform/modules/thinkwork/outputs.tf +55 -0
- package/dist/terraform/modules/thinkwork/variables.tf +42 -0
- package/dist/workspace-defaults/files/TOOLS.md +39 -0
- package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
- package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
- package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
- package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
- package/package.json +4 -2
- /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
variable "enabled" {
|
|
2
|
+
description = "Create the AgentCore Harness trial IAM surface (THINK-311 U4). Pure-IAM and inert — nothing invokes Harness until U5 — so it defaults on; flip off to remove the role without touching root variables."
|
|
3
|
+
type = bool
|
|
4
|
+
default = true
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
variable "stage" {
|
|
8
|
+
description = "Deployment stage (dev, prod, etc.)"
|
|
9
|
+
type = string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "region" {
|
|
13
|
+
description = "AWS region"
|
|
14
|
+
type = string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
variable "account_id" {
|
|
18
|
+
description = "AWS account ID"
|
|
19
|
+
type = string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
variable "bucket_name" {
|
|
23
|
+
description = "Stage workspace S3 bucket name — Harness microVMs read tenant skill sources under tenants/*"
|
|
24
|
+
type = string
|
|
25
|
+
}
|
|
@@ -26,6 +26,11 @@ locals {
|
|
|
26
26
|
chat_agent_activity_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.chat_agent_activity_fn_name}"
|
|
27
27
|
manifest_log_fn_name = "thinkwork-${var.stage}-api-manifest-log"
|
|
28
28
|
manifest_log_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.manifest_log_fn_name}"
|
|
29
|
+
# THINK-280 U2 — capability control plane service (capability_search /
|
|
30
|
+
# connection_research Pi tools). Direct InvokeCommand, RequestResponse;
|
|
31
|
+
# no HTTP route exists for this Lambda.
|
|
32
|
+
capability_control_fn_name = "thinkwork-${var.stage}-api-capability-control-service"
|
|
33
|
+
capability_control_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.capability_control_fn_name}"
|
|
29
34
|
# Release-backed deployments use an immutable, source-derived target tag so
|
|
30
35
|
# changing the release image also changes aws_lambda_function.image_uri.
|
|
31
36
|
# Reusing pi-latest lets Terraform miss the update even after ECR is retagged.
|
|
@@ -259,6 +264,7 @@ resource "aws_iam_role_policy" "agentcore_pi" {
|
|
|
259
264
|
local.chat_agent_finalize_fn_arn,
|
|
260
265
|
local.chat_agent_activity_fn_arn,
|
|
261
266
|
local.manifest_log_fn_arn,
|
|
267
|
+
local.capability_control_fn_arn,
|
|
262
268
|
]
|
|
263
269
|
},
|
|
264
270
|
{
|
|
@@ -384,6 +390,7 @@ resource "aws_lambda_function" "agentcore_pi" {
|
|
|
384
390
|
CHAT_AGENT_FINALIZE_FN_NAME = local.chat_agent_finalize_fn_name
|
|
385
391
|
CHAT_AGENT_ACTIVITY_FN_NAME = local.chat_agent_activity_fn_name
|
|
386
392
|
MANIFEST_LOG_FUNCTION_NAME = local.manifest_log_fn_name
|
|
393
|
+
CAPABILITY_CONTROL_FN_NAME = local.capability_control_fn_name
|
|
387
394
|
HINDSIGHT_ENDPOINT = var.hindsight_endpoint
|
|
388
395
|
# THINK-220 cutover flag — see the variable description.
|
|
389
396
|
HINDSIGHT_DATABASE_NAME = var.hindsight_database_name
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Capability Broker (THINK-280 U3) — action-time policy, replay-safe PoP
|
|
2
|
+
# sessions, and evidence for the governed capability runtime.
|
|
3
|
+
#
|
|
4
|
+
# SHIP-INERT: gated by var.enabled (default false). Nothing here is created
|
|
5
|
+
# until U4 flips it on after the live negative-egress proof. The Lambda code
|
|
6
|
+
# itself also fails closed — its default authorization loader denies every
|
|
7
|
+
# call — so even an accidental enable dispatches no provider operation.
|
|
8
|
+
#
|
|
9
|
+
# THINK-144: this module owns a DEDICATED execute-api interface VPC endpoint
|
|
10
|
+
# with private DNS DISABLED. It is never added to the app VPC's shared
|
|
11
|
+
# okf_wiki_interface_endpoint_services list — that list's private DNS would
|
|
12
|
+
# capture every *.execute-api call in the VPC and 403 the stack's public HTTP
|
|
13
|
+
# APIs. The session bootstrap targets this endpoint's own DNS name instead.
|
|
14
|
+
|
|
15
|
+
locals {
|
|
16
|
+
enabled = var.enabled
|
|
17
|
+
count_enabled = var.enabled ? 1 : 0
|
|
18
|
+
|
|
19
|
+
name_prefix = "thinkwork-${var.stage}-capability-broker"
|
|
20
|
+
session_table_name = "thinkwork-${var.stage}-capability-broker-sessions"
|
|
21
|
+
broker_audience = "thinkwork-${var.stage}-capability-broker"
|
|
22
|
+
use_local_zips = var.lambda_zips_dir != ""
|
|
23
|
+
use_remote_artifacts = trimspace(var.lambda_artifact_bucket) != ""
|
|
24
|
+
broker_zip_local = "${var.lambda_zips_dir}/capability-broker.zip"
|
|
25
|
+
broker_artifact_s3key = "${var.lambda_artifact_prefix}/capability-broker.zip"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# --- Replay-safe session state (DynamoDB, conditional writes + TTL) ----------
|
|
29
|
+
|
|
30
|
+
resource "aws_dynamodb_table" "sessions" {
|
|
31
|
+
count = local.count_enabled
|
|
32
|
+
|
|
33
|
+
name = local.session_table_name
|
|
34
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
35
|
+
hash_key = "pk"
|
|
36
|
+
range_key = "sk"
|
|
37
|
+
|
|
38
|
+
attribute {
|
|
39
|
+
name = "pk"
|
|
40
|
+
type = "S"
|
|
41
|
+
}
|
|
42
|
+
attribute {
|
|
43
|
+
name = "sk"
|
|
44
|
+
type = "S"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Session + nonce records self-expire at session TTL (≤15 min). Epoch seconds.
|
|
48
|
+
ttl {
|
|
49
|
+
attribute_name = "ttl"
|
|
50
|
+
enabled = true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
point_in_time_recovery {
|
|
54
|
+
enabled = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
tags = {
|
|
58
|
+
Name = local.session_table_name
|
|
59
|
+
Purpose = "capability-broker-sessions"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# --- Broker Lambda execution role -------------------------------------------
|
|
64
|
+
|
|
65
|
+
data "aws_iam_policy_document" "broker_assume" {
|
|
66
|
+
statement {
|
|
67
|
+
actions = ["sts:AssumeRole"]
|
|
68
|
+
principals {
|
|
69
|
+
type = "Service"
|
|
70
|
+
identifiers = ["lambda.amazonaws.com"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
resource "aws_iam_role" "broker" {
|
|
76
|
+
count = local.count_enabled
|
|
77
|
+
name = "${local.name_prefix}-role"
|
|
78
|
+
assume_role_policy = data.aws_iam_policy_document.broker_assume.json
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
data "aws_iam_policy_document" "broker_policy" {
|
|
82
|
+
count = local.count_enabled
|
|
83
|
+
|
|
84
|
+
# Session state: conditional reads/writes only, scoped to the one table.
|
|
85
|
+
statement {
|
|
86
|
+
sid = "SessionState"
|
|
87
|
+
actions = [
|
|
88
|
+
"dynamodb:GetItem",
|
|
89
|
+
"dynamodb:PutItem",
|
|
90
|
+
"dynamodb:UpdateItem",
|
|
91
|
+
"dynamodb:DeleteItem",
|
|
92
|
+
"dynamodb:Query",
|
|
93
|
+
]
|
|
94
|
+
resources = [aws_dynamodb_table.sessions[0].arn]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Evidence: append capability_broker_calls rows via the RDS Data API on the
|
|
98
|
+
# existing Aurora cluster (same credential-rotation surface as graphql-http).
|
|
99
|
+
dynamic "statement" {
|
|
100
|
+
for_each = var.db_cluster_arn != "" ? [1] : []
|
|
101
|
+
content {
|
|
102
|
+
sid = "EvidenceDataApi"
|
|
103
|
+
actions = ["rds-data:ExecuteStatement", "rds-data:BatchExecuteStatement"]
|
|
104
|
+
resources = [var.db_cluster_arn]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
dynamic "statement" {
|
|
109
|
+
for_each = var.db_secret_arn != "" ? [1] : []
|
|
110
|
+
content {
|
|
111
|
+
sid = "EvidenceDbSecret"
|
|
112
|
+
actions = ["secretsmanager:GetSecretValue"]
|
|
113
|
+
resources = [var.db_secret_arn]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# VPC ENI management for the in-VPC Lambda.
|
|
118
|
+
statement {
|
|
119
|
+
sid = "VpcEni"
|
|
120
|
+
actions = [
|
|
121
|
+
"ec2:CreateNetworkInterface",
|
|
122
|
+
"ec2:DescribeNetworkInterfaces",
|
|
123
|
+
"ec2:DeleteNetworkInterface",
|
|
124
|
+
"ec2:AssignPrivateIpAddresses",
|
|
125
|
+
"ec2:UnassignPrivateIpAddresses",
|
|
126
|
+
]
|
|
127
|
+
resources = ["*"]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
statement {
|
|
131
|
+
sid = "Logs"
|
|
132
|
+
actions = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]
|
|
133
|
+
resources = ["arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/lambda/${local.name_prefix}*"]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
statement {
|
|
137
|
+
sid = "Xray"
|
|
138
|
+
actions = ["xray:PutTraceSegments", "xray:PutTelemetryRecords"]
|
|
139
|
+
resources = ["*"]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
resource "aws_iam_role_policy" "broker" {
|
|
144
|
+
count = local.count_enabled
|
|
145
|
+
name = "${local.name_prefix}-policy"
|
|
146
|
+
role = aws_iam_role.broker[0].id
|
|
147
|
+
policy = data.aws_iam_policy_document.broker_policy[0].json
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# --- Broker Lambda security group (egress only) ------------------------------
|
|
151
|
+
|
|
152
|
+
resource "aws_security_group" "broker_lambda" {
|
|
153
|
+
count = local.count_enabled
|
|
154
|
+
name = "${local.name_prefix}-lambda"
|
|
155
|
+
description = "Capability broker Lambda egress"
|
|
156
|
+
vpc_id = var.vpc_id
|
|
157
|
+
|
|
158
|
+
egress {
|
|
159
|
+
from_port = 0
|
|
160
|
+
to_port = 0
|
|
161
|
+
protocol = "-1"
|
|
162
|
+
cidr_blocks = ["0.0.0.0/0"]
|
|
163
|
+
description = "Egress to Aurora Data API, DynamoDB, Secrets Manager, provider endpoints"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
tags = { Name = "${local.name_prefix}-lambda" }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
resource "aws_cloudwatch_log_group" "broker" {
|
|
170
|
+
count = local.count_enabled
|
|
171
|
+
name = "/aws/lambda/${local.name_prefix}"
|
|
172
|
+
retention_in_days = var.log_retention_days
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
resource "aws_lambda_function" "broker" {
|
|
176
|
+
count = local.count_enabled
|
|
177
|
+
function_name = local.name_prefix
|
|
178
|
+
role = aws_iam_role.broker[0].arn
|
|
179
|
+
runtime = var.lambda_runtime
|
|
180
|
+
handler = "index.handler"
|
|
181
|
+
timeout = 30
|
|
182
|
+
memory_size = 512
|
|
183
|
+
|
|
184
|
+
filename = local.use_local_zips ? local.broker_zip_local : null
|
|
185
|
+
source_code_hash = local.use_local_zips ? filebase64sha256(local.broker_zip_local) : null
|
|
186
|
+
s3_bucket = local.use_remote_artifacts ? var.lambda_artifact_bucket : null
|
|
187
|
+
s3_key = local.use_remote_artifacts ? local.broker_artifact_s3key : null
|
|
188
|
+
|
|
189
|
+
vpc_config {
|
|
190
|
+
subnet_ids = var.subnet_ids
|
|
191
|
+
security_group_ids = [aws_security_group.broker_lambda[0].id]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
environment {
|
|
195
|
+
variables = {
|
|
196
|
+
CAPABILITY_BROKER_SESSION_TABLE = aws_dynamodb_table.sessions[0].name
|
|
197
|
+
CAPABILITY_BROKER_AUDIENCE = local.broker_audience
|
|
198
|
+
# getDb() (@thinkwork/database-pg) MUST get DATABASE_URL, not just
|
|
199
|
+
# DATABASE_SECRET_ARN: the secret-only path returns an async Proxy whose
|
|
200
|
+
# `db.select()` resolves to a Promise, which breaks Drizzle's fluent
|
|
201
|
+
# `.select().from().where()` chaining (`.from is not a function`) — the
|
|
202
|
+
# cause of the loader's `authorization reload failed`. DATABASE_URL takes
|
|
203
|
+
# the synchronous pool path, matching every api handler's common_env.
|
|
204
|
+
DATABASE_URL = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
|
|
205
|
+
DATABASE_SECRET_ARN = var.db_secret_arn
|
|
206
|
+
DATABASE_HOST = var.db_cluster_endpoint
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
tracing_config {
|
|
211
|
+
mode = "Active"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
depends_on = [aws_cloudwatch_log_group.broker]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# --- Private REST API Gateway fronting the broker ----------------------------
|
|
218
|
+
#
|
|
219
|
+
# PRIVATE endpoint: reachable ONLY through the dedicated execute-api VPCE below.
|
|
220
|
+
# The resource policy denies anything not arriving via that endpoint.
|
|
221
|
+
|
|
222
|
+
resource "aws_api_gateway_rest_api" "broker" {
|
|
223
|
+
count = local.count_enabled
|
|
224
|
+
name = local.name_prefix
|
|
225
|
+
|
|
226
|
+
endpoint_configuration {
|
|
227
|
+
types = ["PRIVATE"]
|
|
228
|
+
vpc_endpoint_ids = [aws_vpc_endpoint.broker_execute_api[0].id]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Only traffic arriving through our own VPCE is allowed; everything else 403s.
|
|
232
|
+
policy = jsonencode({
|
|
233
|
+
Version = "2012-10-17"
|
|
234
|
+
Statement = [
|
|
235
|
+
{
|
|
236
|
+
Effect = "Deny"
|
|
237
|
+
Principal = "*"
|
|
238
|
+
Action = "execute-api:Invoke"
|
|
239
|
+
Resource = "arn:aws:execute-api:${var.region}:${var.account_id}:*/*/*/*"
|
|
240
|
+
Condition = {
|
|
241
|
+
StringNotEquals = { "aws:SourceVpce" = aws_vpc_endpoint.broker_execute_api[0].id }
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
Effect = "Allow"
|
|
246
|
+
Principal = "*"
|
|
247
|
+
Action = "execute-api:Invoke"
|
|
248
|
+
Resource = "arn:aws:execute-api:${var.region}:${var.account_id}:*/*/*/*"
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
resource "aws_api_gateway_resource" "broker_proxy" {
|
|
255
|
+
count = local.count_enabled
|
|
256
|
+
rest_api_id = aws_api_gateway_rest_api.broker[0].id
|
|
257
|
+
parent_id = aws_api_gateway_rest_api.broker[0].root_resource_id
|
|
258
|
+
path_part = "{proxy+}"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
resource "aws_api_gateway_method" "broker_any" {
|
|
262
|
+
count = local.count_enabled
|
|
263
|
+
rest_api_id = aws_api_gateway_rest_api.broker[0].id
|
|
264
|
+
resource_id = aws_api_gateway_resource.broker_proxy[0].id
|
|
265
|
+
http_method = "ANY"
|
|
266
|
+
authorization = "NONE"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
resource "aws_api_gateway_integration" "broker" {
|
|
270
|
+
count = local.count_enabled
|
|
271
|
+
rest_api_id = aws_api_gateway_rest_api.broker[0].id
|
|
272
|
+
resource_id = aws_api_gateway_resource.broker_proxy[0].id
|
|
273
|
+
http_method = aws_api_gateway_method.broker_any[0].http_method
|
|
274
|
+
integration_http_method = "POST"
|
|
275
|
+
type = "AWS_PROXY"
|
|
276
|
+
uri = aws_lambda_function.broker[0].invoke_arn
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
resource "aws_lambda_permission" "broker_apigw" {
|
|
280
|
+
count = local.count_enabled
|
|
281
|
+
statement_id = "AllowBrokerPrivateApiInvoke"
|
|
282
|
+
action = "lambda:InvokeFunction"
|
|
283
|
+
function_name = aws_lambda_function.broker[0].function_name
|
|
284
|
+
principal = "apigateway.amazonaws.com"
|
|
285
|
+
source_arn = "${aws_api_gateway_rest_api.broker[0].execution_arn}/*/*/*"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
resource "aws_api_gateway_deployment" "broker" {
|
|
289
|
+
count = local.count_enabled
|
|
290
|
+
rest_api_id = aws_api_gateway_rest_api.broker[0].id
|
|
291
|
+
|
|
292
|
+
triggers = {
|
|
293
|
+
redeploy = sha1(jsonencode([
|
|
294
|
+
aws_api_gateway_resource.broker_proxy[0].id,
|
|
295
|
+
aws_api_gateway_method.broker_any[0].id,
|
|
296
|
+
aws_api_gateway_integration.broker[0].id,
|
|
297
|
+
aws_api_gateway_rest_api.broker[0].policy,
|
|
298
|
+
]))
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
lifecycle {
|
|
302
|
+
create_before_destroy = true
|
|
303
|
+
}
|
|
304
|
+
depends_on = [aws_api_gateway_integration.broker]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
resource "aws_api_gateway_stage" "broker" {
|
|
308
|
+
count = local.count_enabled
|
|
309
|
+
rest_api_id = aws_api_gateway_rest_api.broker[0].id
|
|
310
|
+
deployment_id = aws_api_gateway_deployment.broker[0].id
|
|
311
|
+
stage_name = var.stage
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# --- Dedicated execute-api VPC endpoint (PRIVATE DNS DISABLED — THINK-144) ----
|
|
315
|
+
|
|
316
|
+
resource "aws_security_group" "broker_vpce" {
|
|
317
|
+
count = local.count_enabled
|
|
318
|
+
name = "${local.name_prefix}-vpce"
|
|
319
|
+
description = "Capability broker execute-api VPCE (broker Lambda + capability-private interpreter subnet only)"
|
|
320
|
+
vpc_id = var.vpc_id
|
|
321
|
+
|
|
322
|
+
ingress {
|
|
323
|
+
from_port = 443
|
|
324
|
+
to_port = 443
|
|
325
|
+
protocol = "tcp"
|
|
326
|
+
cidr_blocks = [var.vpc_cidr_block]
|
|
327
|
+
description = "HTTPS from in-VPC PoP callers (broker Lambda self-calls + U4 capability-private interpreter)"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
tags = { Name = "${local.name_prefix}-vpce" }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
resource "aws_vpc_endpoint" "broker_execute_api" {
|
|
334
|
+
count = local.count_enabled
|
|
335
|
+
|
|
336
|
+
vpc_id = var.vpc_id
|
|
337
|
+
service_name = "com.amazonaws.${var.region}.execute-api"
|
|
338
|
+
vpc_endpoint_type = "Interface"
|
|
339
|
+
subnet_ids = var.subnet_ids
|
|
340
|
+
security_group_ids = [aws_security_group.broker_vpce[0].id]
|
|
341
|
+
|
|
342
|
+
# CRITICAL (THINK-144): private DNS OFF. Callers target the endpoint-specific
|
|
343
|
+
# DNS name (surfaced as an output), never the regional execute-api domain, so
|
|
344
|
+
# this endpoint cannot capture the stack's public HTTP API traffic.
|
|
345
|
+
private_dns_enabled = false
|
|
346
|
+
|
|
347
|
+
tags = {
|
|
348
|
+
Name = "${local.name_prefix}-execute-api-vpce"
|
|
349
|
+
Purpose = "capability-broker-private-ingress"
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# --- Capability-private interpreter SG (no NAT; broker VPCE only) — U4 --------
|
|
354
|
+
#
|
|
355
|
+
# The U4 capability-private VPC-mode Code Interpreter attaches to var.subnet_ids
|
|
356
|
+
# (the no-NAT private egress subnets) under THIS security group. Its ONLY egress
|
|
357
|
+
# is 443 to the broker execute-api VPCE SG plus DNS to the VPC resolver — there
|
|
358
|
+
# is no NAT and no 0.0.0.0/0 route, so direct GitHub / Secrets Manager / internet
|
|
359
|
+
# access does not exist (AE5 negative-egress boundary). The broker_vpce ingress
|
|
360
|
+
# above already admits 443 from the VPC CIDR, which covers these subnets.
|
|
361
|
+
resource "aws_security_group" "capability_private_interpreter" {
|
|
362
|
+
count = local.count_enabled
|
|
363
|
+
name = "${local.name_prefix}-cappriv-interpreter"
|
|
364
|
+
description = "Capability-private Code Interpreter egress: broker execute-api VPCE + DNS only"
|
|
365
|
+
vpc_id = var.vpc_id
|
|
366
|
+
|
|
367
|
+
# execute-api VPCE only (reach the private broker API).
|
|
368
|
+
egress {
|
|
369
|
+
from_port = 443
|
|
370
|
+
to_port = 443
|
|
371
|
+
protocol = "tcp"
|
|
372
|
+
security_groups = [aws_security_group.broker_vpce[0].id]
|
|
373
|
+
description = "HTTPS to the broker execute-api VPCE"
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# DNS to the in-VPC resolver so the endpoint-specific VPCE hostname resolves.
|
|
377
|
+
egress {
|
|
378
|
+
from_port = 53
|
|
379
|
+
to_port = 53
|
|
380
|
+
protocol = "udp"
|
|
381
|
+
cidr_blocks = [var.vpc_cidr_block]
|
|
382
|
+
description = "DNS (UDP) to the VPC resolver"
|
|
383
|
+
}
|
|
384
|
+
egress {
|
|
385
|
+
from_port = 53
|
|
386
|
+
to_port = 53
|
|
387
|
+
protocol = "tcp"
|
|
388
|
+
cidr_blocks = [var.vpc_cidr_block]
|
|
389
|
+
description = "DNS (TCP) to the VPC resolver"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
tags = { Name = "${local.name_prefix}-cappriv-interpreter" }
|
|
393
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Consumed by the API-side broker-session opener to build the sandbox
|
|
2
|
+
# SessionBootstrap. All null when the module is disabled (ship-inert).
|
|
3
|
+
|
|
4
|
+
output "enabled" {
|
|
5
|
+
value = local.enabled
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
output "session_table_name" {
|
|
9
|
+
description = "DynamoDB session table (CAPABILITY_BROKER_SESSION_TABLE)."
|
|
10
|
+
value = local.enabled ? aws_dynamodb_table.sessions[0].name : ""
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
output "session_table_arn" {
|
|
14
|
+
description = "ARN of the broker session table — granted to the lambda-api role so routine-exec-git can mint/advance PoP sessions. Empty when disabled."
|
|
15
|
+
value = local.enabled ? aws_dynamodb_table.sessions[0].arn : ""
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
output "broker_audience" {
|
|
19
|
+
description = "Audience the PoP session signatures are bound to (CAPABILITY_BROKER_AUDIENCE)."
|
|
20
|
+
value = local.enabled ? local.broker_audience : ""
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
output "private_api_id" {
|
|
24
|
+
description = "Private REST API id the sandbox targets through the VPCE (CAPABILITY_BROKER_API_ID)."
|
|
25
|
+
value = local.enabled ? aws_api_gateway_rest_api.broker[0].id : ""
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
output "execute_api_vpce_id" {
|
|
29
|
+
value = local.enabled ? aws_vpc_endpoint.broker_execute_api[0].id : ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
output "execute_api_vpce_dns_name" {
|
|
33
|
+
description = "Endpoint-specific execute-api DNS name (private DNS is OFF). The session bootstrap uses this exact host — CAPABILITY_BROKER_VPCE_DNS."
|
|
34
|
+
value = local.enabled ? tolist(aws_vpc_endpoint.broker_execute_api[0].dns_entry)[0].dns_name : ""
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
output "broker_function_name" {
|
|
38
|
+
value = local.enabled ? aws_lambda_function.broker[0].function_name : ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# --- U4: capability-private interpreter VPC placement ------------------------
|
|
42
|
+
# Consumed by the agentcore-admin Lambda (CAPABILITY_PRIVATE_SUBNET_IDS /
|
|
43
|
+
# CAPABILITY_PRIVATE_SECURITY_GROUP_IDS). Empty lists when disabled so the
|
|
44
|
+
# Lambda skips capability-private provisioning entirely.
|
|
45
|
+
|
|
46
|
+
output "capability_private_interpreter_subnet_ids" {
|
|
47
|
+
description = "No-NAT subnets the capability-private interpreter attaches to (same egress subnets as the broker)."
|
|
48
|
+
value = local.enabled ? var.subnet_ids : []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
output "capability_private_interpreter_security_group_id" {
|
|
52
|
+
description = "Egress-only SG (broker execute-api VPCE + DNS only) for the capability-private interpreter. Empty when disabled."
|
|
53
|
+
value = local.enabled ? aws_security_group.capability_private_interpreter[0].id : ""
|
|
54
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
variable "enabled" {
|
|
2
|
+
description = "Master switch. When false the module creates nothing (ship-inert: U3 lands the broker code + this module gated off; U4 flips it on after the negative-egress proof)."
|
|
3
|
+
type = bool
|
|
4
|
+
default = false
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
variable "stage" {
|
|
8
|
+
type = string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "account_id" {
|
|
12
|
+
type = string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
variable "region" {
|
|
16
|
+
type = string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# --- VPC placement (broker Lambda egress subnets + the dedicated VPCE) -------
|
|
20
|
+
|
|
21
|
+
variable "vpc_id" {
|
|
22
|
+
type = string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
variable "vpc_cidr_block" {
|
|
26
|
+
type = string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
variable "subnet_ids" {
|
|
30
|
+
description = "Private egress subnets the broker Lambda and its dedicated execute-api VPC endpoint attach to."
|
|
31
|
+
type = list(string)
|
|
32
|
+
default = []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# --- Evidence datastore (Aurora, RDS Data API — same cluster as graphql-http)-
|
|
36
|
+
|
|
37
|
+
variable "db_cluster_arn" {
|
|
38
|
+
type = string
|
|
39
|
+
default = ""
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
variable "db_secret_arn" {
|
|
43
|
+
type = string
|
|
44
|
+
default = ""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
variable "db_cluster_endpoint" {
|
|
48
|
+
description = "Aurora writer endpoint hostname — DATABASE_HOST for getDb() (evidence store + authorization loader)."
|
|
49
|
+
type = string
|
|
50
|
+
default = ""
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
variable "db_username" {
|
|
54
|
+
description = "Aurora master username for the broker's DATABASE_URL."
|
|
55
|
+
type = string
|
|
56
|
+
default = "thinkwork_admin"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
variable "db_password" {
|
|
60
|
+
description = "Aurora master password for the broker's DATABASE_URL (synchronous getDb() path)."
|
|
61
|
+
type = string
|
|
62
|
+
default = ""
|
|
63
|
+
sensitive = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
variable "database_name" {
|
|
67
|
+
description = "Aurora database name for the broker's DATABASE_URL."
|
|
68
|
+
type = string
|
|
69
|
+
default = "thinkwork"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# --- Lambda artifact source (mirrors lambda-api's dual local/remote pattern) -
|
|
73
|
+
|
|
74
|
+
variable "lambda_zips_dir" {
|
|
75
|
+
description = "Local path holding dist/lambdas/*.zip for local deploys. Empty when using remote S3 artifacts."
|
|
76
|
+
type = string
|
|
77
|
+
default = ""
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
variable "lambda_artifact_bucket" {
|
|
81
|
+
type = string
|
|
82
|
+
default = ""
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
variable "lambda_artifact_prefix" {
|
|
86
|
+
type = string
|
|
87
|
+
default = ""
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
variable "lambda_runtime" {
|
|
91
|
+
type = string
|
|
92
|
+
default = "nodejs22.x"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
variable "log_retention_days" {
|
|
96
|
+
type = number
|
|
97
|
+
default = 30
|
|
98
|
+
}
|
|
@@ -2075,6 +2075,35 @@ def release_runtime_image(name):
|
|
|
2075
2075
|
return ""
|
|
2076
2076
|
|
|
2077
2077
|
|
|
2078
|
+
def resolve_agentcore_pi_source_image_uri(payload):
|
|
2079
|
+
operation = payload.get("operation") if isinstance(payload.get("operation"), dict) else {}
|
|
2080
|
+
action = str(
|
|
2081
|
+
safe_get(operation, "action", default=safe_get(payload, "action", "phase", default=""))
|
|
2082
|
+
).lower()
|
|
2083
|
+
kind = str(safe_get(operation, "kind", default="")).lower()
|
|
2084
|
+
is_customer_update = action == "update" or kind == "foundation"
|
|
2085
|
+
explicit = safe_get(payload, "agentcorePiSourceImageUri", default="")
|
|
2086
|
+
if explicit:
|
|
2087
|
+
if is_customer_update:
|
|
2088
|
+
account_id = safe_get(payload, "awsAccountId", default="")
|
|
2089
|
+
region = safe_get(payload, "awsRegion", default=os.environ.get("AWS_REGION", ""))
|
|
2090
|
+
customer_registry = f"{account_id}.dkr.ecr.{region}.amazonaws.com/"
|
|
2091
|
+
if not account_id or not region or not explicit.startswith(customer_registry):
|
|
2092
|
+
raise RuntimeError(
|
|
2093
|
+
"Customer foundation updates require agentcorePiSourceImageUri "
|
|
2094
|
+
"to reference the customer-owned ECR registry."
|
|
2095
|
+
)
|
|
2096
|
+
return explicit
|
|
2097
|
+
|
|
2098
|
+
if is_customer_update:
|
|
2099
|
+
raise RuntimeError(
|
|
2100
|
+
"Customer foundation updates require an explicit customer-ECR "
|
|
2101
|
+
"agentcorePiSourceImageUri; refusing to fall back to the release registry."
|
|
2102
|
+
)
|
|
2103
|
+
|
|
2104
|
+
return release_runtime_image("agentcore-pi-amd64")
|
|
2105
|
+
|
|
2106
|
+
|
|
2078
2107
|
def release_git_sha():
|
|
2079
2108
|
if not MANIFEST.exists():
|
|
2080
2109
|
return ""
|
|
@@ -3053,6 +3082,16 @@ def write_runner_files(payload, runner_secrets):
|
|
|
3053
3082
|
"enableHindsight",
|
|
3054
3083
|
default=False,
|
|
3055
3084
|
),
|
|
3085
|
+
# Analyst data-path Lambda VPC egress (stable NAT EIP for external
|
|
3086
|
+
# database IP allowlists). Per-environment infra posture, so the
|
|
3087
|
+
# durable home is the runner-secrets document; a payload boolean
|
|
3088
|
+
# can override per-deploy.
|
|
3089
|
+
"analyst_lambda_vpc_egress": safe_get_bool(
|
|
3090
|
+
runner_secrets,
|
|
3091
|
+
reviewed_payload,
|
|
3092
|
+
"analystLambdaVpcEgress",
|
|
3093
|
+
default=False,
|
|
3094
|
+
),
|
|
3056
3095
|
# THINK-220: dedicated Hindsight database on the stage cluster.
|
|
3057
3096
|
# Empty keeps the legacy hindsight-schema layout; set (e.g.
|
|
3058
3097
|
# "thinkwork_hindsight") points the Hindsight service and platform
|
|
@@ -3161,11 +3200,7 @@ def write_runner_files(payload, runner_secrets):
|
|
|
3161
3200
|
"deployment_evidence_bucket": os.environ.get("THINKWORK_EVIDENCE_BUCKET", ""),
|
|
3162
3201
|
"deployment_terraform_module_source": terraform_module_source,
|
|
3163
3202
|
"deployment_terraform_module_version": terraform_module_version,
|
|
3164
|
-
"agentcore_pi_source_image_uri":
|
|
3165
|
-
payload,
|
|
3166
|
-
"agentcorePiSourceImageUri",
|
|
3167
|
-
default=release_runtime_image("agentcore-pi-amd64"),
|
|
3168
|
-
),
|
|
3203
|
+
"agentcore_pi_source_image_uri": resolve_agentcore_pi_source_image_uri(payload),
|
|
3169
3204
|
}
|
|
3170
3205
|
enforce_customer_domain_preservation(
|
|
3171
3206
|
current_outputs,
|
|
@@ -3262,6 +3297,11 @@ variable "enable_hindsight" {{
|
|
|
3262
3297
|
type = bool
|
|
3263
3298
|
}}
|
|
3264
3299
|
|
|
3300
|
+
variable "analyst_lambda_vpc_egress" {{
|
|
3301
|
+
type = bool
|
|
3302
|
+
default = false
|
|
3303
|
+
}}
|
|
3304
|
+
|
|
3265
3305
|
variable "hindsight_database_name" {{
|
|
3266
3306
|
type = string
|
|
3267
3307
|
default = ""
|
|
@@ -3655,6 +3695,7 @@ module "thinkwork" {{
|
|
|
3655
3695
|
agentcore_pi_source_image_uri = var.agentcore_pi_source_image_uri
|
|
3656
3696
|
|
|
3657
3697
|
enable_hindsight = var.enable_hindsight
|
|
3698
|
+
analyst_lambda_vpc_egress = var.analyst_lambda_vpc_egress
|
|
3658
3699
|
hindsight_database_name = var.hindsight_database_name
|
|
3659
3700
|
enable_workspace_orchestration = true
|
|
3660
3701
|
|