canopycms-cdk 0.0.67-int.90 → 0.0.67-int.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/constructs/asset-support.d.ts +137 -269
  2. package/dist/constructs/asset-support.js +275 -455
  3. package/dist/constructs/cms-distribution.d.ts +23 -40
  4. package/dist/constructs/cms-distribution.js +61 -88
  5. package/dist/constructs/cms-service.d.ts +38 -60
  6. package/dist/constructs/cms-service.js +246 -390
  7. package/dist/constructs/lambda-execution-role.d.ts +23 -39
  8. package/dist/constructs/lambda-execution-role.js +37 -60
  9. package/dist/index.js +0 -2
  10. package/dist/worker.d.ts +0 -2
  11. package/dist/worker.js +0 -2
  12. package/lambda/asset-transform/dist/handler.js +33 -49
  13. package/lambda/asset-transform/dist/node_modules/.package-lock.json +38 -38
  14. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/README.md +1 -1
  15. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +2 -2
  16. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/{libvips-cpp.so.8.18.6 → libvips-cpp.so.8.18.7} +0 -0
  17. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/package.json +2 -2
  18. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/versions.json +11 -11
  19. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/index.cjs +1 -1
  20. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/lib/{sharp-linux-arm64-0.35.4.node → sharp-linux-arm64-0.35.5.node} +0 -0
  21. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/package.json +2 -2
  22. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/index.cjs +1 -1
  23. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.5.node.js +1 -0
  24. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/{sharp-wasm32-0.35.4.node.wasm → sharp-wasm32-0.35.5.node.wasm} +0 -0
  25. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/package.json +1 -1
  26. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/versions.json +7 -7
  27. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.cts +2 -2
  28. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.mts +3 -3
  29. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.cjs +8 -2
  30. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.mjs +8 -2
  31. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.cjs +2 -2
  32. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.mjs +2 -2
  33. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.cjs +1 -0
  34. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.mjs +1 -0
  35. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.cjs +14 -14
  36. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.mjs +14 -14
  37. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.cjs +3 -3
  38. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.mjs +3 -3
  39. package/lambda/asset-transform/dist/node_modules/sharp/lib/index.d.ts +2 -2
  40. package/lambda/asset-transform/dist/node_modules/sharp/package.json +35 -35
  41. package/lambda/asset-transform/dist/node_modules/sharp/src/binding.gyp +1 -1
  42. package/lambda/asset-transform/dist/node_modules/sharp/src/common.cc +4 -4
  43. package/lambda/asset-transform/dist/node_modules/sharp/src/common.h +2 -2
  44. package/lambda/asset-transform/dist/node_modules/sharp/src/pipeline.cc +31 -6
  45. package/lambda/asset-transform/dist/node_modules/sharp/src/utilities.cc +1 -1
  46. package/package.json +4 -4
  47. package/worker/dist/index.js +251 -364
  48. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.4.node.js +0 -1
@@ -11,15 +11,13 @@ export interface AssetUploadBehaviorOptions {
11
11
  * this behavior alone.
12
12
  *
13
13
  * `['*']` by default, and a wildcard is defensible HERE in a way a
14
- * bucket-wide CORS rule is not, because **the edge authorises nothing**:
15
- * measured, a presigned POST with a corrupted signature sent through this
16
- * exact path returns 403 and nothing lands. Authority is entirely the
17
- * presigned policy, which pins the bucket, the exact key, the content type,
18
- * a size range and a 15-minute expiry. So the wildcard widens who may READ
19
- * the response, not who may write - and the response is an empty 204.
20
- *
21
- * Narrow it to your editor origin(s) if you would rather; nothing else here
22
- * depends on the wildcard.
14
+ * bucket-wide CORS rule is not, because **the edge authorises nothing**: a
15
+ * presigned POST with a corrupted signature sent through this exact path
16
+ * returns 403 and nothing lands. Authority is entirely the presigned policy,
17
+ * which pins the bucket, the exact key, the content type, a size range and a
18
+ * 15-minute expiry. So the wildcard widens who may READ the response, not who
19
+ * may write - and the response is an empty 204. Narrow it to your editor
20
+ * origin(s) if you would rather; nothing else here depends on it.
23
21
  *
24
22
  * ORIGINS ARE MATCHED EXACTLY, and `'*'` is honoured only as the sole entry.
25
23
  * CloudFront's response headers policy would accept a leftmost-subdomain
@@ -58,12 +56,10 @@ export interface AssetSupportProps {
58
56
  * `Access-Control-Allow-Origin` from the edge instead, so no bucket rule is
59
57
  * written and no exact origin has to be named anywhere.
60
58
  *
61
- * Optional since that edge route exists - it was required while the bucket
62
- * rule was the only route. Standalone mode still refuses to synth with
63
- * NEITHER, because the resulting failure is a genuinely misleading one (see
64
- * the error's own text). An empty array counts as absent: CloudFormation
65
- * rejects a CORS rule with no origins, so it can only ever have been a
66
- * mistake.
59
+ * Optional, but standalone mode refuses to synth with NEITHER, because the
60
+ * resulting failure is a genuinely misleading one (see the error's own text).
61
+ * An empty array counts as absent: CloudFormation rejects a CORS rule with no
62
+ * origins, so it can only ever have been a mistake.
67
63
  *
68
64
  * @default - no bucket CORS rule; standalone mode then requires `uploadBehavior`
69
65
  */
@@ -71,37 +67,30 @@ export interface AssetSupportProps {
71
67
  /**
72
68
  * Build a CloudFront behavior that accepts the editor's presigned-POST
73
69
  * uploads - the infrastructure half of `media.uploadUrl` (see the README's
74
- * "Routing uploads through your own CDN"). Retrieve the result with
75
- * `uploadBehavior()`, which also documents the distribution it belongs on.
76
- *
77
- * OFF unless set, and deliberately so: this is the only thing in this
70
+ * "Routing uploads through your own CDN"). Retrieve it with
71
+ * `uploadBehavior()`, which documents the distribution it belongs on. OFF
72
+ * unless set, and deliberately so: this is the only thing in this
78
73
  * construct that puts a write-capable, OAC-UNSIGNED origin in front of the
79
- * bucket, and that must never appear in a template by accident. Pass
80
- * `uploadBehavior: {}` to take the defaults.
81
- *
82
- * BYO-BUCKET CAVEATS - three, and the first is about your bucket policy:
83
- *
84
- * 1. YOUR BUCKET POLICY IS THE ONLY GATE ON THIS ROUTE. The read behaviors
85
- * reach the bucket through an OAC-SIGNED origin, so a permissive policy
86
- * there is still gated by a signature CloudFront adds. This origin cannot
87
- * be signed (CloudFront never hashes the body, so an OAC-signed origin
88
- * rejects every multipart POST), so nothing gates it but the policy. The
89
- * route is contained by rewriting every request to `/`, which leaves an
90
- * anonymous caller only the bucket-level operations at the root - so what
91
- * your policy grants anonymously AT THE BUCKET ROOT is what is reachable.
92
- * A bucket this construct creates refuses all of them (BLOCK_ALL, no
93
- * public policy); an existing bucket is yours to have got right, and "the
94
- * read path already works" is not evidence that it is.
95
- * 2. This origin addresses the bucket by `bucketRegionalDomainName`, and for
96
- * a bucket imported with `Bucket.fromBucketName()` that resolves to the
97
- * STACK's region, not the bucket's. The read path tolerates a mismatch
98
- * because CloudFront follows S3's region redirect on an S3-type origin;
99
- * this one is a custom origin and does not, so S3's 301 reaches the
100
- * browser, where a redirected cross-origin POST surfaces as an opaque
101
- * network error. Import a cross-region bucket with
102
- * `Bucket.fromBucketAttributes({ region })` so the domain name is right.
103
- * 3. A DOT in the bucket name breaks this origin specifically - refused at
104
- * synth, with the reason, in `buildUploadBehavior`.
74
+ * bucket, and that must never reach a template by accident. Pass
75
+ * `uploadBehavior: {}` for the defaults. BYO-BUCKET CAVEATS:
76
+ *
77
+ * 1. YOUR BUCKET POLICY IS THE ONLY GATE ON THIS ROUTE. The read behaviors go
78
+ * through an OAC-SIGNED origin, so a permissive policy there is still
79
+ * gated by a signature CloudFront adds; this origin cannot be signed
80
+ * (CloudFront never hashes the body, so an OAC-signed origin rejects every
81
+ * multipart POST). The route is contained by rewriting every request to
82
+ * `/`, so what your policy grants anonymously AT THE BUCKET ROOT is what is
83
+ * reachable. A bucket this construct creates refuses all of it (BLOCK_ALL,
84
+ * no public policy); an existing one is yours, and a read path that already
85
+ * works is not evidence that it is right.
86
+ * 2. This origin addresses the bucket by `bucketRegionalDomainName`, which for
87
+ * a bucket imported with `Bucket.fromBucketName()` resolves to the STACK's
88
+ * region, not the bucket's. The read path tolerates a mismatch because
89
+ * CloudFront follows S3's region redirect on an S3-type origin; this custom
90
+ * origin does not, so S3's 301 reaches the browser as an opaque network
91
+ * error. Import cross-region with `Bucket.fromBucketAttributes({ region })`.
92
+ * 3. A DOT in the bucket name breaks this origin; `buildUploadBehavior`
93
+ * refuses it at synth, with the reason.
105
94
  *
106
95
  * @default - no upload behavior is built
107
96
  */
@@ -144,12 +133,11 @@ export interface AssetSupportProps {
144
133
  * `build:lambda`). Leave this ON for anything that can reach a real
145
134
  * deploy.
146
135
  *
147
- * Set to `false` ONLY in this package's own tests, which deliberately
148
- * synth against the cheap `--skip-native` fixture bundle that
149
- * `build:test-fixtures` produces - the suite never executes the handler,
150
- * so the binary is irrelevant to what it asserts, and requiring a real
151
- * build would put a live `npm install sharp` back in front of every test
152
- * run (which is what kept these tests out of CI in the first place).
136
+ * Set to `false` ONLY in this package's own tests, which synth against the
137
+ * cheap `--skip-native` fixture bundle `build:test-fixtures` produces: the
138
+ * suite never executes the handler, so the binary is irrelevant to what it
139
+ * asserts, and requiring a real build would put a live `npm install sharp` in
140
+ * front of every test run and price the suite out of CI.
153
141
  *
154
142
  * Adopters never need this: the published package's asset is built by
155
143
  * `prepack`'s full `build:lambda`, so the marker is always present.
@@ -186,26 +174,21 @@ export interface AssetSupportProps {
186
174
  * Execution role for the transform Lambda (default: CDK creates one).
187
175
  *
188
176
  * Set this when the role's ARN has to be computable WITHOUT a reference to
189
- * this construct - the case that motivated the prop is an asset bucket in a
190
- * different AWS account from the compute, where the resource-policy half of
191
- * the cross-account grant must be written in the bucket's own stack and needs
192
- * the principal as a plain string. Reading `transformFunction.role` across an
177
+ * this construct - the motivating case is an asset bucket in a different AWS
178
+ * account from the compute, where the resource-policy half of the
179
+ * cross-account grant is written in the bucket's own stack and needs the
180
+ * principal as a plain string. Reading `transformFunction.role` across an
193
181
  * account boundary does not give you that: CDK emits `Fn::GetStackOutput`, a
194
- * CDK-CLI-only intrinsic resolved at deploy time, so the coupling is
195
- * invisible to CloudFormation and unusable by any deploy path that is not
196
- * `cdk deploy`. Create a deterministically NAMED role instead and both stacks
197
- * can compute `arn:aws:iam::<account>:role/<name>` from literals, with
198
- * nothing crossing between them.
199
- *
200
- * A named IAM role means the consuming stack needs `CAPABILITY_NAMED_IAM`,
201
- * and cannot be replaced in place without a rename - that trade is yours to
202
- * make here, which is the point of taking a role rather than a name.
182
+ * CDK-CLI-only intrinsic resolved at deploy time, so the coupling is invisible
183
+ * to CloudFormation and unusable by any deploy path that is not `cdk deploy`.
184
+ * A deterministically NAMED role lets both stacks compute
185
+ * `arn:aws:iam::<account>:role/<name>` from literals instead, with nothing
186
+ * crossing between them - at the cost of `CAPABILITY_NAMED_IAM` in the
187
+ * consuming stack and no in-place replacement without a rename.
203
188
  *
204
189
  * `iam.Role`, not `iam.IRole`, ON PURPOSE - an imported role silently
205
190
  * discards the managed policies this construct has to re-attach. See
206
- * `attachLambdaExecutionPolicies` (./lambda-execution-role) for what CDK
207
- * drops when a role is passed, and why the narrower type is what makes the
208
- * compensation reliable.
191
+ * `attachLambdaExecutionPolicies` (./lambda-execution-role).
209
192
  */
210
193
  readonly transformRole?: iam.Role;
211
194
  /**
@@ -240,15 +223,13 @@ export declare const ASSETS_TRANSFORM_PATH_PATTERN: string;
240
223
  * instead. Exported so `cms-distribution.ts`'s synth-time guard can
241
224
  * recognize the mistake and name it in its error.
242
225
  *
243
- * `uploadBehavior()` deliberately adds NO key here, considered and rejected
244
- * when it was added. It returns a bare `BehaviorOptions` rather than a named
245
- * property of a returned object, so there is no spread to get wrong in the
246
- * first place - and a speculative `'upload'` entry would actively misfire on
247
- * an adopter whose distribution has a real upload route, since CloudFront
248
- * treats a path pattern's leading slash as optional and `upload` is a legal
249
- * spelling of `/upload` (see `normalizePathPattern` in cms-distribution.ts).
250
- * Keep this list to property names that actually exist on
251
- * `AssetCloudFrontBehaviors`.
226
+ * `uploadBehavior()` deliberately adds NO key here: it returns a bare
227
+ * `BehaviorOptions` rather than a named property, so there is no spread to get
228
+ * wrong, and a speculative `'upload'` entry would misfire on an adopter whose
229
+ * distribution has a real upload route - CloudFront treats a path pattern's
230
+ * leading slash as optional, so `upload` is a legal spelling of `/upload` (see
231
+ * `normalizePathPattern` in cms-distribution.ts). Keep this list to property
232
+ * names that actually exist on `AssetCloudFrontBehaviors`.
252
233
  */
253
234
  export declare const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS: readonly ["assets", "assetsTransform"];
254
235
  /**
@@ -257,29 +238,21 @@ export declare const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS: readonly ["assets", "as
257
238
  * (origin included).
258
239
  *
259
240
  * Prefer `AssetSupport.attachTo(distribution)` or `CanopyCmsDistribution`'s
260
- * `assetSupport` prop over consuming this directly - both encode the
261
- * required attachment order in exactly one place instead of asking every
262
- * caller to reproduce it correctly. This return value remains useful as an
263
- * ESCAPE HATCH for a bespoke `new cloudfront.Distribution(...)` assembled
264
- * entirely inline (its `additionalBehaviors` is fixed at construction, so
265
- * there is no distribution yet to call `addBehavior` on) - but manual use
266
- * must still preserve the ordering shown below, and
267
- * `CanopyCmsDistribution`'s synth-time guard (`mergeBehaviors`) actively
268
- * rejects three ways this goes wrong: `/assets/*` listed before
269
- * `/assets/t/*`; the literal keys `assets`/`assetsTransform` (see
270
- * `ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS`) from spreading this object directly
271
- * into a `Record`; or an asset pattern listed here at all while
272
- * `CanopyCmsDistribution`'s `assetSupport` prop is also passed. See
273
- * `assertNoAssetBehaviorOrderingHazards`'s doc comment for the full list.
241
+ * `assetSupport` prop over consuming this directly - both encode the required
242
+ * attachment order in exactly one place, and the latter's synth-time guard
243
+ * rejects the three ways manual wiring goes wrong (see
244
+ * `assertNoAssetBehaviorOrderingHazards`). This value is the ESCAPE HATCH for a
245
+ * bespoke `new cloudfront.Distribution(...)` assembled entirely inline, whose
246
+ * `additionalBehaviors` is fixed at construction with no distribution yet to
247
+ * call `addBehavior` on - there you own the ordering:
274
248
  *
275
249
  * ```ts
276
250
  * const behaviors = assetSupport.assetBehaviors()
277
251
  *
278
- * // Building a new distribution inline. CloudFront matches path patterns in
279
- * // the order they're listed and stops at the first match, so the more
280
- * // specific '/assets/t/*' MUST come before '/assets/*' - otherwise the
281
- * // broader S3-only pattern swallows transform requests first and they
282
- * // 403 with no Lambda fallback.
252
+ * // CloudFront matches path patterns in the order listed and stops at the
253
+ * // first match, so the more specific '/assets/t/*' MUST come before
254
+ * // '/assets/*' - otherwise the broader S3-only pattern swallows transform
255
+ * // requests and they 403 with no Lambda fallback.
283
256
  * new cloudfront.Distribution(this, 'Dist', {
284
257
  * defaultBehavior: ...,
285
258
  * additionalBehaviors: {
@@ -301,9 +274,8 @@ export interface AssetCloudFrontBehaviors {
301
274
  * `/assets/t/*` - transform outputs specifically. Origin group: the same
302
275
  * S3 origin as `assets` primary, falling over to the transform Lambda's
303
276
  * Function URL on 403 OR 404 (a signed OAC origin reports a miss as 403;
304
- * configuring both is defense-in-depth). See the SPIKE RESULT in
305
- * `.claude/future-tasks/assets-media-system.md` - this design is
306
- * confirmed working with CloudFront caching the failover response.
277
+ * configuring both is defense-in-depth). CloudFront caches the failover
278
+ * response.
307
279
  */
308
280
  readonly assetsTransform: cloudfront.BehaviorOptions;
309
281
  }
@@ -339,82 +311,28 @@ export interface AssetUploadBehaviorRouteOptions extends AssetUploadBehaviorOpti
339
311
  * // media.uploadUrl = `https://${uploads.distributionDomainName}/`
340
312
  * ```
341
313
  *
342
- * WHY THIS EXISTS SEPARATELY FROM `AssetSupport.uploadBehavior()`. The upload
343
- * route depends on the bucket and nothing else, which is what lets it live on
344
- * its own one-route distribution (the topology `AssetSupport.uploadBehavior()`
345
- * documents and recommends). But `AssetSupport`'s CONSTRUCTOR builds the
346
- * transform Lambda unconditionally, and with it a log group, a Function URL, a
347
- * role, and `grantRead`/`grantPut` on three prefixes. Reaching the upload
348
- * behavior through the class therefore costs an instantiation whose only
349
- * purpose is to be instantiated.
350
- *
351
- * That footprint is cheaper than it looks, and the reason is worth stating
352
- * because the opposite is easy to assume: those grants do NOT land in the
353
- * bucket policy. `Grant.addToPrincipalOrResource` (aws-cdk-lib,
354
- * `aws-iam/lib/grant.ts`) adds the identity statement first and returns right
355
- * there when two things hold - the identity statement was actually added, AND
356
- * the grantee's account is known to match the resource's (`TokenComparison`
357
- * SAME, or both unresolved). Only when one of those fails does it also write a
358
- * resource statement. A same-account Lambda with a CDK-managed role satisfies
359
- * both, so its grants go to that function's OWN execution role and nowhere
360
- * else. Measured on a synthesized template, an owned bucket plus a
361
- * same-account `AssetSupport` emits no `AWS::S3::BucketPolicy` at all.
362
- * Deleting the construct therefore takes the whole footprint with it and
363
- * leaves nothing behind on a shared bucket's policy.
364
- *
365
- * Nor does the cross-account shape invert it, which is the obvious next
366
- * guess and is also wrong. Measured across all three shapes - an owned
367
- * bucket, one imported by name, and one imported by ARN with an explicitly
368
- * DIFFERENT account - every one emits zero `AWS::S3::BucketPolicy`. The
369
- * second half of `addToPrincipalOrResource` does run for an import, but
370
- * `addToResourcePolicy` on a bucket CDK does not own is a no-op: it cannot
371
- * write a policy onto a resource it did not create. So a cross-account
372
- * adopter gets the identity half here and writes the resource half
373
- * themselves, on the bucket's own side.
374
- *
375
- * What it costs instead is legibility: a second `AssetSupport` standing in a
376
- * stack that has no asset pipeline, existing only so that a method can be
377
- * called on it, is fine on the day and unexplainable six months later. That
378
- * is what this function removes, and it matters most for the shape the
379
- * one-route topology invites - one bucket shared by every environment, owned
380
- * by a stack that holds no per-environment resources, with reads and
381
- * transforms staying per-environment.
382
- *
383
- * `AssetSupport.uploadBehavior()` remains the right call when you already have
384
- * an `AssetSupport`; both funnel into the same builder, so the two cannot
385
- * drift.
386
- *
387
- * TWO THINGS TO KNOW ABOUT `scope`. It receives the CloudFront Function and
388
- * the two policies as children under fixed ids, so calling this twice with
389
- * the SAME scope throws CDK's duplicate-construct-id error rather than
390
- * returning a second behavior - one call per scope. And because the children
391
- * hang off `scope`, moving an EXISTING deployment from
392
- * `AssetSupport.uploadBehavior()` to this function changes their construct
393
- * path and therefore their logical IDs, which replaces all three CloudFront
394
- * resources. There is no reason to make that move on a stack that already has
395
- * an `AssetSupport`; this function is for the stack that would otherwise have
396
- * to grow one.
397
- *
398
- * TWO THINGS THIS DOES NOT DO, both deliberate:
314
+ * Separate from `AssetSupport.uploadBehavior()` because the upload route needs
315
+ * the bucket and nothing else, while that construct's CONSTRUCTOR always builds
316
+ * the transform Lambda, a log group, a Function URL, a role and prefix grants.
317
+ * Those grants never reach the BUCKET policy: `Grant.addToPrincipalOrResource`
318
+ * stops after the identity statement for a same-account grantee, and
319
+ * `addToResourcePolicy` on a bucket CDK does not own is a no-op - an owned
320
+ * bucket, one imported by name, and one imported by ARN in another account all
321
+ * emit zero `AWS::S3::BucketPolicy`, so a cross-account adopter writes the
322
+ * resource half on the bucket's own side. Prefer the method when you already
323
+ * have an `AssetSupport`; both funnel into the same builder and cannot drift.
399
324
  *
400
- * - It writes NO bucket CORS rule, and needs none. The edge supplies
401
- * `Access-Control-Allow-Origin`; S3's acceptance of a presigned POST and its
402
- * ADVERTISEMENT of CORS are independent, and only the second is what a
403
- * bucket CORS rule governs. `AssetSupportProps.editorOrigins` is the prop
404
- * that writes one, and it belongs to the construct, not to this route.
405
- * - It registers no synth-time guard for "built but never attached to a
406
- * distribution". `AssetSupport` has one, but it fires only in standalone
407
- * mode with no `editorOrigins` - where opting into `uploadBehavior` is what
408
- * satisfied the "something must supply ACAO" guard, so opting in and
409
- * stopping leaves a bucket with neither. A caller here always brings their
410
- * own bucket and never passes through that guard, so there is no invariant
411
- * to protect: a discarded return value is an obviously unfinished call, not
412
- * a silently broken one.
325
+ * No bucket CORS rule is written and none is needed: the edge supplies
326
+ * `Access-Control-Allow-Origin`, and S3's ACCEPTANCE of a presigned POST is
327
+ * independent of its ADVERTISEMENT of CORS, which is all a bucket rule governs
328
+ * (`AssetSupportProps.editorOrigins` writes one, for the construct). No
329
+ * "built but never attached" guard either: a caller here brings their own
330
+ * bucket, so never reaches the construct's "something must supply ACAO" guard.
413
331
  */
414
332
  export declare function assetUploadBehavior(scope: Construct, options: AssetUploadBehaviorRouteOptions): cloudfront.BehaviorOptions;
415
333
  /**
416
334
  * Per-site CDK construct for the asset/media delivery system (see
417
- * `.claude/future-tasks/assets-media-system.md` for the full design record).
335
+ * `.claude/future-tasks/resolved/assets-media-system.md` for the full design record).
418
336
  * Wires:
419
337
  *
420
338
  * - The bucket's asset-prefix lifecycle rule + CORS (standalone mode only).
@@ -438,7 +356,6 @@ export declare function assetUploadBehavior(scope: Construct, options: AssetUplo
438
356
  export declare class AssetSupport extends Construct {
439
357
  /** The bucket in use (either created here, or the BYO `props.bucket`). */
440
358
  readonly bucket: s3.IBucket;
441
- /** The transform Lambda function. */
442
359
  readonly transformFunction: lambda.Function;
443
360
  /** The transform Lambda's CloudWatch log group (Lambda stdout/stderr). */
444
361
  readonly transformLogGroup: logs.LogGroup;
@@ -453,14 +370,12 @@ export declare class AssetSupport extends Construct {
453
370
  * Memoized on the first `uploadBehavior()` call, NOT built in the
454
371
  * constructor. Opting in creates three CloudFront resources (function,
455
372
  * origin request policy, response headers policy) and response headers
456
- * policies have a default account quota of 20 - so an adopter who sets the
373
+ * policies have a default account quota of 20, so an adopter who sets the
457
374
  * prop on a per-environment `AssetSupport` while building the single shared
458
375
  * upload distribution the accessor recommends would otherwise mint a set per
459
- * environment for one route. Deferring is invisible other than in what gets
460
- * emitted, with one exception worth knowing rather than discovering: calling
461
- * the accessor AFTER a synth has run modifies the construct tree, and CDK
462
- * refuses that with `ConstructTreeModifiedAfterSynth` naming the constructs
463
- * added. Loud, not silent.
376
+ * environment for one route. Deferring is invisible except that calling the
377
+ * accessor AFTER a synth has run modifies the construct tree, which CDK
378
+ * refuses with `ConstructTreeModifiedAfterSynth`. Loud, not silent.
464
379
  */
465
380
  private upload?;
466
381
  /**
@@ -477,17 +392,13 @@ export declare class AssetSupport extends Construct {
477
392
  /**
478
393
  * The two CloudFront behavior configs this system needs.
479
394
  *
480
- * If you use this rather than `attachTo` -- which takes an `overrides`
481
- * parameter, so needing per-behavior options is not a reason to fall back
482
- * here -- you own the ordering, and you should assert it: read the
483
- * SYNTHESIZED template's `CacheBehaviors` array index, not your own source
484
- * object, since the property is about emitted order.
485
- *
486
- * Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s
487
- * `assetSupport` prop, which attach these to a distribution in the only
488
- * safe order automatically. This method exists as the escape hatch for a
489
- * distribution assembled entirely by hand - see `AssetCloudFrontBehaviors`'s
490
- * doc comment for that shape and its ordering requirements.
395
+ * Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s `assetSupport`
396
+ * prop, which attach these in the only safe order automatically; `attachTo`
397
+ * takes an `overrides` parameter, so needing per-behavior options is not a
398
+ * reason to fall back here. Use this only for a distribution assembled
399
+ * entirely by hand - you then own the ordering, and should assert it against
400
+ * the SYNTHESIZED template's `CacheBehaviors` array index rather than your own
401
+ * source object, since the property is about emitted order.
491
402
  */
492
403
  assetBehaviors(): AssetCloudFrontBehaviors;
493
404
  /**
@@ -503,95 +414,52 @@ export declare class AssetSupport extends Construct {
503
414
  * })
504
415
  * // media.uploadUrl = `https://${uploads.distributionDomainName}/`
505
416
  * ```
506
- *
507
- * No custom domain or certificate is needed - the `d111...cloudfront.net`
508
- * name is a perfectly good `uploadUrl` - and as the default behavior of a
509
- * one-route distribution there is no path pattern and so no ordering
510
- * question of the kind `attachTo()` exists to settle.
511
- *
512
- * Two shapes this deliberately is NOT, both settled 2026-09-11 (the record
513
- * is `.claude/future-tasks/resolved/asset-support-upload-behavior.md`):
514
- *
515
- * - NOT a behavior on the site's own distribution. `CustomErrorResponses`
516
- * are distribution-wide, so a site that maps 403 to its own 404 page
517
- * applies that to S3's upload errors too and the editor reports the
518
- * substituted status. The site's cookies and any cached basic-auth
519
- * credential are also live hazards there that have to be stripped by
520
- * policy and function (`buildUploadBehavior` removes both - cookies via
521
- * the origin request policy, `Authorization` in the viewer-request
522
- * function) rather than simply never
523
- * being sent. It works; it is just strictly worse.
417
+ * No custom domain or certificate is needed, and as the default behavior of a
418
+ * one-route distribution there is no path pattern, so no ordering question of
419
+ * the kind `attachTo()` settles. Two shapes this deliberately is NOT:
420
+ *
421
+ * - NOT a behavior on the site's own distribution. `CustomErrorResponses` are
422
+ * distribution-wide, so a site mapping 403 to its own 404 page applies that
423
+ * to S3's upload errors and the editor reports the substituted status; the
424
+ * site's cookies and any cached basic-auth credential are live hazards
425
+ * there, stripped by policy and function rather than never sent at all.
524
426
  * - NOT one shared distribution serving asset reads AND writes for every
525
- * environment. One assets distribution means one `AssetSupport`, and
526
- * `AssetSupport` owns the transform Lambda - so it would also mean one
527
- * transform Lambda shared by every environment, and that Lambda ships
528
- * inside this package. A `canopycms-cdk` bump would then move every
529
- * environment's asset pipeline at once, which is a graduated rollout
530
- * traded away for an upload path. Reads and transforms stay
531
- * per-environment; only the upload route moves.
532
- *
533
- * The upload route is the one part of this system with no per-environment
534
- * code in it - it depends on the bucket and nothing else - which is why it
535
- * is the part that can be split off this way.
536
- *
537
- * That same property is why `assetUploadBehavior` exists as a free function:
538
- * it builds this behavior from a bucket alone, for a caller who wants the
539
- * one-route distribution above and has no other use for an `AssetSupport`.
540
- * Prefer this method when you already have one; both funnel into the same
541
- * builder. See that function for what instantiating the construct purely to
542
- * reach this method actually costs.
427
+ * environment. That means one `AssetSupport`, which owns the transform
428
+ * Lambda shipping inside this package, so a `canopycms-cdk` bump would move
429
+ * every environment's asset pipeline at once. Only the upload route moves;
430
+ * it depends on the bucket and nothing else, the same property that lets
431
+ * `assetUploadBehavior` build it from a bucket alone.
543
432
  */
544
433
  uploadBehavior(): cloudfront.BehaviorOptions;
545
434
  /**
546
435
  * Attach both asset behaviors to a concrete CloudFront distribution, in the
547
436
  * only safe order.
548
437
  *
549
- * THIS METHOD EXISTS BECAUSE THE ORDER IS THE WHOLE POINT.
550
- * `assetBehaviors()` returns `{ assets, assetsTransform }` with no path
551
- * pattern attached at all (see that method's and `AssetCloudFrontBehaviors`'s
552
- * doc comments) - so nothing stops a caller from attaching the two in
553
- * either order. CloudFront matches path patterns in the order given and
554
- * stops at the first match. `/assets/*` is a broader, S3-only pattern that
555
- * also matches every `/assets/t/*` request; `/assets/t/*` is an origin
556
- * group that fails over to the transform Lambda on a miss. Attach
557
- * `/assets/*` first and every never-yet-computed transform gets a
558
- * permanent 403 (an OAC-signed S3 miss reports 403) while already-computed
559
- * transforms keep working - silent, launch-delayed, and permanent.
560
- * `'/assets/*'` also sorts BEFORE `'/assets/t/*'` lexicographically (`*` =
561
- * 0x2A, `t` = 0x74), so alphabetizing the keys reproduces exactly this
562
- * failure, with no synth or deploy error to catch it.
563
- *
564
- * `overrides` is merged into BOTH behaviors, and exists because without it
565
- * this method is unusable by exactly the adopters who most need the ordering
566
- * guarantee. A distribution that runs a viewer-request function on every
567
- * behavior - tier basic-auth, most commonly - needs the asset behaviors to
568
- * carry that same `functionAssociations`, or `/assets/*` is anonymously
569
- * readable on an authenticated tier. Before this parameter existed such an
570
- * adopter had to fall back to `assetBehaviors()` plus two hand-ordered
571
- * `addBehavior` calls: the precise shape this method was added to eliminate,
572
- * re-entered while believing ordering was handled upstream, which is worse
573
- * than never having had the method. (`responseHeadersPolicy` is the same
574
- * story for a repo with a shared security-headers policy.)
575
- *
576
- * Merged into both rather than per-behavior on purpose: applying one set to
577
- * both is what preserves the ordering guarantee as the only thing this
578
- * method decides. A caller who genuinely needs the two behaviors to differ
579
- * has left this method's remit and should use `assetBehaviors()` - and keep
580
- * their own ordering assertion.
581
- *
582
- * Typed `Partial<AddBehaviorOptions>`, not `BehaviorOptions`, because
583
- * `addBehavior` takes `origin` positionally - so an `origin` key here would be
584
- * a silently ignored no-op. (Measured: passing one changes nothing in the
585
- * emitted template. It is a confusing no-op being prevented, not a broken
586
- * origin group.)
587
- *
588
- * Needs a concrete `cloudfront.Distribution` - `addBehavior` is an instance
589
- * method on that class, not on `IDistribution` (what an imported/looked-up
590
- * distribution reference gives you). For a distribution built entirely
591
- * inline (its `additionalBehaviors` fixed at construction, with no
592
- * distribution yet to call `addBehavior` on), call `assetBehaviors()`
593
- * directly instead and list `/assets/t/*` before `/assets/*` yourself - see
594
- * `AssetCloudFrontBehaviors`'s doc comment.
438
+ * THE ORDER IS THE WHOLE POINT. CloudFront matches path patterns in the order
439
+ * given and stops at the first match. `/assets/*` is a broader, S3-only
440
+ * pattern that also matches every `/assets/t/*` request; `/assets/t/*` is an
441
+ * origin group that fails over to the transform Lambda on a miss. Attach
442
+ * `/assets/*` first and every never-yet-computed transform gets a permanent
443
+ * 403 (an OAC-signed S3 miss reports 403) while already-computed transforms
444
+ * keep working. `'/assets/*'` also sorts BEFORE `'/assets/t/*'`
445
+ * lexicographically (`*` = 0x2A, `t` = 0x74), so alphabetizing the keys
446
+ * reproduces exactly this failure with no synth or deploy error to catch it -
447
+ * and `assetBehaviors()` attaches no path pattern, so nothing there stops a
448
+ * caller getting it wrong.
449
+ *
450
+ * `overrides` is merged into BOTH behaviors. The motivating case is a
451
+ * distribution running a viewer-request function on every behavior - tier
452
+ * basic-auth - where without the same `functionAssociations` `/assets/*` is
453
+ * anonymously readable on an authenticated tier (`responseHeadersPolicy` is
454
+ * the same story for a shared security-headers policy). One set for both is
455
+ * what keeps ordering the only thing this method decides; a caller who needs
456
+ * the two to differ should use `assetBehaviors()` and assert their own order.
457
+ *
458
+ * Typed `Partial<AddBehaviorOptions>`, not `BehaviorOptions`: `addBehavior`
459
+ * takes `origin` positionally, so an `origin` key here is a silent no-op. It
460
+ * needs a concrete `cloudfront.Distribution` too - `addBehavior` is an
461
+ * instance method on that class, not on `IDistribution`. For a distribution
462
+ * built entirely inline, call `assetBehaviors()` and order it yourself.
595
463
  */
596
464
  attachTo(distribution: cloudfront.Distribution, overrides?: Partial<cloudfront.AddBehaviorOptions>): void;
597
465
  /**