/** * Fail-closed structural validation for a single build group, run BEFORE the * bake-guard and before any docker input is resolved. It is the anti-silent * -secret-drop gate: both build entry points (`dockerBuildHelper.buildGroup` * and `EcrBuildOrchestrator.buildGroupViaBuildx`) resolve only the LEADER's * `(buildArgs, buildSecrets)`, so a member of the same build group that * declares DIFFERENT docker inputs would ship an artefact that disagrees with * what it declared — invisibly. This validator rejects that, plus the * collision classes that would otherwise corrupt the buildx argv or the * temp-file mount. * * Every rejection message names the offending IDENTIFIER (a key, a secret id, * a service name, a path) — never a secret VALUE (`security-standards.md § * "Values, not identifiers"`). The inputs reaching this validator are manifest * references, not resolved secrets, so nothing here is value-bearing. * * Rules (each maps to a brief-named class): * - C-f: a `buildArgs` KEY equal to a `buildSecrets[].id` — the public ARG * would shadow the secret mount (ambiguous which channel the Dockerfile * reads). * - C-g: a duplicate `buildSecrets[].id` within the group — the resolver * writes each value to `join(tempDir, ref.id)`, so the second collides and * silently wins. * - C-h: a `buildSecrets[].id` not matching `BUILDKIT_SECRET_ID_PATTERN` * (defence-in-depth — parse catches manifest-sourced ids, but a direct * deploy-core caller can construct refs without going through the schema). * - C-i: `buildContext` is not an existing DIRECTORY. * - C-k: a grouped non-leader member whose normalised `(buildArgs, * buildSecrets)` differs from the leader's — the silent secret-drop. * * No `target`-validity check: buildx validates the target stage itself, and a * pre-check here would be both redundant and a second source of truth. */ import { type Result } from "@fjall/generator"; import { type DockerBuildArgValue, type DockerBuildSecretRef } from "@fjall/util/manifest/schemas"; /** * One member's resolvable docker inputs, used by the C-k leader-parity check. * Carries only the two channels that are resolved per-group from the leader — * the fields whose divergence ships a different artefact than declared. */ export interface BuildGroupMemberInputs { serviceName: string; buildArgs?: Record; buildSecrets?: readonly DockerBuildSecretRef[]; } export interface ValidateBuildGroupInput { /** The group leader's service name — used only for messaging. */ serviceName: string; dockerfilePath: string; buildContext: string; target?: string; /** The leader's declared buildArgs (manifest references, not resolved). */ buildArgs?: Record; /** The leader's declared buildSecrets (manifest references, not resolved). */ buildSecrets?: readonly DockerBuildSecretRef[]; /** * Every member of the group (including the leader). Each member's * `(buildArgs, buildSecrets)` is compared to the leader's top-level inputs; * any divergence is the C-k silent secret-drop. When omitted, the C-k check * is skipped (single-member groups never drift). */ members?: readonly BuildGroupMemberInputs[]; } export declare function validateBuildGroup(input: ValidateBuildGroupInput): Result;