Pin the schema of the default source
Freezing the default-source schema baseline (SELF_MODULE_SOURCE)
When a deployment is submitted with no module_source (the built-in
default), the panel's schema-change view (#213) needs to remember which schema
that deploy ran against. Without help it stores SQL NULL, which later resolves
to whatever schema api/ has since been rebuilt with -- so the "old" side of the
diff floats forward and the view goes blind to exactly the drift it should show.
To pin it, api/ records a fetchTree reference to its own revision at submit
time, read from the SELF_MODULE_SOURCE environment variable (a JSON object).
A revision is a property of the deploy, not of the source tree, so it is supplied
at runtime rather than baked in. See
Default-source baseline freeze
for the full rationale.
With flakes (automatic)
Nothing to do. flake.nix derives the pin from the flake's locked self and
threads it through to the service, so a clean checkout pins itself. A dirty
working tree has no reproducible revision, so the baseline simply floats for that
build -- which is fine, since an uncommitted tree is not reproducible anyway.
Without flakes (or any manual / non-Nix run)
Export SELF_MODULE_SOURCE before starting api/. Generate the pin for the
revision you are running with a single command (it only needs the revision to be
pushed -- fetchTree fetches it from the remote, not your working tree):
export SELF_MODULE_SOURCE="$(
rev=$(git rev-parse HEAD)
url=https://git.fediversity.eu/fediversity/fediversity
nix-instantiate --eval --impure --strict --json --expr "
let t = builtins.fetchTree { type = \"git\"; url = \"$url\"; rev = \"$rev\"; };
in { type = \"git\"; url = \"$url\"; rev = \"$rev\"; narHash = t.narHash; }
"
)"
# then, in the same shell (`devShells.<system>.api-http-stack` puts the
# `api-http-stack` binary on PATH; see #1094 for the non-flake shell parity):
nix-shell -A devShells.x86_64-linux.api-http-stack --run api-http-stack default.nix
builtins.fetchTree fetches the revision from the remote and exposes its
narHash; the surrounding expression projects it to the {type, url, rev, narHash} shape api/ expects (--strict forces the narHash thunk so it
serializes). Point url at the remote that actually hosts the revision (use your
fork's clone URL if you pushed there). The
import ./default.nix { selfModuleSource = { ... }; } argument accepts the same
object if you would rather pass it in Nix than via the environment.
If the variable is unset, malformed, or names a revision whose schema assets cannot be fetched, the baseline falls back to floating and a default form load still works -- the pin is an enhancement, never a hard dependency.
Pinning the schema to one deployment method / effect (PIN_EFFECT)
The demo api serves an effect-agnostic form: its deployment-method schema is a
discriminated union of every member -- the deployment methods (ssh-hosts,
tf-hosts, tf-incus-hosts, tf-incus-operator-hosts, k8s-hosts,
k8s-operator-hosts) and the non-deploy
effects (tf-incus-image, octodns-zone) -- so the form shows a picker and the
submission carries a tag key naming the chosen member.
A production api usually deploys via exactly one of these, so PIN_EFFECT
collapses the union to that single member (issue #723). The tag names any union
member -- a deployment method or a non-deploy effect -- hence "effect", not
"method". With it set the form has no picker: the served schema drops
deployment-method entirely and carries that member's settings at its root, so
the form renders them as ordinary fields and api/ validates submissions in the
same shape. Which effect the build serves is not on the wire at all; it is
recorded in pinned-effect.json beside the schema, and the submit path recovers
it from there.
A value stored against an earlier, unpinned build is keyed under
deployment-method.<tag>, so the pin also emits a rename onto the root
(deployment-method.<tag>.<name> -> <name>) into the same migrations table
that carries declared option renames. Both lanes apply it: the panel's island
migrates on read, and api/ migrates a submission before validating it, so a
pre-pin configuration needs no hand editing.
# api/ devShell (or `./api#api-http-stack` from the repo root)
PIN_EFFECT=tf-incus-operator-hosts nix run --impure .#api-http-stack
A NixOS deployment sets the same pin declaratively, as
services.fediversity-api.pinEffect, which reaches the same pinEffect build
param: the environment variable is the dev-shell / direct-entrypoint lane, the
module option the deployed one, so a node never needs an impure ambient build.
api/nix/component/api.nix pins the hosting-provider group's api to
tf-incus-operator-hosts, the one method whose coordinates that provider
curates.
The pin drives the generated schema.json, which is the same file api/ validates
against -- so the pin is the contract (core/lib/schema-for-source.nix). PIN_EFFECT
is read once on the src side by the top-level entrypoint (default.nix), which
bakes it into the build (pinEffect build param) so the running api recovers
its one effect from its OWN artifact (pinned-effect.json, beside the schema in
one store path) rather than the ambient environment. The api never re-reads
PIN_EFFECT at run time, so the served schema and the pin it interprets
submissions against cannot desync -- there is a single source of truth (issue #723
follow-up). --impure is required because the pin collapses the schema at BUILD
time and Nix only reads the ambient PIN_EFFECT under --impure; the one-step
nix run --impure reaches the build, so the sidecar carries the pin. A
PIN_EFFECT naming no declared effect (a typo such as tf=incus-hosts for
tf-incus-hosts) is rejected loudly -- at build by the schema collapse, and at
submit by the resolver reading the baked value -- never silently mis-resolved. So
is a pinned setting whose name a top-level option already takes, since lifting it
to the root would shadow one of the two.
Unset (the default) bakes the whole union: the wire's tag key names the method.