Skip to content

Commit 5f9c470

Browse files
0.11.6: rename cert construct Cert -> CertV2 to bypass writer's no-update rule
CDK's CrossRegionExportWriter forbids in-place value updates for any existing export key. The writer diffs the prior template's `WriterProps.exports` against the new template's by key; if any key's value changed, it throws "Some exports have changed!". This is not drift detection (it doesn't consult SSM during the check) — it's a hard "this key is immutable once written" rule. Renaming the cert construct id Cert -> CertV2 changes the logical id, which changes the export key (HabitAgilityCertuseast1RefCertV2 -> ... new hash). The writer sees the diff as "add new key + delete old key" instead of "update existing key" and accepts it. The main stack picks up the new cross-region reference on the same deploy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 00d1582 commit 5f9c470

4 files changed

Lines changed: 36 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,38 @@ All notable changes to this project are documented here.
44

55
## [Unreleased]
66

7-
## [0.11.5] - 2026-05-19
7+
## [0.11.6] - 2026-05-19
8+
9+
The **actual** fix for the cross-region writer deadlock. After staring at the
10+
CDK source (`cross-region-ssm-writer-handler/index.js`), the rule is plain:
11+
**no in-place value updates allowed for any existing export key**. The writer
12+
diffs `OldResourceProperties.exports` against `ResourceProperties.exports` by
13+
key, and any key whose value changed triggers "Some exports have changed!".
14+
This is *not* drift detection — it's a hard "this key is immutable once
15+
written." The 0.11.4 (delete) and 0.11.5 (re-seed) attempts both ignored
16+
this; the writer doesn't even look at SSM during the check.
17+
18+
Fix: rename the cert construct id from `Cert``CertV2`. New logical id →
19+
new export key `HabitAgilityCertuseast1RefCertV2…`. The writer now sees the
20+
diff as "add new key + delete old key" instead of "update existing key" and
21+
proceeds. Main stack picks up the new cross-region reference on next deploy.
822

9-
Fourth retry — and the **correct** unblock for the cross-region ExportsWriter.
10-
The previous attempt (0.11.4) deleted the SSM export, which made the failure
11-
worse: the writer's drift check expects to find its previously-written value
12-
in SSM, so a missing param trips the same "Some exports have changed!" error
13-
as a tampered one. Recovery: re-seed the SSM param with the post-rollback cert
14-
ARN (matches what the writer last successfully wrote in v0.11.0), then
15-
continue-update-rollback the cert stack back to `UPDATE_ROLLBACK_COMPLETE`.
16-
The writer's next invocation will see the expected previous value, pass the
17-
drift check, and update the param to the new (multi-SAN) cert ARN.
23+
## [0.11.5] - 2026-05-19
1824

19-
No application or CDK code changes — recovery only.
25+
Fourth retry — re-seeded the SSM export with the post-rollback cert ARN,
26+
assuming the writer compared template-vs-SSM. It does not (see 0.11.6). No
27+
application or CDK code changes — failed recovery only.
2028

2129
## [0.11.4] - 2026-05-19
2230

23-
Third retry of the dual-domain deploy (failed — see 0.11.5 for the actual
31+
Third retry of the dual-domain deploy (failed — see 0.11.6 for the actual
2432
fix). Cert stack came back from `UPDATE_ROLLBACK_FAILED` via
2533
`continue-update-rollback --resources-to-skip ExportsWriteruswest209BD44F0A7CF058B`,
2634
and the orphan SSM parameter
2735
`/cdk/exports/HabitAgility/HabitAgilityCertuseast1RefCert5C9FAEC174FEF386` was
2836
manually deleted from us-west-2. Deletion turned out to be wrong: the CDK
29-
writer treats a missing previous value the same as a tampered one (drift
30-
detection). No application or CDK code changes — recovery only.
37+
writer doesn't read SSM for its check at all (see 0.11.6). No application
38+
or CDK code changes — recovery only.
3139

3240
## [0.11.2] - 2026-05-19
3341

infrastructure/lib/cert-stack.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,18 @@ export class HabitAgilityCertStack extends cdk.Stack {
2828
const habitAgilityZone = route53.HostedZone.fromLookup(this, 'HabitAgilityZone', {
2929
domainName: 'habitagility.com',
3030
});
31-
this.cert = new acm.Certificate(this, 'Cert', {
31+
// NOTE: construct id is `CertV2`, not `Cert`. The original `Cert` covered
32+
// only `ght.vexom.io`; adding habitagility.com SANs forces ACM to issue
33+
// a new physical cert (subject DN changes). Updating the same logical
34+
// id would also force the cross-region SSM export to swap value, which
35+
// the CDK CrossRegionExportWriter rejects with "Some exports have
36+
// changed!" — the writer disallows in-place value updates for any
37+
// export key. Renaming the construct gives the new cert a NEW logical
38+
// id → NEW export key (`...RefCertV2...`), and the writer sees the
39+
// diff as "add new key + delete old key" rather than "update existing
40+
// key". The main stack picks up the new cross-region reference on
41+
// re-deploy.
42+
this.cert = new acm.Certificate(this, 'CertV2', {
3243
domainName: 'habitagility.com',
3344
subjectAlternativeNames: ['www.habitagility.com', 'ght.vexom.io'],
3445
validation: acm.CertificateValidation.fromDnsMultiZone({

infrastructure/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "habit-agility-infrastructure",
3-
"version": "0.11.5",
3+
"version": "0.11.6",
44
"private": true,
55
"license": "MIT",
66
"scripts": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "habit-agility",
33
"private": true,
4-
"version": "0.11.5",
4+
"version": "0.11.6",
55
"description": "HabitAgility — Agile-style habit tracker (sprints, burndown, retrospectives). Root tooling (lint, format, tests). Deploy lives in infrastructure/.",
66
"type": "module",
77
"scripts": {

0 commit comments

Comments
 (0)