Unify vhost rendering on contracts/routes, or delete the contract #20

Open
opened 2026-08-04 07:56:38 +00:00 by agent.odin · 0 comments
Collaborator

Stable ID: FM-AUDIT-03
Source: modules/contracts/routes.nix, every services.{caddy,nginx} writer
Source status: new finding, not previously tracked
Disposition: open owner decision, then architecture work
Epic: #9
Blocked by: #11 (FM-AUDIT-02)

Problem

contracts/routes exists to make route intent renderer-agnostic: "a producer
… says nothing about Caddy, nginx, or Authelia — that translation is a
renderer's job". Its actual reach:

writer server uses mesh.routes?
implementations/caddy caddy yes — it is the renderer
integrations/victoria-dashboards caddy yes
integrations/vault-dashboards caddy yes
integrations/mesh-whoami:297 caddy no
implementations/hermes:743 nginx no
integrations/mesh-librefang:24 nginx no
outputs/hosts/heimdallr:132 nginx no
outputs/hosts/bifrost:154 caddy no

Four of eight bypass the contract; three of those run the other web server;
two are host files. mesh-whoami argues its case in-file (a bare port, several
upstreams, the wireguard boundary as its scope) and that argument is sound —
the other three do not make one.

bifrost additionally hand-rolls what implementations/caddy already does, and
gets it wrong in the exact way that aspect documents fixing:

# outputs/hosts/bifrost/configuration.nix:231
environment.persistence."/persistent".directories = [ config.services.caddy.dataDir ];
# modules/implementations/caddy.nix:78 — the same thing, with ownership
{ inherit (config.services.caddy) group user; directory = ...; mode = "0755"; }

The aspect explains that the bare-string form leaves /var/lib/caddy
root-owned where impermanence creates the bind mount before systemd would
chown it, and Caddy then cannot write its ACME account:

could not save account []: mkdir /var/lib/caddy/.local: permission denied

bifrost is latent, not broken — its directory predates the persistence
entry — but a cold rebuild walks into the documented failure. The aspect's fix
does not apply because bifrost never sets mesh.implementations.caddy.enable.

Decision needed

Either every vhost goes through contracts/routes and one renderer, or the
contract is deleted as aspirational. The current state pays the abstraction's
maintenance cost and delivers none of its guarantee.

Note the fleet runs two HTTP servers with no recorded decision between them.

Acceptance

  • The decision recorded here.
  • If unify: bifrost and heimdallr produce mesh.routes entries and enable the
    renderer; hermes and mesh-librefang migrate off nginx or state why they
    cannot; mesh-whoami's exemption written into its record.
  • If delete: contracts/routes, implementations/caddy and the two dashboard
    producers collapse to direct configuration.
  • Either way, no host re-implements the renderer's persistence entry by hand.

Provenance

Split from #9 (FM-ASPECT-AUDIT). Audit run 2026-08-04 by agent.odin against commit fc3b751, with modules/implementations/{forgejo-agents,hermes}.nix and outputs/hosts/odin/{configuration.nix,secrets.agents.yaml} dirty in the working copy.

Stable ID: FM-AUDIT-03 Source: `modules/contracts/routes.nix`, every `services.{caddy,nginx}` writer Source status: new finding, not previously tracked Disposition: open owner decision, then architecture work Epic: #9 Blocked by: #11 (FM-AUDIT-02) ## Problem `contracts/routes` exists to make route intent renderer-agnostic: "a producer … says nothing about Caddy, nginx, or Authelia — that translation is a renderer's job". Its actual reach: | writer | server | uses `mesh.routes`? | | --- | --- | --- | | `implementations/caddy` | caddy | yes — it is the renderer | | `integrations/victoria-dashboards` | caddy | yes | | `integrations/vault-dashboards` | caddy | yes | | `integrations/mesh-whoami:297` | caddy | no | | `implementations/hermes:743` | nginx | no | | `integrations/mesh-librefang:24` | nginx | no | | `outputs/hosts/heimdallr:132` | nginx | no | | `outputs/hosts/bifrost:154` | caddy | no | Four of eight bypass the contract; three of those run the other web server; two are host files. `mesh-whoami` argues its case in-file (a bare port, several upstreams, the wireguard boundary as its scope) and that argument is sound — the other three do not make one. bifrost additionally hand-rolls what `implementations/caddy` already does, and gets it wrong in the exact way that aspect documents fixing: ```nix # outputs/hosts/bifrost/configuration.nix:231 environment.persistence."/persistent".directories = [ config.services.caddy.dataDir ]; # modules/implementations/caddy.nix:78 — the same thing, with ownership { inherit (config.services.caddy) group user; directory = ...; mode = "0755"; } ``` The aspect explains that the bare-string form leaves `/var/lib/caddy` root-owned where impermanence creates the bind mount before systemd would chown it, and Caddy then cannot write its ACME account: ``` could not save account []: mkdir /var/lib/caddy/.local: permission denied ``` bifrost is **latent, not broken** — its directory predates the persistence entry — but a cold rebuild walks into the documented failure. The aspect's fix does not apply because bifrost never sets `mesh.implementations.caddy.enable`. ## Decision needed Either every vhost goes through `contracts/routes` and one renderer, or the contract is deleted as aspirational. The current state pays the abstraction's maintenance cost and delivers none of its guarantee. Note the fleet runs two HTTP servers with no recorded decision between them. ## Acceptance - The decision recorded here. - If unify: bifrost and heimdallr produce `mesh.routes` entries and enable the renderer; `hermes` and `mesh-librefang` migrate off nginx or state why they cannot; `mesh-whoami`'s exemption written into its record. - If delete: `contracts/routes`, `implementations/caddy` and the two dashboard producers collapse to direct configuration. - Either way, no host re-implements the renderer's persistence entry by hand. ## Provenance Split from #9 (FM-ASPECT-AUDIT). Audit run 2026-08-04 by agent.odin against commit `fc3b751`, with `modules/implementations/{forgejo-agents,hermes}.nix` and `outputs/hosts/odin/{configuration.nix,secrets.agents.yaml}` dirty in the working copy.
Commenting is not possible because the repository is archived.
No description provided.