fix: pre-flight the CloudFront payload limits the dry run is the only guard for
Build and deploy / build-and-deploy (push) Failing after 4s
Build and deploy / build-and-deploy (push) Failing after 4s
The second `--apply` of 2026-09-04 created adr-sml-pdf-noindex and then failed at create-origin-request-policy: InvalidArgument, "The parameter Comment is too big" — 182 characters against a 128 cap. update-distribution never ran, so the distribution is unchanged, but the account was left holding an orphaned policy. Nothing local could have caught it, and that is now measured rather than assumed: botocore/validate.py checks neither `max` nor `pattern` (range_check reads only `min`; the word `pattern` does not appear in the file), and the 128 is not modelled as a constraint at all — `Comment` is a bare `string` and the cap lives in the shape's documentation prose. So the dry run really is the only pre-flight, and it now enforces PAYLOAD_LIMITS: 13 entries across both policy payloads and the function ARN, each with the source it came from. The entries that matter guard CLONED values rather than literals this file authors — a literal is reviewed when it is written, while a value copied out of the default behaviour's policy changes with no diff here. The API declares TooLongCSPInResponseHeadersPolicy for exactly that case and docs/05 already specifies a CSP that would land there. RemoveHeadersConfig is a recorded gap: its cap is real but unpublished, and inventing a number would be worse. Both comments are now 76 and 74 characters. `--function-arn` is validated before any AWS call, and an unrecognised `--flag` is a usage error — the `=` form was invisible to the parser and to the presence check, for a clean exit 0 with no router attached. A skipped section now exits 3, because docs/09 uses exit 0 as its own success stamp and a partial run read as a complete one. Confirmed by measurement, as asked: the next run REUSES the orphan by name, matches every reconciled field, and stages it — create line gone, 4 changes down to 3, no duplicate and no collision. Reviewed twice. Round 2 found that the §7 record broke the table it lives in, and that two comments asserted behaviour the code did not have. 17 findings across both rounds, all fixed. Nothing was applied to the distribution and nothing was deployed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
bbe535d158
commit
a07193d561
+96
-20
@@ -323,19 +323,26 @@ node infra/cloudfront/configure.mjs --dist "$DIST_ID" --api-domain "$API_DOMAIN"
|
||||
|
||||
⚠️ **THE BLOCK BELOW IS THE `+` CHANGE LINES AND THE TWO RESOLVED-POLICY LINES.
|
||||
IT IS NOT THE WHOLE OUTPUT, AND IT SAID "exactly" UNTIL 2026-09-04.** Against
|
||||
the **live** distribution the dry run is **60 lines**, and all 60 account for:
|
||||
2 resolved-policy lines, **10** `·` lines, **4** `+` lines, a **40-line JSON
|
||||
the **live** distribution the dry run is **59 lines**, and all 59 account for:
|
||||
2 resolved-policy lines, **10** `·` lines, **3** `+` lines, a **40-line JSON
|
||||
dump** of the `*.pdf` behaviour it would add, 2 blank lines, the
|
||||
`N change(s) to distribution …` header and the `DRY RUN — nothing was sent.`
|
||||
footer `[measured 2026-09-04, exit 0, nothing written]`.
|
||||
|
||||
⚠️ **THAT 10/4 SPLIT IS THE LIVE STATE, NOT THE STATE THE BLOCK BELOW
|
||||
DESCRIBES.** Each of the four things Parts 1–3 have already applied prints `·`
|
||||
when it is found and `+` when it is staged, so in the Part 0.3 state those four
|
||||
move across: **6** `·` and **8** `+`, same 60 lines. **The total is not the
|
||||
check — the `+` lines are.** An operator told the output is "exactly" 14 lines
|
||||
either stops on a wall of unexplained text or stops comparing, and the paragraph
|
||||
after the block tells them a different set means *"stop and re-read it"*.
|
||||
🛑 **READ THE EXIT STATUS, AND IT HAS THREE VALUES.** `0` — everything this
|
||||
script manages was applied or is already present. `2` — a usage error, before any
|
||||
AWS call. **`3` — sections that could run did, and something was SKIPPED: read
|
||||
the `⚠ … SKIPPED, not changed` block.** Anything else is a throw. `3` exists
|
||||
because a skip used to exit `0`, and this document uses `exit 0` as its own
|
||||
success stamp throughout — so a partial run read as a complete one. A skip is
|
||||
never nothing: it means the `*.pdf` behaviour has no `X-Robots-Tag`, or `/api/*`
|
||||
is not forwarding `CloudFront-Viewer-Address`, and the skip line says which.
|
||||
|
||||
⚠️ **THE SHAPE CHANGES WITH THE STATE, SO READ THE `+` LINES AND NOT THE
|
||||
TOTAL.** Each item Parts 1–3 have already applied prints `·` when it is found
|
||||
and `+` when it is staged, so in the Part 0.3 state four lines cross from one
|
||||
column to the other and the totals move with them. **The `+` lines are the
|
||||
check.**
|
||||
|
||||
**Expect** — this is the dry run, and on a distribution in the state Part 0.3
|
||||
records the change lines are:
|
||||
@@ -370,15 +377,19 @@ Fewer than eight changes means part of this is already done — read which lines
|
||||
are prefixed `·` — but READ THE WORDS, not the bullet: `configure.mjs` uses `·`
|
||||
for *already present* **and** for *would CREATE / would SET / would ADD*, so the
|
||||
prefix alone does not say whether a line is done or still pending. **On the live distribution as at
|
||||
2026-09-04 the dry run returns exactly four `+` lines — the two for section 4
|
||||
and the two for section 5** — `[measured 2026-09-04, dry run against `E1OK7G98KNKUTA`, ETag
|
||||
2026-09-04, after the second `--apply` attempt, the dry run returns exactly
|
||||
three `+` lines — **one** for section 4 (the behaviour; its policy already
|
||||
exists, see the incident below) and the two for section 5** — `[measured 2026-09-04, dry run against `E1OK7G98KNKUTA`, ETag
|
||||
`E2EUQ1WTGCTBG2`, exit 0, nothing written]`. More than eight, or a different set,
|
||||
means the distribution is not in the state 0.3 recorded: stop and re-read it.
|
||||
|
||||
🛑 **INCIDENT, 2026-09-04 — THE FIRST `--apply` FAILED ON ITS FIRST WRITE, AND
|
||||
NOTHING REACHED THE DISTRIBUTION.** Section 4's clone was sent to
|
||||
`create-response-headers-policy` verbatim and the AWS CLI rejected it
|
||||
**client-side**, before the call left the machine:
|
||||
🛑 **INCIDENT — TWO `--apply` ATTEMPTS FAILED ON 2026-09-04, FOR TWO DIFFERENT
|
||||
REASONS, AND THE SECOND ONE LEFT A POLICY BEHIND.** The distribution is
|
||||
unchanged after both. Read both before the next attempt.
|
||||
|
||||
**ATTEMPT 1 — nothing reached the distribution and nothing was created.**
|
||||
Section 4's clone was sent to `create-response-headers-policy` verbatim and the
|
||||
AWS CLI rejected it **client-side**, before the call left the machine:
|
||||
|
||||
```
|
||||
An error occurred (ParamValidation): Parameter validation failed:
|
||||
@@ -425,8 +436,12 @@ aws cloudfront list-origin-request-policies --type custom --output json \
|
||||
```
|
||||
|
||||
An orphan is harmless and self-healing — the next run finds it by name, matches
|
||||
it and attaches it — **so do not delete it by hand.** Both returned `0` after
|
||||
the 2026-09-04 failure, which is why nothing needed doing that time
|
||||
it and attaches it — **so do not delete it by hand.**
|
||||
|
||||
⚠️ **THE EXPECTED VALUES DIFFER BY WHICH FAILURE YOU ARE RECOVERING FROM.** After
|
||||
**attempt 1** both returned `0` and nothing needed doing. After **attempt 2**
|
||||
they return **`1` and `0`** — the response-headers policy is the orphan recorded
|
||||
below, and `1` is the correct reading, not a second problem
|
||||
`[verified 2026-09-04]`.
|
||||
|
||||
**Two changes came out of it.** The clone now **omits** any empty member at
|
||||
@@ -449,9 +464,70 @@ otherwise be reached:
|
||||
node infra/cloudfront/policy-shapes.test.mjs
|
||||
```
|
||||
|
||||
**Expect** `policy-shapes: 23 of 23 cases pass`, exit 0. Its first case is this
|
||||
incident verbatim — the live `SecurityHeadersConfig`, empty `ContentSecurityPolicy`
|
||||
and all `[verified 2026-09-04; 7 of 7 mutations killed]`.
|
||||
**Expect** `policy-shapes: 53 of 53 cases pass`, exit 0. Its first case is this
|
||||
incident verbatim — the live `SecurityHeadersConfig`, empty
|
||||
`ContentSecurityPolicy` and all. The same suite covers attempt 2's limit checks,
|
||||
so this is the only command in this Part that proves both.
|
||||
|
||||
---
|
||||
|
||||
**ATTEMPT 2 — section 4 SUCCEEDED, section 5 FAILED, and the run left an orphan.**
|
||||
With the clone fixed, `create-response-headers-policy` created
|
||||
**`adr-sml-pdf-noindex` = `51c4e79b-d9c6-4c6f-907c-dbb0e73dd374`**. Section 5
|
||||
then failed:
|
||||
|
||||
```
|
||||
An error occurred (InvalidArgument) when calling the CreateOriginRequestPolicy
|
||||
operation: The parameter Comment is too big
|
||||
```
|
||||
|
||||
Its `Comment` was **182 characters** against a **128** cap. `update-distribution`
|
||||
never ran, so the distribution is untouched — **but the account now holds a
|
||||
response-headers policy that no behaviour references.**
|
||||
|
||||
🛑 **DO NOT DELETE THAT POLICY BY HAND.** This is the orphan case Part 3 and §7
|
||||
predicted before it happened, and the recovery is **measured, not asserted**
|
||||
`[measured 2026-09-04, dry run, exit 0, nothing written]`: the next run finds it
|
||||
**by name**, matches it on every reconciled field, and stages it for attachment —
|
||||
|
||||
```
|
||||
· response-headers policy adr-sml-pdf-noindex exists and matches the default behaviour
|
||||
+ CacheBehaviors += *.pdf -> …, adr-sml-pdf-noindex (51c4e79b-d9c6-4c6f-907c-dbb0e73dd374)
|
||||
```
|
||||
|
||||
— with the **create line gone** and the change count down from 4 to 3. **No
|
||||
duplicate and no name collision.** (A collision would not be silent either: a
|
||||
duplicate name returns `ResponseHeadersPolicyAlreadyExists`, which is a
|
||||
different error from the `InvalidArgument` above. The script never reaches it,
|
||||
because it looks the policy up by name first.)
|
||||
|
||||
⚠️ **`Comment` IS NOT RECONCILED**, so `51c4e79b` keeps its original
|
||||
118-character text while the script now carries a shorter one. Deliberate:
|
||||
adding `Comment` to the drift check would throw on this very policy and block
|
||||
the run that attaches it.
|
||||
|
||||
**WHY NOTHING CAUGHT IT LOCALLY, AND THIS IS THE GENERAL LESSON.** Measured
|
||||
2026-09-04: **`botocore/validate.py` checks neither `max` nor `pattern`** — its
|
||||
`range_check()` reads only `min`, and the word `pattern` does not appear in the
|
||||
file. And the 128 cap is not modelled as a constraint at all: on both policy
|
||||
configs `Comment` is a bare `string`, and the number lives in the shape's
|
||||
**`documentation` prose**. So **no client-side validation of any kind stood
|
||||
between that 182-character string and the CloudFront API**, which is exactly why
|
||||
the pre-flight below had to be built rather than relied upon.
|
||||
|
||||
**BOTH POLICY COMMENTS ARE NOW UNDER 80 CHARACTERS**, and the dry run enforces
|
||||
the limits it knows about — `infra/cloudfront/policy-shapes.mjs`,
|
||||
`PAYLOAD_LIMITS`, one entry per limit with the source it came from. **Every
|
||||
entry with a cited AWS source is enforced by the service and by nothing local**
|
||||
— see below. Two entries are stamped `[assumed]` and are not: no AWS source
|
||||
states a policy **name** length, and those two constrain nothing this script
|
||||
sends (our names are 19 and 26 characters). A breach **skips its section** rather than throwing, and — the part that
|
||||
matters — **skips the whole section**, so a policy that is not created is never
|
||||
staged for attachment. The suite named earlier in this Part
|
||||
(`node infra/cloudfront/policy-shapes.test.mjs`) covers both the 182-character
|
||||
`Comment` that failed here and the 118-character one that did not.
|
||||
|
||||
---
|
||||
|
||||
**One of the `·` lines carries a number worth reading**, and it is not a
|
||||
warning:
|
||||
|
||||
Reference in New Issue
Block a user