feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Load Balancer module – Application load balancer with WebSocket support
|
||||
#
|
||||
# Provisions:
|
||||
# - ALB in public subnets
|
||||
# - HTTPS listener with TLS termination
|
||||
# - HTTP → HTTPS redirect
|
||||
# - Weighted target groups for blue/green and canary deployments
|
||||
# - Sticky sessions for WebSocket connections
|
||||
# - Path-based routing for MCP sidecar (/mcp/*)
|
||||
# - Health checks at /api/health
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Production TLS enforcement — prevents deploying production without encryption
|
||||
check "production_tls_required" {
|
||||
assert {
|
||||
condition = var.environment != "production" || var.tls_certificate_arn != ""
|
||||
error_message = "Production deployments require TLS. Set tls_certificate_arn and domain_name."
|
||||
}
|
||||
}
|
||||
|
||||
check "production_domain_required" {
|
||||
assert {
|
||||
condition = var.environment != "production" || var.domain_name != ""
|
||||
error_message = "Production deployments require a domain name. Set domain_name."
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
name_prefix = lower(replace("${var.project_name}-${var.environment}", "_", "-"))
|
||||
|
||||
# Determine whether TLS is configured
|
||||
has_tls = var.tls_certificate_arn != ""
|
||||
|
||||
common_tags = merge(
|
||||
{
|
||||
module = "loadbalancer"
|
||||
},
|
||||
var.tags,
|
||||
)
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Application Load Balancer
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "aws_lb" "main" {
|
||||
name = "${local.name_prefix}-alb"
|
||||
internal = false
|
||||
load_balancer_type = "application"
|
||||
security_groups = var.security_group_ids
|
||||
subnets = var.public_subnet_ids
|
||||
|
||||
enable_deletion_protection = var.environment == "production" ? true : var.enable_deletion_protection
|
||||
enable_http2 = true
|
||||
idle_timeout = 300 # WebSocket connections may be long-lived
|
||||
|
||||
drop_invalid_header_fields = true
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.name_prefix}-alb"
|
||||
})
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = false
|
||||
# For production, set enable_deletion_protection = true above (enforced automatically)
|
||||
}
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# HTTPS listener (primary – with weighted target groups for blue/green)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "aws_lb_listener" "https" {
|
||||
count = local.has_tls ? 1 : 0
|
||||
|
||||
load_balancer_arn = aws_lb.main.arn
|
||||
port = 443
|
||||
protocol = "HTTPS"
|
||||
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
|
||||
certificate_arn = var.tls_certificate_arn
|
||||
|
||||
default_action {
|
||||
type = "forward"
|
||||
|
||||
forward {
|
||||
target_group {
|
||||
arn = var.blue_target_group_arn
|
||||
weight = var.blue_weight
|
||||
}
|
||||
|
||||
target_group {
|
||||
arn = var.green_target_group_arn
|
||||
weight = var.green_weight
|
||||
}
|
||||
|
||||
stickiness {
|
||||
enabled = true
|
||||
duration = 86400
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# HTTP listener – redirect to HTTPS when TLS is configured, else forward
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "aws_lb_listener" "http_redirect" {
|
||||
count = local.has_tls ? 1 : 0
|
||||
|
||||
load_balancer_arn = aws_lb.main.arn
|
||||
port = 80
|
||||
protocol = "HTTP"
|
||||
|
||||
default_action {
|
||||
type = "redirect"
|
||||
|
||||
redirect {
|
||||
port = "443"
|
||||
protocol = "HTTPS"
|
||||
status_code = "HTTP_301"
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "aws_lb_listener" "http_forward" {
|
||||
count = local.has_tls ? 0 : 1
|
||||
|
||||
load_balancer_arn = aws_lb.main.arn
|
||||
port = 80
|
||||
protocol = "HTTP"
|
||||
|
||||
default_action {
|
||||
type = "forward"
|
||||
|
||||
forward {
|
||||
target_group {
|
||||
arn = var.blue_target_group_arn
|
||||
weight = var.blue_weight
|
||||
}
|
||||
|
||||
target_group {
|
||||
arn = var.green_target_group_arn
|
||||
weight = var.green_weight
|
||||
}
|
||||
|
||||
stickiness {
|
||||
enabled = true
|
||||
duration = 86400
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# MCP sidecar target group
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "aws_lb_target_group" "mcp" {
|
||||
name_prefix = "mcp-"
|
||||
port = var.mcp_port
|
||||
protocol = "HTTP"
|
||||
vpc_id = var.vpc_id
|
||||
target_type = "ip"
|
||||
|
||||
health_check {
|
||||
enabled = true
|
||||
path = "/"
|
||||
port = tostring(var.mcp_port)
|
||||
protocol = "HTTP"
|
||||
healthy_threshold = var.health_check_healthy_threshold
|
||||
unhealthy_threshold = var.health_check_unhealthy_threshold
|
||||
timeout = var.health_check_timeout
|
||||
interval = var.health_check_interval
|
||||
matcher = "200-404"
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.name_prefix}-mcp-tg"
|
||||
})
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Path-based routing rule for MCP sidecar (/mcp/*)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "aws_lb_listener_rule" "mcp_https" {
|
||||
count = local.has_tls ? 1 : 0
|
||||
|
||||
listener_arn = aws_lb_listener.https[0].arn
|
||||
priority = 10
|
||||
|
||||
action {
|
||||
type = "forward"
|
||||
target_group_arn = aws_lb_target_group.mcp.arn
|
||||
}
|
||||
|
||||
condition {
|
||||
path_pattern {
|
||||
values = ["/mcp", "/mcp/*"]
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "aws_lb_listener_rule" "mcp_http" {
|
||||
count = local.has_tls ? 0 : 1
|
||||
|
||||
listener_arn = aws_lb_listener.http_forward[0].arn
|
||||
priority = 10
|
||||
|
||||
action {
|
||||
type = "forward"
|
||||
target_group_arn = aws_lb_target_group.mcp.arn
|
||||
}
|
||||
|
||||
condition {
|
||||
path_pattern {
|
||||
values = ["/mcp", "/mcp/*"]
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Weight sum validation
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
check "lb_weight_sum" {
|
||||
assert {
|
||||
condition = var.blue_weight + var.green_weight == 100
|
||||
error_message = "blue_weight (${var.blue_weight}) + green_weight (${var.green_weight}) must sum to 100 for correct traffic routing."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Load Balancer module outputs
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
output "loadbalancer_arn" {
|
||||
description = "ARN of the application load balancer"
|
||||
value = aws_lb.main.arn
|
||||
}
|
||||
|
||||
output "loadbalancer_id" {
|
||||
description = "ID of the application load balancer"
|
||||
value = aws_lb.main.id
|
||||
}
|
||||
|
||||
output "dns_name" {
|
||||
description = "DNS name of the application load balancer"
|
||||
value = aws_lb.main.dns_name
|
||||
}
|
||||
|
||||
output "zone_id" {
|
||||
description = "Route53 zone ID for the load balancer (alias records)"
|
||||
value = aws_lb.main.zone_id
|
||||
}
|
||||
|
||||
output "application_url" {
|
||||
description = "Full URL to access the application"
|
||||
value = local.has_tls ? "https://${var.domain_name != "" ? var.domain_name : aws_lb.main.dns_name}" : "http://${aws_lb.main.dns_name}"
|
||||
}
|
||||
|
||||
output "https_listener_arn" {
|
||||
description = "ARN of the HTTPS listener (empty if TLS not configured)"
|
||||
value = local.has_tls ? aws_lb_listener.https[0].arn : ""
|
||||
}
|
||||
|
||||
output "http_listener_arn" {
|
||||
description = "ARN of the HTTP listener"
|
||||
value = local.has_tls ? aws_lb_listener.http_redirect[0].arn : aws_lb_listener.http_forward[0].arn
|
||||
}
|
||||
|
||||
output "mcp_target_group_arn" {
|
||||
description = "ARN of the MCP sidecar target group"
|
||||
value = aws_lb_target_group.mcp.arn
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Load Balancer module variables
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "project_name" {
|
||||
description = "Project identifier used in resource naming"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Deployment environment (dev, staging, production)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cloud_provider" {
|
||||
description = "Target cloud provider (aws, gcp, azure, oci)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Cloud region for deployment"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
description = "VPC ID for target group association"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "public_subnet_ids" {
|
||||
description = "Public subnet IDs for load balancer placement"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "security_group_ids" {
|
||||
description = "Security group IDs attached to the load balancer"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "app_port" {
|
||||
description = "Application container port"
|
||||
type = number
|
||||
default = 4820
|
||||
}
|
||||
|
||||
variable "mcp_port" {
|
||||
description = "MCP sidecar container port"
|
||||
type = number
|
||||
default = 8819
|
||||
}
|
||||
|
||||
variable "tls_certificate_arn" {
|
||||
description = "ARN of the TLS certificate for HTTPS (required for production)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "domain_name" {
|
||||
description = "Fully qualified domain name for the application (required for production)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "blue_target_group_arn" {
|
||||
description = "ARN of the blue deployment target group"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "green_target_group_arn" {
|
||||
description = "ARN of the green deployment target group"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "blue_weight" {
|
||||
description = "Traffic weight for blue target group (0-100)"
|
||||
type = number
|
||||
default = 100
|
||||
validation {
|
||||
condition = var.blue_weight >= 0 && var.blue_weight <= 100
|
||||
error_message = "blue_weight must be between 0 and 100."
|
||||
}
|
||||
}
|
||||
|
||||
variable "green_weight" {
|
||||
description = "Traffic weight for green target group (0-100)"
|
||||
type = number
|
||||
default = 0
|
||||
validation {
|
||||
condition = var.green_weight >= 0 && var.green_weight <= 100
|
||||
error_message = "green_weight must be between 0 and 100."
|
||||
}
|
||||
}
|
||||
|
||||
variable "health_check_path" {
|
||||
description = "HTTP path for health checks"
|
||||
type = string
|
||||
default = "/api/health"
|
||||
}
|
||||
|
||||
variable "health_check_interval" {
|
||||
description = "Seconds between health checks"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "health_check_timeout" {
|
||||
description = "Seconds before a health check times out"
|
||||
type = number
|
||||
default = 5
|
||||
}
|
||||
|
||||
variable "health_check_healthy_threshold" {
|
||||
description = "Consecutive successes to mark healthy"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "health_check_unhealthy_threshold" {
|
||||
description = "Consecutive failures to mark unhealthy"
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Resource tags"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "enable_deletion_protection" {
|
||||
description = "Enable deletion protection on the load balancer (recommended for production)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
Reference in New Issue
Block a user