Skip to content
sign in

==> read the decisions

ADR-0014: A user repo's layout is the contract, and its flake is one call

Status: Accepted (2026-10-14). Amends ADR-0008: the shared repo, its pin and its build recipe stand; what a user repo declares, and how, changes. Implemented 2026-10-15.

ADR-0008 made every user repo a flake over the shared library, so the toolchain is structural rather than a convention. In practice each user flake grew to about forty-five lines of the same wiring (projects = { … entry; migrations; lang; }, ui, service, release), copied per repo, with the project list restating paths the directory tree already implies. project.json existed and nothing read it.

  • A user flake is one call:

    outputs = { self, shared }: shared.lib.mkRepo { src = self; user = "<name>"; };

    mkRepo emits ui, service, release and checks (cs-check: tsc + eslint).

  • The layout is the contract, discovered by convention and validated when the release is built:

    repo.json { "fallback_lang": "en" }
    backend/index.ts defineUserService(...)
    projects/<slug>/
    project.json { "title": "..." } (required)
    frontend/<slug>.tsx the custom element cs-project-<slug> (ADR-0004)
    db/migrations/NNN_*.sql applied to schema p_<slug> (ADR-0010)
    lang/<lang>.json overlays on the fallback language
  • Breaking the contract fails the build with a message that names the rule: a missing project.json, an entry whose name does not match its slug, a migration that does not match NNN_*.sql, a locale key the fallback lacks, an unknown file in a slot the contract defines.

  • The manifest is still a build product (ADR-0008): rendered from the discovered layout, read by provisioning. One declaration, now the tree itself.

  • Each repo is a Bun project (ADR-0011): package.json and bun.lock, built with importBunLock.

  • A new user repo is the layout plus a five-line flake; the wiring cannot drift between repos.
  • What a project is, is visible in the tree, and checked.
  • Changing the contract is a shared-library change that every repo meets on its next push (the hub builds user releases against shared main).
  • Keep the explicit projects attribute set. It restates the tree, and the restatement was the part that drifted.
  • A JSON manifest at the repo root listing projects. The same restatement in another format.