fix(bootstrap): bring the template under CloudFormation's inline size limit (#864) - #867
Conversation
… limit Fixes aws-samples#864. The generated least-privilege bootstrap template was 54,213 bytes, past the 51,200-byte ceiling CloudFormation applies to an inline `TemplateBody`. Above that ceiling the CDK CLI has to stage the template in S3 — which it cannot do while bootstrapping a fresh account, because the bucket it would stage into is one of the resources bootstrap creates. So a first-time `mise //cdk:bootstrap` failed outright: BootstrapStackRequired: Trying to perform an operation that requires a bootstrap stack; you should not see this error, this is a bug in the CDK CLI. `--force` does not help: the same size branch runs even when CDKToolkit already exists, so the custom template could never be applied through `cdk bootstrap` at all. Working around it meant bootstrapping with the default template and then applying this one by hand via `update-stack` from S3. - Emit the template with js-yaml `flowLevel: 6`, so IAM statements render one per line rather than expanding every key and action string onto its own line. 54,213 -> 39,896 bytes: under the ceiling with 11,304 bytes spare. This is a serialisation change only; the parsed template is unchanged, asserted by a round-trip test and by the existing golden-baseline and artifact-sync suites passing untouched. - Fail generation when the rendered template exceeds a 48,000-byte budget, naming the limit and what to do about it. The budget sits below the hard ceiling deliberately: a policy addition should fail in the generator, where the bytes are produced, instead of surfacing later as an opaque CDK CLI error against a real account. - Tests: assert the committed artifact is under both the ceiling and the budget, and that the compact form parses identically to a fully expanded dump. Co-Authored-By: Claude <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #867 +/- ##
=======================================
Coverage ? 92.52%
=======================================
Files ? 336
Lines ? 97401
Branches ? 10776
=======================================
Hits ? 90116
Misses ? 7285
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@scottschreckengaust @krokoko — review request. (I don't have write access on this repo, so I can't populate the Reviewers field; flagging by mention instead.) Also cc @isadeks and @ClintEastman02 as the most recent contributors to Why this needs eyes despite being small: the diff to
The claim to check hardest is that this is serialisation-only. Three independent signals: an explicit round-trip test, and the pre-existing Judgement call worth confirming: I set the budget at 48,000 against the hard 51,200 ceiling. That reserves ~3.2 KB of headroom, which is roughly what #865's fix will consume. If you'd rather the budget sit elsewhere, it's a one-line change. |
ayushtr-aws
left a comment
There was a problem hiding this comment.
Verdict: Request changes
The change is well-motivated and the write-up is excellent, but the PR does not fix #864. cdk bootstrap --template <file> never sends the on-disk bytes. The CLI parses the file (loadStructuredFile), writes it into a temp cloud assembly as JSON, and makeBodyParameter then re-serialises the parsed object with its own toYAML (yaml@1 stringify(obj, { schema: 'yaml-1.1' }), fold lineWidth = 0) before comparing .length to 50 * 1024. Formatting choices in js-yaml are discarded at the first step.
Measured with the CLI's exact serialiser (aws-cdk 2.1129.0 bundled in this repo) and independently confirmed via npx cdk bootstrap --show-template --template <file> | wc -c, which shares the same toYAML path:
| template | on-disk bytes | bytes the CLI actually sends |
|---|---|---|
main |
54,213 | 53,369 |
| this PR | 39,896 | 53,369 |
Both are 2,169 bytes over 51,200. A fresh-account mise //cdk:bootstrap on this branch will hit the identical BootstrapStackRequired error while the generator prints 8104 under budget. The PR's Testing section lists only unit tests; a real cdk bootstrap run against an account (fresh, or --force on an existing CDKToolkit) would have surfaced this and should be part of the evidence for any re-roll.
Everything else in the PR is sound: the parsed template is deep-equal to main under both js-yaml and the CDK CLI's yaml@1 core-schema parser (20 Resources, 13 Parameters, 13 Conditions, 8 Outputs; no IAM Action/Resource/Condition/Effect changed), the generator reproduces the committed artifact byte-for-byte, and BOOTSTRAP_HASH/BOOTSTRAP_VERSION are correctly unchanged.
Vision alignment
The intent (bounded, reproducible, fail-fast bootstrap per ADR-002) fits. The problem is purely that the fix and its guard measure a quantity CloudFormation never sees.
Blocking
- Fix does not reach the wire (
cdk/scripts/generate-bootstrap-template.ts:256).flowLevel: 6only changes the committed file. The parsed content has to shrink. What I measured through the CLI serialiser on this branch's template:- strip all
Sids: 52,047 (still over) - strip
Sids + managed-policyDescriptions: 51,328 (still over) - merge statements sharing identical Effect/Resource/Condition (38 → 30 statements): 52,420 (still over)
- emit each
PolicyDocumentas a minified JSON string (CloudFormation accepts a JSON string forJson-typed properties such asAWS::IAM::ManagedPolicy.PolicyDocument): 45,378, under the limit with ~5.8 KB headroom. The CLI serialiser emits a string scalar on one line, so this reduction survives re-serialisation. This is the one lever that clears the ceiling without trading away permissions or Sids, but it needs a livecdk bootstrapto confirm CFN accepts it in this position. - Anything that lands in
BootstrapStack.update→deployStackgets re-serialised, so whichever approach you pick, verify withnpx cdk bootstrap --show-template --template bootstrap/bootstrap-template.yaml | wc -c.
- strip all
- The size guard and both size tests measure the wrong thing (
generate-bootstrap-template.ts:281,bootstrap-template.test.ts:280,284). They gatereadFileSync(...).lengthof the flow-style file. They will stay green while the CLI-side body grows past 51,200. Measure the CLI's serialisation instead: theyamlpackage is already hoisted innode_modules;yaml.stringify(template, { schema: 'yaml-1.1' })withstrOptions.fold.lineWidth = 0reproducestoYAMLexactly, and that assertion fails on this branch by 2,169 bytes, which is the number that must be cut. - The round-trip test is tautological (
bootstrap-template.test.ts:298).templateisyaml.loadof the compact file itself (line 29) andexpandedisyaml.dump(template), soload(dump(x)) == xholds for any object regardless of how the file was rendered. It cannot detect a flow-context mis-emission. Compare the committed file against an independently built object instead (e.g. refactor the generator into an importablebuildTemplate()and assertyaml.load(file)equals it; that also gives the repo the artifact-drift check it currently lacks for the template). - Comments and messages assert things that are not true (
generate-bootstrap-template.ts:248,279; error textis N bytes ... cannot bootstrap a fresh account;console.log ... under budget). A maintainer hitting the guard would trim on-disk bytes to no effect. Rewrite once the guard measures the real quantity.
Non-blocking
rendered.lengthcounts UTF-16 code units, not bytes (:281,:283; test:280,:284). ASCII today, so exact by accident. UseBuffer.byteLength(rendered, 'utf8')ascdk/test/main.test.tsalready does for the main stack.CFN_INLINE_TEMPLATE_LIMIT/TEMPLATE_SIZE_BUDGETand the dump options are duplicated between generator and test (test:277). Export fromcdk/src/bootstrap/version.ts, which both already import.- Since the reflow buys nothing on the wire, consider reverting it:
flowLevel: 6yields 1,605-char lines (lineWidthonly folds scalars) and degrades diff review of future policy changes, which is the reviewability tenet this repo cares about. - The 48,000 budget question you raised is moot until the metric is right; once it is, a ~2 KB margin below 51,200 seems reasonable given #865 will add statements.
Governance
- #864 is not labelled
approvedand carries no priority label (ADR-003). Please have a maintainer label it before re-rolling. Branch namefix/864-bootstrap-inline-limitis correct. - Cross-repository PR (fork); noted, not a concern.
Documentation
No docs changes were needed for the current diff. If the fix moves to JSON-string policy documents or otherwise changes artifact shape, docs/design/DEPLOYMENT_ROLES.md (which documents the six-policy split and the 6,144-char IAM limit) should gain a sentence on the 51,200-byte inline ceiling and how the generator enforces it, plus mise //docs:sync.
Tests & CI
CI green (build, secrets/deps scan, dead-code, title). npx jest test/bootstrap/ 122/122 pass in a clean worktree; generator is idempotent against the committed artifact. Bootstrap synth-coverage: not applicable (no construct changes). Coverage is fine on the changed lines but, per the above, the new tests do not exercise the failure they describe.
Review agents run
/security-review(redirected at the PR diff): no findings; deep-equality and flow-context parse checks above come from it./code-reviewat high effort: confirmed the size finding by exact CLI reproduction and the tautological test; refuted a stale-artifact concern (theBootstrapPolicyHashoutput test would catch it).- Silent-failure / comment-accuracy / test-coverage review (general-purpose agent standing in for the pr-review-toolkit agents, which are not installed here): findings folded in above.
- Omitted:
type-design-analyzer(no new types).
Human heuristics
- Proportionality: pass. Two constants, one option, one guard.
- Coherence: concern. The repo's existing size check (
cdk/test/main.test.ts) measures bytes of the artifact CFN receives; this one measures a formatting artifact the CLI discards. - Clarity: concern. Error and log messages give false assurance (
generate-bootstrap-template.ts:279-298). - Appropriateness: concern (AI001/AI005). Integration behaviour of
cdk bootstrapwas inferred rather than verified against the real CLI, and the tests assert what the code does, not what it should do.
Happy to re-review quickly once the metric is switched to the CLI serialisation and the content reduction is verified with a real bootstrap run.
…he CLI's size Refs aws-samples#864. Addresses review on aws-samples#867. The first attempt measured and reduced the wrong quantity. `cdk bootstrap --template` never sends the bytes on disk: it parses the file, discards the formatting, and re-serialises the parsed object before the inline check. // aws-cdk/lib/index.js — makeBodyParameter() const templateJson = toYAML(overrideTemplate ?? stack.template); if (templateJson.length <= LARGE_TEMPLATE_SIZE_KB * 1024) ... So `flowLevel: 6` shrank the artifact 54,213 -> 39,896 while the body CloudFormation receives stayed at 53,369 — byte-identical to main, still 2,169 over the ceiling. A real bootstrap on the previous commit fails exactly as it does on main, while the guard prints "8104 under budget". Confirmed by running it, which is the step missing the first time. - Revert the reflow. It bought nothing on the wire and cost reviewability: max line length went 142 -> 1,605 chars, 44 lines over 200. - Emit each ABCA `PolicyDocument` as a minified JSON string. `PolicyDocument` is `Json`-typed so both shapes are valid, but a string scalar survives the CLI's re-serialisation on one line where a mapping re-expands. Body: 45,743, with 5,457 of headroom. `CdkBoostrapPermissionsBoundaryPolicy` (from the upstream template) is left a mapping — only our six are converted. - Gate on the CLI's own number, via `cdk bootstrap --show-template` against the written artifact. Deliberately not a local copy of the CLI's serialiser: that would mean a direct dependency on `yaml`, which this repo carries only as a transitive `resolutions` pin, and would drift the day the CLI changes writer or options. Needs no credentials and no network. - Keep `String.length`, matching the CLI's `templateJson.length` comparison. UTF-8 byte length would measure a different quantity than the gate. - Replace the round-trip test. Comparing `load(dump(x))` to `x` is true for any object and could not detect the artifact drifting from its generator; export `buildTemplate()` / `renderTemplate()` and compare against an independently built template instead, which also gives the repo the template drift check it lacked. - Add a test that reformatting the artifact cannot move the gated size, so a future budget failure is not "fixed" by reflowing the file — which is what happened here. - Update existing policy assertions to parse the JSON string; document the second, independent size ceiling in DEPLOYMENT_ROLES.md (the 6,144-char limit is per policy, this one is per template) and re-sync the Starlight mirror. Verified end-to-end: `cdk bootstrap --template ... --force` against a live account now succeeds where it previously failed with BootstrapStackRequired, and the four deployed managed policies have statement sets identical to the generated sources — the shape change carries no permission change. A throwaway stack also confirmed CFN accepts a JSON-string PolicyDocument and IAM stores it parsed, not as a literal string. Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks — the blocking finding is correct, and I verified it rather than taking it on trust. Pushed as 199b09b (a follow-up commit, not a force-push, so the delta is reviewable). The core finding: confirmed, and I reproduced the failureYou're right that And the decisive one, which is the step I skipped the first time: a real bootstrap on 4fa1c19 against a live account still failed, at What changed1 — content, not formatting. Reverted Worth noting the small delta from your 45,378: that figure converts all seven managed policies in the template. Converting only ours gives 45,743. Your caveat about needing a live run to confirm CFN accepts it in this position — done, two ways. A throwaway stack with a JSON-string 2 — the guard measures the CLI's number now. One deviation from your suggestion, deliberately: rather than mirroring Reason: A detail your snippet would also have hit: serialising the in-memory template under-reports by ~70 chars versus the committed file, because the js-yaml dump/re-parse normalises some scalars. The guard measures the written artifact. 3 — 4 — tautological test: agreed, replaced. You're right that 5 — false comments and messages: rewritten. The error now names the CLI's re-serialisation, says explicitly that reformatting won't help, and prints the Non-blocking, taken: constants live in the new Documentation: Also updated seven existing assertions that read Verification
GovernanceAcknowledged — #864 is unlabelled and unprioritised. I can't apply labels (no triage permission on this repo), so it needs a maintainer. Flagging the ordering honestly: the PR predates the |
…ance check Refs aws-samples#864. The formatting-invariance test failed in CI by exactly one character (45,834 vs 45,833) while passing locally. Not flakiness — a real property of YAML. The test rendered one template with `lineWidth: 120` and one with `lineWidth: -1`. Folding is not byte-neutral when it wraps a long *quoted scalar*, and each PolicyDocument is now a multi-kilobyte JSON string, so the folded rendering can re-parse a character off. That is a property of YAML folding, not of the size gate the test is about, and conflating the two produced the failure. Both renderings now disable folding and differ only in `flowLevel`, which still makes the point more sharply than before: ~38 KB versus ~46 KB on disk, identical 45,744-char body from the CLI. The committed artifact is unaffected — its integrity is covered by the deep-equality check against `buildTemplate()`, and all six policy JSON strings parse cleanly. Had folding ever corrupted the real artifact, that deep-equality test is what would fail. Co-Authored-By: Claude <noreply@anthropic.com>
… not byte equality Refs aws-samples#864. The formatting-invariance assertion compared two serialisations of the same object for exact byte equality. That is stronger than the claim needs and is not guaranteed: YAML quoting and folding decisions interact with the multi-kilobyte JSON-string policy documents, and the assertion passed locally while differing by one character on CI — in both the folded and unfolded variants, so folding was not the cause. The absolute body size also shifts slightly between environments, which I could not reproduce locally. One character is irrelevant to what the test is for. The claim worth locking in is that reflowing the artifact cannot buy meaningful headroom: aws-samples#864's first attempted fix shrank the file by 14 KB while the body CloudFormation received did not move at all. A reflow that genuinely helped would have to save thousands of characters. So: assert the two renderings differ by >1,000 characters on disk while their CLI bodies agree within 64, and name in the test why exact equality is not asserted. Co-Authored-By: Claude <noreply@anthropic.com>
|
CI update after the follow-up commits:
On the dependency-scan failure. Not from this branch — it changes six files, none of them a lockfile or Confirmed by running On the two extra commits after the review response. The formatting-invariance test asserted that two renderings of the same template produce a byte-identical body from the CLI. It passed locally and failed on CI by exactly one character (45,834 vs 45,833). My first fix assumed YAML line folding, since each Rather than keep guessing at a serialiser detail, the assertion now matches the claim it exists to defend: reflowing the artifact cannot buy meaningful headroom. The two renderings differ by >1,000 characters on disk while their CLI bodies agree within 64. #864's first attempt shrank the file 14 KB with zero movement in the body, so a reflow that genuinely helped would have to save thousands — one character is noise. The test says so explicitly, so the tolerance doesn't read as a fudge. The committed artifact's own integrity is not asserted by tolerance: it's covered exactly by the deep-equality check against |
|
Follow-up on the All six advisories were published today, mid-run
All six carry Against this PR's own run history:
Remediation status — partial, and nothing covers svgo#860 (dependabot) fixes four of the six —
I've flagged the gap on #860 with the one-line completion — a root Practical note for reviewingEvery open PR in the repo is effectively red on this check right now, including any whose badge still shows green from a pre-21:20 run — #868 displays The other three checks on this PR are green, including |
…es#870) * fix(deps): clear 6 osv advisories in astro, sharp, svgo, js-yaml osv-scanner fails on main's yarn.lock, which ejects every PR from the merge queue: the merge_group re-scan runs against the latest main and fails whatever is queued. Observed on aws-samples#867. These advisories were all published after 2026-09-07 — osv-scanner reported "No issues found" in the scheduled run recorded on aws-samples#861 — so PR-level scans that ran before then are green while the re-scan is red. - astro 7.1.3 -> 7.2.8 (docs/package.json, an exact pin). GHSA-26w7-cxv4-gfx2 (9.8), GHSA-376h-93r7-7g6f (6.3). - js-yaml 4.3.1 -> 4.3.2 (GHSA-2883-xcg3-v3hh, 7.5). Root `resolutions` already reads ^4.3.1, so the caret admits the patch and no manifest changes. - sharp 0.35.3 -> 0.35.4 (GHSA-rgj7-g3m4-5g8c, 8.9) and svgo 4.0.2 -> 4.1.0 (GHSA-4vpr-x523-8j87, GHSA-w27v-7q3p-w38r). Transitive-only under caret ranges, so lockfile-only. Re-resolved surgically rather than via `mise run upgrade`, which does `rm -f yarn.lock && yarn install` and moved 328 packages here — including aws-cdk-lib, constructs, jest, eslint and major jumps in glob, commander, chalk and yargs. Burying six CVE fixes in a wholesale upgrade makes the diff unreviewable and any regression unbisectable, so only the four advisory entries (plus the @img/sharp-* platform binaries) were dropped from the lockfile before `yarn install`. The resulting delta is 50 changed / 1 added / 1 removed, all inside astro's own closure and the four targets; no cdk or cli dependency moves. MAL-2026-10726 — the malicious-release advisory that made aws-samples#637 pin astro 7.1.3 rather than 7.1.0 — was withdrawn on 2026-07-17 and no longer constrains the choice. No active malicious advisory affects any of the four packages. Verified: `mise run security:deps` (exact CI command) reports no issues; `mise run security:retire` clean; `mise run drift-prevention` exit 0; `mise //docs:build` builds 77 pages and `//docs:check` reports 0 errors under astro 7.2.8; `mise run build` clean with cdk 211 suites / 4471 tests and cli 60 suites / 903 tests passing. The bootstrap golden-baseline snapshot passes unchanged, confirming js-yaml 4.3.2 does not alter template serialisation (relevant to aws-samples#867, which tunes that dump's flowLevel). * fix(deps): clear GHSA-7w5x-hrqm-74c2 in smol-toml A seventh advisory landed while this PR was in flight, and it is the same race the PR describes happening to the PR itself: GHSA-7w5x-hrqm-74c2 (8.2, denial of service via malformed TOML) was published 2026-09-09T18:07:11Z and the PR's first CI run started at 18:27:21Z, so the local scan twenty minutes earlier could not have seen it. smol-toml 1.7.0 is present on main unchanged, so this is not fallout from the astro bump. Requesters are @astrojs/internal-helpers, astro and knip, all under ^1.6.0 / ^1.6.1, so the caret admits the fix and this is lockfile-only: 1.7.0 -> 1.8.0, the highest in range and past the 1.7.1 first-patched version. Verified: `mise run security:deps` reports no issues; `mise run build` exits 0 with cdk 211 suites / 4471 tests and cli 60 suites / 903 tests; `//docs:check` 0 errors and `//docs:build` 77 pages; security:retire and drift-prevention exit 0. Delta against the previous commit is one package and three lines. Unrelated and pre-existing: `mise run check:deadcode-ratchet` reports 111 findings against an 85 baseline. Confirmed by restoring main's docs/package.json and yarn.lock and re-running, which reports the identical 111 — so it is not caused by this branch. CI's dead-code job is advisory and passes.
ayushtr-aws
left a comment
There was a problem hiding this comment.
Verdict
Approve with required edits. The fix is correct and verified: emitting each ABCA policy document as a minified JSON string brings the body the CDK CLI sends from 53,369 to 45,743 characters with zero permission drift. Three things need to land before merge, none of them in the policy content itself.
Vision alignment
Fits. This restores the single-command least-privilege bootstrap that ADR-002 promises and Quick Start documents. It narrows blast radius rather than widening it. No tenet is traded, so no ADR is needed.
Required before merge
-
Governance. Issue #864 has no
approvedlabel, no assignee, and no priority label. Per ADR-003 the label must be applied before this merges. -
Rewrite the PR description. It describes the first commit on the branch (js-yaml
flowLevel: 6, "54,213 to 39,896 bytes", a 48,000-byte budget, "parsed template unchanged"). None of that matches the shipped code. The 39,896 figure was an on-disk size for a template whose CLI body was still 53,369 and over the limit. The description is the record of what was verified and it currently misleads a reader. Describe the JSON-string approach, the 49,152 budget, and CLI-measured sizes. -
The size tolerance hides a measurement artifact, not YAML noise. Under
CI=true(set in.github/workflows/build.yml:56) the CDK CLI writes "Using bootstrapping template from " to stdout, socloudFormationBodySize()incdk/src/bootstrap/template-size.ts:81counts it. Reproduced:Invocation stdout chars local, no CI45,744 CI=true, repo path45,812 CI=true,/tmp/x.yaml45,790 The two temp filenames in the reformatting test differ by one character, which is the "one character on CI" that motivated
FORMATTING_DRIFT_TOLERANCE = 64atbootstrap-template.test.ts:308. The over-count is in the safe direction and cannot let a bad template through, so this is not a correctness bug. But the tolerance should not exist. Pass--no-ci(verified to suppress the line) and set the tolerance to 0.
Suggestions
- Validate the measured body. An empty YAML file yields stdout
nulland a size of 5, which passes the guard. Parse stdout withyaml.loadand requireResourcesandParametersto be objects so a non-template measurement fails loudly. - Surface CLI failures.
template-size.ts:92ignores stderr, so a non-zero exit reports only "Command failed". Pipe stderr and attach it to the thrown error. - Write-then-measure at
generate-bootstrap-template.ts:309leaves an over-budget artifact on disk after a failed run. Write to a sibling.tmppath, measure, rename on success. - The guard path is untested. Extract the budget comparison into a pure function and test at budget, budget plus one, and past the hard limit.
- Fixed temp filenames at
bootstrap-template.test.ts:336collide across worktrees and parallel runs. UsemkdtempSync. - Add
--no-noticesand an explicit 30 s Jest timeout on the CLI-invoking tests for slower or egress-restricted runners. - Wrong number at
generate-bootstrap-template.ts:179: 45,378 should be 45,743, matching the docs. - New maintenance coupling worth stating in the PR. The byte-identical render test compares the committed YAML against the default template inside the installed
aws-cdkpackage. No prior test did this. Everyaws-cdkbump that touches the upstream template will now fail this test until the generator is re-run and the output committed. That is likely the right behaviour, but it turns some dependency bumps into PRs needing a manual regeneration step and should be a conscious decision. - Consider a patch bump of
BOOTSTRAP_VERSION. The hash covers policy content, which is unchanged, so 1.6.0 is defensible. But an operator inspecting a deployed CDKToolkit cannot otherwise tell which template shape they have. - Pre-existing:
cdk/eslint.config.mjs:49does not lintscripts/, so theeslint-disabledirectives in the generator are inert. Worth a follow-up issue now that tests import that file.
Documentation
docs/design/DEPLOYMENT_ROLES.md gained an accurate callout on the inline limit and the string form. I ran mise //docs:sync on the branch and the tree stayed clean, so the Starlight mirror is in sync. No ADR needed. No new env vars or commands.
Tests and CI
All four CI checks pass. Locally on the branch, all 125 tests across the six bootstrap suites pass and tsc is clean. I did not run the full mise run build myself; a subagent reported the full suite meets coverage thresholds. The "Artifact matches the generator" block is genuinely new coverage. Bootstrap synth-coverage is not applicable, since no CloudFormation resource types or policy content changed.
What this review did not verify
The single most important claim in the PR, that CloudFormation accepts a JSON string for AWS::IAM::ManagedPolicy.PolicyDocument and IAM stores it parsed, rests on the service spec (type is a union of JSON and string) and the author's statement that it was deployed end-to-end. I did not deploy it. Please confirm that deployment actually happened against a real account before this becomes the template every new user bootstraps with. I also did not test how CloudFormation diffs the mapping-to-string change on an existing CDKToolkit stack.
Review agents run
/security-review: no high-confidence findings. Deep-compared all six parsed policy documents between main and the PR, on disk and as the CLI re-serialises them. Identical./code-review 867 high: its finder stage surfaced the CI stdout inflation, which I reproduced independently. Its final verified list did not complete before this write-up.comment-analyzer,pr-test-analyzer,silent-failure-hunterscopes: covered by two general-purpose agents given those briefs. Their claims about CLI telemetry and notices behaviour are theirs, not independently checked by me.type-design-analyzer: omitted, the PR adds no new types beyond two numeric constants.
Human heuristics
- Proportionality: concern. Three CLI invocations per test run to assert a property of that CLI is heavier than needed. One measurement plus a shape check suffices.
- Coherence: pass. Terminology matches the existing bootstrap tree and the docs callout sits beside the existing IAM 6,144-character note.
- Clarity: concern at
template-size.ts:92andbootstrap-template.test.ts:308. Swallowed stderr and a tolerance tuned to make a symptom disappear both hide signal. - Appropriateness: partial. Verifying against the real CLI was the right instinct. The CLI's CI-mode behaviour was not checked, and the tolerance was the result.
Refs aws-samples#864. Addresses review round 2 on aws-samples#867. The tolerance was hiding a measurement bug, not YAML noise. In CI mode the CDK CLI routes progress to *stdout* rather than stderr, so `cloudFormationBodySize` counted "Using bootstrapping template from <path>" as part of the template body — and the inflation tracked the path's length, which is why two temp filenames differed by exactly one character and why I could not reproduce it locally. Reproduced and fixed: no CI, repo path 45,744 CI=true, repo path 45,812 (+68 = the console line) CI=true, long tmp path 45,790 (varies with path length) with --no-ci 45,744 `.github/workflows/build.yml` sets `CI: true`, so every CI measurement was inflated. Passing `--no-ci` makes it 45,743 in all three cases, and `FORMATTING_DRIFT_TOLERANCE` is gone rather than tuned. Also `--no-notices`, since notices are another stdout writer and they hit the network. Other review points: - Validate the measured body. An empty file renders as `null`, measures 5 chars, and passed the budget — a guard that is happiest having measured nothing. Now parses the output and requires `Resources` and `Parameters` to be objects, so a non-template measurement fails loudly. - Surface CLI stderr. A non-zero exit reported only "Command failed" with the CLI's own explanation discarded; stderr is captured and attached to the thrown error. - Write-then-measure left an over-budget artifact on disk after a failed run, so the next reader — including `cdk bootstrap` — would use a template the generator had rejected. Now writes a sibling `.tmp`, measures, and renames only on success. - The budget comparison had no test, because the only way to exercise it was to make the real template too big. Extracted as the pure `checkTemplateBudget`, tested at budget, budget+1, past the hard ceiling, and at 53,369 — the body that aws-samples#864 actually shipped. - Three CLI invocations per run down to one. Formatting invariance is now asserted on *parsed* equality, which is exact and free: the CLI derives its size from the parsed object, so identical parses cannot differ on the wire. That also removes the temp files the tolerance needed, so there are no fixed filenames left to collide across worktrees. - Explicit 30 s Jest timeout on the CLI-invoking hook, well past the 5 s default, so a slow or egress-restricted runner reports a timeout instead of a mystery. - Corrected 45,378 to 45,743 in the generator comment, matching the docs and the committed artifact. Co-Authored-By: Claude <noreply@anthropic.com>
…ivibui/sample-autonomous-cloud-coding-agents into fix/864-bootstrap-inline-limit
|
Thanks — and the CI-mode finding is the good one. You explained a result I'd papered over rather than understood, so I want to be explicit about that below. Pushed as 9ce2492, with your and @ClintEastman02's Required 3 — you found the actual bug, and the tolerance is goneI could not reproduce that one-character difference locally and settled for a tolerance, which was the wrong call. Your diagnosis is exact — in CI mode the CLI writes progress to stdout, so That last column is the whole mystery: the two temp filenames differed in length, not the YAML. Now passes Required 2 — description rewrittenYou're right that it documented the reverted approach, and that the body is the record of what was verified. Rewritten around the JSON-string change, the 49,152 budget, and CLI-measured sizes throughout. It now also states plainly that the reflow approach was tried and reverted, with the 39,896-on-disk / 53,369-on-the-wire numbers as the reason — that failure is the most useful thing in the PR for anyone who later reaches for reformatting. Also folded in the maintenance coupling you flagged, as its own callout rather than a footnote: every On the thing you did not verify — it was deployedFair to ask, and specifics rather than a claim:
Your caveat about the mapping-to-string diff on a pre-existing Suggestions — all taken except one
Verification
Required 1 — governanceStill needs someone with triage permission; I don't have it. @isadeks offered on #868 to apply retroactive One note on history: you and @ClintEastman02 both pushed |
Fixes #864.
Problem
The generated least-privilege bootstrap template was 53,369 characters as CloudFormation received it, past the 51,200 ceiling for an inline
TemplateBody. Above that the CDK CLI must stage the template in S3 — which it cannot do while bootstrapping a fresh account, because the bucket it would stage into is one of the resources bootstrap creates.So a first-time
mise //cdk:bootstrapfailed outright:--forcedoes not help: the same size branch runs even whenCDKToolkitalready exists, so the custom template could never be applied throughcdk bootstrapat all.The gated size is not the file's size on disk.
makeBodyParameterre-serialises the parsed template with the CLI's own writer before measuring, so the committed file's formatting is discarded:This matters because reformatting the artifact is the intuitive fix and it accomplishes nothing. An earlier revision of this PR did exactly that — reflowed the YAML from 54,213 to 39,896 bytes on disk while the body CloudFormation received stayed at 53,369, byte-identical to
mainand still over the limit. That approach was reverted; the reflow also pushed the longest line from 142 to 1,605 characters, which hurt reviewability for no gain.Change
Emit each ABCA
PolicyDocumentas a minified JSON string instead of a nested YAML mapping.PolicyDocumentis aJson-typed property so CloudFormation accepts either, but the shape decides the size: as a mapping the CLI's re-serialisation expands every statement key and action onto its own line, whereas a string scalar survives on one line.53,369 → 45,743 characters, with 5,457 of headroom. Only the six ABCA policies are converted;
CdkBoostrapPermissionsBoundaryPolicyfrom the upstream template stays a mapping.Zero permission drift — the parsed policy documents are identical to
main.Gate on the CLI's own number.
cdk/src/bootstrap/template-size.tsinvokescdk bootstrap --show-templateon the committed artifact rather than reimplementing the CLI's serialiser locally. Mirroring it would mean a direct dependency onyaml(present here only as a transitiveresolutionspin) and would drift silently the day the CLI changes writer or options. Asking the component that makes the inline-vs-S3 decision cannot drift. No credentials or network required.--no-ciis load-bearing: in CI mode the CLI routes progress to stdout, so "Using bootstrapping template from<path>" would be counted as part of the body — 45,812 withCI=trueversus 45,744 without, varying with the path's length.Fail generation over budget.
TEMPLATE_SIZE_BUDGETis 49,152 (48 KiB), deliberately below the hard ceiling so a policy addition fails in the generator — where the content is produced and the author can act on it — rather than surfacing later as an opaque CDK CLI error against somebody's fresh account. The generator writes a sibling.tmp, measures it, and renames only on success, so a rejected template is never left on disk.Verified end-to-end against a real account
The claim that CloudFormation accepts a JSON-string
PolicyDocumentand IAM stores it parsed was checked on live AWS, not inferred from the spec:PolicyDocumentreachedCREATE_COMPLETE, andiam:GetPolicyVersionreturned it parsed as a policy document, not as a literal string.cdk bootstrap --template bootstrap/bootstrap-template.yaml --forceagainst account618731765007succeeded —CDKToolkitwentUPDATE_COMPLETEacross all six managed policies — where the same command on the pre-fix template failed withBootstrapStackRequired.Not tested: how CloudFormation diffs the mapping-to-string change on a
CDKToolkitstack created before this PR. The update above was applied to a stack already carrying the ABCA variant, so that specific path is exercised, but not an upgrade from the stock CDK bootstrap.Testing
mise run build— exit 0, 0 task failures, 4,550 cdk + 928 cli testsnpx jest test/bootstrap/— all suites green, including underCI=truePolicyDocumentparses to a valid policy document; the committed file deep-equals a freshly built template and is byte-identical to a fresh render;checkTemplateBudgetis asserted at budget, budget+1, past the hard ceiling, and at 53,369 — the body this bug actually shippedNotes for reviewers
New maintenance coupling, worth a conscious decision. The byte-identical render test compares the committed YAML against the default template inside the installed
aws-cdkpackage. No prior test did this. Everyaws-cdkbump that touches the upstream bootstrap template will now fail this test until the generator is re-run and its output committed — turning some dependency bumps into PRs with a manual regeneration step. That seems right (silent drift in the bootstrap template is worse), but it is a real cost and reviewers should agree to it rather than discover it.BOOTSTRAP_VERSIONleft at 1.6.0. The version's documented contract is the policy bundle, and the hash covers policy content, which is unchanged — no operator needs to re-bootstrap for permissions. Happy to add a patch bump if you'd rather an operator inspecting a deployedCDKToolkitbe able to tell which template shape they have.Pre-existing, not fixed here:
cdk/eslint.config.mjsdoes not lintscripts/, so theeslint-disabledirectives in the generator are inert. Now that tests import that file, it is worth a follow-up issue.docs/design/DEPLOYMENT_ROLES.mddocuments the 51,200-character ceiling as a second limit distinct from the per-policy IAM 6,144-character one, why on-disk size is not the gated quantity, and the JSON-string shape. Starlight mirror re-synced.🤖 Generated with Claude Code