@fjall/util 36.0.0 → 38.0.0

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/.minified +1 -1
  2. package/dist/appTypes.d.ts +27 -0
  3. package/dist/appTypes.js +1 -0
  4. package/dist/aws/index.d.ts +2 -1
  5. package/dist/aws/index.js +1 -1
  6. package/dist/aws/infraTags.d.ts +18 -0
  7. package/dist/aws/infraTags.js +1 -1
  8. package/dist/aws/resourceTypes.d.ts +54 -0
  9. package/dist/aws/resourceTypes.js +1 -0
  10. package/dist/backupTiers.d.ts +30 -3
  11. package/dist/backupTiers.js +1 -1
  12. package/dist/ciSetup.d.ts +25 -0
  13. package/dist/ciSetup.js +1 -0
  14. package/dist/codemod/statementTypes.d.ts +16 -0
  15. package/dist/codemod/statementTypes.js +1 -0
  16. package/dist/complianceIssue.d.ts +10 -0
  17. package/dist/complianceIssue.js +1 -1
  18. package/dist/deploy/approvalTokenOutput.d.ts +0 -7
  19. package/dist/deploy/approvalTokenOutput.js +1 -1
  20. package/dist/fjallHome.d.ts +4 -3
  21. package/dist/index.d.ts +12 -7
  22. package/dist/index.js +1 -1
  23. package/dist/infra/backupVault.d.ts +79 -2
  24. package/dist/infra/backupVault.js +1 -1
  25. package/dist/infra/tokenScopes.d.ts +15 -0
  26. package/dist/infra/tokenScopes.js +1 -1
  27. package/dist/insights/vocabulary.d.ts +29 -0
  28. package/dist/insights/vocabulary.js +1 -0
  29. package/dist/mcpProtocol/index.d.ts +47 -10
  30. package/dist/mcpProtocol/index.js +1 -1
  31. package/dist/nonEmptyPartial.d.ts +19 -0
  32. package/dist/nonEmptyPartial.js +1 -0
  33. package/dist/patterns/frameworkPatterns.d.ts +1 -8
  34. package/dist/patterns/index.d.ts +2 -2
  35. package/dist/patterns/index.js +1 -1
  36. package/dist/patterns/patternTypes.d.ts +57 -2
  37. package/dist/patterns/patternTypes.js +1 -1
  38. package/dist/posturePolicy/index.d.ts +3 -1
  39. package/dist/posturePolicy/index.js +1 -1
  40. package/dist/posturePolicy/registry.d.ts +73 -8
  41. package/dist/posturePolicy/registry.js +1 -1
  42. package/dist/posturePolicy/resourceKinds.d.ts +27 -0
  43. package/dist/posturePolicy/resourceKinds.js +1 -0
  44. package/dist/posturePolicy/tierConsent.d.ts +14 -7
  45. package/dist/posturePolicy/tierConsent.js +1 -1
  46. package/dist/scaffold/ensureGitignore.js +6 -6
  47. package/dist/securityHelpers.js +1 -1
  48. package/package.json +6 -2
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Insight list vocabulary — the filter and ordering tokens `GET /api/insights`
3
+ * accepts, minted once so every consumer spells them the same way.
4
+ *
5
+ * Owner today: the webapp (`webapp/app/lib/insights/insights.ts` —
6
+ * `TAB_VALUES`, `INSIGHT_CATEGORIES`, `INSIGHT_SEVERITIES`, `SORT_VALUES`).
7
+ * The MCP's `list_insights` tool re-typed all four by hand and advertised
8
+ * them on its wire surface, so a token added or renamed in the webapp left
9
+ * the tool silently offering a filter the route rejects. The MCP now derives
10
+ * from here, and the route's 400-on-unknown gate is what keeps the two honest
11
+ * while the webapp still carries its own copies.
12
+ *
13
+ * Client-safe (no node built-ins): exported from the root barrel and as the
14
+ * `@fjall/util/insights/vocabulary` subpath so browser code can take it
15
+ * without the root barrel.
16
+ */
17
+ export declare const INSIGHT_TAB_VALUES: readonly ["open", "snoozed", "resolved", "all"];
18
+ /** @public — tuple companion type; ships with its tuple (knip skips @public) */
19
+ export type InsightTab = (typeof INSIGHT_TAB_VALUES)[number];
20
+ export declare const INSIGHT_CATEGORIES: readonly ["performance", "cost", "reliability", "security", "deployment"];
21
+ /** @public — tuple companion type; ships with its tuple (knip skips @public) */
22
+ export type InsightCategory = (typeof INSIGHT_CATEGORIES)[number];
23
+ /** Most severe first. */
24
+ export declare const INSIGHT_SEVERITIES: readonly ["critical", "warning", "recommendation", "info"];
25
+ /** @public — tuple companion type; ships with its tuple (knip skips @public) */
26
+ export type InsightSeverity = (typeof INSIGHT_SEVERITIES)[number];
27
+ export declare const INSIGHT_SORT_VALUES: readonly ["matters", "severity", "newest", "oldest", "savings"];
28
+ /** @public — tuple companion type; ships with its tuple (knip skips @public) */
29
+ export type InsightSort = (typeof INSIGHT_SORT_VALUES)[number];
@@ -0,0 +1 @@
1
+ const e=["open","snoozed","resolved","all"],o=["performance","cost","reliability","security","deployment"],t=["critical","warning","recommendation","info"],n=["matters","severity","newest","oldest","savings"];export{o as INSIGHT_CATEGORIES,t as INSIGHT_SEVERITIES,n as INSIGHT_SORT_VALUES,e as INSIGHT_TAB_VALUES};
@@ -176,23 +176,32 @@ export declare const TargetAccountSchema: z.ZodObject<{
176
176
  source: z.ZodString;
177
177
  }, z.core.$strict>;
178
178
  export type TargetAccount = z.infer<typeof TargetAccountSchema>;
179
+ /**
180
+ * Every input `plan_app_scaffold` accepts, as the plan records it and
181
+ * `apply_app_scaffold` replays it (`buildApplyCommand`). Plan keys ⊆ these
182
+ * keys is pinned by the MCP's resolvedInputsParity test: a key the plan
183
+ * accepts but this schema drops was silently absent from the apply argv
184
+ * (`type`, `database`, `containerPort` were, until Train 4).
185
+ */
179
186
  export declare const ResolvedInputsSchema: z.ZodObject<{
180
187
  name: z.ZodString;
181
- pattern: z.ZodOptional<z.ZodString>;
188
+ pattern: z.ZodOptional<z.ZodEnum<{
189
+ payload: "payload";
190
+ staticsite: "staticsite";
191
+ }>>;
182
192
  patternTier: z.ZodOptional<z.ZodEnum<{
193
+ lightweight: "lightweight";
183
194
  standard: "standard";
184
195
  resilient: "resilient";
185
- enterprise: "enterprise";
186
- tinkerer: "tinkerer";
187
- lightweight: "lightweight";
196
+ custom: "custom";
188
197
  }>>;
189
198
  patternDomain: z.ZodOptional<z.ZodString>;
190
199
  network: z.ZodOptional<z.ZodEnum<{
200
+ lightweight: "lightweight";
191
201
  standard: "standard";
192
202
  resilient: "resilient";
193
203
  enterprise: "enterprise";
194
204
  none: "none";
195
- lightweight: "lightweight";
196
205
  }>>;
197
206
  template: z.ZodOptional<z.ZodString>;
198
207
  docker: z.ZodOptional<z.ZodObject<{
@@ -200,6 +209,19 @@ export declare const ResolvedInputsSchema: z.ZodObject<{
200
209
  context: z.ZodOptional<z.ZodString>;
201
210
  target: z.ZodOptional<z.ZodString>;
202
211
  }, z.core.$strict>>;
212
+ type: z.ZodOptional<z.ZodEnum<{
213
+ tinkerer: "tinkerer";
214
+ lightweight: "lightweight";
215
+ standard: "standard";
216
+ resilient: "resilient";
217
+ enterprise: "enterprise";
218
+ custom: "custom";
219
+ }>>;
220
+ database: z.ZodOptional<z.ZodBoolean>;
221
+ containerPort: z.ZodOptional<z.ZodNumber>;
222
+ source: z.ZodOptional<z.ZodString>;
223
+ buildCommand: z.ZodOptional<z.ZodString>;
224
+ outputDir: z.ZodOptional<z.ZodString>;
203
225
  git: z.ZodOptional<z.ZodBoolean>;
204
226
  github: z.ZodOptional<z.ZodBoolean>;
205
227
  repoVisibility: z.ZodOptional<z.ZodEnum<{
@@ -214,21 +236,23 @@ export declare const ScaffoldPlanSchema: z.ZodObject<{
214
236
  normalisedPath: z.ZodString;
215
237
  resolvedInputs: z.ZodObject<{
216
238
  name: z.ZodString;
217
- pattern: z.ZodOptional<z.ZodString>;
239
+ pattern: z.ZodOptional<z.ZodEnum<{
240
+ payload: "payload";
241
+ staticsite: "staticsite";
242
+ }>>;
218
243
  patternTier: z.ZodOptional<z.ZodEnum<{
244
+ lightweight: "lightweight";
219
245
  standard: "standard";
220
246
  resilient: "resilient";
221
- enterprise: "enterprise";
222
- tinkerer: "tinkerer";
223
- lightweight: "lightweight";
247
+ custom: "custom";
224
248
  }>>;
225
249
  patternDomain: z.ZodOptional<z.ZodString>;
226
250
  network: z.ZodOptional<z.ZodEnum<{
251
+ lightweight: "lightweight";
227
252
  standard: "standard";
228
253
  resilient: "resilient";
229
254
  enterprise: "enterprise";
230
255
  none: "none";
231
- lightweight: "lightweight";
232
256
  }>>;
233
257
  template: z.ZodOptional<z.ZodString>;
234
258
  docker: z.ZodOptional<z.ZodObject<{
@@ -236,6 +260,19 @@ export declare const ScaffoldPlanSchema: z.ZodObject<{
236
260
  context: z.ZodOptional<z.ZodString>;
237
261
  target: z.ZodOptional<z.ZodString>;
238
262
  }, z.core.$strict>>;
263
+ type: z.ZodOptional<z.ZodEnum<{
264
+ tinkerer: "tinkerer";
265
+ lightweight: "lightweight";
266
+ standard: "standard";
267
+ resilient: "resilient";
268
+ enterprise: "enterprise";
269
+ custom: "custom";
270
+ }>>;
271
+ database: z.ZodOptional<z.ZodBoolean>;
272
+ containerPort: z.ZodOptional<z.ZodNumber>;
273
+ source: z.ZodOptional<z.ZodString>;
274
+ buildCommand: z.ZodOptional<z.ZodString>;
275
+ outputDir: z.ZodOptional<z.ZodString>;
239
276
  git: z.ZodOptional<z.ZodBoolean>;
240
277
  github: z.ZodOptional<z.ZodBoolean>;
241
278
  repoVisibility: z.ZodOptional<z.ZodEnum<{
@@ -1 +1 @@
1
- var p=Object.defineProperty;var a=(n,r)=>p(n,"name",{value:r,configurable:!0});import{z as t}from"zod";const g=2,v={AUTHENTICATION_REQUIRED:"AUTHENTICATION_REQUIRED",VALIDATION_ERROR:"VALIDATION_ERROR",INTERNAL_ERROR:"INTERNAL_ERROR",INVALID_SUBCOMMAND:"INVALID_SUBCOMMAND",INVALID_FRAME:"INVALID_FRAME"},s=1e3,e=t.literal(g),u=t.enum(["completed","error","skipped"]),d=t.object({v:e,kind:t.literal("step.start"),stepId:t.string().min(1),name:t.string().min(1),index:t.number().int().min(0),total:t.number().int().min(1)}).strict(),b=t.object({v:e,kind:t.literal("step.complete"),stepId:t.string().min(1),name:t.string().min(1),status:u,index:t.number().int().min(0),total:t.number().int().min(1),errorMessage:t.string().optional()}).strict(),h=t.object({v:e,kind:t.literal("warning"),message:t.string().min(1)}).strict(),R=t.object({code:t.string().min(1),message:t.string().min(1),details:t.record(t.string(),t.unknown()).optional()}).strict(),A=t.object({v:e,kind:t.literal("error"),error:R}).strict(),S=t.object({v:e,kind:t.literal("result"),data:t.unknown()}).strict(),I=t.object({v:e,kind:t.literal("auth.browser.required"),url:t.string().url(),externalId:t.string().min(1),region:t.string().min(1),environment:t.string().min(1),reason:t.string().min(1)}).strict(),x=t.object({v:e,kind:t.literal("auth.browser.complete"),awsAccountId:t.string().min(1),accountName:t.string().min(1),roleArn:t.string().min(1)}).strict(),k=t.discriminatedUnion("kind",[d,b,h,A,S,I,x]),c=t.object({path:t.string().min(1),sizeBytes:t.number().int().nonnegative(),collisionMode:t.enum(["create","overwrite"])}).strict(),m=t.object({type:t.enum(["register-application","create-activity","auto-link-repository","writeback-config-path"]),description:t.string().min(1)}).strict(),E=t.enum(["tinkerer","lightweight","standard","resilient","enterprise"]),N=t.enum(["lightweight","standard","resilient","enterprise","none"]),l=t.object({name:t.string().min(1),environment:t.string().min(1).nullable(),source:t.string().min(1)}).strict(),y=t.object({name:t.string().min(1),pattern:t.string().min(1).optional(),patternTier:E.optional(),patternDomain:t.string().min(1).optional(),network:N.optional(),template:t.string().min(1).optional(),docker:t.object({path:t.string().min(1).optional(),context:t.string().min(1).optional(),target:t.string().min(1).optional()}).strict().optional(),git:t.boolean().optional(),github:t.boolean().optional(),repoVisibility:t.enum(["private","public"]).optional(),intent:t.string().max(s).optional()}).strict(),w=t.object({planId:t.string().regex(/^[0-9a-f]{32}$/),normalisedPath:t.string().min(1),resolvedInputs:y,targetAccount:l.nullable(),targetAccountCandidates:t.array(l),filesToWrite:t.array(c),npmPackages:t.array(t.string().min(1)),registryActions:t.array(m),patternUnsupportedForDryRun:t.boolean().optional(),intent:t.string().max(s).optional(),framework:t.string().min(1).optional(),summary:t.string().min(1)}).strict(),f=t.object({filesToWrite:t.array(c),npmPackages:t.array(t.string().min(1)),registryActions:t.array(m),patternUnsupportedForDryRun:t.boolean().optional()}).strict(),O=t.object({path:t.string().min(1),name:t.string().min(1),mode:t.enum(["application","pattern"]),dependenciesInstalled:t.boolean().optional(),warning:t.string().optional(),dryRunArtefacts:f.optional()}).strict(),D=t.object({step:t.string().min(1),current:t.number().int().nonnegative(),total:t.number().int().positive().optional(),message:t.string().min(1)}).strict();function F(n){let r;try{r=JSON.parse(n)}catch(o){return{ok:!1,reason:"json",raw:n,error:o instanceof Error?o.message:String(o)}}const i=k.safeParse(r);return i.success?{ok:!0,frame:i.data}:{ok:!1,reason:"schema",raw:n,error:i.error.message}}a(F,"parseFrame");export{x as AuthBrowserCompleteFrameSchema,I as AuthBrowserRequiredFrameSchema,f as DryRunArtefactsSchema,A as ErrorFrameSchema,c as FileToWriteSchema,s as INTENT_MAX_LENGTH,v as MCP_ERROR_CODES,g as MCP_PROTOCOL_VERSION,k as McpProtocolFrameSchema,m as RegistryActionSchema,y as ResolvedInputsSchema,S as ResultFrameSchema,w as ScaffoldPlanSchema,D as ScaffoldProgressFrameSchema,O as ScaffoldResultDataSchema,b as StepCompleteFrameSchema,d as StepStartFrameSchema,l as TargetAccountSchema,h as WarningFrameSchema,F as parseFrame};
1
+ var l=Object.defineProperty;var a=(e,r)=>l(e,"name",{value:r,configurable:!0});import{z as t}from"zod";import{APP_TYPES as g}from"../appTypes.js";import{DEPLOYABLE_PATTERN_TYPES as u,PATTERN_TIER_VALUES as d}from"../patterns/patternTypes.js";const b=2,L={AUTHENTICATION_REQUIRED:"AUTHENTICATION_REQUIRED",VALIDATION_ERROR:"VALIDATION_ERROR",INTERNAL_ERROR:"INTERNAL_ERROR",INVALID_SUBCOMMAND:"INVALID_SUBCOMMAND",INVALID_FRAME:"INVALID_FRAME"},s=1e3,n=t.literal(b),A=t.enum(["completed","error","skipped"]),R=t.object({v:n,kind:t.literal("step.start"),stepId:t.string().min(1),name:t.string().min(1),index:t.number().int().min(0),total:t.number().int().min(1)}).strict(),S=t.object({v:n,kind:t.literal("step.complete"),stepId:t.string().min(1),name:t.string().min(1),status:A,index:t.number().int().min(0),total:t.number().int().min(1),errorMessage:t.string().optional()}).strict(),E=t.object({v:n,kind:t.literal("warning"),message:t.string().min(1)}).strict(),h=t.object({code:t.string().min(1),message:t.string().min(1),details:t.record(t.string(),t.unknown()).optional()}).strict(),I=t.object({v:n,kind:t.literal("error"),error:h}).strict(),x=t.object({v:n,kind:t.literal("result"),data:t.unknown()}).strict(),T=t.object({v:n,kind:t.literal("auth.browser.required"),url:t.string().url(),externalId:t.string().min(1),region:t.string().min(1),environment:t.string().min(1),reason:t.string().min(1)}).strict(),y=t.object({v:n,kind:t.literal("auth.browser.complete"),awsAccountId:t.string().min(1),accountName:t.string().min(1),roleArn:t.string().min(1)}).strict(),N=t.discriminatedUnion("kind",[R,S,E,I,x,T,y]),m=t.object({path:t.string().min(1),sizeBytes:t.number().int().nonnegative(),collisionMode:t.enum(["create","overwrite"])}).strict(),c=t.object({type:t.enum(["register-application","create-activity","auto-link-repository","writeback-config-path"]),description:t.string().min(1)}).strict(),f=t.enum(d),k=t.enum(u),P=t.enum(g),_=t.enum(["lightweight","standard","resilient","enterprise","none"]),p=t.object({name:t.string().min(1),environment:t.string().min(1).nullable(),source:t.string().min(1)}).strict(),j=t.object({name:t.string().min(1),pattern:k.optional(),patternTier:f.optional(),patternDomain:t.string().min(1).optional(),network:_.optional(),template:t.string().min(1).optional(),docker:t.object({path:t.string().min(1).optional(),context:t.string().min(1).optional(),target:t.string().min(1).optional()}).strict().optional(),type:P.optional(),database:t.boolean().optional(),containerPort:t.number().int().min(1).max(65535).optional(),source:t.string().min(1).optional(),buildCommand:t.string().min(1).optional(),outputDir:t.string().min(1).optional(),git:t.boolean().optional(),github:t.boolean().optional(),repoVisibility:t.enum(["private","public"]).optional(),intent:t.string().max(s).optional()}).strict(),C=t.object({planId:t.string().regex(/^[0-9a-f]{32}$/),normalisedPath:t.string().min(1),resolvedInputs:j,targetAccount:p.nullable(),targetAccountCandidates:t.array(p),filesToWrite:t.array(m),npmPackages:t.array(t.string().min(1)),registryActions:t.array(c),patternUnsupportedForDryRun:t.boolean().optional(),intent:t.string().max(s).optional(),framework:t.string().min(1).optional(),summary:t.string().min(1)}).strict(),D=t.object({filesToWrite:t.array(m),npmPackages:t.array(t.string().min(1)),registryActions:t.array(c),patternUnsupportedForDryRun:t.boolean().optional()}).strict(),M=t.object({path:t.string().min(1),name:t.string().min(1),mode:t.enum(["application","pattern"]),dependenciesInstalled:t.boolean().optional(),warning:t.string().optional(),dryRunArtefacts:D.optional()}).strict(),U=t.object({step:t.string().min(1),current:t.number().int().nonnegative(),total:t.number().int().positive().optional(),message:t.string().min(1)}).strict();function V(e){let r;try{r=JSON.parse(e)}catch(i){return{ok:!1,reason:"json",raw:e,error:i instanceof Error?i.message:String(i)}}const o=N.safeParse(r);return o.success?{ok:!0,frame:o.data}:{ok:!1,reason:"schema",raw:e,error:o.error.message}}a(V,"parseFrame");export{y as AuthBrowserCompleteFrameSchema,T as AuthBrowserRequiredFrameSchema,D as DryRunArtefactsSchema,I as ErrorFrameSchema,m as FileToWriteSchema,s as INTENT_MAX_LENGTH,L as MCP_ERROR_CODES,b as MCP_PROTOCOL_VERSION,N as McpProtocolFrameSchema,c as RegistryActionSchema,j as ResolvedInputsSchema,x as ResultFrameSchema,C as ScaffoldPlanSchema,U as ScaffoldProgressFrameSchema,M as ScaffoldResultDataSchema,S as StepCompleteFrameSchema,R as StepStartFrameSchema,p as TargetAccountSchema,E as WarningFrameSchema,V as parseFrame};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A read-only partial record that cannot be empty: the union, over every
3
+ * key of `T`, of "this key required, the rest optional". `{}` is
4
+ * unrepresentable whether or not `T` declares the key optional (the
5
+ * required arm strips `?`, so the carried key is present with its value),
6
+ * a key `T` does not carry fails typecheck on a literal, and each present
7
+ * key keeps its own value type — `T[K]` may differ per key, which is what a
8
+ * plain `Partial<T>` with a non-emptiness check cannot express. The shape a
9
+ * cure map is declared on: the posture registries' `PolicyCure`, and a
10
+ * consumer mirroring the cure map declares its own on this helper rather
11
+ * than re-spelling the union.
12
+ */
13
+ export type NonEmptyPartial<T> = {
14
+ [Key in keyof T]-?: {
15
+ readonly [K in Key]-?: T[K];
16
+ } & {
17
+ readonly [K in Exclude<keyof T, Key>]?: T[K];
18
+ };
19
+ }[keyof T];
@@ -0,0 +1 @@
1
+ const n={first:1},o={second:"two"},t={first:1,second:"two"},s={},e={first:1,third:3},c={second:"two"},i={},d={second:void 0};
@@ -30,7 +30,7 @@
30
30
  * than left behind as a lie. The reverse holds too: a pattern that loses its
31
31
  * construct cannot stay in `recommended`.
32
32
  */
33
- import { PATTERN_REGISTRY, type PatternType, type PatternCreateInput } from "./patternTypes.js";
33
+ import { PATTERN_REGISTRY, type PatternType, type PatternCreateInput, type DeployablePatternType } from "./patternTypes.js";
34
34
  /**
35
35
  * Every framework the detector can name.
36
36
  *
@@ -50,13 +50,6 @@ export declare const FRAMEWORK_VALUES: readonly ["nextjs", "payload", "nextjs+pa
50
50
  export type Framework = (typeof FRAMEWORK_VALUES)[number];
51
51
  export declare const FRAMEWORKS: ReadonlySet<string>;
52
52
  export declare function isFramework(value: unknown): value is Framework;
53
- /**
54
- * The patterns that can deploy end-to-end, derived from the registry's own
55
- * flag at the type level. `DEPLOYABLE_PATTERN_TYPES` is the value-level twin.
56
- */
57
- export type DeployablePatternType = {
58
- [K in PatternType]: (typeof PATTERN_REGISTRY)[K]["deployable"] extends true ? K : never;
59
- }[PatternType];
60
53
  /**
61
54
  * The patterns the generator knows but cannot yet synthesise. Uninhabited once
62
55
  * every pattern is deployable — at which point the `blocked` verdict below
@@ -7,6 +7,6 @@
7
7
  * the webapp bundles) can take the vocabulary without depending on the root
8
8
  * barrel's contents staying pure.
9
9
  */
10
- export { PATTERN_TYPE_VALUES, type PatternType, PATTERN_TYPES, isPatternType, type PatternArtefact, type PatternStackPlacement, type PatternDescriptor, PATTERN_REGISTRY, patternConstructId, DEPLOYABLE_PATTERN_TYPES, type OpenNextPatternType, OPENNEXT_PATTERN_TYPES, isOpenNextPatternType, CLOUDFRONT_PRICE_CLASS_VALUES, type CloudFrontPriceClass, STATIC_SITE_CONFIG_KEYS, type StaticSiteConfigKey, STATIC_SITE_DEPLOY_INJECTED_KEYS, type StaticSiteDeployInjectedKey, STATIC_SITE_CDN_CONFIG_KEYS, type StaticSiteCdnConfigKey, STATIC_SITE_ROUTING_VALUES, STATIC_SITE_ROUTING_INFO, type StaticSiteRouting, type StaticSiteRoutingDescriptor, PATTERN_CREATE_INPUT_VALUES, type PatternCreateInput, PATTERN_CREATE_INPUT_FLAGS } from "./patternTypes.js";
11
- export { FRAMEWORK_VALUES, type Framework, FRAMEWORKS, isFramework, type DeployablePatternType, type UndeployablePatternType, type PatternRecommendation, FRAMEWORK_PATTERN_RECOMMENDATIONS, recommendPatternForFramework, CREATABLE_FRAMEWORKS, requiredInputsForFramework } from "./frameworkPatterns.js";
10
+ export { PATTERN_TYPE_VALUES, type PatternType, PATTERN_TYPES, isPatternType, type PatternArtefact, type PatternStackPlacement, type PatternDescriptor, PATTERN_REGISTRY, patternConstructId, type DeployablePatternType, DEPLOYABLE_PATTERN_TYPES, PATTERN_TIER_VALUES, type PatternTier, type OpenNextPatternType, OPENNEXT_PATTERN_TYPES, isOpenNextPatternType, OPENNEXT_BUCKET_KEYS, type OpenNextBucketKey, isOpenNextBucketKey, patternBucketConstructId, CLOUDFRONT_PRICE_CLASS_VALUES, type CloudFrontPriceClass, STATIC_SITE_CONFIG_KEYS, type StaticSiteConfigKey, STATIC_SITE_DEPLOY_INJECTED_KEYS, type StaticSiteDeployInjectedKey, STATIC_SITE_CDN_CONFIG_KEYS, type StaticSiteCdnConfigKey, STATIC_SITE_ROUTING_VALUES, STATIC_SITE_ROUTING_INFO, type StaticSiteRouting, type StaticSiteRoutingDescriptor, PATTERN_CREATE_INPUT_VALUES, type PatternCreateInput, PATTERN_CREATE_INPUT_FLAGS } from "./patternTypes.js";
11
+ export { FRAMEWORK_VALUES, type Framework, FRAMEWORKS, isFramework, type UndeployablePatternType, type PatternRecommendation, FRAMEWORK_PATTERN_RECOMMENDATIONS, recommendPatternForFramework, CREATABLE_FRAMEWORKS, requiredInputsForFramework } from "./frameworkPatterns.js";
12
12
  export { DEFAULT_FORMS_FROM_LOCAL_PART, defaultFormsFromAddress, defaultFormsCorsOrigin, isAddressAtDomain } from "./staticSiteForms.js";
@@ -1 +1 @@
1
- import{PATTERN_TYPE_VALUES as _,PATTERN_TYPES as A,isPatternType as r,PATTERN_REGISTRY as R,patternConstructId as S,DEPLOYABLE_PATTERN_TYPES as I,OPENNEXT_PATTERN_TYPES as N,isOpenNextPatternType as e,CLOUDFRONT_PRICE_CLASS_VALUES as P,STATIC_SITE_CONFIG_KEYS as O,STATIC_SITE_DEPLOY_INJECTED_KEYS as F,STATIC_SITE_CDN_CONFIG_KEYS as C,STATIC_SITE_ROUTING_VALUES as o,STATIC_SITE_ROUTING_INFO as t,PATTERN_CREATE_INPUT_VALUES as L,PATTERN_CREATE_INPUT_FLAGS as s}from"./patternTypes.js";import{FRAMEWORK_VALUES as U,FRAMEWORKS as a,isFramework as n,FRAMEWORK_PATTERN_RECOMMENDATIONS as Y,recommendPatternForFramework as d,CREATABLE_FRAMEWORKS as i,requiredInputsForFramework as p}from"./frameworkPatterns.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as M,defaultFormsFromAddress as K,defaultFormsCorsOrigin as G,isAddressAtDomain as f}from"./staticSiteForms.js";export{P as CLOUDFRONT_PRICE_CLASS_VALUES,i as CREATABLE_FRAMEWORKS,M as DEFAULT_FORMS_FROM_LOCAL_PART,I as DEPLOYABLE_PATTERN_TYPES,a as FRAMEWORKS,Y as FRAMEWORK_PATTERN_RECOMMENDATIONS,U as FRAMEWORK_VALUES,N as OPENNEXT_PATTERN_TYPES,s as PATTERN_CREATE_INPUT_FLAGS,L as PATTERN_CREATE_INPUT_VALUES,R as PATTERN_REGISTRY,A as PATTERN_TYPES,_ as PATTERN_TYPE_VALUES,C as STATIC_SITE_CDN_CONFIG_KEYS,O as STATIC_SITE_CONFIG_KEYS,F as STATIC_SITE_DEPLOY_INJECTED_KEYS,t as STATIC_SITE_ROUTING_INFO,o as STATIC_SITE_ROUTING_VALUES,G as defaultFormsCorsOrigin,K as defaultFormsFromAddress,f as isAddressAtDomain,n as isFramework,e as isOpenNextPatternType,r as isPatternType,S as patternConstructId,d as recommendPatternForFramework,p as requiredInputsForFramework};
1
+ import{PATTERN_TYPE_VALUES as _,PATTERN_TYPES as A,isPatternType as r,PATTERN_REGISTRY as R,patternConstructId as S,DEPLOYABLE_PATTERN_TYPES as e,PATTERN_TIER_VALUES as N,OPENNEXT_PATTERN_TYPES as t,isOpenNextPatternType as I,OPENNEXT_BUCKET_KEYS as P,isOpenNextBucketKey as O,patternBucketConstructId as C,CLOUDFRONT_PRICE_CLASS_VALUES as F,STATIC_SITE_CONFIG_KEYS as o,STATIC_SITE_DEPLOY_INJECTED_KEYS as L,STATIC_SITE_CDN_CONFIG_KEYS as s,STATIC_SITE_ROUTING_VALUES as n,STATIC_SITE_ROUTING_INFO as U,PATTERN_CREATE_INPUT_VALUES as m,PATTERN_CREATE_INPUT_FLAGS as a}from"./patternTypes.js";import{FRAMEWORK_VALUES as d,FRAMEWORKS as p,isFramework as K,FRAMEWORK_PATTERN_RECOMMENDATIONS as i,recommendPatternForFramework as u,CREATABLE_FRAMEWORKS as D,requiredInputsForFramework as M}from"./frameworkPatterns.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as V,defaultFormsFromAddress as c,defaultFormsCorsOrigin as f,isAddressAtDomain as k}from"./staticSiteForms.js";export{F as CLOUDFRONT_PRICE_CLASS_VALUES,D as CREATABLE_FRAMEWORKS,V as DEFAULT_FORMS_FROM_LOCAL_PART,e as DEPLOYABLE_PATTERN_TYPES,p as FRAMEWORKS,i as FRAMEWORK_PATTERN_RECOMMENDATIONS,d as FRAMEWORK_VALUES,P as OPENNEXT_BUCKET_KEYS,t as OPENNEXT_PATTERN_TYPES,a as PATTERN_CREATE_INPUT_FLAGS,m as PATTERN_CREATE_INPUT_VALUES,R as PATTERN_REGISTRY,N as PATTERN_TIER_VALUES,A as PATTERN_TYPES,_ as PATTERN_TYPE_VALUES,s as STATIC_SITE_CDN_CONFIG_KEYS,o as STATIC_SITE_CONFIG_KEYS,L as STATIC_SITE_DEPLOY_INJECTED_KEYS,U as STATIC_SITE_ROUTING_INFO,n as STATIC_SITE_ROUTING_VALUES,f as defaultFormsCorsOrigin,c as defaultFormsFromAddress,k as isAddressAtDomain,K as isFramework,O as isOpenNextBucketKey,I as isOpenNextPatternType,r as isPatternType,C as patternBucketConstructId,S as patternConstructId,u as recommendPatternForFramework,M as requiredInputsForFramework};
@@ -15,6 +15,7 @@
15
15
  * re-deriving the answer from a ternary chain whose `else` branch would
16
16
  * otherwise absorb the new pattern.
17
17
  */
18
+ import type { PolicyResourceKind } from "../posturePolicy/resourceKinds.js";
18
19
  export declare const PATTERN_TYPE_VALUES: readonly ["payload", "nextjs", "staticsite"];
19
20
  export type PatternType = (typeof PATTERN_TYPE_VALUES)[number];
20
21
  export declare const PATTERN_TYPES: ReadonlySet<string>;
@@ -74,6 +75,15 @@ export interface PatternDescriptor {
74
75
  * generated wholesale and therefore knows its own layout.
75
76
  */
76
77
  readonly requiredCreateInputs: readonly PatternCreateInput[];
78
+ /**
79
+ * The CloudFormation resource types the pattern's construct synthesises,
80
+ * at every tier it can be created at, projected onto the posture
81
+ * registry's vocabulary (`POLICY_RESOURCE_KINDS`): the footprint the
82
+ * create surfaces hand to `expectedRowsAtDefault`, so a pattern is told
83
+ * only about the findings its own resources produce. Pinned to the
84
+ * synthesised templates by the constructs' pattern-footprint test.
85
+ */
86
+ readonly synthesises: readonly PolicyResourceKind[];
77
87
  }
78
88
  export declare const PATTERN_REGISTRY: {
79
89
  readonly payload: {
@@ -83,6 +93,7 @@ export declare const PATTERN_REGISTRY: {
83
93
  readonly stackPlacement: "compute";
84
94
  readonly deployable: true;
85
95
  readonly requiredCreateInputs: readonly [];
96
+ readonly synthesises: readonly ["AWS::S3::Bucket", "AWS::KMS::Key"];
86
97
  };
87
98
  readonly nextjs: {
88
99
  readonly label: "Next.js";
@@ -91,6 +102,7 @@ export declare const PATTERN_REGISTRY: {
91
102
  readonly stackPlacement: "compute";
92
103
  readonly deployable: false;
93
104
  readonly requiredCreateInputs: readonly [];
105
+ readonly synthesises: readonly ["AWS::S3::Bucket", "AWS::KMS::Key"];
94
106
  };
95
107
  readonly staticsite: {
96
108
  readonly label: "Static site";
@@ -99,6 +111,7 @@ export declare const PATTERN_REGISTRY: {
99
111
  readonly stackPlacement: "cdn";
100
112
  readonly deployable: true;
101
113
  readonly requiredCreateInputs: readonly ["source", "buildCommand", "outputDir"];
114
+ readonly synthesises: readonly ["AWS::S3::Bucket"];
102
115
  };
103
116
  };
104
117
  /**
@@ -115,12 +128,54 @@ export declare const PATTERN_REGISTRY: {
115
128
  * detection silently re-admits the synth-fatal id collision.
116
129
  */
117
130
  export declare function patternConstructId(name: string, type: PatternType): string;
131
+ /**
132
+ * The buckets an OpenNext pattern's construct builds, keyed as the pattern
133
+ * statement's `storage` config spells them (`storage.media.versioned`): the
134
+ * build output, the ISR cache and the uploads. The constructs' props type
135
+ * and the generator's config schema are both pinned to this tuple, so a
136
+ * bucket the construct starts building appears in the config vocabulary
137
+ * before either can compile without it.
138
+ */
139
+ export declare const OPENNEXT_BUCKET_KEYS: readonly ["assets", "cache", "media"];
140
+ export type OpenNextBucketKey = (typeof OPENNEXT_BUCKET_KEYS)[number];
141
+ export declare function isOpenNextBucketKey(value: unknown): value is OpenNextBucketKey;
142
+ /**
143
+ * The construct id of one bucket an OpenNext pattern builds — the FIRST
144
+ * ARGUMENT of the `StorageFactory.build` call the construct makes for it:
145
+ * the pattern statement's `name` prop (the workload name the engine lists
146
+ * as `resourceName`), a hyphen, the bucket key. The bucket's CloudFormation
147
+ * logical id is derived from this id, so a consumer matching a logical id
148
+ * back to the statement that owns the bucket derives the same string here
149
+ * rather than re-composing it; a drifted re-composition agrees for every
150
+ * kebab-case name and diverges the first time a name carries a case
151
+ * boundary the id derivation folds.
152
+ */
153
+ export declare function patternBucketConstructId(name: string, bucket: OpenNextBucketKey): string;
154
+ /**
155
+ * The patterns that can deploy end-to-end, derived from the registry's own
156
+ * flag at the type level. `DEPLOYABLE_PATTERN_TYPES` is the value-level twin.
157
+ */
158
+ export type DeployablePatternType = {
159
+ [K in PatternType]: (typeof PATTERN_REGISTRY)[K]["deployable"] extends true ? K : never;
160
+ }[PatternType];
118
161
  /**
119
162
  * Patterns the toolchain may offer for creation, derived from the registry —
120
163
  * pickers and validation reject the rest at create time instead of letting a
121
- * scaffold fail at first deploy.
164
+ * scaffold fail at first deploy. Typed to the deployable subset so an enum
165
+ * built from it (`z.enum(DEPLOYABLE_PATTERN_TYPES)`) infers exactly the
166
+ * members it accepts at runtime.
167
+ */
168
+ export declare const DEPLOYABLE_PATTERN_TYPES: readonly DeployablePatternType[];
169
+ /**
170
+ * Tier presets a pattern may be created with — the ONE spelling the generator
171
+ * (`PatternTierSchema`), the CLI's SKILL vocabulary, the MCP protocol's
172
+ * `ResolvedInputsSchema` and the MCP `plan_app_scaffold` input derive from.
173
+ * Not the infrastructure tiers (`TIER_NAMES`: tinkerer … enterprise), which
174
+ * govern `fjall add … --tier` — the two vocabularies were conflated on the MCP
175
+ * surface until Train 4 of the 2026-09-07 hardening prompt.
122
176
  */
123
- export declare const DEPLOYABLE_PATTERN_TYPES: readonly PatternType[];
177
+ export declare const PATTERN_TIER_VALUES: readonly ["lightweight", "standard", "resilient", "custom"];
178
+ export type PatternTier = (typeof PATTERN_TIER_VALUES)[number];
124
179
  /**
125
180
  * The OpenNext-shaped patterns, derived from the registry at both the type and
126
181
  * the value level — a new OpenNext pattern joins this accept-set by declaring
@@ -1 +1 @@
1
- var n=Object.defineProperty;var e=(t,a)=>n(t,"name",{value:a,configurable:!0});import{toPascalCase as s}from"../naming/caseConversion.js";const o=["payload","nextjs","staticsite"],c=new Set(o);function T(t){return typeof t=="string"&&c.has(t)}e(T,"isPatternType");const d=["source","buildCommand","outputDir"],E={source:"--source",buildCommand:"--build-command",outputDir:"--output-dir"},r={payload:{label:"Payload CMS",constructIdSuffix:"Payload",artefact:"opennext-lambda",stackPlacement:"compute",deployable:!0,requiredCreateInputs:[]},nextjs:{label:"Next.js",constructIdSuffix:"Nextjs",artefact:"opennext-lambda",stackPlacement:"compute",deployable:!1,requiredCreateInputs:[]},staticsite:{label:"Static site",constructIdSuffix:"StaticSite",artefact:"static-assets",stackPlacement:"cdn",deployable:!0,requiredCreateInputs:["source","buildCommand","outputDir"]}};function _(t,a){return`${s(t)}${r[a].constructIdSuffix}`}e(_,"patternConstructId");const m=o.filter(t=>r[t].deployable),i=o.filter(t=>r[t].artefact==="opennext-lambda"),l=new Set(i);function S(t){return t!=null&&l.has(t)}e(S,"isOpenNextPatternType");const x=["multipage","directory","spa"],P={multipage:{label:"Multi-page (.html files)",summary:"/about serves /about.html"},directory:{label:"Directory index",summary:"/about serves /about/index.html"},spa:{label:"Single-page app",summary:"unknown paths fall back to /index.html"}},I=["PriceClass_100","PriceClass_200","PriceClass_All"],C=["type","name","source","build","routing","security","domain","managedDomain","zoneName","hostedZoneId","forms","cdn","accessGate"],N=["managedDomain"],b=["behaviours","priceClass","invalidateOnDeploy"];export{I as CLOUDFRONT_PRICE_CLASS_VALUES,m as DEPLOYABLE_PATTERN_TYPES,i as OPENNEXT_PATTERN_TYPES,E as PATTERN_CREATE_INPUT_FLAGS,d as PATTERN_CREATE_INPUT_VALUES,r as PATTERN_REGISTRY,c as PATTERN_TYPES,o as PATTERN_TYPE_VALUES,b as STATIC_SITE_CDN_CONFIG_KEYS,C as STATIC_SITE_CONFIG_KEYS,N as STATIC_SITE_DEPLOY_INJECTED_KEYS,P as STATIC_SITE_ROUTING_INFO,x as STATIC_SITE_ROUTING_VALUES,S as isOpenNextPatternType,T as isPatternType,_ as patternConstructId};
1
+ var n=Object.defineProperty;var e=(t,s)=>n(t,"name",{value:s,configurable:!0});import{toPascalCase as r}from"../naming/caseConversion.js";const o=["payload","nextjs","staticsite"],c=new Set(o);function S(t){return typeof t=="string"&&c.has(t)}e(S,"isPatternType");const d=["source","buildCommand","outputDir"],_={source:"--source",buildCommand:"--build-command",outputDir:"--output-dir"},a={payload:{label:"Payload CMS",constructIdSuffix:"Payload",artefact:"opennext-lambda",stackPlacement:"compute",deployable:!0,requiredCreateInputs:[],synthesises:["AWS::S3::Bucket","AWS::KMS::Key"]},nextjs:{label:"Next.js",constructIdSuffix:"Nextjs",artefact:"opennext-lambda",stackPlacement:"compute",deployable:!1,requiredCreateInputs:[],synthesises:["AWS::S3::Bucket","AWS::KMS::Key"]},staticsite:{label:"Static site",constructIdSuffix:"StaticSite",artefact:"static-assets",stackPlacement:"cdn",deployable:!0,requiredCreateInputs:["source","buildCommand","outputDir"],synthesises:["AWS::S3::Bucket"]}};function m(t,s){return`${r(t)}${a[s].constructIdSuffix}`}e(m,"patternConstructId");const i=["assets","cache","media"],u=new Set(i);function x(t){return typeof t=="string"&&u.has(t)}e(x,"isOpenNextBucketKey");function P(t,s){return`${t}-${s}`}e(P,"patternBucketConstructId");const N=o.filter(t=>a[t].deployable),I=["lightweight","standard","resilient","custom"],p=o.filter(t=>a[t].artefact==="opennext-lambda"),l=new Set(p);function A(t){return t!=null&&l.has(t)}e(A,"isOpenNextPatternType");const C=["multipage","directory","spa"],f={multipage:{label:"Multi-page (.html files)",summary:"/about serves /about.html"},directory:{label:"Directory index",summary:"/about serves /about/index.html"},spa:{label:"Single-page app",summary:"unknown paths fall back to /index.html"}},y=["PriceClass_100","PriceClass_200","PriceClass_All"],b=["type","name","source","build","routing","security","domain","managedDomain","zoneName","hostedZoneId","forms","cdn","accessGate"],R=["managedDomain"],h=["behaviours","priceClass","invalidateOnDeploy"];export{y as CLOUDFRONT_PRICE_CLASS_VALUES,N as DEPLOYABLE_PATTERN_TYPES,i as OPENNEXT_BUCKET_KEYS,p as OPENNEXT_PATTERN_TYPES,_ as PATTERN_CREATE_INPUT_FLAGS,d as PATTERN_CREATE_INPUT_VALUES,a as PATTERN_REGISTRY,I as PATTERN_TIER_VALUES,c as PATTERN_TYPES,o as PATTERN_TYPE_VALUES,h as STATIC_SITE_CDN_CONFIG_KEYS,b as STATIC_SITE_CONFIG_KEYS,R as STATIC_SITE_DEPLOY_INJECTED_KEYS,f as STATIC_SITE_ROUTING_INFO,C as STATIC_SITE_ROUTING_VALUES,x as isOpenNextBucketKey,A as isOpenNextPatternType,S as isPatternType,P as patternBucketConstructId,m as patternConstructId};
@@ -9,5 +9,7 @@
9
9
  */
10
10
  export { ISSUE_STATUS_LABELS, countClause, listStatusClauses, storedStatusClause, type IssueStatusLabels } from "./issueStatusLabels.js";
11
11
  export { MACHINE_LANE_STATUSES, isMachineLaneStatus, type MachineLaneStatus } from "./machineLane.js";
12
- export { DEFAULT_POLICY_REGISTRY, EXPECTED_LANE_STAGES, EXPECTED_POLICY, MITIGATED_POLICY, cureClause, evidenceClause, expectedRowCause, findPolicyRow, findPolicyRowByKey, isExpectedLaneStage, laneGroupHeading, policyRowKey, policyRowPresentation, type ExpectedLaneStage, type ExpectedPolicyRow, type MitigatedPolicyRow, type PolicyRegistry, type PolicyResourceScope, type PolicyRowLookup, type PolicyRowMatch, type PolicyRowPresentation } from "./registry.js";
12
+ export { CODEMOD_STATEMENT_TYPES, isCodemodStatementType, type CodemodStatementType } from "../codemod/statementTypes.js";
13
+ export { POLICY_RESOURCE_KINDS, isPolicyResourceKind, policyResourceFootprint, type PolicyResourceFootprint, type PolicyResourceKind } from "./resourceKinds.js";
14
+ export { DEFAULT_POLICY_REGISTRY, EXPECTED_LANE_STAGES, EXPECTED_POLICY, MITIGATED_POLICY, cureClause, cureEdits, cureEntries, evidenceClause, expectedRowCause, findPolicyRow, findPolicyRowByKey, isExpectedLaneStage, laneGroupHeading, policyRowKey, policyRowPresentation, type ExpectedLaneStage, type ExpectedPolicyRow, type MitigatedPolicyRow, type PatternCure, type PolicyCure, type PolicyCureEdit, type PolicyCureEntry, type PolicyRegistry, type PolicyResourceScope, type PolicyRowLookup, type PolicyRowMatch, type PolicyRowPresentation } from "./registry.js";
13
15
  export { TIER_CONSENT_NOTE_LABEL, expectedLaneWhereClause, expectedRowsAtDefault, tierConsentLead, tierConsentLine, type TierConsentLead, type TierConsentLine } from "./tierConsent.js";
@@ -1 +1 @@
1
- import{ISSUE_STATUS_LABELS as o,countClause as E,listStatusClauses as a,storedStatusClause as s}from"./issueStatusLabels.js";import{MACHINE_LANE_STATUSES as i,isMachineLaneStatus as C}from"./machineLane.js";import{DEFAULT_POLICY_REGISTRY as r,EXPECTED_LANE_STAGES as L,EXPECTED_POLICY as u,MITIGATED_POLICY as T,cureClause as l,evidenceClause as _,expectedRowCause as c,findPolicyRow as p,findPolicyRowByKey as A,isExpectedLaneStage as d,laneGroupHeading as I,policyRowKey as R,policyRowPresentation as x}from"./registry.js";import{TIER_CONSENT_NOTE_LABEL as f,expectedLaneWhereClause as y,expectedRowsAtDefault as w,tierConsentLead as N,tierConsentLine as D}from"./tierConsent.js";export{r as DEFAULT_POLICY_REGISTRY,L as EXPECTED_LANE_STAGES,u as EXPECTED_POLICY,o as ISSUE_STATUS_LABELS,i as MACHINE_LANE_STATUSES,T as MITIGATED_POLICY,f as TIER_CONSENT_NOTE_LABEL,E as countClause,l as cureClause,_ as evidenceClause,y as expectedLaneWhereClause,c as expectedRowCause,w as expectedRowsAtDefault,p as findPolicyRow,A as findPolicyRowByKey,d as isExpectedLaneStage,C as isMachineLaneStatus,I as laneGroupHeading,a as listStatusClauses,R as policyRowKey,x as policyRowPresentation,s as storedStatusClause,N as tierConsentLead,D as tierConsentLine};
1
+ import{ISSUE_STATUS_LABELS as t,countClause as E,listStatusClauses as r,storedStatusClause as s}from"./issueStatusLabels.js";import{MACHINE_LANE_STATUSES as a,isMachineLaneStatus as n}from"./machineLane.js";import{CODEMOD_STATEMENT_TYPES as C,isCodemodStatementType as T}from"../codemod/statementTypes.js";import{POLICY_RESOURCE_KINDS as u,isPolicyResourceKind as _,policyResourceFootprint as p}from"./resourceKinds.js";import{DEFAULT_POLICY_REGISTRY as l,EXPECTED_LANE_STAGES as d,EXPECTED_POLICY as R,MITIGATED_POLICY as A,cureClause as I,cureEdits as P,cureEntries as x,evidenceClause as y,expectedRowCause as f,findPolicyRow as O,findPolicyRowByKey as m,isExpectedLaneStage as D,laneGroupHeading as N,policyRowKey as w,policyRowPresentation as Y}from"./registry.js";import{TIER_CONSENT_NOTE_LABEL as U,expectedLaneWhereClause as G,expectedRowsAtDefault as K,tierConsentLead as B,tierConsentLine as g}from"./tierConsent.js";export{C as CODEMOD_STATEMENT_TYPES,l as DEFAULT_POLICY_REGISTRY,d as EXPECTED_LANE_STAGES,R as EXPECTED_POLICY,t as ISSUE_STATUS_LABELS,a as MACHINE_LANE_STATUSES,A as MITIGATED_POLICY,u as POLICY_RESOURCE_KINDS,U as TIER_CONSENT_NOTE_LABEL,E as countClause,I as cureClause,P as cureEdits,x as cureEntries,y as evidenceClause,G as expectedLaneWhereClause,f as expectedRowCause,K as expectedRowsAtDefault,O as findPolicyRow,m as findPolicyRowByKey,T as isCodemodStatementType,D as isExpectedLaneStage,n as isMachineLaneStatus,_ as isPolicyResourceKind,N as laneGroupHeading,r as listStatusClauses,p as policyResourceFootprint,w as policyRowKey,Y as policyRowPresentation,s as storedStatusClause,B as tierConsentLead,g as tierConsentLine};
@@ -22,8 +22,12 @@
22
22
  * the remediation edit each cure names.
23
23
  */
24
24
  import type { AccountStage } from "../environments.js";
25
+ import { type PatternType } from "../patterns/patternTypes.js";
25
26
  import type { BackupTierDefaults } from "../backupTiers.js";
27
+ import { type CodemodStatementType } from "../codemod/statementTypes.js";
26
28
  import type { MachineLaneStatus } from "./machineLane.js";
29
+ import type { NonEmptyPartial } from "../nonEmptyPartial.js";
30
+ import type { PolicyResourceKind } from "./resourceKinds.js";
27
31
  /** Account stages on which an expected-lane finding is softened. */
28
32
  export declare const EXPECTED_LANE_STAGES: readonly ["development", "staging"];
29
33
  export type ExpectedLaneStage = (typeof EXPECTED_LANE_STAGES)[number];
@@ -60,8 +64,42 @@ export type PolicyResourceScope = {
60
64
  type BackupTierFindingKnob = {
61
65
  [K in keyof BackupTierDefaults]: BackupTierDefaults[K] extends boolean ? K : never;
62
66
  }[keyof BackupTierDefaults];
67
+ /**
68
+ * A cure on the `pattern` family. A pattern statement is one of several
69
+ * pattern types, each with a config schema of its own, so the edit names the
70
+ * pattern types whose config carries its path: the media bucket is an
71
+ * OpenNext resource, and a static site's config has no `storage` key at
72
+ * all. The generator's modify gate refuses the edit on any other type, and
73
+ * the webapp offers it only where it applies.
74
+ */
75
+ export interface PatternCure {
76
+ /** The edit as the user writes it on the pattern statement: `storage.media.versioned: true`. */
77
+ readonly edit: string;
78
+ /** The pattern types whose config carries the edit's path; never empty. */
79
+ readonly patterns: readonly PatternType[];
80
+ }
81
+ /** The cure on one family: the edit alone, or for `pattern` the edit with the pattern types it applies to. */
82
+ export type PolicyCureEdit<Family extends CodemodStatementType> = Family extends "pattern" ? PatternCure : string;
83
+ /**
84
+ * The cure per family of statement the finding's resource can belong to,
85
+ * keyed by the codemod's vocabulary: a `NonEmptyPartial` record, so `{}` is
86
+ * unrepresentable and a key the codemod cannot address fails typecheck.
87
+ * Each value is the edit as the user writes it in `infrastructure.ts` on a
88
+ * statement of that family (`versioned: true` on a `storage` statement,
89
+ * `storage.media.versioned: true` on the `pattern` statement that owns the
90
+ * bucket, scoped to the pattern types that carry the path).
91
+ */
92
+ export type PolicyCure = NonEmptyPartial<{
93
+ [Family in CodemodStatementType]: PolicyCureEdit<Family>;
94
+ }>;
63
95
  export interface ExpectedPolicyRow {
64
96
  ruleId: string;
97
+ /**
98
+ * The CloudFormation resource type the row's findings sit on: one of the
99
+ * rule's `resourceTypes` (the webapp's parity test pins it), and what a
100
+ * create surface's footprint is matched against (`./tierConsent`).
101
+ */
102
+ kind: PolicyResourceKind;
65
103
  /** Present on a resource-scoped row: for that resource a scoped row wins over the rule's unscoped row. */
66
104
  resource?: PolicyResourceScope;
67
105
  /** The knob whose default causes the finding: what the user sets, and on which construct. Groups findings by cause on the asset page. */
@@ -84,20 +122,27 @@ export interface ExpectedPolicyRow {
84
122
  */
85
123
  expectation: string;
86
124
  /**
87
- * The cure exactly as the user writes it in `infrastructure.ts`: the one
88
- * edit the rule's RemediationHint applies, so this clause and the Remediate
89
- * button name the same change (`${edit.property}: ${formatEditValue(edit.value)}`
90
- * — a string value quoted, as the PR writes it — pinned by the webapp's
91
- * expectedPolicy.test). Where the knob is a tier (`backup.tier`) the cure is
92
- * deliberately the per-construct override the remediation lane writes, not
93
- * the tier: the heading names the cause, the cure names the edit.
125
+ * The cure exactly as the user writes it in `infrastructure.ts`, per
126
+ * family of statement the resource can belong to (`PolicyCure`). Each
127
+ * entry is the one edit the rule's RemediationHint applies for that
128
+ * family, so the clause and the Remediate button name the same change
129
+ * (`${edit.property}: ${formatEditValue(edit.value)}` — a string value
130
+ * quoted, as the PR writes it — pinned per family, and for `pattern` per
131
+ * applicable pattern type, by the webapp's expectedPolicy.test). Where
132
+ * the knob is a tier (`backup.tier`) the cure is deliberately the
133
+ * per-construct override the remediation lane writes, not the tier: the
134
+ * heading names the cause, the cure names the edit. A resource whose
135
+ * statement no entry applies to — a static site's bucket, whose config
136
+ * has no versioning key — is cured by the tier alone.
94
137
  */
95
- cure: string;
138
+ cure: PolicyCure;
96
139
  /** A high-severity finding is softened only with this per-row opt-in (§ 5c); critical never is. */
97
140
  allowHigh?: true;
98
141
  }
99
142
  export interface MitigatedPolicyRow {
100
143
  ruleId: string;
144
+ /** The CloudFormation resource type the control covers: one of the rule's `resourceTypes`. */
145
+ kind: PolicyResourceKind;
101
146
  resource?: PolicyResourceScope;
102
147
  /**
103
148
  * The resource the control covers — a short noun phrase ("cold-tier
@@ -187,6 +232,26 @@ export interface PolicyRowPresentation {
187
232
  export declare function policyRowPresentation(match: PolicyRowMatch): PolicyRowPresentation;
188
233
  /** The cause an expected row's findings are grouped under: the knob, and the construct it sits on. */
189
234
  export declare function expectedRowCause(row: ExpectedPolicyRow): string;
235
+ /** One family's cure, read back from a row: the edit, and for `pattern` the pattern types it applies to. */
236
+ export interface PolicyCureEntry {
237
+ readonly family: CodemodStatementType;
238
+ readonly edit: string;
239
+ /** Present on the `pattern` family only. */
240
+ readonly patterns?: readonly PatternType[];
241
+ }
242
+ /** A row's cure entries, one per statement family, in the vocabulary's order. */
243
+ export declare function cureEntries(row: ExpectedPolicyRow): readonly PolicyCureEntry[];
244
+ /**
245
+ * The edits a row's cure names, as one phrase: the edit alone where one
246
+ * family carries the resource and that family is not `pattern`, else each
247
+ * edit tagged with the statement it is written on and joined by "or" —
248
+ * `versioned: true (storage resource) or storage.media.versioned: true
249
+ * (payload or nextjs pattern resource)`. A pattern cure is always tagged:
250
+ * its edit applies to some pattern types and not others, and the tag is
251
+ * what says which. Worded once for the cure clause and the lane-reopen
252
+ * reason.
253
+ */
254
+ export declare function cureEdits(row: ExpectedPolicyRow): string;
190
255
  /** The cure clause of an expected row, worded once for the reason, the digest and the asset panel. */
191
256
  export declare function cureClause(row: ExpectedPolicyRow): string;
192
257
  /** The evidence clause of a mitigated row, worded once for the reason, the digest and the asset panel. */
@@ -1 +1 @@
1
- var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{KMS_RETENTION_TAG_KEY as b,S3_VERSIONING_EXEMPT_TAG_KEY as d,S3_VERSIONING_EXEMPTIONS as m}from"../aws/index.js";import{ISSUE_STATUS_LABELS as I}from"./issueStatusLabels.js";const y=["development","staging"];function C(e){return y.includes(e)}r(C,"isExpectedLaneStage");const E=[{ruleId:"SNS_TOPIC_NOT_ENCRYPTED",resource:{logicalIdContains:"AlarmTopicAlarmNotificationsAlarmNotificationsTopic"},knob:{name:"alarmTopicEncryption",construct:"the alarm topic"},allowHigh:!0,expectation:"the alarm topic is unencrypted because alarmTopicEncryption is off (a customer-managed key costs about $1 to $3 a month per account and region)",cure:"alarmTopicEncryption: true"},{ruleId:"SNS_TOPIC_NOT_ENCRYPTED",knob:{name:"backup.tier",construct:"the app"},tierDefault:"customerManagedKeys",allowHigh:!0,expectation:"the topic is unencrypted because the app's backup tier does not encrypt topics (the enterprise tier's shared customer-managed key costs about $1 to $3 a month per stack)",cure:'encryption: "SSE_KMS"'},{ruleId:"ECS_CONTAINER_INSIGHTS_DISABLED",knob:{name:"backup.tier",construct:"the app"},tierDefault:"containerInsights",expectation:"Container Insights is off because the app's backup tier does not enable it (it bills per metric per cluster)",cure:"containerInsights: true"},{ruleId:"S3_VERSIONING_DISABLED",knob:{name:"backup.tier",construct:"the app"},tierDefault:"s3Versioned",expectation:"bucket versioning is off because the app's backup tier does not version buckets (every overwrite would keep a billed copy)",cure:"versioned: true"}],k={"build-output":{name:"build-output bucket",control:"the bucket holds OpenNext build output that every deploy rewrites and prunes, so versioning would retain a billed copy of each superseded build; the build is reproduced from the commit that made it",site:"createAssetsBucket in fjall/components/infrastructure/lib/patterns/aws/payload.ts",stamped:"on the assets bucket it leaves unversioned"},"isr-cache":{name:"ISR cache bucket",control:"the bucket is the ISR cache Next.js regenerates at runtime, so versioning would retain a billed copy of every regenerated page; a lost entry is rendered again on the next request",site:"createCacheBucket in fjall/components/infrastructure/lib/patterns/aws/payload.ts",stamped:"on the cache bucket it leaves unversioned"},"cold-tier":{name:"cold-tier bucket",control:"the cold tier is MergeTree tiered storage owned by the database, so parts are written and deleted continuously and object versioning would retain every deleted part at unbounded cost; recoverability is the ClickHouse backup lane",site:"ClickHouseColdTierBucket in fjall/components/infrastructure/lib/patterns/aws/clickhouseDatabase.ts",stamped:"(versioned: false by design), the versioned backup bucket beside it"},"access-logs":{name:"access-log bucket",control:"the bucket is the stack's access-log delivery target (application load balancer and CloudFront access logs, expired after 90 days by its lifecycle rule), so versioning would only multiply expired objects",site:"stackAccessLogBucket in fjall/components/infrastructure/lib/resources/aws/logging/accessLogBucket.ts",stamped:"(versioned: false, ACCESS_LOG_RETENTION_DAYS expiry)"}},h=[...m.map(e=>{const{name:t,control:n,site:i,stamped:a}=k[e];return{ruleId:"S3_VERSIONING_DISABLED",resource:{tag:{key:d,value:e}},name:t,control:n,evidence:`${i} stamps ${d}=${e} ${a}`}}),{ruleId:"KMS_KEY_ORPHAN",resource:{tag:{key:b}},name:"retained customer key",control:"the key was retained on purpose (D17) so existing ciphertext stays decryptable; scheduling deletion would render that data unreadable",evidence:"CustomerManagedKey in fjall/components/infrastructure/lib/resources/aws/secrets/kms.ts stamps fjall:kms:retention with the protects scope on every retained key"}],l={expected:E,mitigated:h};function p(e,t){const n=e.resource;if(n===void 0)return!1;if("logicalIdContains"in n)return t.logicalId!==null&&t.logicalId.includes(n.logicalIdContains);if(t.tags===null)return!1;const i=t.tags[n.tag.key];return i!==void 0&&(n.tag.value===void 0||i===n.tag.value)}r(p,"scopeMatches");function O(e,t=l){const n=t.mitigated.filter(o=>o.ruleId===e.ruleId),i=t.expected.filter(o=>o.ruleId===e.ruleId),a=n.find(o=>p(o,e));if(a!==void 0)return{lane:"mitigated",row:a};const s=i.find(o=>p(o,e));if(s!==void 0)return{lane:"expected",row:s};const c=n.find(o=>o.resource===void 0);if(c!==void 0)return{lane:"mitigated",row:c};const u=i.find(o=>o.resource===void 0);return u===void 0?void 0:{lane:"expected",row:u}}r(O,"findPolicyRow");function S(e){return e===void 0?"":"logicalIdContains"in e?e.logicalIdContains:e.tag.value===void 0?`tag:${e.tag.key}`:`tag:${e.tag.key}=${e.tag.value}`}r(S,"scopeKey");function f(e){return`${e.lane}:${e.row.ruleId}:${S(e.row.resource)}`}r(f,"policyRowKey");function R(e,t=l){const n=t.expected.find(a=>f({lane:"expected",row:a})===e);if(n!==void 0)return{lane:"expected",row:n};const i=t.mitigated.find(a=>f({lane:"mitigated",row:a})===e);return i===void 0?void 0:{lane:"mitigated",row:i}}r(R,"findPolicyRowByKey");function A(e){return e.lane==="expected"?{cause:x(e.row),cure:_(e.row)}:{cause:e.row.name,evidence:w(e.row)}}r(A,"policyRowPresentation");function x(e){return`${e.knob.name} on ${e.knob.construct}`}r(x,"expectedRowCause");function _(e){return`Cure: set ${e.cure} in your infrastructure code.`}r(_,"cureClause");function w(e){return`Evidence: ${e.evidence}.`}r(w,"evidenceClause");function $(e,t){const n=I[e].heading;return t===null?n:`${n} \u2014 ${t}`}r($,"laneGroupHeading");export{l as DEFAULT_POLICY_REGISTRY,y as EXPECTED_LANE_STAGES,E as EXPECTED_POLICY,h as MITIGATED_POLICY,_ as cureClause,w as evidenceClause,x as expectedRowCause,O as findPolicyRow,R as findPolicyRowByKey,C as isExpectedLaneStage,$ as laneGroupHeading,f as policyRowKey,A as policyRowPresentation};
1
+ var g=Object.defineProperty;var i=(e,t)=>g(e,"name",{value:t,configurable:!0});import{KMS_RETENTION_TAG_KEY as m,S3_VERSIONING_EXEMPT_TAG_KEY as d,S3_VERSIONING_EXEMPTIONS as b}from"../aws/index.js";import{OPENNEXT_PATTERN_TYPES as E}from"../patterns/patternTypes.js";import{CODEMOD_STATEMENT_TYPES as S}from"../codemod/statementTypes.js";import{ISSUE_STATUS_LABELS as k}from"./issueStatusLabels.js";const y=["development","staging"];function M(e){return y.includes(e)}i(M,"isExpectedLaneStage");const I=[{ruleId:"SNS_TOPIC_NOT_ENCRYPTED",kind:"AWS::SNS::Topic",resource:{logicalIdContains:"AlarmTopicAlarmNotificationsAlarmNotificationsTopic"},knob:{name:"alarmTopicEncryption",construct:"the alarm topic"},allowHigh:!0,expectation:"the alarm topic is unencrypted because alarmTopicEncryption is off (a customer-managed key costs about $1 to $3 a month per account and region)",cure:{organisation:"alarmTopicEncryption: true"}},{ruleId:"SNS_TOPIC_NOT_ENCRYPTED",kind:"AWS::SNS::Topic",knob:{name:"backup.tier",construct:"the app"},tierDefault:"customerManagedKeys",allowHigh:!0,expectation:"the topic is unencrypted because the app's backup tier does not encrypt topics (the enterprise tier's shared customer-managed key costs about $1 to $3 a month per stack)",cure:{messaging:'encryption: "SSE_KMS"'}},{ruleId:"ECS_CONTAINER_INSIGHTS_DISABLED",kind:"AWS::ECS::Cluster",knob:{name:"backup.tier",construct:"the app"},tierDefault:"containerInsights",expectation:"Container Insights is off because the app's backup tier does not enable it (it bills per metric per cluster)",cure:{compute:"containerInsights: true"}},{ruleId:"S3_VERSIONING_DISABLED",kind:"AWS::S3::Bucket",knob:{name:"backup.tier",construct:"the app"},tierDefault:"s3Versioned",expectation:"bucket versioning is off because the app's backup tier does not version buckets (every overwrite would keep a billed copy)",cure:{storage:"versioned: true",pattern:{edit:"storage.media.versioned: true",patterns:E}}}],h={"build-output":{name:"build-output bucket",control:"the bucket holds OpenNext build output that every deploy rewrites and prunes, so versioning would retain a billed copy of each superseded build; the build is reproduced from the commit that made it",site:"createAssetsBucket in fjall/components/infrastructure/lib/patterns/aws/payload.ts",stamped:"on the assets bucket it leaves unversioned"},"isr-cache":{name:"ISR cache bucket",control:"the bucket is the ISR cache Next.js regenerates at runtime, so versioning would retain a billed copy of every regenerated page; a lost entry is rendered again on the next request",site:"createCacheBucket in fjall/components/infrastructure/lib/patterns/aws/payload.ts",stamped:"on the cache bucket it leaves unversioned"},"cold-tier":{name:"cold-tier bucket",control:"the cold tier is MergeTree tiered storage owned by the database, so parts are written and deleted continuously and object versioning would retain every deleted part at unbounded cost; recoverability is the ClickHouse backup lane",site:"ClickHouseColdTierBucket in fjall/components/infrastructure/lib/patterns/aws/clickhouseDatabase.ts",stamped:"(versioned: false by design), the versioned backup bucket beside it"},"access-logs":{name:"access-log bucket",control:"the bucket is the stack's access-log delivery target (application load balancer and CloudFront access logs, expired after 90 days by its lifecycle rule), so versioning would only multiply expired objects",site:"stackAccessLogBucket in fjall/components/infrastructure/lib/resources/aws/logging/accessLogBucket.ts",stamped:"(versioned: false, ACCESS_LOG_RETENTION_DAYS expiry)"}},T=[...b.map(e=>{const{name:t,control:n,site:r,stamped:o}=h[e];return{ruleId:"S3_VERSIONING_DISABLED",kind:"AWS::S3::Bucket",resource:{tag:{key:d,value:e}},name:t,control:n,evidence:`${r} stamps ${d}=${e} ${o}`}}),{ruleId:"KMS_KEY_ORPHAN",kind:"AWS::KMS::Key",resource:{tag:{key:m}},name:"retained customer key",control:"the key was retained on purpose (D17) so existing ciphertext stays decryptable; scheduling deletion would render that data unreadable",evidence:"CustomerManagedKey in fjall/components/infrastructure/lib/resources/aws/secrets/kms.ts stamps fjall:kms:retention with the protects scope on every retained key"}],l={expected:I,mitigated:T};function p(e,t){const n=e.resource;if(n===void 0)return!1;if("logicalIdContains"in n)return t.logicalId!==null&&t.logicalId.includes(n.logicalIdContains);if(t.tags===null)return!1;const r=t.tags[n.tag.key];return r!==void 0&&(n.tag.value===void 0||r===n.tag.value)}i(p,"scopeMatches");function L(e,t=l){const n=t.mitigated.filter(a=>a.ruleId===e.ruleId),r=t.expected.filter(a=>a.ruleId===e.ruleId),o=n.find(a=>p(a,e));if(o!==void 0)return{lane:"mitigated",row:o};const s=r.find(a=>p(a,e));if(s!==void 0)return{lane:"expected",row:s};const c=n.find(a=>a.resource===void 0);if(c!==void 0)return{lane:"mitigated",row:c};const u=r.find(a=>a.resource===void 0);return u===void 0?void 0:{lane:"expected",row:u}}i(L,"findPolicyRow");function _(e){return e===void 0?"":"logicalIdContains"in e?e.logicalIdContains:e.tag.value===void 0?`tag:${e.tag.key}`:`tag:${e.tag.key}=${e.tag.value}`}i(_,"scopeKey");function f(e){return`${e.lane}:${e.row.ruleId}:${_(e.row.resource)}`}i(f,"policyRowKey");function G(e,t=l){const n=t.expected.find(o=>f({lane:"expected",row:o})===e);if(n!==void 0)return{lane:"expected",row:n};const r=t.mitigated.find(o=>f({lane:"mitigated",row:o})===e);return r===void 0?void 0:{lane:"mitigated",row:r}}i(G,"findPolicyRowByKey");function K(e){return e.lane==="expected"?{cause:x(e.row),cure:v(e.row)}:{cause:e.row.name,evidence:A(e.row)}}i(K,"policyRowPresentation");function x(e){return`${e.knob.name} on ${e.knob.construct}`}i(x,"expectedRowCause");function N(e){const t=[];for(const n of S){if(n==="pattern"){const o=e.cure.pattern;o!==void 0&&t.push({family:n,edit:o.edit,patterns:o.patterns});continue}const r=e.cure[n];r!==void 0&&t.push({family:n,edit:r})}return t}i(N,"cureEntries");function w({family:e,patterns:t}){return t===void 0?`${e} resource`:`${t.join(" or ")} ${e} resource`}i(w,"cureEntryTag");function C(e){const t=N(e),[n]=t;return t.length===1&&n!==void 0&&n.patterns===void 0?n.edit:t.map(r=>`${r.edit} (${w(r)})`).join(" or ")}i(C,"cureEdits");function v(e){return`Cure: set ${C(e)} in your infrastructure code.`}i(v,"cureClause");function A(e){return`Evidence: ${e.evidence}.`}i(A,"evidenceClause");function B(e,t){const n=k[e].heading;return t===null?n:`${n} \u2014 ${t}`}i(B,"laneGroupHeading");export{l as DEFAULT_POLICY_REGISTRY,y as EXPECTED_LANE_STAGES,I as EXPECTED_POLICY,T as MITIGATED_POLICY,v as cureClause,C as cureEdits,N as cureEntries,A as evidenceClause,x as expectedRowCause,L as findPolicyRow,G as findPolicyRowByKey,M as isExpectedLaneStage,B as laneGroupHeading,f as policyRowKey,K as policyRowPresentation};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The CloudFormation resource types the policy registries speak for: the
3
+ * `kind` every row carries, and the vocabulary a create surface's footprint
4
+ * is expressed in (`expectedRowsAtDefault`). Drawn from `AWS_RESOURCE_TYPES`
5
+ * — the literals are CloudFormation's own (`AWS::S3::Bucket`), the strings
6
+ * the scan stores as an asset's resource type and a webapp rule lists in its
7
+ * `resourceTypes` — so a row's kind is checked against its rule by equality,
8
+ * never through a mapping. A kind joins the tuple when a row needs it; a
9
+ * footprint can only name a kind some row can match.
10
+ */
11
+ export declare const POLICY_RESOURCE_KINDS: readonly ["AWS::S3::Bucket", "AWS::ECS::Cluster", "AWS::SNS::Topic", "AWS::KMS::Key"];
12
+ export type PolicyResourceKind = (typeof POLICY_RESOURCE_KINDS)[number];
13
+ /**
14
+ * The resource kinds a workload synthesises, projected onto the registry's
15
+ * vocabulary: what a plan's entries or a pattern's construct produce, as
16
+ * CloudFormation types the registry has a row for. Required wherever a
17
+ * consent surface lists rows — an unknown footprint is not an empty one.
18
+ */
19
+ export type PolicyResourceFootprint = ReadonlySet<PolicyResourceKind>;
20
+ export declare function isPolicyResourceKind(value: unknown): value is PolicyResourceKind;
21
+ /**
22
+ * Projects CloudFormation resource types — a synthesised template's, a
23
+ * plan's — onto the registry vocabulary, dropping every type no row speaks
24
+ * for. The one projection the constructs' footprint test, the generator's
25
+ * plan footprint and the create surfaces share.
26
+ */
27
+ export declare function policyResourceFootprint(resourceTypes: Iterable<string>): PolicyResourceFootprint;
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty;var r=(o,t)=>e(o,"name",{value:t,configurable:!0});import{AWS_RESOURCE_TYPES as n}from"../aws/resourceTypes.js";const E=[n.S3_BUCKET,n.ECS_CLUSTER,n.SNS_TOPIC,n.KMS_KEY],i=new Set(E);function _(o){return typeof o=="string"&&i.has(o)}r(_,"isPolicyResourceKind");function s(o){const t=new Set;for(const S of o)_(S)&&t.add(S);return t}r(s,"policyResourceFootprint");export{E as POLICY_RESOURCE_KINDS,_ as isPolicyResourceKind,s as policyResourceFootprint};
@@ -11,16 +11,23 @@
11
11
  */
12
12
  import { type BackupTier } from "../backupTiers.js";
13
13
  import { type ExpectedPolicyRow } from "./registry.js";
14
+ import type { PolicyResourceFootprint } from "./resourceKinds.js";
14
15
  /**
15
16
  * The K1 rows a backup tier leaves at the default that produces the finding,
16
- * in registry order. An undefined tier reads the `standard` column, as the
17
- * constructs do. Only rows keyed on the backup tier are lines: creating the
18
- * app is what produces them. A row on another knob — the alarm topic's
19
- * account-level `alarmTopicEncryption`, scoped to the Account's own
20
- * construct — is produced by the account, not by the app being created, so
21
- * it is not a line at any tier.
17
+ * on a resource the workload synthesises, in registry order. An undefined
18
+ * tier reads the `standard` column, as the constructs do. Only rows keyed on
19
+ * the backup tier are lines: creating the app is what produces them. A row
20
+ * on another knob — the alarm topic's account-level `alarmTopicEncryption`,
21
+ * scoped to the Account's own construct — is produced by the account, not
22
+ * by the app being created, so it is not a line at any tier. And only rows
23
+ * whose `kind` is in the footprint — the CloudFormation resource types the
24
+ * plan or the pattern produces, projected onto `POLICY_RESOURCE_KINDS` — are
25
+ * lines: an app with no topic is not told about the topic row, a pattern
26
+ * with no cluster not about Container Insights. The footprint is required,
27
+ * not defaulted: an unknown footprint is not an empty one, so a caller that
28
+ * cannot derive it fails typecheck rather than list nothing.
22
29
  */
23
- export declare function expectedRowsAtDefault(backupTier: BackupTier | undefined, registry?: readonly ExpectedPolicyRow[]): ExpectedPolicyRow[];
30
+ export declare function expectedRowsAtDefault(backupTier: BackupTier | undefined, footprint: PolicyResourceFootprint, registry?: readonly ExpectedPolicyRow[]): ExpectedPolicyRow[];
24
31
  /** One line of the note: the cause, the finding as it is listed, and the registry's own expectation words. */
25
32
  export interface TierConsentLine {
26
33
  /** `policyRowKey` of the expected row: the line's identity. */