/** * Pure Zod schemas for the Fjall build-time manifest. * * This module is the canonical, environment-agnostic source of truth for: * - DockerBuild — the universal `{ path, context?, target? }` primitive * consumed by every container-image-producing role (service today, * migrations next, future sidecars and batch jobs). * - DockerBuildPartial — the all-optional variant used by `migrations.docker` * for per-field inheritance from the parent service. * - mergeDockerBuild() — single source of truth for the merge semantics * (target-only override, full override, no override → inherit). * - The full Fjall manifest schema (services, lambdas, pattern, ecr, * stacks, resourceMap). * * Purity: this module imports only `zod`. No `fs`, no `path`, no logger. * That lets the generator (which is forbidden from doing I/O) consume * docker-shape types without dragging Node-only modules into the cloud * assembly. I/O helpers live in the sibling `./io` module. */ import { z } from "zod"; /** Manifest file name — single source of truth across CLI, deploy-core, and infrastructure. */ export declare const FJALL_MANIFEST_FILENAME = "fjall-manifest.json"; /** Current manifest schema version. */ export declare const MANIFEST_SCHEMA_VERSION: 1; /** * Highest manifest `version` this build knows how to consume. Distinct from * {@link MANIFEST_SCHEMA_VERSION} (the version this build *emits*): the two * coincide today, but the read ceiling is what the deploy-time compatibility * chokepoint (`assertEngineCompatibleWithAssembly`) compares an assembly's * `version` against to loudly refuse a structurally-newer manifest rather than * silently drop unknown top-level sections. Bump this only when a build learns * to read a newer structural schema. The `engineCompat` field added alongside * this constant is additive-optional, so it does NOT bump either version. */ export declare const MAX_SUPPORTED_MANIFEST_SCHEMA_VERSION: 1; /** * Allowed character set for a BuildKit secret id. Deliberately conservative — * NO comma, equals, or whitespace, all of which would break the * `--secret id=,src=…` argv buildx receives (`buildxArgvBuilder` joins the * id and source with a comma and an `=`). Pinned at parse time so a manifest * can never declare an id that mangles the buildx argv downstream. */ export declare const BUILDKIT_SECRET_ID_PATTERN: RegExp; /** * A single build-time SECRET reference. * * Carries a REFERENCE only (never the value): exactly one of `ssm`, * `secretsManager`, or `env` identifies where deploy-core resolves the value * just-in-time before the build. The resolved value is injected via a BuildKit * `--secret` mount (tmpfs, scoped to one `RUN --mount=type=secret`), so — unlike * `buildArgs` — it NEVER lands in `--build-arg`, an image layer, `docker history`, * or the manifest JSON. `id` is the BuildKit secret id the Dockerfile mounts * (`RUN --mount=type=secret,id=`). * * - `ssm` — an SSM Parameter Store name (resolved with decryption) * - `secretsManager` — a Secrets Manager secret by `name` XOR `arn`, with an * optional `field` to extract one key from a JSON secret value * - `env` — a build-host environment variable name (read at build time) */ export declare const DockerBuildSecretRefSchema: z.ZodObject<{ id: z.ZodString; ssm: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>; export type DockerBuildSecretRef = z.infer; /** * A single `buildArgs` value. * * Backward-compatible: a plain `string` is the common case — a public literal * baked verbatim via `--build-arg KEY=VALUE`. The object form is the * "public-but-sensitive" escape hatch (a restricted Stripe publishable key, a * URL-scoped Mapbox token, a Sentry DSN): the value MUST be inlined into the * client bundle, yet committing it as a literal would publish it to git too, so * it is sourced from an SSM/Secrets Manager reference (or a build-host env var) * and resolved just-in-time. Such a value still lands in `docker history`, * provenance/SBOM, and the `-cache` mode=max ECR repo, so the object form * carries `acknowledgePublic: true` to opt out of the bake-guard deliberately * (see `decisions/2026-04-27` / the build-time-env design § C3). * * Exactly one source (`ssm`, `secretsManager`, or `env`) is required on the * object form — the same one-source contract as `DockerBuildSecretRef`. */ export declare const DockerBuildArgValueSchema: z.ZodUnion; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>; export type DockerBuildArgValue = z.infer; /** * The universal Docker-build role primitive. * * `path` is the Dockerfile path (absolute or relative to `context`); `context` * is the build context (defaults to the directory containing `path` when * absent — that default lives in the build orchestrator, not in the schema); * `target` selects a multi-stage Dockerfile target. * * Two build-time injection channels, with a hard public/secret boundary: * * - `buildArgs` — PUBLIC, world-readable. Forwarded to * `docker buildx build --build-arg KEY=VALUE`, baked into the image, and * recorded in `docker history` + provenance. The only mechanism by which * Vite-style `import.meta.env.VITE_*` variables reach the production client * bundle (substitution at build time, so container ENV cannot reach the client). * NEVER put a credential here — a plain-string value is the common case; the * object form (`DockerBuildArgValue`) is the public-but-sensitive escape hatch * that sources from an SSM/SM ref with `acknowledgePublic: true`. * - `buildSecrets` — SECRET references resolved just-in-time and injected via * BuildKit `--secret` mounts. The value never enters `--build-arg`, an image * layer, `docker history`, provenance, or the manifest JSON — only the * REFERENCE is serialised. Use for npm tokens, private-registry creds, etc. */ export declare const DockerBuildSchema: z.ZodObject<{ path: z.ZodString; context: z.ZodOptional; target: z.ZodOptional; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>; export type DockerBuild = z.infer; /** * All-optional variant used by `migrations.docker` for per-field inheritance. * * Every field is optional; missing fields inherit from the parent service's * `docker` via `mergeDockerBuild()`. A migrations override of just * `{ target: "migrate" }` keeps the service's `path` and `context`. */ export declare const DockerBuildPartialSchema: z.ZodObject<{ path: z.ZodOptional; context: z.ZodOptional>; target: z.ZodOptional>; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>>; }, z.core.$strict>; export type DockerBuildPartial = z.infer; /** * Per-field merge of a service's `docker` and an optional migrations override. * * - When `migrationsOverride` is undefined, the service's docker is returned unchanged. * - When set, each field on the override wins; missing fields inherit from the service. * - The result preserves the structural mutex with `image` because callers only * invoke this when they have a service-side `docker` to merge against. */ export declare function mergeDockerBuild(service: DockerBuild, migrationsOverride: DockerBuildPartial | undefined): DockerBuild; /** * Normalise a candidate `docker` sub-object: empty-string fields are coerced * to undefined at the parser boundary so downstream consumers never have to * distinguish `target: ""` from `target: undefined` (AC17). * * `buildArgs` is carried through verbatim — it is the only channel by which * Vite-style `VITE_*` values reach the production client bundle, so dropping it * here ships a bundle built with no `--build-arg` flags. A value may be a plain * string (the common case) OR the public-but-sensitive object form * (`DockerBuildArgValue`); both are validated by the final `DockerBuildSchema` * safeParse below (a malformed value makes the whole docker candidate invalid, * never throwing — safeParse returns undefined). An empty map is coerced to * undefined for parity with the empty-string scalar handling. * * `buildSecrets` is carried through verbatim too — an empty array is coerced to * undefined for parity. Each ref is validated by the final `DockerBuildSchema` * safeParse below. */ export declare function normaliseDockerBuild(value: unknown): DockerBuild | undefined; /** * Canonical comparator for "identical docker configs". Both deploy-time * guards (assertNoCrossEntityBuildKeyDivergence in deploy-core's * dockerBuildHelper; parseLambdaDockerServicesFromManifest in ./io.ts) and * the synth-time guard (getOrCreateImageTagParameter in * @fjall/components-infrastructure) must agree byte-for-byte — the * fingerprint routes through normaliseDockerBuild so the empty-value * coercions and field order match on every side. */ export declare function dockerBuildFingerprint(docker: DockerBuild | undefined): string | undefined; declare const ManifestServiceSchema: z.ZodObject<{ name: z.ZodString; clusterName: z.ZodOptional; docker: z.ZodOptional; target: z.ZodOptional; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>>; containerPort: z.ZodOptional; secrets: z.ZodOptional>; ssmSecretsPath: z.ZodOptional; importedSecretNames: z.ZodOptional>; schemaGateImageTag: z.ZodOptional; }, z.core.$strict>; export type ManifestService = z.infer; declare const ManifestPatternSchema: z.ZodObject<{ type: z.ZodEnum<{ payload: "payload"; nextjs: "nextjs"; staticsite: "staticsite"; }>; name: z.ZodString; source: z.ZodString; }, z.core.$strict>; export type ManifestPattern = z.infer; declare const ManifestEcrSchema: z.ZodObject<{ repositoryName: z.ZodString; }, z.core.$strict>; export type ManifestEcr = z.infer; /** * CPU architecture values a container Lambda's `architecture` prop can take, * mirrored from CDK's `Architecture.ARM_64.name` / `Architecture.X86_64.name` * so the manifest never depends on aws-cdk-lib. Consumed by the Docker build * pipeline (`dockerPlatformForArchitecture`, `@fjall/util/docker`) to pick the * `buildx --platform` that matches the Lambda's own CPU architecture. */ export declare const LAMBDA_ARCHITECTURE_VALUES: readonly ["x86_64", "arm64"]; export type LambdaArchitecture = (typeof LAMBDA_ARCHITECTURE_VALUES)[number]; declare const ManifestLambdaSchema: z.ZodObject<{ name: z.ZodString; secrets: z.ZodOptional>; ssmSecretsPath: z.ZodOptional; importedSecretNames: z.ZodOptional>; docker: z.ZodOptional; target: z.ZodOptional; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>>; imageKey: z.ZodOptional; architecture: z.ZodOptional>; }, z.core.$strict>; export type ManifestLambda = z.infer; declare const ManifestStackHashSchema: z.ZodObject<{ templateHash: z.ZodString; synthTimestamp: z.ZodString; }, z.core.$strict>; export type ManifestStackHash = z.infer; /** * Construct-map entry shape — duplicated structurally with `ResourceMapEntry` * in `../constructMap.ts`. The two shapes must stay in lockstep; the canonical * runtime type is the schema-inferred form here, and `recordToConstructMap()` * produces a Map that is structurally compatible. */ declare const ResourceMapEntrySchema: z.ZodObject<{ constructPath: z.ZodString; group: z.ZodString; resourceType: z.ZodString; }, z.core.$strict>; export type ResourceMapEntry = z.infer; /** * Engine ↔ assembly compatibility contract, stamped into the manifest at synth * by `@fjall/components-infrastructure` (the emitter is the constructs, so the * declaration is self-honest — whatever synthesised the assembly is what * records the engine floor it needs). The deploy-time chokepoint * `assertEngineCompatibleWithAssembly` (deploy-core) reads this and refuses to * build/push/deploy an assembly the running engine is too old — or structurally * too new — to honour. * * This is the WRITE contract: `.strict()`, so the emitter cannot silently add a * field readers were never told about. The deploy-time READ path deliberately * hand-walks this shape instead (`readEngineCompatEnvelope` in ./io.ts) so a * same-major engine tolerates an assembly carrying additive sub-fields it does * not yet understand — breaking constraints always travel via * `minimumEngineVersion`, which every reader since Phase 0 understands. * * - `synthesisedBy` — the `@fjall/components-infrastructure` version that * stamped the assembly (audit/diagnostics; never a gate input). * - `minimumEngineVersion` — the lowest engine (deploy-core) semver that can * safely deploy this assembly. Under Lerna FIXED lockstep this derives as * `${constructsMajor}.0.0`; it is the primary refuse-if-below gate. * - `maximumEngineMajor` — optional upper bound; an engine whose major exceeds * this is warned (engine-ahead), never refused, unless a future emitter needs * a hard ceiling. * - `minimumAwsCdkCli` — optional floor for the bundled aws-cdk CLI, guarding * the cross-tree cloud-assembly schema-skew failure mode (FM1). */ declare const EngineCompatSchema: z.ZodObject<{ synthesisedBy: z.ZodString; minimumEngineVersion: z.ZodString; maximumEngineMajor: z.ZodOptional; minimumAwsCdkCli: z.ZodOptional; }, z.core.$strict>; export type EngineCompat = z.infer; /** * One capacity-slot identity alias, emitted at synth for EVERY EC2 capacity * slot unconditionally (pinned or not) by the constructs' capacity factory. * Ties the slot's CURRENT identity derivation to the HYPOTHETICAL pre-6.0 * (config-keyed) derivation computed from the same config, so: * - rename detection (deploy-core) can confirm a legacy→anchor rename pair * from the emitter's own testimony (matcher (a), the highest-precedence * evidence) and name the config fields that used to drive the identity; * - the pin writer (`fjall migrate identity`) can route a CREATE-side row * back to `{ appName, slot }` when composing a pin. * * WRITE contract: `.strict()` like `EngineCompatSchema` — emitter and reader * live in Lerna FIXED lockstep, and a future additive field revs through * `minimumEngineVersion` before any older reader can meet it. */ export declare const ManifestCapacityAliasSchema: z.ZodObject<{ appName: z.ZodString; slot: z.ZodString; clusterName: z.ZodString; anchor: z.ZodString; pinned: z.ZodBoolean; currentServiceName: z.ZodString; legacyKey: z.ZodString; legacyAnchor: z.ZodString; legacyServiceName: z.ZodString; legacyKeyFields: z.ZodArray; }, z.core.$strict>; export type ManifestCapacityAlias = z.infer; /** * One residuals-contract entry: a synth site whose resource deliberately * outlives — or is structurally invisible to — CFN stack teardown (a * RETAIN/SNAPSHOT removal policy, or an adopted pre-existing resource). * Emitted at synth so what survives a destroy is known BEFORE any destroy * runs; the destroy lane composes these entries with its own outcomes into * the destroy-time residuals report. * * - `policy` records the RESOLVED value for this synth, never the source * expression — removal policies are env-aware, and recording the * expression would hide exactly the env-resolution drift the contract * exists to surface. * - `destroyDisposition` distinguishes sites the explicit destroy lane * removes SDK-side (`deleted-on-explicit-destroy`) from sites that * genuinely survive (`survives`), so the manifest and the destroy-time * report cannot contradict each other. * * Rollout is two-phase additive (the `engineCompat` precedent): this schema * field lands first; the only v1 emitter is the DevSubstrate construct, * whose synth workspace pins `@fjall/*` to the consuming engine's version — * generic wrapper-level emission follows once the field has propagated to * readers. * * WRITE contract: `.strict()` like `EngineCompatSchema`. */ export declare const ManifestResidualSchema: z.ZodObject<{ constructPath: z.ZodString; resourceType: z.ZodString; physicalNameHint: z.ZodString; policy: z.ZodEnum<{ retain: "retain"; snapshot: "snapshot"; adopted: "adopted"; }>; destroyDisposition: z.ZodEnum<{ survives: "survives"; "deleted-on-explicit-destroy": "deleted-on-explicit-destroy"; }>; reason: z.ZodString; costClass: z.ZodEnum<{ none: "none"; storage: "storage"; "fixed-monthly": "fixed-monthly"; }>; cleanupHint: z.ZodString; }, z.core.$strict>; export type ManifestResidual = z.infer; /** * Fjall manifest schema — generated during CDK synth. * Location: `/fjall-manifest.json`. */ export declare const FjallManifestSchema: z.ZodObject<{ version: z.ZodLiteral<1>; generatedAt: z.ZodString; appName: z.ZodString; services: z.ZodArray; docker: z.ZodOptional; target: z.ZodOptional; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>>; containerPort: z.ZodOptional; secrets: z.ZodOptional>; ssmSecretsPath: z.ZodOptional; importedSecretNames: z.ZodOptional>; schemaGateImageTag: z.ZodOptional; }, z.core.$strict>>; lambdas: z.ZodArray>; ssmSecretsPath: z.ZodOptional; importedSecretNames: z.ZodOptional>; docker: z.ZodOptional; target: z.ZodOptional; buildArgs: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; acknowledgePublic: z.ZodOptional; }, z.core.$strict>]>>>; buildSecrets: z.ZodOptional; secretsManager: z.ZodOptional; arn: z.ZodOptional; field: z.ZodOptional; }, z.core.$strict>>; env: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>>; imageKey: z.ZodOptional; architecture: z.ZodOptional>; }, z.core.$strict>>; pattern: z.ZodOptional; name: z.ZodString; source: z.ZodString; }, z.core.$strict>>; ecr: z.ZodOptional>; stacks: z.ZodRecord>; resourceMap: z.ZodOptional>>; engineCompat: z.ZodOptional; minimumAwsCdkCli: z.ZodOptional; }, z.core.$strict>>; identityAliases: z.ZodOptional; }, z.core.$strict>>>; residuals: z.ZodOptional; destroyDisposition: z.ZodEnum<{ survives: "survives"; "deleted-on-explicit-destroy": "deleted-on-explicit-destroy"; }>; reason: z.ZodString; costClass: z.ZodEnum<{ none: "none"; storage: "storage"; "fixed-monthly": "fixed-monthly"; }>; cleanupHint: z.ZodString; }, z.core.$strict>>>; }, z.core.$strict>; export type FjallManifest = z.infer; export { ManifestServiceSchema, ManifestPatternSchema, ManifestEcrSchema, ManifestLambdaSchema, ManifestStackHashSchema, ResourceMapEntrySchema, EngineCompatSchema };