Mosaic one model, many lenses

ADR 0007: Deployments are data, not code

  • Status: accepted
  • Date: 2026-09-23

Context

Deployment logic written as scripts (build steps, kubectl calls, registry pushes) is the part of a codebase that rots first: it is environment-coupled, unauditable, and duplicated per target.

Decision

A deployment is a declarative DeploySpec (deploy/<name>.yaml, or the inline deploy: sugar in mosaic.yaml): name, target (local | docker | k8s), host, base_path, port, replicas, literal env, and secret names (values never live in the repo). The renderer turns each target into inert artifacts — run.sh, Dockerfile, or a deployment/service/ingress triple — under deploy/<name>/ in the output directory. Conflicting sources (inline deploy: and a deploy/ directory) are a hard error. local is a first-class target, not an afterthought. Config precedence, when a later version adds overrides: compiled default < mosaic config < deploy overrides < runtime env var.

Consequences

  • Deploying is diff-able: the artifacts are generated files, reviewed like anything else.
  • Adding a target is adding a vocabulary value plus a renderer branch.
  • Secrets enter the system by name; no value is ever authored.

Proof

cargo test -p mosaic-core → load::tests::inline_deploy_conflicts_with_deploy_dir (plus inline_deploy_alone_is_accepted and missing_deploy_dir_yields_implicit_local, which pin the sugar and the implicit local deployment).