For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Expose the Link service (port 9999) to the internet at link.getorcha.com.
Architecture: Add ALB routing, target group, and DNS for Link service following the same pattern as ERP (port 8888) and Admin (port 7777). Link handles OAuth internally, so no ALB Cognito action needed.
Tech Stack: AWS CDK (Python), Route53, ACM, ALB, CloudWatch
Purpose: Create the SSL certificate before CDK changes (cross-account DNS validation requires manual steps).
Step 1: Run the certificate creation script
cd /home/volrath/code/orcha/orcha/infra
./scripts/create-cross-account-cert.sh \
--name link \
--primary-domain link.prod.getorcha.com \
--san link.getorcha.com
Expected: Script creates certificate, adds validation CNAMEs to both zones, waits for ISSUED status, outputs ARN.
Step 2: Save the certificate ARN
Note the ARN from script output (format: arn:aws:acm:eu-central-1:700558745280:certificate/<uuid>).
Step 3: Add short link CNAME in management account
AWS_PROFILE=orcha aws route53 change-resource-record-sets \
--hosted-zone-id Z02414383CQNYTPGX2EIK \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "link.getorcha.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{"Value": "link.prod.getorcha.com"}]
}
}]
}'
Expected: ChangeInfo with PENDING status.
Files:
infra/stacks/foundation_stack.py:199-209Step 1: Add Link port security group rules
After the Admin port rules (around line 209), add:
# Link service traffic ALB -> EC2 (port 9999)
self.alb_sg.add_egress_rule(
self.ec2_sg,
ec2.Port.tcp(9999),
"Link traffic to EC2",
)
self.ec2_sg.add_ingress_rule(
self.alb_sg,
ec2.Port.tcp(9999),
"Link port from ALB",
)
Step 2: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 3: Commit
git add infra/stacks/foundation_stack.py
git commit -m "infra: Add security group rules for Link service (port 9999)"
Files:
infra/stacks/compute_stack.py:596-616Step 1: Add class attribute for link_target_group
At line ~98, add to class attributes:
link_target_group: elbv2.IApplicationTargetGroup
Step 2: Add Link target group after Admin target group
After the Admin target group and ASG attachment (around line 616), add:
# =====================================================================
# Link Target Group
# =====================================================================
self.link_target_group = elbv2.ApplicationTargetGroup(
self,
"LinkTargetGroup",
target_group_name="v1-orcha-link-tg",
vpc=vpc,
port=9999,
protocol=elbv2.ApplicationProtocol.HTTP,
target_type=elbv2.TargetType.INSTANCE,
health_check=elbv2.HealthCheck(
path="/health",
protocol=elbv2.Protocol.HTTP,
healthy_threshold_count=2,
unhealthy_threshold_count=3,
interval=Duration.seconds(30),
timeout=Duration.seconds(5),
),
deregistration_delay=Duration.seconds(60),
)
# Register ASG with link target group
self.asg.attach_to_application_target_group(self.link_target_group)
Step 3: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 4: Commit
git add infra/stacks/compute_stack.py
git commit -m "infra: Add Link target group (port 9999)"
Files:
infra/stacks/compute_stack.py:175-193Step 1: Add class attribute for link_certificate
At line ~98, add to class attributes:
link_certificate: acm.ICertificate
Step 2: Add Link certificate after Admin certificate
After the Admin certificate block (around line 193), add:
# =====================================================================
# Link ACM Certificate
# =====================================================================
#
# Same pattern as ERP/Admin - manually created for cross-account
# DNS validation. Covers link.prod.getorcha.com + link.getorcha.com
#
if env_name == "prod":
self.link_certificate = acm.Certificate.from_certificate_arn(
self,
"LinkCertificate",
certificate_arn="<REPLACE_WITH_ARN_FROM_TASK_0>",
)
else:
self.link_certificate = acm.Certificate(
self,
"LinkCertificate",
domain_name=f"link.{env_name}.getorcha.com",
validation=acm.CertificateValidation.from_dns(hosted_zone),
)
Step 3: Replace placeholder with actual ARN
Replace <REPLACE_WITH_ARN_FROM_TASK_0> with the ARN from Task 0.
Step 4: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 5: Commit
git add infra/stacks/compute_stack.py
git commit -m "infra: Add Link SSL certificate"
Files:
infra/stacks/compute_stack.py:619-686Step 1: Add Link certificate to HTTPS listener
After https_listener.add_certificates("AdminCerts", ...) (around line 632), add:
# Add link certificate for SNI
https_listener.add_certificates("LinkCerts", [self.link_certificate])
Step 2: Add Link routing rule
After the Admin routing action (around line 653), add:
# Link routing (handles OAuth internally, no ALB auth)
https_listener.add_action(
"LinkRouting",
priority=20,
conditions=[
elbv2.ListenerCondition.host_headers([
"link.prod.getorcha.com",
"link.getorcha.com",
])
],
action=elbv2.ListenerAction.forward([self.link_target_group]),
)
Step 3: Add Link DNS A record
After the Admin A record (around line 686), add:
# Link A Record
route53.ARecord(
self,
"LinkAliasRecord",
zone=hosted_zone,
record_name="link", # Creates link.{env}.getorcha.com
target=route53.RecordTarget.from_alias(
targets.LoadBalancerTarget(self.alb),
),
)
Step 4: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 5: Commit
git add infra/stacks/compute_stack.py
git commit -m "infra: Add Link listener rule and DNS record"
Files:
infra/stacks/compute_stack.py:745-760Step 1: Add Link outputs
After the Admin outputs (around line 760), add:
CfnOutput(
self,
"LinkTargetGroupArn",
value=self.link_target_group.target_group_arn,
description="Link Target Group ARN",
export_name=f"{env_name}-link-tg-arn",
)
CfnOutput(
self,
"LinkCertificateArn",
value=self.link_certificate.certificate_arn,
description="Link ACM Certificate ARN",
export_name=f"{env_name}-link-cert-arn",
)
CfnOutput(
self,
"LinkUrl",
value=f"https://link.{env_name}.getorcha.com",
description="Link service URL",
)
Step 2: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 3: Commit
git add infra/stacks/compute_stack.py
git commit -m "infra: Add Link CDK outputs"
Files:
infra/stacks/ops_stack.py:70-81infra/stacks/ops_stack.py:172-193infra/app.py:129Step 1: Add link_target_group parameter to OpsStack
In ops_stack.py, add to __init__ parameters (around line 74):
link_target_group: elbv2.IApplicationTargetGroup,
Step 2: Add Link unhealthy alarm
After the Admin unhealthy alarm (around line 193), add:
# 1c. Link ALB Unhealthy Hosts
link_alb_unhealthy_alarm = cloudwatch.Alarm(
self,
"LinkAlbUnhealthyAlarm",
alarm_name="v1-orcha-link-unhealthy",
alarm_description="Link target group has no healthy targets",
metric=cloudwatch.Metric(
namespace="AWS/ApplicationELB",
metric_name="HealthyHostCount",
dimensions_map={
"TargetGroup": link_target_group.target_group_full_name,
"LoadBalancer": alb.load_balancer_full_name,
},
statistic="Minimum",
period=Duration.seconds(60),
),
threshold=1,
comparison_operator=cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
evaluation_periods=2,
treat_missing_data=cloudwatch.TreatMissingData.BREACHING,
)
link_alb_unhealthy_alarm.add_alarm_action(cw_actions.SnsAction(self.alert_topic))
Step 3: Pass link_target_group from app.py
In app.py, add to the OpsStack instantiation (after line 129):
link_target_group=compute.link_target_group,
Step 4: Verify CDK synth succeeds
cd /home/volrath/code/orcha/orcha/infra
cdk synth --context env_name=prod -q
Expected: No errors.
Step 5: Commit
git add infra/stacks/ops_stack.py infra/app.py
git commit -m "infra: Add Link CloudWatch unhealthy hosts alarm"
Files:
deploy/docker-compose.yml:22-25Step 1: Add port 9999 mapping
Change the ports section from:
ports:
- "8888:8888"
- "7777:7777"
- "9878:9878"
To:
ports:
- "8888:8888"
- "7777:7777"
- "9999:9999"
- "9878:9878"
Step 2: Commit
git add deploy/docker-compose.yml
git commit -m "deploy: Expose Link service port 9999"
Step 1: Run CDK diff
cd /home/volrath/code/orcha/orcha/infra
cdk diff --context env_name=prod
Expected: Shows changes to FoundationStack (security groups), ComputeStack (target group, listener, DNS), OpsStack (alarm).
Step 2: Deploy (if diff looks correct)
cdk deploy --all --context env_name=prod
Expected: All stacks deploy successfully.
Step 1: Verify DNS resolution
dig link.prod.getorcha.com +short
dig link.getorcha.com +short
Expected: Both return the ALB DNS name (or CNAME chain to it).
Step 2: Verify health check
curl -I https://link.getorcha.com/health
Expected: HTTP 200.
Step 3: Verify OAuth discovery
curl https://link.getorcha.com/.well-known/oauth-authorization-server
Expected: JSON with OAuth server metadata.
Step 4: Commit any final changes and tag
git log --oneline -10 # Review commits