/** * Authoritative schema for scans uploaded to CDK Insights when the user * has opted in to scan history. * * Two consumers today: the CLI uploader (this package) and the dashboard * renderer (cdki-website). The website holds a verbatim copy of this file * at src/types/scanReport.types.ts; a CI sync check (scripts/check-schema- * sync.ts) fails the build if they diverge. Treat this file as the single * source of truth — edit here and copy across. * * Compatibility: * - Bump SCAN_REPORT_SCHEMA_VERSION on any change. * - Adding optional fields = minor bump. * - Removing fields, renaming fields, or changing field semantics = major. * - License-service stores the schemaVersion alongside each scan so the * dashboard can render older scans correctly when the schema evolves. */ export declare const SCAN_REPORT_SCHEMA_VERSION: "1.0.0"; export type ScanReportSchemaVersion = typeof SCAN_REPORT_SCHEMA_VERSION; export type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'; export type WAFPillar = 'Operational Excellence' | 'Security' | 'Cost Optimization' | 'Reliability' | 'Performance Efficiency' | 'Sustainability'; export type SeverityCounts = Record; export type WAFPillarCounts = Record; /** * Source location of a finding in the user's CDK source. Recovered from * the CDK manifest `trace` property when available; absent on older CDK * versions or for synthesised constructs without a user-side frame. */ export interface ScanReportSourceLocation { filePath: string; line: number; column: number; confidence: 'high' | 'medium' | 'low'; endLine?: number; endColumn?: number; } /** * A single finding on a single resource. Findings produced by static rules * carry a stable `ruleId`; AI-generated findings carry `aiConfidence` and * may have no `ruleId`. */ export interface ScanReportFinding { issue: string; recommendation?: string; severity: Severity; wafPillar: WAFPillar; foundBy: 'cdkInsights' | 'cdkNag' | 'validationReport'; /** Plugin name when foundBy === 'validationReport'. */ validationPluginName?: string; /** Mirror of `Issue.seenInSynth` — true when CDK's validation report * already surfaced this finding at synth time. */ seenInSynth?: boolean; ruleId?: string; codeSnippet?: string; constructPath?: string; constructType?: string; sourceLocation?: ScanReportSourceLocation; /** AI confidence 1-10. Only set on AI-generated findings. */ aiConfidence?: number; } /** * One resource within a stack. Only resources that produced at least one * finding appear in `resources`. The full set of analysed resource IDs is * captured separately via `scannedResourceIds` on the parent stack so the * dashboard can show "X resources analysed, Y with findings." */ export interface ScanReportResource { resourceId: string; logicalId?: string; cdkPath?: string; friendlyName?: string; resourceName?: string; constructType?: string; serviceCategory?: string; tags?: Record; sourceLocation?: ScanReportSourceLocation; findings: ScanReportFinding[]; } /** * One stack within a scan. A `cdk-insights analyse` run can cover multiple * stacks; each gets its own entry here. The diff engine groups by * (license, stackName) — see project_effectiveness_engine.md. */ export interface ScanReportStack { stackName: string; stackId?: string; totalResources: number; /** All resource IDs analysed in this stack, including those with zero findings. */ scannedResourceIds: string[]; severityCounts: SeverityCounts; wafPillarCounts: WAFPillarCounts; resources: ScanReportResource[]; } /** * Cross-stack rollup. Pre-computed at upload time so the scan list view * doesn't have to fetch each scan's full body to render severity totals. */ export interface ScanTotals { totalResources: number; totalFindings: number; resourcesWithFindings: number; severityCounts: SeverityCounts; wafPillarCounts: WAFPillarCounts; } export type AiSkippedReason = 'cap-reached' | 'auth' | 'local-mode' | 'unavailable'; /** * The full scan body — what gets stored in S3 and rendered on the scan * detail page in the dashboard. */ export interface ScanReport { schemaVersion: ScanReportSchemaVersion; scanId: string; cliVersion: string; scanStartedAt: string; scanCompletedAt: string; aiAnalysis: { ran: boolean; skippedReason?: AiSkippedReason; }; stacks: ScanReportStack[]; totals: ScanTotals; } /** * Upload envelope POSTed by the CLI to /v1/scans on the license service. * License-service strips the auth/identity fields and stores `report` in * S3 plus a metadata row in DynamoDB. * * `hashedLicenseId` mirrors the privacy posture of the existing telemetry * envelope (see telemetryClient.ts) — the backend never persists the raw * license key against a scan. */ export interface ScanReportEnvelope { schemaVersion: ScanReportSchemaVersion; scanId: string; hashedLicenseId: string; tier: string; cliVersion: string; platform: string; scanStartedAt: string; scanCompletedAt: string; report: ScanReport; } /** * Metadata row returned by GET /v1/scans (list endpoint) and embedded * in GET /v1/scans/{scanId}. Mirrors the row shape persisted in DDB * by CreateScan. `report` is NOT included here — fetched separately * via GET /v1/scans/{scanId} which returns a presigned S3 URL for the * body. */ export interface ScanMetadata { scanId: string; /** ISO 8601 — server-side timestamp at upload time. */ createdAt: string; /** Unix epoch seconds (DDB TTL format). UI converts to ISO for display. */ expiresAt: number; cliVersion: string; platform: string; schemaVersion: ScanReportSchemaVersion; stackNames: string[]; totals: ScanTotals; /** ISO 8601 — when the CLI started the scan. */ scanStartedAt: string; /** ISO 8601 — when the CLI finished the scan, just before upload. */ scanCompletedAt: string; /** License tier at scan time: 'free' | 'pro' | 'team' | 'trial'. */ tier: string; /** Set true when the underlying S3 object has been deleted but the metadata row hasn't been cleaned up yet. Hidden from list responses. */ deleted?: boolean; } /** * Response shape of GET /v1/scans/{scanId}. Metadata plus a short-lived * presigned URL the browser uses to fetch the report body directly from * S3 without proxying the bytes through Lambda. */ export interface ScanDetailResponse { metadata: ScanMetadata; reportUrl: string; reportUrlExpiresAt: string; }