/** * The expected and mitigated lanes of the alert policy (design * 2026-09-01-tier-aware-alert-policy § 13). Two static registries keyed by * rule id, client-safe (no node built-ins) and shared by every surface that * speaks for a finding: the webapp's scan predicate, asset panel, digest and * export, and the CLI's create-time consent note. * * - `EXPECTED_POLICY`: findings a construct knob's default deliberately * produces on Fjall-managed infrastructure. Softened to `expected` only on * a development- or staging-stage account (`EXPECTED_LANE_STAGES`) of the * account tier; on every other stage, on an organisation- or platform-tier * account, and for an organisation that set posture to strict, they flag. * - `MITIGATED_POLICY`: findings a compensating control already meets * (§ 13.4a). Never a finding at any stage or tier, counted as met in both * scores, visible with the evidence pointer. * * Everything unlisted flags, and every predicate failure flags * (fail-toward-flag). The predicate is the webapp's, server-side * (`app/.server/services/compliance-rules/expectedPolicy.ts`): it reads the * scan's policy context, which no client has. The rule ids are the webapp's * rule registry's; its tests cross-check every row against it, and against * the remediation edit each cure names. */ import type { AccountStage } from "../environments.js"; import { type PatternType } from "../patterns/patternTypes.js"; import type { BackupTierDefaults } from "../backupTiers.js"; import { type CodemodStatementType } from "../codemod/statementTypes.js"; import type { MachineLaneStatus } from "./machineLane.js"; import type { NonEmptyPartial } from "../nonEmptyPartial.js"; import type { PolicyResourceKind } from "./resourceKinds.js"; /** Account stages on which an expected-lane finding is softened. */ export declare const EXPECTED_LANE_STAGES: readonly ["development", "staging"]; export type ExpectedLaneStage = (typeof EXPECTED_LANE_STAGES)[number]; export declare function isExpectedLaneStage(stage: AccountStage): stage is ExpectedLaneStage; /** * Narrows a row to the resources it speaks for, by one of two signals: * * - `logicalIdContains`: CloudFormation tags each resource with its logical * id, which the scan stores as `iacInfo.logicalId`; the id is the construct * path joined with a trailing hash, so the row matches on the construct's * own id as a substring. Only for an id nothing but that construct can * carry — a fragment a customer's own construct could share * (`assetsBucket`) would match their production uploads bucket. * - `tag`: a tag the construct stamps on the resource, read from the asset's * stored tags. The key alone matches on presence; with `value`, on that * value. An asset whose tags the scan has never read (`tags: null`) * matches nothing, and a partial read keeps the prior tags, so a throttled * scan neither gains nor loses a row here. */ export type PolicyResourceScope = { logicalIdContains: string; } | { tag: { key: string; value?: string; }; }; /** * A `BACKUP_TIER_DEFAULTS` column whose `false` produces a K1 finding: the * knob the app's backup tier owns and the construct reads through * `tierDefault`. Boolean columns only — the K1 rows are cost-saving defaults * that are off, so "off" is the one polarity the registry speaks. */ type BackupTierFindingKnob = { [K in keyof BackupTierDefaults]: BackupTierDefaults[K] extends boolean ? K : never; }[keyof BackupTierDefaults]; /** * A cure on the `pattern` family. A pattern statement is one of several * pattern types, each with a config schema of its own, so the edit names the * pattern types whose config carries its path: the media bucket is an * OpenNext resource, and a static site's config has no `storage` key at * all. The generator's modify gate refuses the edit on any other type, and * the webapp offers it only where it applies. */ export interface PatternCure { /** The edit as the user writes it on the pattern statement: `storage.media.versioned: true`. */ readonly edit: string; /** The pattern types whose config carries the edit's path; never empty. */ readonly patterns: readonly PatternType[]; } /** The cure on one family: the edit alone, or for `pattern` the edit with the pattern types it applies to. */ export type PolicyCureEdit = Family extends "pattern" ? PatternCure : string; /** * The cure per family of statement the finding's resource can belong to, * keyed by the codemod's vocabulary: a `NonEmptyPartial` record, so `{}` is * unrepresentable and a key the codemod cannot address fails typecheck. * Each value is the edit as the user writes it in `infrastructure.ts` on a * statement of that family (`versioned: true` on a `storage` statement, * `storage.media.versioned: true` on the `pattern` statement that owns the * bucket, scoped to the pattern types that carry the path). */ export type PolicyCure = NonEmptyPartial<{ [Family in CodemodStatementType]: PolicyCureEdit; }>; export interface ExpectedPolicyRow { ruleId: string; /** * The CloudFormation resource type the row's findings sit on: one of the * rule's `resourceTypes` (the webapp's parity test pins it), and what a * create surface's footprint is matched against (`./tierConsent`). */ kind: PolicyResourceKind; /** Present on a resource-scoped row: for that resource a scoped row wins over the rule's unscoped row. */ resource?: PolicyResourceScope; /** The knob whose default causes the finding: what the user sets, and on which construct. Groups findings by cause on the asset page. */ knob: { name: string; construct: string; }; /** * Where the knob is a tier (`backup.tier`): the tier-defaults column that * decides the finding, `false` producing it. The create-app wizard and * the CLI's create flow read it to list, for a chosen tier, the rows the * tier leaves at that default (`./tierConsent`); a row without one is * produced at every tier. */ tierDefault?: BackupTierFindingKnob; /** * What the knob's default chose and why it is cheaper. A lower-case clause * that reads after "Expected on a development-stage account: ", no * trailing full stop. */ expectation: string; /** * The cure exactly as the user writes it in `infrastructure.ts`, per * family of statement the resource can belong to (`PolicyCure`). Each * entry is the one edit the rule's RemediationHint applies for that * family, so the clause and the Remediate button name the same change * (`${edit.property}: ${formatEditValue(edit.value)}` — a string value * quoted, as the PR writes it — pinned per family, and for `pattern` per * applicable pattern type, by the webapp's expectedPolicy.test). Where * the knob is a tier (`backup.tier`) the cure is deliberately the * per-construct override the remediation lane writes, not the tier: the * heading names the cause, the cure names the edit. A resource whose * statement no entry applies to — a static site's bucket, whose config * has no versioning key — is cured by the tier alone. */ cure: PolicyCure; /** A high-severity finding is softened only with this per-row opt-in (§ 5c); critical never is. */ allowHigh?: true; } export interface MitigatedPolicyRow { ruleId: string; /** The CloudFormation resource type the control covers: one of the rule's `resourceTypes`. */ kind: PolicyResourceKind; resource?: PolicyResourceScope; /** * The resource the control covers — a short noun phrase ("cold-tier * bucket"; capitals only for an acronym, "ISR cache bucket") that reads * after "Mitigated — " as the row's cause on every surface that heads its * group. The S3 rows take it from their exemption control, so a stamp * value without a name fails typecheck like one without a control. */ name: string; /** The compensating control. A lower-case clause that reads after "Mitigated: ", no trailing full stop. */ control: string; /** Where a reader verifies the control. */ evidence: string; allowHigh?: true; } /** * The K1 rows: findings a construct's cost-saving default produces on * Fjall-managed infrastructure by design. A rule two constructs produce for * two causes carries two rows — the construct with a fixed path scoped by * its logical id, the open set unscoped — and the scoped row wins. */ export declare const EXPECTED_POLICY: readonly ExpectedPolicyRow[]; /** * The K4 rows: a shape Fjall's own engineering chose for one construct at * every tier, met by a compensating control. Each is scoped to the resources * its construct produces (`PolicyResourceScope`); precedence among scoped * rows is `findPolicyRow`'s. The S3 rows are one per value of the * versioning-exemption stamp, in the stamp's own order (the pinned wire * order, not a precedence — a tag carries one value), and the stamp is the * whole contract: a bucket a construct left unversioned before the stamp * existed (constructs < 33.1.0) falls to the K1 row until it is redeployed — * an existing mitigated row reopens as `no_mitigation_row`, the reason the * customer reads — because the stamp is what tells posture the omission is * by design. */ export declare const MITIGATED_POLICY: readonly MitigatedPolicyRow[]; export interface PolicyRegistry { expected: readonly ExpectedPolicyRow[]; mitigated: readonly MitigatedPolicyRow[]; } export declare const DEFAULT_POLICY_REGISTRY: PolicyRegistry; export type PolicyRowMatch = { lane: Extract; row: ExpectedPolicyRow; } | { lane: Extract; row: MitigatedPolicyRow; }; export interface PolicyRowLookup { ruleId: string; /** `iacInfo.logicalId`: the CloudFormation logical id the resource carries, or `null`. */ logicalId: string | null; /** The asset's stored tags, or `null` when the scan has never read them — a tag-scoped row then matches nothing. */ tags: Readonly> | null; } /** * The row that speaks for this finding. A resource-scoped row wins over an * unscoped one across both registries; at equal scope a mitigated row wins * over an expected one, a compensating control being the stronger claim; * among scoped rows of one lane, registry order. Nothing matches: the rule * flags. */ export declare function findPolicyRow(input: PolicyRowLookup, registry?: PolicyRegistry): PolicyRowMatch | undefined; /** * The identity of a registry row, stable across the wire: lane, rule and * scope. `findPolicyRowByKey` inverts it. The pair is what lets a finding * carry its row to the client as one string and the digest fold a lane's * findings under one heading per row — a cause with many findings reads as * one cause, the cure or the evidence written once. */ export declare function policyRowKey(match: PolicyRowMatch): string; /** The row a `policyRowKey` names, or `undefined` once the registry no longer carries it. */ export declare function findPolicyRowByKey(key: string, registry?: PolicyRegistry): PolicyRowMatch | undefined; /** * What a surface writes once for a row's group — the cause its heading * carries (an expected row's knob, and the construct it sits on; a * mitigated row's `name`, the resource its control covers) and the cure or * the evidence. Derived here so the overview digest (server) and the asset * panel (client) present a row identically. Every registry row has a cause; * `cause` is null only where a surface presents an uncovered group itself. */ export interface PolicyRowPresentation { cause: string | null; cure?: string; evidence?: string; } export declare function policyRowPresentation(match: PolicyRowMatch): PolicyRowPresentation; /** The cause an expected row's findings are grouped under: the knob, and the construct it sits on. */ export declare function expectedRowCause(row: ExpectedPolicyRow): string; /** One family's cure, read back from a row: the edit, and for `pattern` the pattern types it applies to. */ export interface PolicyCureEntry { readonly family: CodemodStatementType; readonly edit: string; /** Present on the `pattern` family only. */ readonly patterns?: readonly PatternType[]; } /** A row's cure entries, one per statement family, in the vocabulary's order. */ export declare function cureEntries(row: ExpectedPolicyRow): readonly PolicyCureEntry[]; /** * The edits a row's cure names, as one phrase: the edit alone where one * family carries the resource and that family is not `pattern`, else each * edit tagged with the statement it is written on and joined by "or" — * `versioned: true (storage resource) or storage.media.versioned: true * (payload or nextjs pattern resource)`. A pattern cure is always tagged: * its edit applies to some pattern types and not others, and the tag is * what says which. Worded once for the cure clause and the lane-reopen * reason. */ export declare function cureEdits(row: ExpectedPolicyRow): string; /** The cure clause of an expected row, worded once for the reason, the digest and the asset panel. */ export declare function cureClause(row: ExpectedPolicyRow): string; /** The evidence clause of a mitigated row, worded once for the reason, the digest and the asset panel. */ export declare function evidenceClause(row: MitigatedPolicyRow): string; /** * The heading a lane's findings sit under, on the overview digest, the * asset panel and the export alike: the lane's own heading from the * status-label table (the two lanes share no word), then the cause — an * expected row's knob, a mitigated row's resource. Only an uncovered group * has no cause, so only it is headed by the lane word alone. */ export declare function laneGroupHeading(lane: MachineLaneStatus, cause: string | null): string; export {};