From 6c2461eebcdd3558ba85b740f8f9d0dcba992008 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Mon, 7 Sep 2026 13:09:52 -0700 Subject: [PATCH] maint: move the website and documentation to nix-caisson.github.io Every consumer of this flake fetches the whole tree, and the landing page, book source, theme, and brand assets were over half of it by bytes. They now live in nix-caisson/nix-caisson.github.io and publish at the organisation root, with the book at /docs/. This repository keeps the contributor notes under docs/development/, and the README and those notes link to the published pages. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/pages.yml | 33 -- .gitignore | 3 - README.md | 25 +- assets/brand/README.md | 65 --- assets/brand/badge.svg | 26 -- assets/brand/emblem-dark.svg | 66 --- assets/brand/emblem.svg | 68 --- assets/brand/favicon.svg | 10 - assets/brand/social-card-dark.svg | 95 ---- assets/brand/social-card.svg | 77 ---- assets/brand/wordmark-dark.svg | 8 - assets/brand/wordmark.svg | 7 - book.toml | 15 - docs/SUMMARY.md | 32 -- docs/concepts/closed-inputs.md | 90 ---- docs/concepts/ecosystem-sources.md | 44 -- docs/concepts/library-overlays.md | 154 ------- docs/concepts/module-classes.md | 120 ----- docs/deep-dives/how-inputs-are-closed-over.md | 155 ------- docs/deep-dives/how-lib-is-composed.md | 170 ------- docs/development/testing-architecture.md | 4 +- docs/eval-weight.md | 93 ---- docs/faq.md | 85 ---- docs/getting-started.md | 261 ----------- docs/layout.md | 97 ---- docs/overview.md | 55 --- docs/positioning.md | 125 ----- docs/reference/lib.md | 426 ------------------ docs/reference/options.md | 85 ---- docs/testing.md | 87 ---- site/assets/favicon.svg | 10 - site/assets/social-card.png | Bin 38455 -> 0 bytes site/docs-brand.css | 61 --- site/index.html | 310 ------------- site/style.css | 369 --------------- theme/index.hbs | 374 --------------- 36 files changed, 16 insertions(+), 3689 deletions(-) delete mode 100644 .github/workflows/pages.yml delete mode 100644 assets/brand/README.md delete mode 100644 assets/brand/badge.svg delete mode 100644 assets/brand/emblem-dark.svg delete mode 100644 assets/brand/emblem.svg delete mode 100644 assets/brand/favicon.svg delete mode 100644 assets/brand/social-card-dark.svg delete mode 100644 assets/brand/social-card.svg delete mode 100644 assets/brand/wordmark-dark.svg delete mode 100644 assets/brand/wordmark.svg delete mode 100644 book.toml delete mode 100644 docs/SUMMARY.md delete mode 100644 docs/concepts/closed-inputs.md delete mode 100644 docs/concepts/ecosystem-sources.md delete mode 100644 docs/concepts/library-overlays.md delete mode 100644 docs/concepts/module-classes.md delete mode 100644 docs/deep-dives/how-inputs-are-closed-over.md delete mode 100644 docs/deep-dives/how-lib-is-composed.md delete mode 100644 docs/eval-weight.md delete mode 100644 docs/faq.md delete mode 100644 docs/getting-started.md delete mode 100644 docs/layout.md delete mode 100644 docs/overview.md delete mode 100644 docs/positioning.md delete mode 100644 docs/reference/lib.md delete mode 100644 docs/reference/options.md delete mode 100644 docs/testing.md delete mode 100644 site/assets/favicon.svg delete mode 100644 site/assets/social-card.png delete mode 100644 site/docs-brand.css delete mode 100644 site/index.html delete mode 100644 site/style.css delete mode 100644 theme/index.hbs diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index cf21f4c..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: pages -on: - push: - branches: [main] - workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write -concurrency: - group: pages - cancel-in-progress: true -jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: DeterminateSystems/nix-installer-action@v22 - with: - # Upstream Nix, not Determinate Nix: the action has - # defaulted determinate to true since v21, and this - # project targets the Nix everyone else runs. - determinate: false - - run: nix run 'nixpkgs#mdbook' -- build - - uses: actions/configure-pages@v5 - - uses: actions/upload-pages-artifact@v3 - with: - path: site - - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index e0d285d..09175a3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ examples/*/flake.lock # Claude Code /.claude/ - -# Built by CI (mdBook build-dir); never committed -/site/docs/ diff --git a/README.md b/README.md index 11053ac..b6fca04 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@

- - caisson + + caisson

The foundation framework for composable Nix flakes.

- Website · - Documentation · - Getting started · - Reference + Website · + Documentation · + Getting started · + Reference

--- @@ -105,18 +105,21 @@ aligned, and there is no chain of `follows` to enumerate downstream. ## Going deeper -- [Concepts](https://nix-caisson.github.io/caisson/docs/concepts/closed-inputs.html): +- [Concepts](https://nix-caisson.github.io/docs/concepts/closed-inputs.html): closed inputs, module classes, library overlays, and ecosystem sources, each explained with the reasoning behind the design. -- [Reference](https://nix-caisson.github.io/caisson/docs/reference/lib.html): +- [Reference](https://nix-caisson.github.io/docs/reference/lib.html): the `lib.caisson` API and module options. -- [Deep dives](https://nix-caisson.github.io/caisson/docs/deep-dives/how-lib-is-composed.html): +- [Deep dives](https://nix-caisson.github.io/docs/deep-dives/how-lib-is-composed.html): how `lib` is composed and how inputs are closed over. - [`examples/literate-flake/`](examples/literate-flake/): a working, annotated flake demonstrating the whole structure end to end. -The documentation in this repository lives under [`docs/`](docs/) and is -published as [the caisson docs](https://nix-caisson.github.io/caisson/docs/). +The documentation is maintained in +[nix-caisson/nix-caisson.github.io](https://github.com/nix-caisson/nix-caisson.github.io) +and published as [the caisson docs](https://nix-caisson.github.io/docs/); +this repository carries only the contributor notes under +[`docs/development/`](docs/development/). ## Verification diff --git a/assets/brand/README.md b/assets/brand/README.md deleted file mode 100644 index 57fbb38..0000000 --- a/assets/brand/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Brand assets - -Working drafts of the caisson visual identity ("Caisson Waterline" -concept: the engineered foundation below the waterline, the visible -structure above it). - -| File | Use | -|---|---| -| `emblem.svg` | Square mark (512), large surfaces | -| `badge.svg` | Reduced mark for avatar sizes (32–260 px); the org profile picture | -| `favicon.svg` | Reduced mark for 16–64 px | -| `wordmark.svg` | Horizontal logotype | -| `social-card.svg` | GitHub social preview source (1280×640) | -| `emblem-dark.svg` | Night variant: the Bay Lights on the suspenders, structure as silhouette | -| `social-card-dark.svg` | Night variant of the card, same vocabulary | -| `wordmark-dark.svg` | Night logotype for dark surfaces | - -The wordmarks have transparent backgrounds by design: place the day -wordmark on light surfaces (fog tokens) and the night wordmark on dark -ones (night tokens) — and composite over the intended surface when -generating previews. The favicon is dark-native and serves both modes. - -## Palette - -| Token | Hex | -|---|---| -| iron | `#43464B` | -| fog-light | `#D8DBDE` | -| deep-water | `#27394E` | -| bedrock | `#1B2635` | -| nix-blue | `#5277C3` | -| nix-sky | `#7EBAE4` | -| gold (rivet only) | `#C9A227` | -| violet (easter egg) | `#7F5AB6` | - -Night tokens (dark variants — night falling on the same bridge; the -suspenders become the Bay Lights and only the foundation keeps its color): - -| Token | Hex | -|---|---| -| night-sky | `#151B24` | -| night-water | `#0E141D` | -| night-bedrock | `#090E15` | -| silhouette (structure) | `#39414C` | -| led | `#E8ECF2` | -| caisson fill (night) | `#1C2836` | - -## Rendering - -PNGs are generated with resvg, e.g.: - -``` -resvg assets/brand/social-card.svg social-card.png -``` - -The wordmark and social card use a font stack (`Inter`, IBM Plex Sans, -DejaVu Sans, system fallback); rendered output depends on fonts present -at render time. Pin a font before publishing final raster assets. - -## Rules - -One metaphor per surface; fog greys are the neutral field; blue marks -the foundation lattice; gold appears exactly once per surface (a rivet); -violet stays at easter-egg subtlety. Do not derive marks from the NixOS -snowflake logo. diff --git a/assets/brand/badge.svg b/assets/brand/badge.svg deleted file mode 100644 index 7e5b41a..0000000 --- a/assets/brand/badge.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/brand/emblem-dark.svg b/assets/brand/emblem-dark.svg deleted file mode 100644 index 1c59167..0000000 --- a/assets/brand/emblem-dark.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/brand/emblem.svg b/assets/brand/emblem.svg deleted file mode 100644 index 49420f0..0000000 --- a/assets/brand/emblem.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/brand/favicon.svg b/assets/brand/favicon.svg deleted file mode 100644 index 01420df..0000000 --- a/assets/brand/favicon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/brand/social-card-dark.svg b/assets/brand/social-card-dark.svg deleted file mode 100644 index d69e90c..0000000 --- a/assets/brand/social-card-dark.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - caisson - The foundation framework - for composable Nix flakes - diff --git a/assets/brand/social-card.svg b/assets/brand/social-card.svg deleted file mode 100644 index 8bf53b5..0000000 --- a/assets/brand/social-card.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - caisson - The foundation framework - for composable Nix flakes - diff --git a/assets/brand/wordmark-dark.svg b/assets/brand/wordmark-dark.svg deleted file mode 100644 index f6c3a4e..0000000 --- a/assets/brand/wordmark-dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - caisson - - - diff --git a/assets/brand/wordmark.svg b/assets/brand/wordmark.svg deleted file mode 100644 index 542390a..0000000 --- a/assets/brand/wordmark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - caisson - - - diff --git a/book.toml b/book.toml deleted file mode 100644 index eacf379..0000000 --- a/book.toml +++ /dev/null @@ -1,15 +0,0 @@ -[book] -title = "caisson" -description = "The foundation framework for composable Nix flakes" -src = "docs" -language = "en" - -[build] -build-dir = "site/docs" - -[output.html] -default-theme = "light" -preferred-dark-theme = "navy" -additional-css = ["site/docs-brand.css"] -site-url = "/caisson/docs/" -git-repository-url = "https://github.com/nix-caisson/caisson" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index ea64608..0000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,32 +0,0 @@ -# Summary - -[Overview](overview.md) -[Getting started](getting-started.md) -[Choosing a flake framework](positioning.md) -[FAQ](faq.md) - -# Reference - -- [Library](reference/lib.md) -- [Options](reference/options.md) - -# Conventions - -- [Repository layout](layout.md) - -# Concepts - -- [Closed inputs](concepts/closed-inputs.md) -- [Module classes](concepts/module-classes.md) -- [Library overlays](concepts/library-overlays.md) -- [Ecosystem sources](concepts/ecosystem-sources.md) - -# Deep dives - -- [How `lib` is composed](deep-dives/how-lib-is-composed.md) -- [How inputs are closed over](deep-dives/how-inputs-are-closed-over.md) - -# Guides - -- [Testing](./testing.md) -- [Evaluation weight](eval-weight.md) diff --git a/docs/concepts/closed-inputs.md b/docs/concepts/closed-inputs.md deleted file mode 100644 index 4e17bc1..0000000 --- a/docs/concepts/closed-inputs.md +++ /dev/null @@ -1,90 +0,0 @@ -# Closed Inputs - -## Overview - -Nix flake modules often need access to the defining flake's `inputs`, but `flake-parts` does not provide a built-in mechanism for closing over them. This means every module and library overlay would need inputs threaded explicitly through its call site, a tedious and error-prone pattern. - -caisson solves this with an **explicit closure convention**: everything registered through `mkModule` or `mkLibOverlay` takes a closure attrset as its first arg list. Plain modules and already-built overlays are registered directly instead. - -## The Problem - -In a standard flake-parts setup, accessing inputs from a module requires either: -- Passing them via `specialArgs` (fragile, global) -- Using `config._module.args` (implicit, hard to trace) -- Threading them manually through every import - -None of these compose well when modules are re-exported for downstream consumption. - -## How caisson Handles It - -### mkModule - -`mkModule` is a factory: - -```nix -mkModule = class: freeformModule: ... -``` - -You first choose a module class, then use the returned class-specific normalizer. For flake-parts modules: - -```nix -mkFlakeModule = mkModule "flake" -``` - -Everything passed to the class-specific normalizer takes the closure attrset as its first arg list, followed by an ordinary module: - -```nix -# Uses closure values -{ closure-inputs, closure-lib, mkModule, ... }: -{ config, lib, ... }: -{ ... } - -# Ignores the closure but still takes the arg list -{ ... }: -{ config, lib, ... }: -{ ... } -``` - -The closure attrset contains: - -| Key | Value | -|---|---| -| `closure-inputs` | The defining flake's `inputs` (distinct from the flake-parts `inputs` module arg, which belongs to the consuming flake) | -| `closure-lib` | The defining flake's composed `lib` (distinct from the `lib` module arg) | -| `closure-self-modules` | The defining flake's registered modules in the same class | -| `mkModule` | A normalizer bound to the same class, for nested composition | - -Path modules are wrapped with `_file` for error locations and `key = toString path`, so a file passed through `mkModule` at two sites deduplicates exactly like importing the same path twice. - -Passing a non-function (attrset, path to a plain module, `null`) is an error: plain modules are imported or registered directly rather than wrapped in `mkModule`. - -### mkLibOverlay - -`mkLibOverlay` follows the same convention. A registered overlay takes `{ closure-inputs, mkLibOverlay, ... }` as its first arg list and returns an `{ imports ? [ ], overlay }` attrset: the `final: prev:` function under `overlay`, and the overlays it depends on under `imports`: - -```nix -{ closure-inputs, ... }: -{ - imports = [ closure-inputs.some-flake.libOverlays.default ]; - overlay = final: prev: { ... }; -} -``` - -Already-built overlays (for example another flake's exported `libOverlays.default`) are registered directly rather than wrapped in `mkLibOverlay`. - -## Key Functions - -| Function | Purpose | -|---|---| -| `mkModule` | Creates class-specific module normalizers with closed inputs | -| `mkLibOverlay` | Applies the closure to registered library overlays | -| `importApply` | Applies static arguments to a module through the import chain | -| `mkLib` | Bootstraps a composed library with closed overlays | -| `mkFlake` | Creates flake outputs with closed modules | - -## Further Reading - -- [How inputs are closed over](../deep-dives/how-inputs-are-closed-over.md): the mechanism behind this convention -- [How `lib` is composed](../deep-dives/how-lib-is-composed.md): the whole composition pass -- [Module Classes](./module-classes.md): class-keyed module registration and export -- `examples/literate-flake/`: a working example demonstrating closed input wiring diff --git a/docs/concepts/ecosystem-sources.md b/docs/concepts/ecosystem-sources.md deleted file mode 100644 index aede642..0000000 --- a/docs/concepts/ecosystem-sources.md +++ /dev/null @@ -1,44 +0,0 @@ -# Ecosystem sources - -Integrations do not pin their ecosystems: `caisson.nixos` has no -nixpkgs pin, and `caisson.home-manager` has no home-manager pin. You -pass the ecosystem in, as an argument called the ecosystem source, and -the integration calls the evaluator inside that source. A single -caisson revision therefore works with any nixpkgs, home-manager, or -colmena revision with a compatible evaluation contract, and two -consumers of that same caisson revision can pin different revisions -of each ecosystem. - -## What a source is - -An ecosystem is a community library outside of caisson at the center -of an extensible Nix abstraction framework. Most ecosystems are built -on NixOS modules, but some use other abstractions -(e.g. package sets, `lib` ecosystems). The ecosystem source is that -project's source tree or -flake, in whatever shape its evaluator expects. Each integration -documents the shape it takes; in practice: - -- `caisson.nixos` takes a nixpkgs source tree (it evaluates - `nixos/lib/eval-config.nix` from it). -- `caisson.home-manager` takes a home-manager source tree. -- `caisson.colmena`, `caisson.terranix`, and - `caisson.system-manager` take their project's flake (they call - `lib.makeHive`, `lib.terranixConfiguration`, and - `lib.makeSystemConfig` on it). - -## How does caisson get access to ecosystem sources? - -A source comes from one of three places, in priority order: - -1. **Explicit argument.** `ecosystemSrc = inputs.nixpkgs` at the - call site always wins. -2. **Flake-level default.** `ecosystems.nixpkgs = inputs.nixpkgs` at - `mkLib` declares the composition's default for that name. -3. **Exact-name input.** As a final fallback, an input of the - composing flake named exactly like the ecosystem (`nixpkgs`, - `home-manager`, ...) is used. Handy for leaf flakes that declare - the input anyway. - -If none of the three places can provide a needed ecosystem source, -it triggers an evaluation error. diff --git a/docs/concepts/library-overlays.md b/docs/concepts/library-overlays.md deleted file mode 100644 index 8746ea3..0000000 --- a/docs/concepts/library-overlays.md +++ /dev/null @@ -1,154 +0,0 @@ -# Library Overlays - -## The Problem People Actually Had - -Nix overlays have a reputation problem. Package overlays in nixpkgs caused real pain (attribute collisions, silent shadowing, unpredictable evaluation order), and the community learned to be cautious. That caution has grown into a broader skepticism that prevented the community from really embracing library overlays, out of fear that a worse version of the same problems might occur. - -The skepticism is understandable but misdirected. The problems were never inherent to overlays as a mechanism. They came from how overlays were used: global modifications to shared namespaces, no convention for scoping additions, no way to declare dependencies between overlays, and no isolation between unrelated consumers. Fix those problems and overlays become a safe, composable extension mechanism. - -caisson fixes those problems for library overlays. The result is something the Nix ecosystem has been missing: composed, layered `lib` extensions that multiple flakes can contribute to without stepping on each other. - -## What Makes Overlays Safe - -Four properties, applied together, address the collision and ordering risks that gave overlays a bad name: - -### Namespacing - -Every overlay adds its functions under a dedicated attribute path rather than mixing into the top-level `lib`. A project called `myProject` puts its functions at `lib.myProject.*`: - -```nix -overlay = final: prev: { - myProject = (prev.myProject or {}) // { - helper = x: x + 1; - }; -}; -``` - -This means two independent projects do not collide unless the project names do: `lib.projectA.helper` and `lib.projectB.helper` coexist without interference, as they would in any language with a module system. caisson's `configInfo.configName` convention helps here: if every project uses its canonical flake name as the namespace, collisions are unlikely in practice. Choose a distinctive name for your flake; generic names like `utils` or `helpers` invite collisions, while project-specific names like `caisson` or `acme-infra` make them vanishingly rare. This is a convention, not an enforcement mechanism: if two upstream flakes happen to choose the same `configName`, their `lib` contributions will merge into the same namespace. - -### prev-Based Merging - -The `(prev.myProject or {}) // { ... }` pattern ensures that if multiple overlays contribute to the same namespace (e.g., a base overlay and an extension overlay within the same project), their contributions are merged rather than one silently replacing the other. This is the standard Nix overlay contract, and it matters: it means overlays compose additively. - -### Input Closure - -caisson's `mkLibOverlay` automatically closes over the flake's `inputs` when registering an overlay. This binds each overlay to the specific set of inputs it was written against, rather than relying on the leaf flake to do the right thing. The result is that overlays from different upstream flakes don't interfere with each other's inputs, even when composed into the same final `lib`. - -See [Closed Inputs](./closed-inputs.md) for the full mechanism. - -### Dependency Tracking - -Library overlays sometimes need to call functions defined by other library overlays. Without explicit dependency management, this requires manually ensuring that overlays are applied in the right order, a fragile arrangement that breaks when overlays are reorganized or new ones are added. - -caisson solves this. A registered overlay is an `{ imports ? [ ], overlay }` attrset, and `imports` is where its dependencies go. Entries are built overlays; the closure's `mkLibOverlay` member exists exactly so a dependency can be built in place: - -```nix -{ mkLibOverlay, ... }: -{ - overlay = final: prev: { - myProject = (prev.myProject or {}) // { - fullName = person: "${prev.myProject.greet person} (${person})"; - }; - }; - imports = [ (mkLibOverlay ./greet-overlay.nix) ]; -} -``` - -When `mkExtendedLib` encounters this structure, it recursively applies the `imports` before applying the `overlay` itself. This guarantees that `prev.myProject.greet` exists by the time `fullName` is evaluated, regardless of the order overlays were listed in `libOverlays`. - -The resolution is recursive: imported overlays can themselves declare imports, and the framework handles the full dependency graph. - -## Package Overlays and Library Overlays - -The safety techniques described here (namespacing, input closure, dependency tracking) apply equally to package overlays and library overlays. The underlying mechanism is the same: both are functions of `final: prev:` that extend an attribute set. - -caisson provides the tooling for safe library overlays. The same principles apply to package overlays, but package overlay tooling is the domain of `caisson-nixpkgs` (not yet published), which builds on caisson's foundation. - -## Tradeoffs - -The safety mechanisms described here aren't free. Import chains are -flattened depth-first with duplicates preserved (an overlay's imports -are applied before it, every time it appears) and folded through the -caisson-core, so evaluation cost grows with the number of -overlays and the depth of the dependency graph. One contract worth -knowing: the base library is contributed as an opaque attribute set, -so overriding one of its attributes changes what readers of the -composed library see, without re-tying the base's own internal -references. - -For most flakes the overhead is negligible, but it's worth being aware of, especially if you're composing a large number of upstream library overlays. The eval-weight harness (see the guides) is the tool for holding it to a measured ceiling. - -## Practical Patterns - -### A Simple Library Overlay - -The most common case: adding namespaced functions to `lib`. - -```nix -# lib-overlays/default/default.nix -{ closure-inputs, ... }: -{ - imports = [ ]; - overlay = final: prev: { - myProject = (prev.myProject or {}) // { - greet = name: "Hello, ${name}!"; - double = x: x * 2; - upstreamVersion = closure-inputs.some-flake.lib.version; - }; - }; -} -``` - -The closure arg list always comes first: `closure-inputs` here is the -*defining* flake's inputs, so `some-flake` resolves against the inputs -this overlay was written with, no matter which downstream flake -eventually composes it. An overlay that needs nothing from the closure -still takes the arg list, as `{ ... }:`. - -Register it in your flake's `mkLib` call: - -```nix -libOverlays = mkLibOverlay: { - default = mkLibOverlay ./lib-overlays/default; -}; -``` - -After composition, `lib.myProject.greet "world"` returns `"Hello, world!"`. - -### An Overlay With Dependencies - -When one overlay needs functions from another, declare the dependency: - -```nix -# lib-overlays/extended/default.nix -{ mkLibOverlay, ... }: -{ - overlay = final: prev: { - myProject = (prev.myProject or {}) // { - greetLoud = name: final.toUpper (prev.myProject.greet name); - }; - }; - imports = [ (mkLibOverlay ../default) ]; -} -``` - -The `imports` list ensures `default` is applied first, so `prev.myProject.greet` is available. Register the extended overlay normally: - -```nix -libOverlays = mkLibOverlay: { - default = mkLibOverlay ./lib-overlays/default; - extended = mkLibOverlay ./lib-overlays/extended; -}; -``` - -### Common Mistakes to Avoid - -- **Top-level additions.** Don't add attributes directly to `lib` (e.g., `{ helper = ...; }`). Always namespace under a project-specific attribute. -- **Forgetting `prev` merge.** Writing `myProject = { helper = ...; }` instead of `myProject = (prev.myProject or {}) // { helper = ...; }` will silently discard any functions added to `myProject` by earlier overlays. -- **Implicit ordering assumptions.** If overlay B uses a function from overlay A, declare the dependency with `{ overlay = ...; imports = [...]; }` rather than hoping the registration order is correct. - -## Further Reading - -- [How `lib` is composed](../deep-dives/how-lib-is-composed.md): the whole composition pass -- [Closed Inputs](./closed-inputs.md): how inputs are closed over in overlays and modules -- `examples/literate-flake/`: a working example with a custom library overlay diff --git a/docs/concepts/module-classes.md b/docs/concepts/module-classes.md deleted file mode 100644 index 285c15b..0000000 --- a/docs/concepts/module-classes.md +++ /dev/null @@ -1,120 +0,0 @@ -# Module Classes - -## Overview - -caisson models modules as class-keyed sets. A class is a string key used to group related modules and control where they are exported in flake outputs. - -- Registered modules live under `modules..` -- Exported modules are published under `flake.modules..` - -This builds on flake-parts' generic `flake.modules` support while adding closed-inputs module normalization. - -## mkModule Factory - -`lib.caisson-core.mkModule` is class-parameterized: - -```nix -mkModule = class: freeformModule: ... -``` - -Example: - -```nix -modules = { - flake = { - default = lib.caisson.mkFlakeModule ./modules/flake-parts/default; - }; - - generic = { - helper = lib.caisson-core.mkModule "generic" ./modules/generic/helper; - }; -}; -``` - -The returned class-specific normalizer applies the closure attrset -(`{ closure-inputs, closure-lib, mkModule, ... }`) as the module's first -arg list. The `mkModule` closure member is bound to the same class, so -nested use of `mkModule` stays in that class. - -## Registration APIs - -Modules enter the class-keyed registry (`lib.caisson-core.modules`) -in three ways: - -- **Local registration**, `mkLib`'s `modules` hook: a function - `lib: { ... }` receiving the composed `lib` (whose helpers, like - `lib.caisson.mkFlakeModule`, build the entries) and returning the - class-keyed registration. This is for the flake's own modules. -- **Overlay contribution**, for modules contributed by a library - overlay: the overlay closure contains `mkModule` and - `contributeModules`, and the overlay merges its entries into the - registry: - - ```nix - { mkModule, contributeModules, ... }: - { - imports = [ ]; - overlay = - final: prev: - contributeModules prev { - nixos."my-flake/my-service" = mkModule "nixos" ./modules/my-service.nix; - } - // { - my-flake = (prev.my-flake or { }) // { ... }; - }; - } - ``` - - `mkModule` here is bound to the defining flake's composition, so the - contributed module closes over the definer's inputs and library, not - the consumer's. A consumer who registers the exported overlay gets - its library namespace and its modules together, transitively through - the overlay's `imports` chain; no re-registration is involved. -- **Project consumption**, `mkLib`'s `projects` hook: registering a - whole upstream contribution (`projects.my-dep = inputs.my-dep`) - places its exported modules in the registry under - `/` per class, beside its overlays in the overlay - dictionary. Selection stays per item at each use site, and a - local registration beats a same-named project entry. - -The registry is a shared, class-keyed space per composition, so two -rules keep multiple contributors coherent. Names within a class are a -single flat space: qualify contributed names with your project prefix -(`my-flake/my-service`), the same discipline as top-level library -namespaces; short names are for the composing flake's own -registrations. And precedence is deterministic: the composing flake's -local registrations apply last, so a local entry always wins over a -same-named contribution. - -Use class `flake` for flake-parts modules and other class keys for other module ecosystems. The shipped integrations (`caisson.nixos`, `caisson.home-manager`, `caisson.terranix`, `caisson.colmena`, `caisson.system-manager`, and `caisson.nixpkgs`) each register their own class this way; see the [library reference](../reference/lib.md). - -## Consuming Exported Modules - -A downstream flake can import modules published under any class via the upstream flake's `modules` output: - -```nix -# In a downstream NixOS configuration: -imports = [ inputs.my-upstream.modules.nixos.myModule ]; - -# In a downstream home-manager configuration: -imports = [ inputs.my-upstream.modules.homeManager.myModule ]; -``` - -The exported modules have their inputs already closed over, so importing one is possible without threading the upstream's dependencies. - -## Export Controls - -Per-class export controls live under: - -- `caisson.modules..export.enabled` -- `caisson.modules..exported` - -For flake-parts compatibility, `flake.flakeModules` mirrors -`flake.modules.flake`, and the `flake` class always exports a `default` -entry (an empty module unless the selection provides one) so -`flakeModules.default` exists for consumers that import it by -convention. - -## Relationship to flake-parts - -The `flake.modules` output is provided by flake-parts' `modules` extra module. When caisson wires exported modules into `flake.modules..`, flake-parts stamps each module with `_class` and `_file` metadata. This means exported modules carry their class identity and source location, which module systems can use for diagnostics and class-checking (e.g., preventing a `nixos` module from being accidentally imported into a `homeManager` evaluation). diff --git a/docs/deep-dives/how-inputs-are-closed-over.md b/docs/deep-dives/how-inputs-are-closed-over.md deleted file mode 100644 index 41e4192..0000000 --- a/docs/deep-dives/how-inputs-are-closed-over.md +++ /dev/null @@ -1,155 +0,0 @@ -# How inputs are closed over - -Every file registered through caisson takes a closure attrset as its -first argument list. This page traces the mechanism behind that -convention: where the closure comes from, when it is applied, what it -contains for each kind of registration, and what it means when a -registered file is evaluated inside another flake's composition. - -## The convention - -A registered file is a function of two argument lists: the closure -attrset first, then whatever the file ordinarily takes. - -```nix -{ closure-inputs, ... }: # the closure arg list -{ config, lib, pkgs, ... }: # the ordinary module arg list -{ - services.foo.package = closure-inputs.foo-flake.packages.x86_64-linux.default; -} -``` - -A file that needs nothing from the closure still takes the arg list -(`{ ... }:`), so every registered file has the same shape and a -reader always knows what the first line is. - -## Where the closure comes from - -The flake defines the closure by calling `mkLib`: - -```nix -lib = caisson.lib.caisson-core.mkLib { - inherit inputs; - libOverlays = mkLibOverlay: { ... }; - modules = lib: { ... }; -}; -``` - -`mkLib` builds registration helpers closed over the `inputs` it was -given, and hands them to the registration arguments: `libOverlays` -receives the input-closed `mkLibOverlay`, and `modules` receives the -composed `lib`, whose helpers (`lib.caisson.mkFlakeModule`, -`lib.caisson-core.mkModule`) are closed the same way. There is no -ambient lookup anywhere in this chain: the only `inputs` a -registration can see is the attrset its own flake passed to `mkLib`. - -## When the closure is applied - -At registration time, not at evaluation time. `mkLibOverlay` and -`mkModule` call the registered function with the closure attrset -immediately and keep the result, so what sits in the registry, and -what the flake exports, is an ordinary overlay or module with the -closure values already baked in. - -This timing is what makes export work. A consumer who imports -`my-flake.modules.nixos.my-service` receives a plain NixOS module; -the closure was applied when my-flake registered the file, so the -module references my-flake's inputs without the consumer declaring, -`follows`-pinning, or even knowing about them. - -## What the closure contains - -The contents differ by registration kind, because the two kinds are -evaluated at different times. - -A library overlay's closure contains registration helpers and inputs: - -- `closure-inputs`: the defining flake's `inputs`. -- `mkLibOverlay`: the same helper, for building nested overlays. -- `mkModule`: the module normalizer bound to the defining - composition, so modules contributed by the overlay close over the - definer's inputs and library. -- `contributeModules`: merges class-keyed module contributions into - the registry from inside an overlay. It is threaded through the - closure rather than read from `final` because an overlay's output - attribute names must not depend on `final` (see - [How `lib` is composed](how-lib-is-composed.md)). - -The composed `lib` is deliberately absent: an overlay runs inside the -composition that builds `lib`, so it reads the library through its -`final` and `prev` arguments instead. - -A module's closure contains the definer's finished world: - -- `closure-inputs`: the defining flake's `inputs`. -- `closure-lib`: the defining flake's composed library. This is not - the `lib` module argument; see the next section. -- `closure-self-modules`: the defining flake's registrations in the - same class, for modules that import their siblings. -- `mkModule`: a normalizer bound to the same class, so nested module - composition stays in that class. - -Plain values bypass the mechanism: an already-built overlay (another -flake's export) and a plain module are registered directly, because -their closures were applied by whoever built them. - -## Two worlds in one file - -Inside a registered module, two sets of similar-looking values are in -scope, and they answer different questions: - -| Value | Whose world | -|---|---| -| `closure-inputs` | The flake that registered the file | -| `closure-lib` | The flake that registered the file | -| `inputs` module arg | The flake being evaluated | -| `lib` module arg | The flake being evaluated | - -While a flake consumes its own registrations the distinction is -invisible, because both worlds are the same flake. It starts to -matter the moment a module is exported: a consumer evaluates the -module inside their own composition, so the ordinary `lib` argument -is the consumer's composed library, while `closure-lib` remains the -definer's. A module that formats a string with a helper from its own -flake's namespace wants `closure-lib`; a module that inspects the -configuration it is being evaluated into wants the ordinary -arguments. - -The same split governs version skew. An exported module built against -`closure-inputs.nixpkgs` uses the definer's nixpkgs pin even when the -consumer runs a different one. That skew is a designed property, not -an accident: each flake's files run against the pins that flake -tested with, the pins are visible in each flake's lock, and -nothing forces the fleet to upgrade in lockstep. Where a consumer -does want to override a definer's pin, flake-level `follows` on the -definer's input still works, because `closure-inputs` is the -definer's `inputs` attrset and `follows` rewrites what that attrset -contains. - -## importApply - -Closure application composes with module `imports` through -`lib.caisson-core.importApply`, which applies static arguments to a -module without losing its file identity: a path is imported and -wrapped with its `_file`, wrapper modules produced by registration -are walked rather than replaced, and the arguments are applied to -the innermost function. - -```nix -imports = [ - (closure-lib.caisson-core.importApply ./listener.nix { port = 8080; }) -]; -``` - -Here `listener.nix` begins `{ port }:` and receives the arguments as -its first arg list. Wrapping the module in a plain lambda instead -would work, but the module system would see an anonymous function: -error messages would no longer point at the file. - -## Seeing it in the example - -`examples/literate-flake/` wires all of this in a working flake: its -overlay reads `closure-inputs`, and its modules take the two argument -lists. Step 5 of [Getting started](../getting-started.md) shows the -consuming side, a second flake that imports the exports without -declaring the definer's dependencies. diff --git a/docs/deep-dives/how-lib-is-composed.md b/docs/deep-dives/how-lib-is-composed.md deleted file mode 100644 index 58e5880..0000000 --- a/docs/deep-dives/how-lib-is-composed.md +++ /dev/null @@ -1,170 +0,0 @@ -# How `lib` is composed - -A caisson flake's `lib` is canonically built by a call to -`caisson-core.mkLib`. This page traces what happens between that -call and the finished attrset: what goes in, the order things apply, -the rules that decide conflicts, and how to drive the same machinery -without `mkLib` when you want to. - -## What goes in - -`mkLib` is given a base library and a set of declarations: - -- `baseLib`: the library everything else extends, passed as a plain - value. Nothing is looked up by input name. When you call the - `caisson-core.mkLib` found inside a composed library, `baseLib` - defaults to that composition's own base, which is why a typical - flake passes only the arguments below. -- `libOverlays`: the flake's own overlay registrations, built with - the input-closed `mkLibOverlay` helper or registered directly when - already built (another flake's export, for example). -- `modules`: the flake's own class-keyed module registrations. -- `projects`: whole upstream contributions, each carrying exported - overlays and modules that register under `/`. -- `ecosystems`: declared default ecosystem sources (see - [Ecosystem sources](../concepts/ecosystem-sources.md)). - -Registration and application are separate steps: `libOverlayImports` -selects which of the registered overlays apply to this flake's own -`lib` (default: all of them), and registration also feeds export, so -a flake can register overlays for downstream consumers that it does -not apply to itself. - -## The sequence - -The selected overlays are applied over the base in one pass, wrapped -by overlays caisson adds itself: - -``` -baseLib - -> caisson-core namespace injection - -> selected overlays (flattened, imports first) - -> consumed projects' modules - -> the flake's own module registrations - -> the manifest -``` - -The first added overlay injects the machinery and the empty module -registry under `caisson-core`. The last one records the manifest, -the composition's self-description, at -`lib.caisson-core.manifest`. Module registrations apply after every -selected overlay so that a local name always beats a same-named -contribution from an overlay or a consumed project. - -Before application, each selected overlay is flattened: a built -overlay is an `{ imports, overlay }` value, and the flattening walks -depth-first with imports before the overlay itself. This guarantees -that anything an overlay depends on has already applied when its -`overlay` function runs, regardless of registration order. Within -`mkLib`, imports guarantee order, not uniqueness: an overlay that two -registrations both import is applied once per appearance, which is -harmless for overlays that follow the merge conventions. - -## The fold - -Application is the standard Nix overlay contract, folded over the -sequence above. Each `overlay = final: prev: { ... }` receives -`prev`, everything accumulated so far, and `final`, the finished -fixpoint. For an attribute defined by two overlays, the later -definition wins, and `prev` gives it the earlier one to build on, -which is what the `my-flake = (prev.my-flake or { }) // { ... }` -merge convention relies on. - -Two consequences of the fixpoint are worth knowing: - -- An overlay's output attribute *names* must not depend on `final`; a - fixpoint whose shape depends on itself diverges. -- The base library is contributed as an opaque value. Overriding one - of its attributes changes what readers of the composed library see, - and does not change what the base's own internals call; a function - patched for everything downstream of the base has to be patched in - the base source you pass in. - -## Composing without mkLib - -`mkLib` is a convenience over `caisson-core.compose`, which works on -*entries*, overlay-shaped pieces with identity: - -```nix -{ - key = "example.base"; # stable identity: a string, or null - imports = [ ]; # entries this entry depends on - overlay = final: prev: { greet = name: "hello, ${name}"; }; -} -``` - -Keys change the rules. A keyed entry applies once no matter how many -entries import it; the first occurrence of a key fixes its position -and the last occurrence supplies its value, so mentioning a key again -replaces that entry wholesale; replacement is the only override -mechanism. An entry with `key = null` -cannot be imported, applies after the whole keyed world in list -order, and can never be replaced by another entry, because -replacement addresses keys and it has none; keyless entries are a -consumer's private patch layer. Import cycles terminate (a key -already on the walk's own path is skipped) and grant the cycle's -members no ordering relative to each other. - -Identity is what makes patching a dependency reliable. A polyfill -imports the entry it patches, which guarantees the target is present -and already applied when the polyfill reads `prev`: - -```nix -polyfill = { - key = "example.backport"; - imports = [ base ]; - overlay = final: prev: { - concatLines = prev.concatLines or (lines: prev.concatStringsSep "\n" lines + "\n"); - }; -}; -``` - -The `prev.concatLines or ...` shape adds the function only where the -base does not already provide it, so the same entry composes -correctly over old and new bases. - -The exact contract (walk order, replacement slots, metadata) is -specified in -[caisson-core](https://github.com/nix-caisson/caisson-core), where -the code lives. - -caisson exports its own contributions in entry form through -`lib.composition.entriesFor`: - -```nix -caisson.lib.composition.entriesFor { - # a directory importable as nixpkgs' lib, e.g. "${nixpkgs}/lib" - # or "${nixpkgs-lib}/lib" for the nixpkgs.lib mirror - ecosystemSrc = "${inputs.nixpkgs-lib}/lib"; -} -# => { base, caisson-lib, -# flake-parts, tooling, -# nixpkgs, nixos, home-manager, -# colmena, terranix, system-manager } -``` - -`base` contributes the nixpkgs library the composition builds on; -`caisson-lib` imports it and contributes the `caisson-core` -machinery, the same injection `mkLib` performs; the rest are the -integrations and tooling, each importing `caisson-lib`. - -## Where the composed `lib` goes - -`mkFlake` passes the composed library to flake-parts as -`specialArgs.lib`, so modules receive it as their ordinary `lib` -argument. `flake.lib` publishes a selection of it when -`caisson.lib.export.enabled` is set; the default selection is the -flake's own namespace only, and that is the convention: exporting -the full composed library would make all of nixpkgs-lib, at your -pin, part of your public contract. Consumers build their own -composed library against their own inputs instead. - -## Inspecting the result - -```bash -nix repl . -# :p lib.my-flake -- your namespaced additions -# :p lib.my-flake.helper 41 -- call a function - -nix eval .#lib --apply builtins.attrNames -``` diff --git a/docs/development/testing-architecture.md b/docs/development/testing-architecture.md index 132ace3..ad2272c 100644 --- a/docs/development/testing-architecture.md +++ b/docs/development/testing-architecture.md @@ -89,7 +89,7 @@ expression and applies its `outputs` function to explicitly constructed inputs. Each input the flake declares resolves by name: an entry in `overrides` wins, then a `follows` chain, then the `pool`; a name that resolves nowhere throws. Nothing is fetched and no lockfile is read or -written. The [library reference](../reference/lib.md) documents the full +written. The [library reference](https://nix-caisson.github.io/docs/reference/lib.html) documents the full signature. The imported `flake.nix` is not a resolved flake: it has no resolved inputs, no @@ -304,4 +304,4 @@ The checks partition merges outputs from all test and example flakes into a sing | `literate-flake-default` | `examples/literate-flake/` | Example default package builds | | `literate-flake-greeting` | `examples/literate-flake/` | Example greeting package builds | | `debug-disabled` | `checks.nix` (inline) | `self.debug` is not exposed in production | -| `eval-weight` | `checks.nix` + `tests/eval-weight/` | Framework evaluation cost held to committed ceilings ([guide](../eval-weight.md)) | +| `eval-weight` | `checks.nix` + `tests/eval-weight/` | Framework evaluation cost held to committed ceilings ([guide](https://nix-caisson.github.io/docs/eval-weight.html)) | diff --git a/docs/eval-weight.md b/docs/eval-weight.md deleted file mode 100644 index 7ba1a17..0000000 --- a/docs/eval-weight.md +++ /dev/null @@ -1,93 +0,0 @@ -# Evaluation Weight - -`lib.caisson.eval-weight` measures what an evaluation costs and can -gate that cost in `checks`, so a framework regression is caught by CI -rather than noticed as slowness later. This repository uses it to gate -its own overhead; the numbers quoted in these docs come from it. - -## How it measures - -A scenario runs a pinned Nix evaluator inside a derivation sandbox -against explicitly wired inputs and captures the evaluator's own -statistics. The deterministic counters (thunks, values, environments, -function and primop calls, total allocations) are reproducible for a -fixed lock set and Nix version, so they can be gated. CPU and -wall-clock time are machine-dependent, so they are always reported but -not gated. - -Three semantic counters are derived from the same run, keyed to stable anchors in the -evaluated source rather than line numbers: - -- `nixpkgsEvals`: full nixpkgs instantiations -- `nixpkgsLibEvals`: nixpkgs-lib bootstraps (distinct lib sources) -- `moduleSystemEvals`: `evalModules` runs, including submodules - -These are gated **exactly**, with no growth allowance: one extra -nixpkgs instantiation *is* the regression. - -## mkCheck - -```nix -checks.eval-weight = lib.caisson.eval-weight.mkCheck { - inherit pkgs; - name = "my-flake"; - - scenarios = { - raw-flake-parts = { - entry = self.outPath + "/tests/eval-weight/raw-flake-parts.nix"; - args = { /* store paths and system for the entry */ }; - }; - minimal-consumer = { - entry = self.outPath + "/tests/eval-weight/minimal-consumer.nix"; - args = { /* ... */ }; - }; - }; - - gates = [ - # framework overhead, isolated from ecosystem churn - { - name = "my-flake-overhead"; - minuend = "minimal-consumer"; - subtrahend = "raw-flake-parts"; - } - # loose ceiling on the whole consumer - { - name = "minimal-consumer-total"; - scenario = "minimal-consumer"; - maxGrowth = 0.25; - } - ]; - - baseline = - builtins.fromJSON (builtins.readFile ./tests/eval-weight/baseline.json); -}; -``` - -- **scenarios**: `entry` is a self-contained file imported inside the - sandbox and applied to `args` (store paths arrive as absolute-path - strings); the resulting value is forced strictly, so the entry decides - exactly what evaluation gets measured. Entries that need to evaluate a - flake import the shared `call-flake.nix` kernel from a store path in - `args`, the same kernel under - [`callConsumerFlake`](reference/lib.md#callconsumerflake). -- **gates**: one per scenario by default. A subtraction gate measures - the *difference* between two scenarios, which is the important trick: - a 2× regression in framework machinery is invisible in a - whole-nixpkgs total but obvious in the delta. -- **baseline**: `null` runs in measure-only bootstrap mode: metrics - are printed, including a paste-ready baseline, and the check passes. - Commit the pasted baseline (this repository keeps it at - `tests/eval-weight/baseline.json`) and subsequent runs gate against - it: deterministic metrics may grow up to `maxGrowth` (10% by - default; exact metrics not at all), and marked shrinkage logs a - note suggesting the baseline be tightened. - -## The workflow - -1. Write an entry per scenario under `tests/eval-weight/`. -2. Run once with `baseline = null`; paste the printed baseline into - `tests/eval-weight/baseline.json`. -3. Wire `mkCheck` into `checks` with the committed baseline. -4. When a gate fails, the report shows which metric moved and by how - much; either fix the regression or, for intended changes, update - the baseline in the same change, where review can see both. diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 7b54826..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,85 +0,0 @@ -# FAQ - -### What goes wrong without caisson that this fixes? - -Caisson addresses two major failure patterns seen in poly-flake -setups: - -First, input explosion: a flake-parts module that uses `inputs.foo` -works only if every downstream flake also pulls in `foo`. This ends -up meaning that each level of a dependency tree has to re-mention all -of its transitive dependencies, either directly or in the form of a -bunch of "follow" pins, or else you get an explosion of flake -versions. - -Second, collisions: Overlays that write top-level attributes tend to -fight over one flat namespace. - -### What is `closure-inputs`, and who sets it? - -`closure-inputs` is the `inputs` attrset your flake passes to -`mkLib`, threaded in by the caisson framework: `mkLibOverlay` and -`mkModule` apply it to each registered file as the file's first -argument list. A file always receives the inputs of the flake that -registered it: an overlay or module consumed from another flake sees -the inputs of the flake it came from, not the inputs of the flake -consuming it. -[Closed inputs](concepts/closed-inputs.md) is the full convention. - -### What happens when two overlays define the same thing? - -Composition is done via an ordered pass across the specified overlays, -traversing dependencies in a depth-first, postfix manner. For attrSets -and lists, following the conventions results in a merge. For atomic -attributes, contentions mean that the later overlay's definition -wins. I say "convention" because overlays are actually capable of -addressing their predecessor directly, so they can technically -implement whatever merging logic they deem appropriate. - - -### Can I adopt this incrementally in an existing flake-parts flake? - -Yes. `mkFlake` wraps flake-parts' own `mkFlake`, and plain -flake-parts modules work unchanged. It is recommended to start by -composing a library with `caisson-core.mkLib`. You can hand your -existing top-level module to `mkFlake` and let the conventions -spread file by file from there. - -### What does `mkFlakeModule` do to my module? - -A few things: - -- applies the closure argument list, so the module can use - `closure-inputs` -- records the file's path as `_file`, for better error messages -- gives path-registered modules a deduplication `key`. - -### Do I pin nixpkgs, home-manager, and the rest myself? - -Yes, you manage the ecosystem pins in your own flake, which is the -point: a caisson integration is glue over the ecosystem's evaluator, -composing with whatever ecosystem source version you give it, so -caisson imposes no transitive pins and two consumers of the same -caisson revision can run different nixpkgs revisions. Version skew within your -ecosystems lives in your own locks, where you can see and manage it. A -flake that uses one version of an ecosystem everywhere can set a -flake-level default at `mkLib` (`ecosystems.nixpkgs = inputs.nixpkgs`). But it's -also possible to explicitly pass it on each caisson call. The explicit -argument wins, and an input named exactly like the ecosystem is the -final fallback (handy for leaf nodes). - - -### What does this cost at evaluation time? - -Measurements show: relatively little. The eval-weight harness runs in -CI and holds caisson's overhead (a minimal caisson consumer minus a -raw flake-parts flake) to ceilings on deterministic counters: -thunks, values, allocations, and full nixpkgs, nixpkgs-lib, and -module-system evaluation counts. The overhead is orders of magnitude -below a single nixpkgs evaluation. [Evaluation -weight](eval-weight.md) documents the harness and the current numbers. - -### How mature is this? - -Pre-release. At the time of writing, it is used heavily by the author -and by no one else. diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 2c458c6..0000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,261 +0,0 @@ -# Getting started - -A step-by-step first flake: create it, add a library overlay, register -a module, use an integration, then consume your flake from a second -one. The finished shape of each step also exists as a working flake -under `examples/literate-flake/` in the repository, with commentary. - -## 1. A minimal caisson flake - -Create a directory with this `flake.nix`: - -```nix -{ - description = "my first caisson flake"; - - inputs = { - caisson.url = "github:nix-caisson/caisson"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - }; - - outputs = - inputs@{ caisson, ... }: - let - lib = caisson.lib.caisson-core.mkLib { - inherit inputs; - projects = { - inherit caisson; - }; - }; - in - lib.caisson.mkFlake { - name = "my-flake"; - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/my-flake; - }; -} -``` - -`caisson-core.mkLib` composes a library: nixpkgs' lib, the machinery -under `lib.caisson-core`, and the overlays you register. Consuming -caisson as a project registers everything it exports, its -integrations included, which contributes `lib.caisson` (`mkFlake` -and friends); `mkFlake` then evaluates flake-parts with that library -and your config module, using caisson's own flake-parts pin, so your -flake declares none. - -The config module is the flake's own top-level configuration. Create -`configs/flake-parts/my-flake/default.nix`: - -```nix -{ ... }: -{ pkgs, ... }: -{ - systems = [ "x86_64-linux" ]; - - caisson.configInfo.configName = "my-flake"; - - perSystem = - { pkgs, ... }: - { - packages.default = pkgs.hello; - }; -} -``` - -Note the two argument lists: every registered file takes the closure -attrset (`{ closure-inputs, ... }`) first, then its ordinary module -arguments. That convention is the subject of -[Closed inputs](concepts/closed-inputs.md). - -Check it: - -```sh -nix flake check -nix build -``` - -## 2. Add a library overlay - -An overlay contributes a namespace to the composed library. Create -`lib-overlays/default/default.nix`: - -```nix -{ ... }: -{ - imports = [ ]; - overlay = final: prev: { - my-flake = (prev.my-flake or { }) // { - greet = name: "hello, ${name}"; - }; - }; -} -``` - -Register it in `flake.nix` and export it, and turn on the lib export -in the config module: - -```nix - lib = caisson.lib.caisson-core.mkLib { - inherit inputs; - projects = { - inherit caisson; - }; - libOverlays = mkLibOverlay: { - default = mkLibOverlay ./lib-overlays/default; - }; - }; -``` - -```nix - caisson = { - configInfo.configName = "my-flake"; - libOverlays.exported = libOverlays: { inherit (libOverlays) default; }; - lib.export.enabled = true; - }; -``` - -Now `lib.my-flake.greet` is available everywhere the composed library -flows: in the config module, in registered modules, and (with the -export enabled) to consumers as `flake.lib`. Use it in `perSystem`: - -```nix - packages.default = pkgs.writeText "greeting" (lib.my-flake.greet "Nix"); -``` - -## 3. Register a module - -Modules are class-keyed: `flake` modules feed flake-parts, and -integration classes (`nixos`, `homeManager`, ...) feed their module -systems. Register a flake-class module: - -```nix - lib = caisson.lib.caisson-core.mkLib { - inherit inputs; - projects = { - inherit caisson; - }; - modules = lib: { - flake.default = lib.caisson.mkFlakeModule ./modules/flake-parts/default; - }; - libOverlays = mkLibOverlay: { - default = mkLibOverlay ./lib-overlays/default; - }; - }; -``` - -`modules/flake-parts/default/default.nix`: - -```nix -{ ... }: -{ ... }: -{ - perSystem = - { pkgs, ... }: - { - devShells.default = pkgs.mkShell { packages = [ pkgs.nixfmt ]; }; - }; -} -``` - -`mkFlake` applies the selected flake-class modules alongside the -config module (`moduleImports` returns the list to apply, like -`libOverlayImports`; the default is all of them). -[Module classes](concepts/module-classes.md) covers registration, -selection, and export. - -## 4. Use an integration - -Integrations bring the same conventions to other module ecosystems -and take their ecosystem as an explicit `ecosystemSrc`. A NixOS system, -in the config module's `perSystem` or at the top level: - -```nix - flake.nixosConfigurations.example = lib.caisson.nixos.mkSystem { - ecosystemSrc = inputs.nixpkgs; - pkgSets.pkgs = import inputs.nixpkgs { system = "x86_64-linux"; }; - configModule = - { ... }: - { - boot.loader.grub.enable = false; - fileSystems."/" = { - device = "none"; - fsType = "tmpfs"; - }; - system.stateVersion = "25.05"; - }; - }; -``` - -Instead of passing `ecosystemSrc` at every call, a flake can set a -flake-level default at `mkLib` (`ecosystems.nixpkgs = inputs.nixpkgs`) -and drop the argument; an explicit argument still wins, and an input -named exactly `nixpkgs` is the last fallback. - -With caisson consumed as a project, its integration overlays are -already registered and applied, so `caisson.nixos` is present. To -compose only some of them, keep the project registration and select -per item over the combined dictionary: - -```nix - libOverlayImports = overlays: [ - overlays."caisson/flake-parts" - overlays."caisson/nixos" - overlays.default - ]; -``` - -Registering a single overlay by hand -(`nixos = caisson.libOverlays.nixos`) remains the way to cherry-pick -or rename one. The [library reference](reference/lib.md) documents -the integration namespaces. - -## 5. Consume your flake from another flake - -A consumer registers your exported overlay the same way: - -```nix -{ - inputs = { - caisson.url = "github:nix-caisson/caisson"; - my-flake.url = "github:you/my-flake"; - }; - - outputs = - inputs@{ caisson, my-flake, ... }: - let - lib = caisson.lib.caisson-core.mkLib { - inherit inputs; - projects = { - inherit caisson my-flake; - }; - }; - in - lib.caisson.mkFlake { - name = "consumer"; - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/consumer; - }; -} -``` - -The consumer's composed library now has `lib.my-flake.greet`: the -project registration brings in the overlays you exported, each -overlay's `imports` chain guarantees anything it depends on composes -with it, and your exported modules land in the consumer's registry -under `my-flake/`, selectable at each use site. Overlays that -contribute modules via `contributeModules` (see -[Module classes](concepts/module-classes.md)) deliver them the same -way. A consumer who wants only part of your project selects with -`libOverlayImports`, or registers single overlays from -`my-flake.libOverlays.` by hand; the -`my-flake.modules..` flake outputs remain for consumers -who import modules without composing anything. - -## Where next - -- [Closed inputs](concepts/closed-inputs.md), the convention every - registered file follows. -- [How `lib` is composed](deep-dives/how-lib-is-composed.md): the - whole composition pass, and composing with `caisson-core` directly. -- [Testing](./testing.md), including `callConsumerFlake` for testing - consumer flakes without a push/lock cycle. -- [FAQ](faq.md) for the questions this page tends to raise. diff --git a/docs/layout.md b/docs/layout.md deleted file mode 100644 index b058b03..0000000 --- a/docs/layout.md +++ /dev/null @@ -1,97 +0,0 @@ -# Repository Layout - -caisson mandates nothing about layout beyond the repository being a -flake: registration takes paths, and any arrangement evaluates. We -recommend the conventions below because they have proven to work for -us, they resolve ambiguity about where a thing belongs, and they make -it easier for someone new to a repository to come up to speed. This -repository and its integrations use them, and the documentation and -the `examples/literate-flake` example assume them. - -## flake.nix - -Wiring only: `mkLib` and `mkFlake`. The substance lives in the -directories below. - -## configs/ - -``` -configs/// -``` - -Configurations, grouped by module class and named for **what they -configure**. A flake-parts config configures the flake itself, so there -is typically exactly one, named after the flake (this repository uses -`configs/flake-parts/caisson/`) or `default`. In other classes, -a config is named for the thing it describes: a machine, a home, a -deployment. - -## lib-overlays/ - -``` -lib-overlays// -``` - -Library overlays by overlay name; most flakes start with a single -overlay called `default`. Each is a file taking the closure arg list -and returning `{ imports ? [ ], overlay }`; see -[Library Overlays](concepts/library-overlays.md). - -## modules/ - -``` -modules///default.nix -modules////*.nix -``` - -Reusable modules, keyed first by module class (directory names use the -ecosystem's name: `flake-parts`, `nixos`, `home-manager`), then by the -module's own name; the conventional exported module is -`modules//default/`. `default.nix` is the module's entry point, -and its implementation files sit under a directory named for the -defining flake, grouped by the option namespace they declare, in this -repository, `modules/flake-parts/default/caisson/lib.nix` declares the -`caisson.lib.*` options. - -## pkgs/ - -``` -pkgs// # or, in flakes with many packages: -pkgs/// -``` - -Package definitions. How package sets and package overlays are composed -and surfaced as outputs is the domain of `caisson-nixpkgs` (in active -use, not yet published); until its documentation is available, `pkgs/` -is best read as the conventional home for package expressions. - -## Package overlays - -Package overlays follow the same safety ideas as library overlays -(namespacing, input closure) but their tooling belongs to -`caisson-nixpkgs`, not to caisson itself. See -[Library Overlays](concepts/library-overlays.md) for the -shared principles. - -## tests/ - -``` -tests/unit/ # pure evaluation tests, wired into checks -tests/integration/ # nested flakes that consume this flake -tests/dependencies/ # a small flake whose lock pins test-only inputs -``` - -Unit tests are pure Nix expressions evaluated as a check. Integration -tests are nested flakes that take the project as an input and assert -that composition behaves as documented: consumption tested from the -outside, the way a consumer would experience it. `tests/dependencies/` -is a lock-bearing flake that pins inputs used only by the test and -formatter machinery, so the main `flake.lock` stays free of -test-only pins (it feeds the checks partition via -`partitionExtraInputs`). The [Testing](./testing.md) page covers how the -nested flakes are evaluated. - -## Other directories - -`examples/` (worked examples; `examples/literate-flake/` here) and -`docs/` (these pages) appear where a repository has use for them. diff --git a/docs/overview.md b/docs/overview.md deleted file mode 100644 index 4b3decc..0000000 --- a/docs/overview.md +++ /dev/null @@ -1,55 +0,0 @@ -# Overview - -This is the documentation for the framework's design and API. - -New here? [Getting started](getting-started.md) walks through a first -flake step by step; [Choosing a flake framework](positioning.md) -places caisson relative to plain flake-parts, flakelight, and -snowfall-lib; the [FAQ](faq.md) answers the questions the concept -pages tend to raise. - -**Reference** documents the exported surface: the -[Library](reference/lib.md) functions and the -[Options](reference/options.md). - -**Conventions**: [Repository layout](layout.md) describes the -directory conventions the documentation and examples assume. - -**Concepts** explain the machinery and the reasoning behind it, in -reading order: - -- [Closed inputs](concepts/closed-inputs.md): how modules and library - overlays close over the defining flake's inputs, and the explicit - closure convention every registered thing follows. -- [Module classes](concepts/module-classes.md): class-keyed module - registration and export. -- [Library overlays](concepts/library-overlays.md): namespaced, - dependency-declaring `lib` composition, and the patterns for writing - overlays. -- [Ecosystem sources](concepts/ecosystem-sources.md): why - integrations pin nothing, and the three places a source can come - from. - -**Deep dives**: -[How `lib` is composed](deep-dives/how-lib-is-composed.md) traces a -`mkLib` call from arguments to finished attrset, including the rules -that decide conflicts and composing through caisson-core directly. -[How inputs are closed over](deep-dives/how-inputs-are-closed-over.md) -traces the closure attrset from `mkLib` to a registered file, -including what each kind of registration receives and which values -cross flake boundaries. - -**Guides**: [Testing](./testing.md) covers unit and integration testing -(including `callConsumerFlake`), and -[Evaluation weight](eval-weight.md) covers measuring and gating -evaluation cost. - -For a complete working flake with commentary, see -`examples/literate-flake/` in the repository. The repository README -covers the quick start. - ---- - -Despite the org name, caisson is an independent project and is not -affiliated with, endorsed by, or sponsored by the NixOS Foundation. -Nix and NixOS are trademarks of the NixOS Foundation. diff --git a/docs/positioning.md b/docs/positioning.md deleted file mode 100644 index be39934..0000000 --- a/docs/positioning.md +++ /dev/null @@ -1,125 +0,0 @@ -# Choosing a flake framework - -Where caisson sits relative to plain flake-parts, flakelight, -snowfall-lib, and the dendritic pattern, characterized from those -projects' own documentation. The honest summary first: all five -produce working flakes, and the differences are about which -conventions you want enforced by machinery rather than by -discipline. - -## Plain flake-parts - -flake-parts is a minimal module system mirroring the flake schema: -it splits configuration into modules, handles `perSystem`, and -deliberately avoids broader opinions, positioning itself as "a single -module that other repositories can build upon" with an ecosystem of -independent compatible modules. - -caisson is built on flake-parts and keeps all of it. What it adds is -a set of enforced conventions on top: closed inputs (every registered -file takes an explicit closure argument list instead of reaching for -inputs ambiently), namespaced library overlays with declared -dependencies composed by caisson-core, class-keyed -module registration and export, integrations that take their -ecosystems as explicit `ecosystemSrc` arguments, and measured -evaluation-cost gates. Use plain flake-parts when you want the module -system and your own conventions; use caisson when you want these -conventions machine-enforced, particularly across several flakes that -consume each other's libraries and modules. - -## flakelight - -flakelight is a module-driven framework emphasizing automation: -sensible defaults, automatic import of nix files from a directory, -and auto-generated outputs (packages, overlays, formatters), with the -stance that what can be done automatically, should be. - -caisson leans the other way: registration is explicit, namespaces are -explicit, dependencies between overlays are declared, and nothing is -inferred from file layout. If you value minimal ceremony in a single -project, flakelight gets a working flake with fewer lines. If you -value being able to trace any attribute of a composed library to a -declared registration, especially across a fleet of interdependent -flakes, that explicitness is caisson's point. - -## snowfall-lib - -snowfall-lib generates systems, packages, modules, and shells from -directory-structure conventions: predictable filesystem hierarchies -in exchange for eliminated boilerplate, targeting multi-system NixOS -and nix-darwin setups. Its repository currently describes it as -seeking new maintainers. - -The comparison is similar to flakelight but stronger: snowfall infers -the most from layout, caisson infers nothing from layout. caisson's -integrations also differ structurally from a generator: they are thin -adapters over each ecosystem's evaluator, taking the ecosystem as an -explicit source argument and pinning nothing. - -## The dendritic pattern - -The dendritic pattern is an organizational discipline over -flake-parts rather than a framework: every Nix file except the entry -points is a module of the top-level configuration, each file -implements one feature across all the configurations it touches, and -lower-level modules (NixOS, home-manager, nix-darwin) live as -`deferredModule` values inside the top-level config, merged by name. -Files are commonly auto-imported with import-tree, and cross-cutting -values are read from the shared top-level `config` instead of -`specialArgs` threading. - -caisson agrees with more of this than with the generators above: -both build on flake-parts, both eliminate ambient `specialArgs` -plumbing (dendritic through the shared top-level config, caisson -through closed inputs and the composed library), and both group -modules by the module system they belong to (dendritic by option -path, caisson by class key). The differences are scope and -mechanism. Dendritic organizes one repository's configurations by -feature and, with import-tree, derives the import set from the file -tree; caisson registers modules and overlays explicitly and -infers nothing from layout. Dendritic keeps everything inside a -single module evaluation; caisson separates library composition from -module evaluation and adds export machinery so several repositories -can publish and consume each other's overlays and modules. Use -dendritic to structure one flake's configurations by aspect with -almost no machinery; use caisson when the unit of reuse is a -repository and the conventions need to hold across a fleet. - -## What is caisson-specific - -Independently of the convention trade-offs above, three things are -distinctive here rather than variations on a shared theme: - -- **Library composition with identity** - ([deep dive](deep-dives/how-lib-is-composed.md)): dedup, wholesale - replacement, and reliable polyfills, implemented in caisson-core, a - zero-dependency flake usable without caisson. -- **Explicit ecosystem sources**: integrations pin none of their - ecosystems; the consumer hands each in, so a single caisson - revision works with any nixpkgs, home-manager, or colmena revision - with a compatible evaluation contract. -- **Evaluation-weight gates** ([guide](eval-weight.md)): framework - overhead is measured and held to committed ceilings in CI rather - than described. - -## The relationship to flakes - -Flakes do two jobs today: acquisition (fetching, pinning, integrity) -and composition (deciding which copy of each dependency an evaluation -actually uses, via the `follows` pin bucket). caisson separates the -two. Flakes keep acquisition. Composition moves to the evaluation -layer, with real semantics: deduplication is -key identity, override is wholesale replacement of a keyed entry, -local patches are the keyless tail, and ecosystems (nixpkgs, -home-manager, and the rest) are handed in as explicit -`ecosystemSrc` arguments instead of being re-pinned and re-wired -through the input graph. - -Everything caisson adds is published through the flake schema's only -freeform slot, the `lib` output: composed libraries, the module -registry, and the manifest (the composition's self-description) all -live there, and the remaining flake outputs (`modules.`, -`libOverlays`, per-system products) are projections from it that keep -the standard schema's addresses. A flake built this way needs only a -small, regular subset of the flake schema; nothing about it requires -upstream changes to evaluate. diff --git a/docs/reference/lib.md b/docs/reference/lib.md deleted file mode 100644 index 309e265..0000000 --- a/docs/reference/lib.md +++ /dev/null @@ -1,426 +0,0 @@ -# Library Reference - -A composed library carries two framework namespaces. `lib.caisson-core` -holds the machinery, injected by `mkLib` itself (its code lives in -[caisson-core](https://github.com/nix-caisson/caisson-core), which -caisson pins internally). `lib.caisson` -holds the integrations and the pkgs-dependent tooling, contributed by -the overlays this flake exports. The flake-level `lib` output mirrors -both namespaces (`caisson.lib.caisson-core`, `caisson.lib.caisson`). - -Type notation used below: - -- `lib`: a composed nixpkgs-style library attrset -- `module`: a module for some module class's module system -- `overlayFn`: `final: prev: attrs`, the standard overlay function -- `libOverlay`: `{ imports : listOf libOverlay; overlay : overlayFn }`, - the built overlay produced by `mkLibOverlay` (both keys always present) -- `path` arguments are imported before the rules below apply - -## The caisson-core namespace - -- **Source:** caisson-core's `lib/` (`lifecycle.nix` for the - machinery, `default.nix` for keyed composition and the resolver) - -### `mkLib` - -``` -mkLib : - { inputs : attrs # the defining flake's inputs - , baseLib : lib # the base library, a plain argument - , modules ? (lib: { }) - : lib -> attrsOf (attrsOf module) # class -> name -> module - , libOverlays ? (mkLibOverlay: { }) - : (freeformOverlay -> libOverlay) -> attrsOf libOverlay - , libOverlayImports ? builtins.attrValues - : attrsOf libOverlay -> listOf libOverlay - , ecosystems ? { } : attrs # declared ecosystem sources, by exact name - , projects ? { } : attrs # consumed upstream contributions, by project name - } -> lib -``` - -Builds a composed library by extending `baseLib` with the -`caisson-core` namespace injection and the selected registered -overlays, then two synthetic overlays: the local module registrations -(so local names win over overlay-borne contributions) and the -manifest. Nothing is looked up by input name: `baseLib` is a plain -argument, and the `caisson-core.mkLib` found in a composed library -defaults it to that composition's own base. - -- `modules` receives the composed `lib` (usable through the fixpoint) - and returns the class-keyed registration, typically built with - helpers like `lib.caisson-core.mkModule` and - `lib.caisson.mkFlakeModule`. -- `libOverlays` receives the input-closed `mkLibOverlay` helper and - returns the registered overlays. Both arguments take exactly the - function shape shown; passing anything else is an error. -- `libOverlayImports` selects which registered overlays apply to this - flake's own `lib`; registration also feeds export, so the two can - differ. -- `ecosystems` declares default ecosystem sources for this - composition (`{ nixpkgs = inputs.nixpkgs; ... }`), keyed by the - exact names the integrations resolve. mkLib only captures them into - the manifest; the integrations interpret them. -- `projects` consumes whole upstream contributions - (`{ my-dep = inputs.my-dep; }`): each value carries `libOverlays` - and class-keyed `modules` dictionaries, which a caisson-built - flake's outputs already do. A project's overlays join the - registered dictionary and its modules join the class registry under - `/`, so the existing selections keep per-item - choice: `libOverlayImports` decides which overlays apply, the - registry selection at each use site decides which modules load, and - a local registration beats a same-named project entry. Registering - a single overlay by hand stays the way to cherry-pick or rename - one. - -### `mkLibOverlay` - -``` -mkLibOverlay : freeformOverlay -> libOverlay - -freeformOverlay = path | (closure -> { imports ? listOf libOverlay - ; overlay : overlayFn }) -closure = { closure-inputs : attrs - ; mkLibOverlay : freeformOverlay -> libOverlay - ; mkModule : string -> freeformModule -> module - ; contributeModules : attrs -> attrsOf (attrsOf module) -> attrs - } -``` - -Applies the closure attrset to an overlay given as a function or a path -to one, and normalizes the result: the built `libOverlay` always carries -both keys, with `imports` defaulted to `[ ]`. Already-built overlays are -registered directly rather than wrapped. - -The closure's `mkModule` is bound to the defining composition, so -modules contributed by an overlay close over the definer's inputs and -library. `contributeModules prev { . = module; }` -returns the `caisson-core.modules` registry merge for the overlay's -output (merge its result with any namespace contributions); it -is passed through the closure rather than the composed library because -an overlay's output attribute names must not depend on `final`. -Qualify contributed names with your project prefix -(`my-flake/my-service`); the composing flake's local registrations -apply last and win over same-named contributions. See -[Module classes](../concepts/module-classes.md) for the ways -modules enter the registry. - -### `mkModule` - -``` -mkModule : string -> freeformModule -> module - -freeformModule = path | (closure -> module) -closure = { closure-inputs : attrs # the defining flake's inputs - ; closure-lib : lib # the defining flake's composed lib - ; closure-self-modules : attrs # the defining flake's registrations - # in the same class - ; mkModule : freeformModule -> module # bound to the class - } -``` - -Factory for class-specific module normalizers. Given a class name, returns a normalizer that applies the closure attrset to a module given as a function or a path to one; the module takes the closure as its first arg list (`{ ... }:` when unused). Plain modules are imported/registered directly rather than wrapped. Path modules gain `_file` and a path-based dedup `key`. - -The `mkModule` closure member is bound to the same class, so nested module composition stays in that class. - -### `modules` - -``` -modules : attrsOf (attrsOf module) # class -> name -> module -``` - -The class-keyed module registry of this composition: the flake's own -registrations merged with every overlay-borne contribution, locals -winning on name conflicts. Integration adapters read their class from -here (`caisson-core.modules.`) as the default module -selection. - -### `manifest` - -``` -manifest : { inputs : attrs; modules : attrsOf (attrsOf module); - libOverlays : attrsOf libOverlay; ecosystems : attrs; - projects : attrs } -``` - -The composition's self-description, injected as its final overlay. -`inputs`, `ecosystems`, and `projects` are the `mkLib` arguments as -given; `libOverlays` and `modules` are the registered dictionaries, -so consumed projects' entries appear under `/` beside -the local registrations, with a local winning a name collision. An -mkLib composition self-describes: a consumer's composed library -carries the consumer's own manifest. Checks live on the export side -only (the flake-parts integration type-checks it and projects the -`flake.libOverlays` and `flake.modules` outputs from it, so an -`exported` selection can re-export a project-borne entry the same -way as a hand-registered one); producers validate their own -manifests in their own CI. - -### `importApply` - -``` -importApply : freeformModule -> attrs -> module -``` - -Applies static arguments to a module through `_file`/`imports` wrappers while preserving wrapper metadata. Used for threading arguments through module import chains. - -### `callConsumerFlake` - -``` -callConsumerFlake : - { path : path | string # directory containing flake.nix - , pool ? { } : attrs # inputs resolvable by name - , overrides ? { } : attrs # highest-precedence injections - , sourceInfo ? { } : attrs # extra self attrs (lastModified, rev, ...) - } -> flakeOutputs # self: inputs, outputs, outPath, _type -``` - -Evaluates a consumer-style flake from source with explicitly supplied -inputs: the heart of integration testing. The flake's declared inputs -resolve by name: `overrides` first, then `follows` chains through the -other resolved inputs, then `pool`; an unresolvable input throws an -error naming it. The self fixpoint and decoration are handled by the -shared `call-flake` kernel (also used by the eval-weight harness). -Nothing is fetched: locks are not read, and `sourceInfo` attrs appear -only if supplied. See [Testing](../testing.md). - -### `compose`, `resolve`, `partitionExtraInputs` - -Keyed composition (`compose`), the layered ecosystem-source -resolver (`resolve`), and the read-only-eval-safe partition -extra-inputs loader, re-exposed from caisson-core. See -[How `lib` is composed](../deep-dives/how-lib-is-composed.md) and -caisson-core's own documentation. - -## The caisson namespace - -### `mkFlake` - -- **Source:** `lib-overlays/flake-parts/default.nix` - -``` -mkFlake : - { configModule : module # flake class - , moduleImports ? builtins.attrValues - : attrsOf module -> listOf module # selection from the flake class registry - , name ? null : nullOr string # rev-independent module identity - , ... # forwarded to flake-parts mkFlake - } -> flakeOutputs -``` - -Builds final flake outputs via `flake-parts` using the composed -`lib`: the flake's `inputs` come from `lib.caisson-core.manifest` -(so `mkFlake` requires a manifest-carrying, mkLib-built composition), -and `moduleImports` selects over the `flake` class of -`lib.caisson-core.modules`, the same registry every adapter selects -from, so modules arriving by local registration, overlay -contribution, or consumed project are all selectable. The -flake-parts pin is caisson's own, closed over at the integration's -definition; consumers declare no flake-parts input. `name` sets -flake-parts' `moduleLocation` (so exported modules deduplicate across -revs) and defaults `caisson.configInfo.configName`. - -### `mkFlakeModule` - -- **Source:** `lib-overlays/flake-parts/default.nix` - -``` -mkFlakeModule : freeformModule -> module # = caisson-core.mkModule "flake" -``` - -Convenience form of `mkModule "flake"` for flake-parts modules. - -### `modules.flake."caisson/partitions"` - -flake-parts' partitions module, registered and exported in caisson's -flake class so a consumer selects it from the registry -(`moduleImports = modules: [ modules."caisson/partitions" ... ]`) -rather than declaring a flake-parts input for it. - -### `modules.flake."caisson/nixpkgs"`, `modules.flake."caisson/nixpkgs-interface"` - -- **Source:** `modules/flake-parts/nixpkgs/`, - `modules/flake-parts/nixpkgs-interface/` - -The nixpkgs integration's flake modules. `nixpkgs-interface` declares -only the overlay registry, `caisson.nixpkgs.overlays.all`: an attrset -of named overlay-producing functions (each takes the flake's -`configName` and returns an overlay; `mkPackagesOverlay` and -`mkPolyfillOverlay` below build them). Registering an overlay does -nothing by itself; a sibling flake module imports the interface to -make an overlay available and leaves selection to the consumer. - -`nixpkgs` imports the interface and adds the package-set machinery, -the `caisson.nixpkgs.*` options: - -- `pkgSets.`: a package-set definition: `pkgFunction` (a - nixpkgs-style entry point, e.g. `import inputs.nixpkgs`) and - `overlayImports` (a selection function from the registry to the - overlays to apply, default all). Each set is reified per system and - handed to `perSystem` modules as the `pkgSets` argument; - `pkgSets.pkgs` also becomes the default `perSystem` `pkgs`. -- `config`: the nixpkgs config applied to every generated package set. -- `overlays.exported` and `overlays.export.enabled`: the selection - from the registry published as the flake's `overlays` output. -- `pkgs.export.enabled`, `packages.export.enabled`: whether to export - `legacyPackages`, and the flake's own package scope - (`pkgs.`) as `packages`. - -### `eval-weight` - -- **Source:** `lib-overlays/tooling/eval-weight/` - -The evaluation-cost measurement harness: `eval-weight.mkCheck` builds a -check derivation that measures eval scenarios in a sandbox and gates -deterministic metrics against a committed baseline. Documented in -[Evaluation weight](../eval-weight.md). - -### `mkMemoizedDerivationRead` - -- **Source:** `lib-overlays/tooling/mk-memoized-derivation-read.nix` - -Builds memoized derivation-content readers; see the source header. - -## Types - -### `types.libOverlay` - -- **Source:** `lib-overlays/flake-parts/default.nix` - -A module-system option type for built library overlays. Its `check` -verifies the structure recursively: an attrset with an `overlay` -function and a (possibly absent) `imports` list whose entries are -themselves valid `libOverlay`s. Used by options that carry overlays, -such as `caisson.libOverlays.exported`. - -### `types.manifest` - -- **Source:** `lib-overlays/flake-parts/default.nix` - -A structural option type for the caisson-core manifest -(`{ inputs, modules, libOverlays }`). The export-side check: the core -flake-parts module reads `lib.caisson-core.manifest` through an -option of this type before projecting the `flake.libOverlays` and -`flake.modules` outputs. - -## Integration namespaces - -Each integration is a library overlay exported by this flake -(`libOverlays.`) and available as a keyed entry via -`lib.composition.entriesFor`. Composing one contributes its -`lib.caisson.` namespace, documented below (the flake-parts -integration contributes directly under `lib.caisson`, plus the -`lib.flake-parts` mirror of flake-parts' own library). Each entry -point takes its ecosystem as an `ecosystemSrc` argument, and -the integrations pin nothing themselves, with one exception: -flake-parts, whose pin is caisson's own hidden input. - -An adapter's ecosystem source resolves in layers: the explicit -`ecosystemSrc` argument first, then the composition's declared -`ecosystems.` (an mkLib argument, carried by the -manifest), then an input of the composing flake named exactly -``. The names are `nixpkgs` (the nixos integration), -`home-manager`, `colmena`, `terranix`, and `system-manager`. A full -miss throws at the adapter, naming the three places; a composition -built without mkLib (no manifest) accepts only the explicit argument. -Common conventions: - -- `pkgSets`: an attrset of package sets; `pkgSets.pkgs` is required - where present and becomes the evaluation's package set (also passed - through in `specialArgs`/`extraSpecialArgs`). -- `moduleImports`: a selection function over the corresponding class - registry (`lib.caisson-core.modules.`), returning the list - of modules to apply; the default, `builtins.attrValues`, applies - all registered modules. The list shape matches `libOverlayImports`; - for order-sensitive list-typed options, prefer `mkOrder` over - selection position. -- Framework-provided special arguments compose first; the caller's - win on conflict. - -### `caisson.nixos` (module class `nixos`) - -- **Source:** `lib-overlays/nixos/default.nix` -- `mkNixosModule : freeformModule -> module`: class-bound `mkModule`. -- `mkSystem : { ecosystemSrc, pkgSets, configModule, moduleImports?, - specialArgs?, ... } -> nixosSystem`: evaluates - `/nixos/lib/eval-config.nix` (a nixpkgs source tree) - with the selected class modules, the config module, and a framework - module pinning `nixpkgs.pkgs` to `pkgSets.pkgs`. Extra arguments - pass through to `eval-config.nix`. -- `mkSystemFull`: as `mkSystem`, additionally passing nixpkgs' - `module-list.nix` as `baseModules`. -- `mkSystemMinimal : { ecosystemSrc, prefix?, ... }`: bare - `evalModules` from `/nixos/lib`; no NixOS base - modules, so the config module declares any options it uses. - -### `caisson.home-manager` (module class `homeManager`) - -- **Source:** `lib-overlays/home-manager/default.nix` -- `mkHomeManagerModule : freeformModule -> module`. -- `mkHomeConfiguration : { ecosystemSrc, pkgSets, configModule, - moduleImports?, extraSpecialArgs?, osConfig?, check?, minimal?, - sourceMeta? } -> homeConfiguration`: runs home-manager's own - evaluator (`/modules`). Source metadata defaults - derive from what actually composes: `homeManagerOutPath` from - `ecosystemSrc` and `nixpkgsOutPath` from `pkgSets.pkgs.path` - (`schemaVersion` 3). -- `mkHomeConfigurationMinimal`: `mkHomeConfiguration` with - `minimal = true`. -- `mkStandaloneAdapter : { moduleImports?, ... } -> { homeModules, - buildHome }`: the selected class modules as a list plus a - `buildHome` closure over the same arguments. -- `mkNixosAdapter : { users, ecosystemSrc, hostName?, hostKind?, - baseSystem?, sourceMeta?, moduleImports?, sharedModules?, - useGlobalPkgs?, useUserPackages?, activationMode?, - extraSpecialArgs?, ... } -> module (nixos class)`: embeds - home-manager in a NixOS generation. `activationMode = "upstream"` - uses home-manager's own NixOS module; `"user-service"` embeds - standalone activation packages behind a `ConditionUser` user unit - and leaves `users.users` untouched, which keeps it safe for systemd-homed hosts (one - hosted user). Both write `/etc/caisson-home-manager/source.json` - for the drift check. -- `mkSourceMeta`, `assertSourceCoherence`: source-provenance records - and the fingerprint comparison used by the drift machinery. - -### `caisson.nixpkgs` - -- **Source:** `lib-overlays/nixpkgs/default.nix` -- `mkScope : pkgs -> (callPackage -> attrs) -> scope`: a - `makeScope` wrapper handing the scope function its `callPackage`. -- `mkPackagesOverlay : pkgsFn -> name -> overlayFn`: turns a scope - function (or path; optionally context-taking - `{ callPackage, inputs, lib }`) into an overlay that merges the - scope under attribute `name`. -- `mkPolyfillOverlay : overlayFn -> name -> overlayFn`: wraps an - overlay (or path; optionally context-taking) for registration - alongside package overlays; the name is ignored. -- `types.nixpkgsOverlay`, `types.nixpkgs`: option types. - -### `caisson.colmena` (module class `colmena`) - -- **Source:** `lib-overlays/colmena/default.nix` -- `mkColmenaModule : freeformModule -> module`. -- `mkColmenaHive : { ecosystemSrc, modules?, moduleImports?, - specialArgs?, ... } -> hive`: `ecosystemSrc.lib.makeHive` over the - passthrough arguments, with the selected class modules and - framework `specialArgs` merged into `meta` and `defaults`. - -### `caisson.terranix` (module class `terranix`) - -- **Source:** `lib-overlays/terranix/default.nix` -- `mkTerranixModule : freeformModule -> module`. -- `mkTerranixConfiguration : { ecosystemSrc, modules?, moduleImports?, - extraArgs?, ... } -> derivation`: - `ecosystemSrc.lib.terranixConfiguration` with the selected class - modules and framework `extraArgs`. - -### `caisson.system-manager` (module class `systemManager`) - -- **Source:** `lib-overlays/system-manager/default.nix` -- `mkSystemManagerModule : freeformModule -> module`. -- `mkSystemConfig : { ecosystemSrc, modules?, moduleImports?, - specialArgs?, ... } -> systemConfig`: - `ecosystemSrc.lib.makeSystemConfig` with the selected class - modules, plus a compatibility bridge for the current - nixos-unstable restructuring of the NixOS nix module (each half - self-retires; see the source comments). diff --git a/docs/reference/options.md b/docs/reference/options.md deleted file mode 100644 index 22b66b1..0000000 --- a/docs/reference/options.md +++ /dev/null @@ -1,85 +0,0 @@ -# Module Options Reference - -This reference documents the caisson framework's module options. All descriptions are sourced from the Nix-native `description` fields in the module code. - -For `lib.caisson` functions, see [Library Reference](./lib.md). - -## Options - -### `caisson.configInfo.configName` - -- **Type:** `nullOr str` -- **Default:** `null` -- **Source:** `modules/flake-parts/default/caisson/configInfo.nix` - -The canonical name of this flake. Used in doc/version strings and as a default namespace name for exports. Some export options (e.g. `caisson.lib.export.enabled`) require this to be set. - -### `caisson.lib.export.enabled` - -- **Type:** `bool` -- **Default:** `false` -- **Source:** `modules/flake-parts/default/caisson/lib.nix` - -Whether to enable lib export. When enabled, publishes the selection made by `caisson.lib.exported` as `flake.lib`. - -### `caisson.lib.exported` - -- **Type:** `function -> lazyAttrsOf raw` -- **Default:** `composedLib: composedLib.${configName}` (requires `configInfo.configName`) -- **Source:** `modules/flake-parts/default/caisson/lib.nix` - -Function that selects which parts of the composed library to publish as the flake's `lib` output. The default exports the flake's own namespace; caisson itself sets `composedLib: { inherit (composedLib) caisson caisson-core; }` so flake-level and composed-level addresses match. - -### `caisson.manifest` - -- **Type:** `caisson.types.manifest` (read-only) -- **Default:** the composed library's `caisson-core.manifest` -- **Source:** `modules/flake-parts/core/caisson/manifest.nix` - -The composition's manifest: `inputs`, `ecosystems`, and `projects` as given to `mkLib`, plus the registered `libOverlays` and `modules` dictionaries (project entries under `/`, locals winning). Reading it type-checks the manifest; the `flake.modules` and `flake.libOverlays` projections are drawn from it. - -### `caisson.modules` - -- **Type:** `attrsOf (submodule { export.enabled; exported; })` -- **Default:** `{}` -- **Source:** `modules/flake-parts/core/caisson/modules.nix` - -Export settings for each registered module class. Each class key defines: - -- `export.enabled` (`bool`, default `true`) -- `exported` (`function -> attrsOf deferredModule`, default `modules: { }`) - -The selected modules are published under `flake.modules.`. For the `"flake"` -class specifically, the same modules are also mirrored to `flake.flakeModules`. - -### `caisson.modules..export.enabled` - -- **Type:** `bool` -- **Default:** `true` -- **Source:** `modules/flake-parts/core/caisson/modules.nix` - -Whether to export modules for a given class. - -### `caisson.modules..exported` - -- **Type:** `function -> attrsOf deferredModule` -- **Default:** `modules: { }` -- **Source:** `modules/flake-parts/core/caisson/modules.nix` - -Function that selects which modules in a class to publish under `flake.modules.`. - -### `caisson.libOverlays.export.enabled` - -- **Type:** `bool` -- **Default:** `true` -- **Source:** `modules/flake-parts/core/caisson/libOverlays.nix` - -Whether to enable lib overlay export. When enabled, publishes the overlays selected by `caisson.libOverlays.exported` under `flake.libOverlays`. - -### `caisson.libOverlays.exported` - -- **Type:** `function -> attrsOf libOverlay` -- **Default:** `overlays: { }` -- **Source:** `modules/flake-parts/core/caisson/libOverlays.nix` - -Function that selects which registered library overlays to export as flake outputs. Receives the set of overlays registered via `mkLib` and returns the subset to publish under `flake.libOverlays`. diff --git a/docs/testing.md b/docs/testing.md deleted file mode 100644 index af8847c..0000000 --- a/docs/testing.md +++ /dev/null @@ -1,87 +0,0 @@ -# Testing - -The conventions and tools for testing a caisson flake, as used by this -repository's own test suite (`tests/` here is a worked example of all -of it). - -## Unit tests - -`tests/unit/` holds pure evaluation tests, wired into `checks`; this -repository runs them with nix-unit. Anything that can be asserted by -evaluating `lib` belongs here. - -## Integration tests: consumption from the outside - -The strongest test of a flake framework is what a consumer experiences, -so integration tests are **nested flakes** under `tests/integration/` -that take the project as an input and assert that composition behaves -as documented. They are written as completely normal flakes: - -```nix -{ - inputs = { - # Standalone equivalent (without shared deps infrastructure): - # nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - # flake-parts.url = "github:hercules-ci/flake-parts"; - # parent.url = "github:example/my-flake"; - - deps.url = "path:../../dependencies"; - parent.url = "path:../../.."; - - nixpkgs.follows = "deps/nixpkgs"; - flake-parts.follows = "deps/flake-parts"; - }; - - outputs = inputs@{ parent, ... }: { - # consume `parent` the way any downstream flake would - }; -} -``` - -## tests/dependencies - -A small lock-bearing flake whose only job is to pin the inputs that -tests (and formatters) need, so the main `flake.lock` stays free of -test-only pins. `partitionExtraInputs` feeds it to the checks -partition. - -## Evaluating the nested flakes - -Hand-threading a nested flake's inputs (the recursive self fixpoint, -the resolved input graph) is the genuinely hard part, and -`callConsumerFlake` owns it. In the parent's checks: - -```nix -let - consumerPool = { - inherit (inputs) flake-parts nixpkgs; - deps = inputs.self; # the tests/dependencies flake - parent = self; # the flake under test - }; - - minimalConsumer = lib.my-flake.callConsumerFlake { - path = self.outPath + "/tests/integration/minimal-consumer"; - pool = consumerPool; - }; -in -{ - checks = minimalConsumer.checks.${system}; -} -``` - -The nested flake's declared inputs resolve by name (`overrides` first, -then `follows` chains, then the pool), and an unresolvable input throws -an error naming it and what to do. The contract is deliberately -explicit, in the same spirit as closed inputs: nothing is fetched, no -lock is read, and you supply exactly the input graph you mean the test -to see. One consumer's outputs can feed another's `overrides`, so -chains of consumers (a flake consuming a flake that consumes yours) are -plain data flow. - -See [`callConsumerFlake`](reference/lib.md#callconsumerflake) for the -full signature. - -## Gating evaluation cost - -Beyond correctness, `checks` can gate what evaluation *costs*; see -[Evaluation weight](eval-weight.md). diff --git a/site/assets/favicon.svg b/site/assets/favicon.svg deleted file mode 100644 index 01420df..0000000 --- a/site/assets/favicon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/site/assets/social-card.png b/site/assets/social-card.png deleted file mode 100644 index aca78e3a33c45246600ab4cef6c41c61c5e05a32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38455 zcmd?QWmFtp^Do*sK|%!gIPQ`_&?FFo%ivCMmw}*z1PBly3BlcAu;4Oy0wlp@7+`RR zL4z~M={(Q-p0&|8;d*<$j7u-- zfNXYGZH&`4{dy`?m+SOF`N3^yFX2>OXWCrj^Zt$R{?7&**JgbO8s}m+sf!x@cfH%z z&StI;o>#;6yew$Q!5M;Apka#Rzb#;y8i)1Y<|hmMz<*oPF}DHSf1A(b0OEgJ%d|MG z|F-_`cI_rIf48wKM0^LrrU0j~5L;6MK~=iuJ@xBE|iTER4R)8A7oKE87A5OZwF~7b)5f7`+r#LRf51i-2Z6E zG8I+ee_rtajoPqQ52TA*t7JTCaEiO_=Y0k5CzO~8K^_5Zc)>5Vi0zZE6(|5TJu3~K}Ff=^U4 z1gr;JZD;)s9OkW`T()IO!Dc^3rwgG>U@a%QPnW!ON_EXv`%|qGc#JkB5|Yji)>2D{ zo6;qG$4%x$=ln)t__k;p-%{OS$;)c*gVj{a-UQ=wU$e`Q>zJvko~@rhKU`n;#Ij`W z?~2822XNJy)UOe9*(O8lCUrkGL9{BkdtrJB%km@0G%KHm1C|O%3PDU(UabUd}73M9t3bh^8GClK&c!#gv7WkP{wW zD~$MXQr4nb%Nzo!>F15F^3bOG0sNrEyd>b_I+BQ%($iLUQj5xR0+8WJo`x7rP`{W{(4Yo z*wt-szW&0cVH_ieo0&d{jS~vhsU-%W)|;lFP_ppWTVd+yx`WQF5GX28`Kn2xF8{Khnin%+jO* z=}o>H!~kpU2;+qwoNM^cqu~j6@@jeo%%wc+uqWWOc&CF+ZRqH9NmZz{#96=S&t|f9 zhOMs7T+L`>dEirSw0zGts`dCItfl4p%2!i!fgyZ+MbYHk?R#C>>`Cm^x@w`(G0%r(_W_8U<9B@GY$}k2+9(5%R^$%*uVv^dN=ok$sg~WzzBd zyaZA@1eC{ZJfK!_>?(lp`BGSxU`&Xw?r;4FU}4gN7b~_ zdj!ljdtY5A>RT&f&R0{dF9{ESz7|*-XXhJo6YBlJD&`qSd%*jyna8MUtS?h)5FL9E zrE)*(5_)y9^6fiw{Z!(yZn-D{qr&?WeJ`eZv9Rs^5=RY(FHFjLW|IfH=*6u`(M!kA z)bI;MxOiDzFSy|g>my=PS&X3?LF(CXPHV;0=MFpImFvumKgi*1c0w5zy11p?g`ZI^(?gsz>ScG@4G#|;*I4yLX zK-T_(J#^h3vtN&T?2mKM#T&prF7o`{9_v3-{A&lvvsrUeTr0<(I=y`HJB)a9>SRPw zA|g7SM!UkL`VM*?Vlz)qztUJ)V%j6ztWEH_&iVmC?A*i0`5*3H`5#wj+OE4ghA>Jv zu%-wZ2s4IrysVA3yM4;3rbRVJ({{R|d+*!$Mtcw*l-zkq?et`FP*OY2jQB9cXZz{B zta7?0&oTHW6%goFew9A>{J4z{05)GoXFbXE`+c!U)5cdm_RaTnA-t~%nqKgEEKu`P zPIt{!#!Cw$@)yEvyRF!Pmk1#p`;K7h(aFZ~i~hL_QNd@==*~_K4H5o!uh4z@cLsOr z?TUSr&<{8WnvO~@N?xL~tE;OwLEZRPsG1479f-wcHSG9y95so1&2t6jT(QU3qp4?3mN?Q4zYTEb`T&1|R_*lL_wh;BY^RZ{qd<}0>$ zYwXeBeLm}hrCxetqC4g@`R<=-#qKSYMJNbjIAQ0IYX(tJ{2jICy;TcyBWZ(NsaEhW zQyWjO5|5$Nu0tbRW5t@EGfUL&#~NK3o?y6@1&${>Lgoa@J^V)-A%m`xm##QoJ03TW zR>Wt$jkcGS6{?pWkGnI#Icwm)MwE|NpE&_Q5shVls>IpXWavUCLABSQ@hBQ?5B^;1 z&)b#CuND8|JuJGACpYD1=d0<;p^?5v7en|w-plmBDpVZ5tLzG`{(bUOZ-hY?wb!3S zeY-L1mucIgvik%;mde*7_5;mh(cY`QJujk*mo9#O(*U4G>RNJ5$;jt!?&HwnU!sW| z`n;RZ$yQV!N_M)NDpe5>uNR#Y;cQqeTk{zu3aPZEGd%i{#MG=!e@0i?$wJs!#tGG^ zcEJPGBC8}U{eGX9>UBIxlaz4Ez;^Qp3JEy?z-#@qNf9cc8Lw-9!j0@Vj}q;85_hWf z=BNC3&uN!REraYod_EzWjk9=i8D7G~GUr6kJs_}7nqK2!2{|%(U|Zlp^F(zOlLERe z8cUzw$6xme`bnhJErT{QY6r2}&USXcL5PhHrNgRx+>{dD+I&`UH^zG1H0%F+0!Oqj3hD3E#BCYEW=a^MrO+YwZ!$j3i&^3m0HC*MGELULg za?9{VTVCTMg3Q|}-sV{;mVwMz=143mlrfV62t3zFoRjc9PKk(!_%fIb*&#TLQk6XW zN=Hk}2LM_YZ-4j?xp!YG@(Q0CS+(_zCh3$%A2-LGtkAEodQVqB#T4*9wYwA9lV>?B z&}g?DfD|Zv^m54$0E8k1#4Wv6I;Ged0Da!JY5U_H|Ia)fK;VR&oC>W@sz@WWMAoP4 zM&N-g1mbZLrE@Ql%C)=GeOpAI!nM`IcDXUVAUR*B^utx?hXyTD5V?F5Xj=2!{v(KB zyH609!;o6xw^Cx|;V}jP^Rn?b%W*z*L(R9^Jlu=l{W7SHvdrgt*!UawaR z|K9K}zhdzD(bsbtReAv6prdo$Wlk)ptPK2iOdIFZ48r@>{hrL95HC{{t5$~hDjh7W zso8<3EZhnT{ao<7mJellV14E!9qK$T^?(-d7BK&YF0JaRr5n%JGj?pMg!KSpH58+L zE$2Dc>i@atOx)jR8n(o3Q1_$h3=a^BeTwAwxGpn(=TM%9*ldis%wKrM-3f408p5U@f{O zeWn-R2ckI?z)-RDy3oJD$-O~cM*;Gt>jq4VKOaz{fI@XXBozE2GqjmRlAmr(R(Hq8 zs6(T)PYd~_YPqX)kyM3WdTS9j5?y+Aa$42-Wu#E!R?q_FMMWN-pP%`oc? z!*zA5B*$Bj3jYm76A`2uRT?u%2PGQ^@mQuUBTG5u%yqGWv!Pf3lnY}P0wIXn-tHAt zr=zvzsuv1><(d$^QwjhNRlB^;J9Q;ojKJ^i;2j}NHQM@B5A{l=n(aqrNcg5V;Y*z@ zYpy<+O?B9tB?AH*61Xic+RVp(YU_F||5WF=(2$lQmLcgGp+uqw)Xa% z=eyO4mpK6o^)T#c0CCY~Yf(*LMQn6RnL*tEn=n2g^aQF5?ZRgjWWP1MRV>2zn}fj8 z8}qo{eeJ<27*)(AAFRct4+}EBpCsw%4bpAuHVHT!PG1E>mRRKxheS^ZQ6#zC(W9Ul zW<5b?<8$l5flh$}@B35$usHIP#rI^#wftN&tZ=VlddRQ3wgrJdNI^|jYr(tn;om}t+EyfPj`WM>Q}%)ACl>kef`mkycn7U>n%wkj zitpR7Ow#OiD|V*@&gnv#7&6>>ZQY3R$atuJc>~l-2GFqi`(LWu_5a|r?^#}w*3vI> zdp&f@$?o6@u{u3nDD~+|qHn+JF8XR4UB@p25?ePZ4~sTq%Ud^eD)uR(4de_WpkPf(|vDnu-hI;kYJ707(1&o;*Itr^70 z2(D8${VgEnLvNy-1~FF-Ok_p*-i7b@vJU;Rqho^E#>k^pit9a?Xspzgb%YYx9UT{K zqZwZ$R&m0>`I4NM%*ed;9QlNPN?5C7+T?15^y-D#`gmEY=Gd#Jv8V>QQqA#ti-Ul1 z9O-J!j5=qNSa$;j?%=zhf-}?KobBIec>CuTTPI)#Bcs>Lk&c|2lkl!~9x`oy7C7J)Xp3zij`7 zkz;NReh^8!=hj5Rx~p1D(>z?f>T$I?ZBhd3-Vz8lL>#Byeqsy*eM#1H$R)d!xZk_> z291CMd0}&+z>d(N`d_>)HG{?UYMiC1>i(pP!eH<+Y1FaDIMv@E4(p%^GshxG$-L}DNU$gP4+yDUi;kPuCy~+7V zV*i8K=NczV-P&^0R=W~*G0fZ!;d1YDe6Hi3TvHrOSX@W$ftimnCD`1(gEuCbuE@sn z(dqFS%P(JE12(oD+BLi03E;s0xuX?SWXTqM+15{t^u(K#O)jQv%%}s zFs~2@adCJz*;gOJToRVdcZ-7;d_pna2h(*%+$XjghSm?qZh_3~e3c#iN>U^DOyCX$ z(3P3cfatB^HdqyP5E?Smnv$-FZE>n|ijbxPA1(1MYSF?gB` zN(!EzCynm1%a@z&KohQMTm$$?j#-yG2* zZTxE*_=Ibfhkk)5go2vG5 z<|w%ewqpkL^|QO8SPn~Yt z4b}sHr{lvdukv$ydxKS-OQiccLa!jE#@uVpKT%3DOYv2v$ntCp?>R-{K0Xc*T`1iF zWo+jn`;v*Dn5AiPU8Yw(l^2@OGHK8cu#=B=gAPZ%As@<27f-}g?xejSbY>VhsFQyn zSs!(VriIAT@Jf84Tss=6BDa_6`DKxL`SEOorfuta40`|b?P~TXai%sBl<(p8)tpRd zMMm7RNBr-1r@u@xTCWjc-8uX5cp*5+Kr=? zxX>JWTc(^U2F2@2GN)*u=eP>HYZ{YHG`w%0DR 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 - - - - -caisson: the foundation framework for composable Nix flakes - - - - - - - - - - -
- -
- - -
-
-

caisson

-

The foundation framework for composable Nix flakes.

-

- A caisson is sunk to bedrock, sealed, and becomes the foundation: - the part of the bridge no one sees, and the part everything else - stands on. caisson can provide that structure for your flake: closed inputs, - disciplined library composition, and module classes, based upon - flake.parts. -

-

- Get started - Read the docs -

-
- -
- -
- -
-
-
-

Closed inputs

-

- Modules and overlays close over your flake's inputs, not - your consumer's. Downstream users are not required to re-declare - your internal dependencies, and can still override any input - through standard flake mechanisms. -

-
-
# a module, closed over the
-# defining flake's inputs
-{ closure-inputs, ... }:
-{ ... }:
-{
-  imports = [
-    closure-inputs.some-helper.modules.flake.default
-  ];
-  # consumers are not required to declare
-  # their own dependency on some-helper
-}
-
- -
-
-

Library overlays

-

- Flakes should export their logic under lib.<namespace>: - namespacing instead of a fight over the global namespace. - Overlays declare their dependencies explicitly and compose in - dependency order, so layered abstractions stay predictable. -

-
-
# lib-overlays/default/default.nix
-{ closure-inputs, ... }:
-{
-  imports = [
-    closure-inputs.other-flake.libOverlays.default
-  ];
-  overlay = final: prev: {
-    my-flake = (prev.my-flake or { }) // {
-      greet = name: "hello, ${name}";
-    };
-  };
-}
-
- -
-
-

Module classes

-

- Modules register under a class key naming the module system - they belong to: flake for flake-parts modules. - The class keeps a module from being imported into an - evaluation that can't understand it; each shipped - integration registers its own class for its module - ecosystem. -

-
-
modules = lib: {
-  flake = {
-    default = lib.caisson.mkFlakeModule
-      ./modules/flake-parts/default;
-  };
-};
-# exportable to consumers,
-# closed over your inputs
-
-
- -
-

Get started

-
    -
  1. Add the input. inputs.caisson.url = "github:nix-caisson/caisson";
  2. -
  3. Compose your library. caisson-core.mkLib merges your overlays with caisson's into your flake's lib.
  4. -
  5. Emit your outputs. mkFlake evaluates your config module and module selections into flake outputs.
  6. -
-
# flake.nix
-{
-  inputs.caisson.url = "github:nix-caisson/caisson";
-
-  outputs = inputs@{ caisson, ... }:
-    let
-      lib = caisson.lib.caisson-core.mkLib {
-        inherit inputs;
-        projects = { inherit caisson; };
-        libOverlays = mkLibOverlay: {
-          default = mkLibOverlay ./lib-overlays/default;
-        };
-      };
-    in lib.caisson.mkFlake {
-      configModule =
-        lib.caisson.mkFlakeModule ./configs/flake-parts/my-flake;
-    };
-}
-

- The repository's - examples/literate-flake walks the whole structure with - commentary; the docs explain each concept - (closed inputs, module classes, library overlays, and the library - lifecycle) with the reasoning behind the design, and the - layout conventions cover how a - caisson repository is arranged. -

-
- -
-

Integrations

-

- caisson ships integrations that carry the same conventions - (closed inputs, library overlays, class-keyed modules) into - other module ecosystems, each registering its own module class. - flake-parts itself is one of them: -

- - - - - - - - - -
Integrationbrings caisson's conventions to…
flake-partsflake outputs: mkFlake, plus exporting modules and overlays
nixpkgsnixpkgs package sets and overlays
nixosNixOS configurations
home-managerHome Manager configurations
terranixTerranix / Terraform configurations
colmenaColmena deployment hives
system-managersystem-manager configurations on foreign distros
-

- Each integration is a library overlay exported by this flake, - also consumable as keyed entries through - caisson-core - directly. Integrations take their ecosystem as - an explicit ecosystemSrc argument and pin nothing - themselves. -

-
- -
- -
-

- Docs · - GitHub · - MIT licensed · Built on flake-parts -

-

Despite the org name, caisson is an independent project, not affiliated with or endorsed - by the NixOS Foundation. Nix and NixOS are trademarks of the NixOS - Foundation.

-

Fog rolls in and lights come on.

-
- - - diff --git a/site/style.css b/site/style.css deleted file mode 100644 index 96d20ad..0000000 --- a/site/style.css +++ /dev/null @@ -1,369 +0,0 @@ -/* caisson site — the page is the emblem: sky, bridge, water, bedrock. - Theming: day/night variables set below; night applies either through - the system preference (no override stored, or no JS) or through - html[data-theme="dark"] set by the toggle. */ - -:root { - --iron: #43464B; - --iron-soft: #5C6167; - --fog-light: #D8DBDE; - --fog-lighter: #E4E6E9; - --deep-water: #27394E; - --bedrock: #1B2635; - --nix-blue: #5277C3; - --nix-sky: #7EBAE4; - --gold: #C9A227; - --violet: #7F5AB6; - - /* day theme */ - --t-bg: var(--fog-light); - --t-text: var(--iron); - --t-link: var(--nix-blue); - --t-sky-hi: var(--fog-lighter); - --t-sky-lo: var(--fog-light); - --t-muted: var(--iron-soft); - --t-ghost-text: var(--iron); - --t-ghost-border: var(--iron-soft); - --t-pre-bg: var(--bedrock); - --t-pre-text: #D5DCE4; - --t-comment: #8A97A6; - --t-water-bg: var(--deep-water); - --t-h2: #E4E6E9; - --t-bedrock-bg: var(--bedrock); - --bb-struct: var(--iron); - --bb-struct-op: 0.8; - --bb-susp-disp: initial; - --bb-led-disp: none; - --bb-embellish-disp: none; - --bb-waterline: var(--bedrock); - --bb-caisson: #2E4158; - --bb-violet-op: 0.18; - --toggle-glyph: "\263E"; /* offer the moon by day */ -} - -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { - --t-bg: #151B24; - --t-text: #C7CCD3; - --t-link: var(--nix-sky); - --t-sky-hi: #171D27; - --t-sky-lo: #131820; - --t-muted: #93A0AE; - --t-ghost-text: #C7CCD3; - --t-ghost-border: #93A0AE; - --t-pre-bg: #090E15; - --t-pre-text: #C7CCD3; - --t-comment: #6C7886; - --t-water-bg: #0E141D; - --t-h2: #D8DBDE; - --t-bedrock-bg: #090E15; - --bb-struct: #39414C; - --bb-struct-op: 0.9; - --bb-susp-disp: none; - --bb-led-disp: initial; - --bb-embellish-disp: initial; - --bb-waterline: #05080C; - --bb-caisson: #1C2836; - --bb-violet-op: 0.25; - --toggle-glyph: "\2600"; /* offer the sun by night */ - } -} - -:root[data-theme="dark"] { - --t-bg: #151B24; - --t-text: #C7CCD3; - --t-link: var(--nix-sky); - --t-sky-hi: #171D27; - --t-sky-lo: #131820; - --t-muted: #93A0AE; - --t-ghost-text: #C7CCD3; - --t-ghost-border: #93A0AE; - --t-pre-bg: #090E15; - --t-pre-text: #C7CCD3; - --t-comment: #6C7886; - --t-water-bg: #0E141D; - --t-h2: #D8DBDE; - --t-bedrock-bg: #090E15; - --bb-struct: #39414C; - --bb-struct-op: 0.9; - --bb-susp-disp: none; - --bb-led-disp: initial; - --bb-embellish-disp: initial; - --bb-waterline: #05080C; - --bb-caisson: #1C2836; - --bb-violet-op: 0.25; - --toggle-glyph: "\2600"; -} - -* { box-sizing: border-box; } - -body { - margin: 0; - font-family: Inter, "IBM Plex Sans", system-ui, sans-serif; - line-height: 1.6; - color: var(--t-text); - background: var(--t-bg); -} - -::selection { background: var(--violet); color: #fff; } - -a { color: var(--t-link); text-decoration: none; } -a:hover { text-decoration: underline; } - -/* --- sky --- */ - -.sky { - background: linear-gradient(var(--t-sky-hi), var(--t-sky-lo)); - padding: 0 1.5rem; -} - -nav { - max-width: 46rem; - margin: 0 auto; - padding: 1.25rem 0; - display: flex; - justify-content: space-between; - align-items: center; -} - -.brand { - font-weight: 600; - letter-spacing: -0.02em; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -.brand img { border-radius: 6px; } - -.links { display: inline-flex; align-items: center; gap: 1.25rem; } - -.links a { color: var(--t-muted); } - -.theme-toggle { - background: none; - border: none; - padding: 0.1rem 0.3rem; - cursor: pointer; - font-size: 1.05rem; - line-height: 1; - color: var(--t-muted); -} - -.theme-toggle::after { content: var(--toggle-glyph); } - -.theme-toggle:hover { color: var(--t-text); } - -/* the hero sits in the valley of the cables */ - -.hero-scene { - position: relative; - background: var(--t-sky-lo); - /* past the width where the text-driven height would squash the scene, - grow with the viewport at the scene's own aspect ratio */ - min-height: 48.4375vw; -} - -/* the text drives the container height; the scene is a backdrop that - covers it, so the deck always clears the reserved bottom band */ -.hero { - position: relative; - z-index: 1; - padding: 2.25rem 1.5rem 400px; - text-align: center; -} - -.hero h1 { - font-size: clamp(2.5rem, 5.5vw, 4.25rem); - font-weight: 500; - letter-spacing: -0.03em; - margin: 0; -} - -.tagline { - font-size: clamp(1rem, 1.9vw, 1.35rem); - color: var(--t-muted); - margin: 0.25rem 0 1.25rem; -} - -.lede { - max-width: 38rem; - margin: 0 auto; -} - -.hero-actions { margin: 1.5rem 0 0; } - -@media (max-width: 900px) { - .hero { - text-align: left; - max-width: 46rem; - margin: 0 auto; - padding: 2.5rem 1.5rem 1rem; - } - .lede { margin: 0; } -} - -.button { - display: inline-block; - padding: 0.55rem 1.2rem; - border-radius: 8px; - background: var(--nix-blue); - color: #fff; - font-weight: 500; - margin-right: 0.75rem; -} - -.button:hover { text-decoration: none; filter: brightness(1.08); } - -.button.ghost { - background: var(--t-sky-lo); - color: var(--t-ghost-text); - border: 1px solid var(--t-ghost-border); -} - -/* --- the bridge band --- */ - -.bridge-band { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - display: block; -} - -@media (max-width: 900px) { - .bridge-band { - position: static; - height: 380px; - } -} - -.bb-sky { fill: var(--t-sky-lo); } -.bb-water { fill: var(--t-water-bg); } -.bb-structure { stroke: var(--bb-struct); opacity: var(--bb-struct-op); } -.bb-susp { display: var(--bb-susp-disp); } -.bb-led { stroke: #E8ECF2; display: var(--bb-led-disp); } -.bb-cover { display: var(--bb-embellish-disp); stroke: var(--t-sky-lo); } -.bb-bloom { display: var(--bb-embellish-disp); stroke: #E8ECF2; } -.bb-anchorage { fill: var(--bb-struct); } -.bb-waterline { stroke: var(--bb-waterline); } -.bb-caisson { fill: var(--bb-caisson); stroke: var(--nix-sky); } -.bb-walls { stroke: var(--nix-blue); } -.bb-violet { fill: var(--violet); fill-opacity: var(--bb-violet-op); } -.bb-lambda { stroke: var(--nix-blue); } -.bb-rivet { fill: var(--gold); } - -/* --- water --- */ - -.water { - background: var(--t-water-bg); - color: #C9CDD2; - padding: 3rem 1.5rem 4rem; -} - -.water a { color: var(--nix-sky); } - -.water h2 { - font-size: 1.35rem; - font-weight: 600; - color: var(--t-h2); -} - -.water pre code { background: none; } - -.water code { - background: rgba(126, 186, 228, 0.12); - padding: 0.1em 0.35em; - border-radius: 4px; - font-size: 0.9em; - font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace; -} - -pre { - background: var(--t-pre-bg); - color: var(--t-pre-text); - padding: 1.1rem 1.3rem; - border-radius: 10px; - overflow-x: auto; - font-size: 0.85rem; - line-height: 1.5; - border-bottom: 3px solid var(--nix-blue); - margin: 0; -} - -pre code { background: none; padding: 0; font-size: inherit; } - -pre .c { color: var(--t-comment); } - -.features { - max-width: 62rem; - margin: 0 auto; - display: grid; - gap: 2.5rem; -} - -.feature { - display: grid; - grid-template-columns: 1fr 1.1fr; - gap: 2rem; - align-items: center; -} - -.feature:nth-child(even) pre { order: -1; } - -@media (max-width: 46rem) { - .feature { grid-template-columns: 1fr; } - .feature:nth-child(even) pre { order: 0; } -} - -.get-started, .design, .integrations { - max-width: 46rem; - margin: 3.5rem auto 0; -} - -.steps { padding-left: 1.25rem; } -.steps li { margin-bottom: 0.5rem; } -.get-started pre { margin: 1.25rem 0; } - -.design h3 { - font-size: 1.05rem; - font-weight: 600; - color: var(--t-h2); - margin-top: 2rem; -} - -.design pre { margin: 1.25rem 0; } - -.where { - border-collapse: collapse; - width: 100%; - margin: 1.25rem 0; - font-size: 0.95rem; -} - -.where th { - text-align: left; - color: var(--t-h2); - border-bottom: 2px solid var(--nix-blue); - padding: 0.4rem 0.75rem 0.4rem 0; -} - -.where td { - vertical-align: top; - border-bottom: 1px solid rgba(126, 186, 228, 0.18); - padding: 0.55rem 0.75rem 0.55rem 0; -} - -/* --- bedrock --- */ - -.bedrock { - background: var(--t-bedrock-bg); - color: #8A97A6; - text-align: center; - padding: 1.5rem; - font-size: 0.9rem; -} - -.bedrock a { color: #A9B4C0; } - -.colophon { font-size: 0.8rem; opacity: 0.8; margin-bottom: 0; } diff --git a/theme/index.hbs b/theme/index.hbs deleted file mode 100644 index 41a8937..0000000 --- a/theme/index.hbs +++ /dev/null @@ -1,374 +0,0 @@ - - - - - - {{ title }} - {{#if is_print }} - - {{/if}} - {{#if base_url}} - - {{/if}} - - - - {{> head}} - - - - - - {{#if favicon_svg}} - - {{/if}} - {{#if favicon_png}} - - {{/if}} - - - - {{#if print_enable}} - - {{/if}} - - - - - - - - - - - {{#each additional_css}} - - {{/each}} - - {{#if mathjax_support}} - - - {{/if}} - - - - - - - -
-
-

Keyboard shortcuts

-
-

Press or to navigate between chapters

- {{#if search_enabled}} -

Press S or / to search in the book

- {{/if}} -

Press ? to show this help

-

Press Esc to hide this help

-
-
-
-
- - - - - - - - - - - - - -
- -
- {{> header}} -
- - - {{#if search_enabled}} - - {{/if}} - - - - - -
- - - -
- - - - - - - - {{#if live_reload_endpoint}} - - - {{/if}} - - {{#if playground_line_numbers}} - - {{/if}} - - {{#if playground_copyable}} - - {{/if}} - - {{#if playground_js}} - - - - - - {{/if}} - - {{#if search_js}} - - - - {{/if}} - - - - - - - {{#each additional_js}} - - {{/each}} - - {{#if is_print}} - {{#if mathjax_support}} - - {{else}} - - {{/if}} - {{/if}} - - {{#if fragment_map}} - - {{/if}} - -
- -