From ecc1505d68c221cdacb5935082adcb42b709ba91 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 18:57:11 +0100 Subject: [PATCH 1/9] chore(coverage): bootstrap COVERAGE.md baseline Coverage ratchet bootstrap for #417; measured on main at c1cd9061. --- COVERAGE.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 COVERAGE.md diff --git a/COVERAGE.md b/COVERAGE.md new file mode 100644 index 00000000..b8b0214e --- /dev/null +++ b/COVERAGE.md @@ -0,0 +1,33 @@ +# Coverage + +Generated by the AI Coverage phase. Do not edit by hand. + +## .NET + +| Project | Line Coverage | +| --- | --- | +| BuildBot | n/a (no instrumented lines) | +| BuildBot.CloudFormation | 100.0% | +| BuildBot.Discord | 96.8% | +| BuildBot.GitHub | 100.0% | +| BuildBot.Health | 100.0% | +| BuildBot.Json | 100.0% | +| BuildBot.ServiceModel | 100.0% | +| BuildBot.Watchtower | 100.0% | +| **Overall (.NET)** | **99.0%** | + +## Node + +n/a (no code) + +## Python + +n/a (no code) + +## Shell + +excluded + +--- + +Captured at commit `c1cd906133caf5d3c594e2af9663e240305213c3` on 2026-10-12. From 28de66f23b379da2afa04f3673106ca3a288a912 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 18:58:07 +0100 Subject: [PATCH 2/9] docs(changelog): add placeholder entry for #417 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1db3c55..6e78a77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - Dependencies - Updated xunit.analyzers to 2.2.0 - Dependencies - Updated xunit.v3 to 4.0.2 - Dependencies - Updated Meziantou.Analyzer to 3.0.299 +- TBD - to be finalized after review ### Deprecated ### Removed - Removed NuGet audit suppressions for Scriban 6.2.0 vulnerabilities now that the transitive dependency has been dropped From 350bee97cff86226afbb239efd6f290ef6892bf6 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:00:23 +0100 Subject: [PATCH 3/9] chore(cloudformation): reduce task memory to 1 GB Reduce the Fargate task definition memory default from 4096 MB to 1024 MB. CPU stays at 512 (0.5 vCPU). Observed peak memory is 4.6% of 4 GB (about 190 MB). Closes #417 --- cloudformation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudformation.json b/cloudformation.json index dbfa780c..b2618edb 100644 --- a/cloudformation.json +++ b/cloudformation.json @@ -47,7 +47,7 @@ }, "TaskDefinitionMemory": { "Type": "String", - "Default": "4096" + "Default": "1024" }, "TaskRole": { "Type": "String", From cf4d15bdedddecabd86414ece4d559a18d09e457 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:03:38 +0100 Subject: [PATCH 4/9] docs(changelog): describe task memory reduction for #417 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e78a77e..d493c523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,7 +77,7 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - Dependencies - Updated xunit.analyzers to 2.2.0 - Dependencies - Updated xunit.v3 to 4.0.2 - Dependencies - Updated Meziantou.Analyzer to 3.0.299 -- TBD - to be finalized after review +- Reduced BuildBot ECS task memory from 4 GB to 1 GB ### Deprecated ### Removed - Removed NuGet audit suppressions for Scriban 6.2.0 vulnerabilities now that the transitive dependency has been dropped From 29e9bcc765011f8eafba8a03378855edd7d5283c Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:32:12 +0100 Subject: [PATCH 5/9] fix(cloudformation): move task settings into Mappings so they apply on deploy The deploy action only passes parameters it has an env value for, so a changed parameter Default never reached the existing stack. Move ECSClusterName, LoadBalancerArn, ContainerPort, TaskDefinitionCPU, TaskDefinitionMemory, TaskRole, DesiredCount, MinimumHealthPercent, MaximumHealthPercent and SecretsManagerArn into a Deployment mapping read with Fn::FindInMap, so every deploy applies the template's values, including the 1 GB task memory. Refs #417 --- cloudformation.json | 128 ++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/cloudformation.json b/cloudformation.json index b2618edb..388d861a 100644 --- a/cloudformation.json +++ b/cloudformation.json @@ -11,10 +11,6 @@ "ApplicationVersion": { "Type": "String" }, - "ECSClusterName": { - "Type": "String", - "Default": "DiscordBots" - }, "SecurityGroupIDs": { "Type": "CommaDelimitedList", "Default": "sg-e3e65a9a,sg-0272027302777cb4d" @@ -27,51 +23,37 @@ "Type": "String", "Default": "vpc-ad3bd9ca" }, - "LoadBalancerArn": { - "Type": "String", - "Default": "arn:aws:elasticloadbalancing:eu-west-1:117769150821:listener/app/testing/c383c5dd580a74f5/e82083ed48688c7a" - }, "LoadBalancerHostName": { "Type": "String" }, "LoadBalancerPriority": { "Type": "Number" }, - "ContainerPort": { - "Type": "Number", - "Default": "8080" - }, - "TaskDefinitionCPU": { - "Type": "String", - "Default": "512" - }, - "TaskDefinitionMemory": { - "Type": "String", - "Default": "1024" - }, - "TaskRole": { - "Type": "String", - "Default": "" - }, "TaskExecutionRole": { "Type": "String", "Default": "arn:aws:iam::117769150821:role/ecsTaskExecutionRole" - }, - "DesiredCount": { - "Type": "Number", - "Default": "1" - }, - "MinimumHealthPercent": { - "Type": "Number", - "Default": "100" - }, - "MaximumHealthPercent": { - "Type": "Number", - "Default": "200" - }, - "SecretsManagerArn": { - "Type": "String", - "Default": "arn:aws:secretsmanager:eu-west-1:117769150821:secret:BuildBot-ph1YIJ" + } + }, + "Mappings": { + "Deployment": { + "Cluster": { + "Name": "DiscordBots" + }, + "LoadBalancer": { + "ListenerArn": "arn:aws:elasticloadbalancing:eu-west-1:117769150821:listener/app/testing/c383c5dd580a74f5/e82083ed48688c7a" + }, + "Task": { + "Cpu": "512", + "Memory": "1024", + "Role": "", + "ContainerPort": "8080", + "SecretsManagerArn": "arn:aws:secretsmanager:eu-west-1:117769150821:secret:BuildBot-ph1YIJ" + }, + "Service": { + "DesiredCount": "1", + "MinimumHealthPercent": "100", + "MaximumHealthPercent": "200" + } } }, "Conditions": { @@ -80,7 +62,7 @@ { "Fn::Equals": [ { - "Ref": "TaskRole" + "Fn::FindInMap": ["Deployment", "Task", "Role"] }, "" ] @@ -125,7 +107,12 @@ "Value": { "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", - { "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, "Key": "DISCORD_TOKEN" } + { + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, + "Key": "DISCORD_TOKEN" + } ] } }, @@ -134,7 +121,12 @@ "Value": { "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", - { "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, "Key": "DISCORD_SERVER" } + { + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, + "Key": "DISCORD_SERVER" + } ] } }, @@ -144,7 +136,9 @@ "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { - "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, "Key": "DISCORD_CHANNEL" } ] @@ -156,7 +150,9 @@ "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { - "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, "Key": "DISCORD_RELEASE_CHANNEL" } ] @@ -168,7 +164,9 @@ "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { - "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, "Key": "OCTOPUS_URL" } ] @@ -180,7 +178,9 @@ "Fn::Sub": [ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { - "SecretsManagerArn": { "Ref": "SecretsManagerArn" }, + "SecretsManagerArn": { + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] + }, "Key": "OCTOPUS_API_KEY" } ] @@ -193,7 +193,7 @@ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { "SecretsManagerArn": { - "Ref": "SecretsManagerArn" + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] }, "Key": "SNS_TOPIC_ARN" } @@ -207,7 +207,7 @@ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { "SecretsManagerArn": { - "Ref": "SecretsManagerArn" + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] }, "Key": "AWS_REGION" } @@ -221,7 +221,7 @@ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { "SecretsManagerArn": { - "Ref": "SecretsManagerArn" + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] }, "Key": "AWS_ACCESS_KEY_ID" } @@ -235,7 +235,7 @@ "{{resolve:secretsmanager:${SecretsManagerArn}:SecretString:${Key}}}", { "SecretsManagerArn": { - "Ref": "SecretsManagerArn" + "Fn::FindInMap": ["Deployment", "Task", "SecretsManagerArn"] }, "Key": "AWS_SECRET_KEY" } @@ -259,8 +259,8 @@ }, "PortMappings": [ { - "ContainerPort": { "Ref": "ContainerPort" }, - "HostPort": { "Ref": "ContainerPort" }, + "ContainerPort": { "Fn::FindInMap": ["Deployment", "Task", "ContainerPort"] }, + "HostPort": { "Fn::FindInMap": ["Deployment", "Task", "ContainerPort"] }, "Protocol": "tcp" } ], @@ -284,10 +284,10 @@ "Ref": "ApplicationName" }, "Cpu": { - "Ref": "TaskDefinitionCPU" + "Fn::FindInMap": ["Deployment", "Task", "Cpu"] }, "Memory": { - "Ref": "TaskDefinitionMemory" + "Fn::FindInMap": ["Deployment", "Task", "Memory"] }, "ExecutionRoleArn": { "Ref": "TaskExecutionRole" @@ -296,7 +296,7 @@ "Fn::If": [ "HasTaskRole", { - "Ref": "TaskRole" + "Fn::FindInMap": ["Deployment", "Task", "Role"] }, { "Ref": "AWS::NoValue" @@ -330,13 +330,13 @@ "Ref": "ApplicationName" }, "Cluster": { - "Ref": "ECSClusterName" + "Fn::FindInMap": ["Deployment", "Cluster", "Name"] }, "TaskDefinition": { "Ref": "TaskDefinition" }, "DesiredCount": { - "Ref": "DesiredCount" + "Fn::FindInMap": ["Deployment", "Service", "DesiredCount"] }, "EnableECSManagedTags": false, "Tags": [], @@ -350,16 +350,16 @@ }, "DeploymentConfiguration": { "MaximumPercent": { - "Ref": "MaximumHealthPercent" + "Fn::FindInMap": ["Deployment", "Service", "MaximumHealthPercent"] }, "MinimumHealthyPercent": { - "Ref": "MinimumHealthPercent" + "Fn::FindInMap": ["Deployment", "Service", "MinimumHealthPercent"] } }, "LoadBalancers": [ { "ContainerName": { "Ref": "ApplicationName" }, - "ContainerPort": { "Ref": "ContainerPort" }, + "ContainerPort": { "Fn::FindInMap": ["Deployment", "Task", "ContainerPort"] }, "TargetGroupArn": { "Ref": "TargetGroup" } } ] @@ -373,12 +373,12 @@ "Ref": "ApplicationName" }, "HealthCheckPath": "/ping?source=target-group", - "Port": { "Ref": "ContainerPort" }, + "Port": { "Fn::FindInMap": ["Deployment", "Task", "ContainerPort"] }, "Protocol": "HTTP", "TargetType": "ip", "HealthCheckEnabled": true, "HealthCheckProtocol": "HTTP", - "HealthCheckPort": { "Ref": "ContainerPort" }, + "HealthCheckPort": { "Fn::FindInMap": ["Deployment", "Task", "ContainerPort"] }, "HealthCheckTimeoutSeconds": 4, "HealthyThresholdCount": 5, "HealthCheckIntervalSeconds": 5, @@ -416,7 +416,7 @@ "Values": [{ "Ref": "LoadBalancerHostName" }] } ], - "ListenerArn": { "Ref": "LoadBalancerArn" }, + "ListenerArn": { "Fn::FindInMap": ["Deployment", "LoadBalancer", "ListenerArn"] }, "Priority": { "Ref": "LoadBalancerPriority" } } } From bd43ca99ea45eac4eb5a61ae7c263657b03ca3fd Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:37:48 +0100 Subject: [PATCH 6/9] docs(changelog): describe CloudFormation Mappings fix for #417 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d493c523..d7014609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - Added missing IncludeAssets on NuGet package references required by FunFair.BuildCheck - Resolved cfn-lint W1030 warning on the TaskRoleArn CloudFormation property by only setting it when a TaskRole is provided - Added missing build;buildTransitive IncludeAssets on Serilog package reference required by FunFair.BuildCheck +- CloudFormation task settings now live in Mappings so every deploy applies them, reducing ECS task memory from 4 GB to 1 GB ### Changed - Dependencies - Updated NSubstitute.Analyzers.CSharp to 1.0.17 - Switched to use minimal APIs @@ -77,7 +78,6 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - Dependencies - Updated xunit.analyzers to 2.2.0 - Dependencies - Updated xunit.v3 to 4.0.2 - Dependencies - Updated Meziantou.Analyzer to 3.0.299 -- Reduced BuildBot ECS task memory from 4 GB to 1 GB ### Deprecated ### Removed - Removed NuGet audit suppressions for Scriban 6.2.0 vulnerabilities now that the transitive dependency has been dropped From 928a720e8570c7da93280d872118c8beaad82415 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:41:00 +0100 Subject: [PATCH 7/9] refactor(cloudformation): remove always-false task role condition Deployment.Task.Role was a constant empty string, so HasTaskRole was always false and TaskRoleArn always resolved to AWS::NoValue. Remove the mapping key, the condition and the property; behaviour is unchanged. Refs #417 --- cloudformation.json | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/cloudformation.json b/cloudformation.json index 388d861a..40a38d5f 100644 --- a/cloudformation.json +++ b/cloudformation.json @@ -45,7 +45,6 @@ "Task": { "Cpu": "512", "Memory": "1024", - "Role": "", "ContainerPort": "8080", "SecretsManagerArn": "arn:aws:secretsmanager:eu-west-1:117769150821:secret:BuildBot-ph1YIJ" }, @@ -56,20 +55,6 @@ } } }, - "Conditions": { - "HasTaskRole": { - "Fn::Not": [ - { - "Fn::Equals": [ - { - "Fn::FindInMap": ["Deployment", "Task", "Role"] - }, - "" - ] - } - ] - } - }, "Metadata": { "Application": { "Ref": "ApplicationName" @@ -292,17 +277,6 @@ "ExecutionRoleArn": { "Ref": "TaskExecutionRole" }, - "TaskRoleArn": { - "Fn::If": [ - "HasTaskRole", - { - "Fn::FindInMap": ["Deployment", "Task", "Role"] - }, - { - "Ref": "AWS::NoValue" - } - ] - }, "RequiresCompatibilities": ["FARGATE"], "NetworkMode": "awsvpc", "Volumes": [], From ea570db40d30284e19e2944d3301d0d2c1ac4a9f Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Mon, 12 Oct 2026 19:41:51 +0100 Subject: [PATCH 8/9] docs(changelog): drop obsolete TaskRoleArn W1030 entry for #417 --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7014609..a6371fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,6 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - Guard against IndexOutOfRangeException when Branches list is empty in GithubStatusNotificationHandler - Fixed CS0433 ambiguous xunit type conflict in test projects caused by FunFair.Test.Common now depending on the AOT-flavoured xunit v3 packages, by switching test projects to the matching xunit.v3.aot.mtp-v2 runner package - Added missing IncludeAssets on NuGet package references required by FunFair.BuildCheck -- Resolved cfn-lint W1030 warning on the TaskRoleArn CloudFormation property by only setting it when a TaskRole is provided - Added missing build;buildTransitive IncludeAssets on Serilog package reference required by FunFair.BuildCheck - CloudFormation task settings now live in Mappings so every deploy applies them, reducing ECS task memory from 4 GB to 1 GB ### Changed From 4038765c588f6f64178f28b3137218ebb1298d13 Mon Sep 17 00:00:00 2001 From: Mark Ridgwell Date: Tue, 13 Oct 2026 11:00:06 +0100 Subject: [PATCH 9/9] chore(coverage): refresh COVERAGE.md for branch head Coverage ratchet bootstrap measurement on ea570db4 for #417; .NET overall unchanged at 99.0%. --- COVERAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COVERAGE.md b/COVERAGE.md index b8b0214e..9e84ec76 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -30,4 +30,4 @@ excluded --- -Captured at commit `c1cd906133caf5d3c594e2af9663e240305213c3` on 2026-10-12. +Captured at commit `ea570db40d30284e19e2944d3301d0d2c1ac4a9f` on 2026-10-13.