/** * Node-only I/O helpers for the Fjall manifest. * * This module imports `fs/promises` and `path` and is therefore Node-only. * The pure schema/types live in the sibling `./schemas` module — consumers * that need the docker-shape types without I/O (e.g. the generator) MUST * import from `./schemas` directly. */ import { type DockerBuild, type EngineCompat, type FjallManifest, type LambdaArchitecture, type ResourceMapEntry } from "./schemas.js"; /** * Get the manifest file path for a cdk.out directory. */ export declare function getManifestFilePath(cdkOutPath: string): string; /** * Completeness-signalled manifest read: consumers whose verdicts authorise * or withhold work (identity-pin candidate collection, compatibility gates) * must distinguish a genuinely absent manifest from one that is present but * unreadable — collapsing both to "no manifest" turns a corrupt assembly * into a clean "nothing to do" verdict. `detail` is pre-masked at * construction. */ export type ManifestFileReadOutcome = { status: "ok"; manifest: FjallManifest; } | { status: "absent"; } | { status: "unreadable"; detail: string; }; export declare function readManifestFileOutcome(cdkOutPath: string): Promise; /** * Read manifest file from cdk.out directory. * Returns null if file doesn't exist or is corrupt. */ export declare function readManifestFile(cdkOutPath: string): Promise; /** * Write manifest file atomically using temp file + rename pattern. */ export declare function writeManifestFile(cdkOutPath: string, manifest: FjallManifest): Promise; /** * Create an empty manifest structure. */ export declare function createEmptyManifest(appName: string): FjallManifest; /** * Read the construct map from the fjall-manifest.json file. * Returns an empty map if the file doesn't exist, is malformed, * or has no resourceMap section. Never throws. */ export declare function readConstructMap(cdkOutPath: string): Promise>; /** * A service entry from the manifest with its `docker` block guaranteed present. * * Used by deploy-core to gate Docker pre-build steps. Entries without a * `docker` block (e.g. lambda-only manifests, or future role types that * carry no Dockerfile) are filtered out — the deploy-core call sites only * care about the build set, so the parser narrows here. */ export interface ManifestDockerService { readonly name: string; readonly docker: DockerBuild; /** * True when this entry is a container-Lambda image (from `manifest.lambdas`) * rather than an ECS service (from `manifest.services`). Build groups * containing a Lambda entry must build without provenance/SBOM attestations * — canonical rationale at `DockerBuildParams.targetsLambda` * (`@fjall/deploy-core` dockerInterface.ts). */ readonly isLambda?: boolean; /** * The Lambda's CPU architecture (absent for ECS services and for * bring-your-own-image Lambdas). Lets the Docker build pipeline pick a * `buildx --platform` matching the function's configured `Architectures` * instead of always building the default arm64 — see * `dockerPlatformForArchitecture` in `@fjall/util/docker`. */ readonly architecture?: LambdaArchitecture; /** * The ECS cluster the service is placed in — the construct id of the * cluster, which is also its physical cluster name and the token the * `DeployableService` export is built from. Present for every service the * compute pattern records (constructs >= 0.87.0); absent for a manifest * written by an older assembly, in which case the deploy engine cannot * bind the entry to a live service by name and must say so. */ readonly clusterName?: string; } /** * The engine-compatibility envelope, hand-walked from the manifest rather than * strict-parsed. The deploy-time chokepoint (`assertEngineCompatibleWithAssembly` * in deploy-core) needs to distinguish three states a strict `FjallManifest` * parse cannot express: manifest absent/malformed, manifest present with no * constraint, and constraint present. It must also TOLERATE a same-major * assembly carrying additive `engineCompat` sub-fields this build predates — * `EngineCompatSchema` is `.strict()` (the write contract), so a full strict * parse would reject such a manifest outright and turn a forward-compatible * deploy into a hard refusal. Hand-walking reads only the fields this build * understands and ignores the rest; breaking constraints always travel via * `minimumEngineVersion`, which every reader understands. */ export interface EngineCompatEnvelope { /** The manifest's top-level `version`, or undefined if absent/non-numeric. */ readonly manifestVersion: number | undefined; /** * True when the manifest carries an `engineCompat` object — even a malformed * one. Lets the verdict layer separate "present-but-malformed" (a contract * violation to refuse) from genuinely absent (no constraint → proceed). */ readonly engineCompatPresent: boolean; /** * The extracted constraint, or undefined when `engineCompat` is absent OR * malformed (missing/empty `minimumEngineVersion` — the primary gate input, * without which the constraint cannot be honoured). */ readonly engineCompat: EngineCompat | undefined; } /** * Read the engine-compatibility envelope from a cdk.out directory. Never * throws: a missing/unreadable/non-JSON manifest yields an empty envelope * (`manifestVersion: undefined, engineCompatPresent: false`), which the verdict * layer classifies as "no constraint → proceed" — matching today's behaviour, * where a manifest that can't be read simply produces no build declarations. * * Reads synchronously to match the deploy-core chokepoint's synchronous * orchestration context; the file is small and sealed by CDK synth ahead of * the deploy phase. */ export declare function readEngineCompatEnvelope(cdkOutPath: string): EngineCompatEnvelope; /** * Read the manifest's declared `appName` from a cdk.out directory — the * assembly's own identity, which can diverge from the deploy operation's * on-disk app name (folder "web-app" vs manifest "WebApp"). Returns undefined * when the manifest is missing/unreadable or carries no usable appName. * * Reads synchronously to match the deploy-core chokepoints' synchronous * orchestration context; the file is small and sealed by CDK synth ahead of * the deploy phase. */ export declare function readManifestAppName(cdkOutPath: string): string | undefined; /** * Completeness-signalled variant of {@link readManifestAppName}: gates whose * verdicts hinge on the manifest identity (the AC-3.7 downgrade tripwire) * must distinguish an absent manifest (no identity to diverge from — folder * matching is complete) from a present-but-unreadable one (the identity * exists but cannot be proven — fail closed). `appName: undefined` on an * `ok` outcome means the manifest is readable but declares no usable name. */ export type ManifestAppNameOutcome = { status: "ok"; appName: string | undefined; } | { status: "absent"; } | { status: "unreadable"; }; export declare function readManifestAppNameOutcome(cdkOutPath: string): ManifestAppNameOutcome; /** * Extract services with a Docker build configuration from the Fjall manifest. * * The manifest is the authoritative post-synth source for Docker * configuration — `docker.path` may be absolute (cross-repo monorepo * layouts) or relative to the app root, and a filesystem-only check * cannot see absolute paths outside the app root. * * Returns an empty array if the manifest does not exist, cannot be parsed, * or contains no Docker services. Never throws. * * Reads synchronously because deploy-core's call sites are inside synchronous * orchestration loops; the file is small and sealed by CDK synth ahead of * the deploy phase. */ export declare function parseDockerServicesFromManifest(cdkOutPath: string): ManifestDockerService[]; /** * Extract Lambda container-image entries with a Docker build configuration * from the Fjall manifest, projected into the same `{name, docker}` shape * `parseDockerServicesFromManifest` returns for ECS services — this lets both * lists feed the same downstream build/grouping/tagging pipeline unchanged. * * The projected `name` is the Lambda's `imageKey` (falling back to its own * manifest `name` when absent): several Lambda functions may deliberately * share one built image, so the build/tag identity is keyed by the shared * image, not by each function's own name. Entries sharing one `imageKey` are * deduped to a single build; a divergent `docker` config across entries * sharing an `imageKey` is a real config mistake (ambiguous which build * should win) and throws rather than silently picking one. * * Returns an empty array if the manifest does not exist, cannot be parsed, or * contains no Lambda Docker entries. Lambdas without a `docker` block * (bring-your-own-image) are skipped, not an error. * * Throws deliberately (not a `Result`) on entries sharing an `imageKey` with * divergent docker configs or `architecture` even though `Result`-returning * callers sit above it: the throw is a config error surfaced before any build * starts, and every production boundary converts it — the CLI's `catch` in * `deployApplication` * (`cli/src/services/deployment/applicationDeployment.ts`), the webapp * worker's job-level `catch` in `deploymentJobHandler`, and the `fjall build` * orchestrator's call-site `catch` in `resolveLambdaBuildIdentities` * (`cli/src/services/container/EcrBuildOrchestrator.ts`). Sanctioned per the * 2026-07-16 docker-lambda review; do not re-litigate as a Pitfall-4 escape. */ export declare function parseLambdaDockerServicesFromManifest(cdkOutPath: string): ManifestDockerService[]; /** A single container-Lambda manifest entry that declared a `docker` block. */ export interface ManifestLambdaDockerEntry { /** The Lambda's own manifest/function name (NOT the shared image key). */ readonly name: string; /** Shared build/tag key — `imageKey` if set, else `name`. */ readonly imageKey: string; readonly docker: DockerBuild; readonly architecture?: LambdaArchitecture; } /** * Extract every container-Lambda manifest entry that declared a `docker` * block, WITHOUT deduping by `imageKey` — unlike * `parseLambdaDockerServicesFromManifest`, this preserves each function's own * `name`, needed to map a built image's shared `imageKey` back to the actual * Lambda function name(s) that need a fresh published version after deploy * (`lambda:PublishVersion` takes a function name, not an image key). */ export declare function parseLambdaDockerEntriesFromManifest(cdkOutPath: string): ManifestLambdaDockerEntry[]; /** * Every Docker build the post-synth manifest declares, across BOTH * independent namespaces: ECS service `docker` configs (`manifest.services`) * and container-Lambda `docker` blocks (`manifest.lambdas`). */ export interface ManifestDockerDeclarations { readonly ecsServices: ManifestDockerService[]; readonly lambdaEntries: ManifestLambdaDockerEntry[]; /** * True when either namespace declares at least one build. This is the * canonical "does this app need a Docker build?" presence gate — see * `parseDockerDeclarationsFromManifest`. */ readonly declaresBuild: boolean; } /** * Aggregate view of every Docker build declared in the manifest. Presence * gates ("should a Docker build run for this app?") MUST consume * `declaresBuild` from here rather than composing the namespace parsers * themselves: the namespaces are independent, and a gate that checks only one * silently misses apps whose builds live entirely in the other — detection * once checked only `manifest.services`, so a container-Lambda-only app never * built and fell back to welcome-image seeding. * * When adding a new docker-carrying manifest namespace, extend this aggregate * (fields + `declaresBuild`) in the same change as the build-group * composition in deploy-core's `dockerBuildHelper` — the two must agree on * emptiness, or presence and execution drift apart again. * * Never throws: both underlying parsers return `[]` on a missing or * malformed manifest. */ export declare function parseDockerDeclarationsFromManifest(cdkOutPath: string): ManifestDockerDeclarations; /** * Outcome of a schema-gate tag extraction. `"ok"` with `tags: []` means the * assembly positively declares no default-image gates (absent manifest — * pre-gate constructs — or no service entry carrying a tag); the pre-flight * may proceed without mirroring. `"manifest-unreadable"` means the manifest * EXISTS but cannot be trusted (unreadable, invalid JSON, malformed * `services`, or an invalid tag value) — the assembly may declare gates this * reader cannot see, so the fail-closed pre-flight must refuse rather than * ship a task definition referencing an unmirrored image. */ export type SchemaGateTagsOutcome = { outcome: "ok"; tags: string[]; } | { outcome: "manifest-unreadable"; reason: string; }; /** * Collect the distinct schema-gate image tags declared by the manifest's * services, sorted for determinism. A service entry carries * `schemaGateImageTag` only when the construct materialised the gate * container with the DEFAULT image (a BYO `schemaGate.image` records * nothing — its availability is the author's responsibility). The deploy * engine must ensure `fjall/schema-gate:` exists in the target * account's private ECR before any task definition referencing it ships. * * Unlike the sibling `parse*FromManifest` extractors (best-effort readers * feeding advisory surfaces), this feeds a fail-closed deploy gate, so it * distinguishes genuine absence from a failed read instead of collapsing * both to "nothing declared". Never throws. */ export declare function parseSchemaGateImageTagsFromManifest(cdkOutPath: string): SchemaGateTagsOutcome;