# pom's tiers and nodes. Design: wiki [[sando-bento-boundary]]. # # No [repo] and no [[backup]]-driven migration gate. pom is intake-only: Bento # builds it natively on astra and on the Hetzner box, and hands Sando finished # bytes with an artifact record. Sando fetches no source for it, so there is no # bare repo on this host to name, and declaring one would be a claim /rebuild # would then act on. backup = [] # ---- host: where an accepted artifact is gated before it goes anywhere ---- # # Not a machine pom runs on. It is the tier every intake lands on, the same way # a Sando-built product's host tier is where its build lands: gates run here, and # a green run is what makes the bundle promotable. sandod's own box (fw13) is not # a pom node and never runs pom. # # The gate list is empty on purpose, and that is the boundary rather than an # omission. Everything Sando would have run here is artifact-scoped — cargo_test, # clippy, fmt, the audits — and artifact-scoped evidence belongs to the builder # under this split. Bento already ran them, and the record carries their verdicts # (`intake::accept` reports whether the builder's gates passed). Asking Sando to # re-run them would need a source checkout it does not have, and the gates say so # rather than passing on having run nothing. # # What is left for Sando is evidence about the artifact *in an environment*, and # every one of those lives on a tier with a node in it, below. [[tier]] name = "host" provisioned = true canary = "sequential" gates = [] # ---- astra: aarch64, and pom's own CI runner ---- # # First real tier because it is the machine that matters least if pom is down for # a moment: it watches, it does not serve anyone. The Hetzner instance keeps # watching while this one restarts. # # The bootstrap caveat is why the two are a ladder and not a fan-out: pom watches # its own deploy, so deploying pom restarts the watcher mid-operation. One # instance at a time, health read from the peer over the mesh, never both at once. [[tier]] name = "astra" provisioned = true canary = "sequential" # Guards astra -> hetzner. node_health proves the restarted instance is actually # serving; burn_in makes production wait on a real interval of the new binary # running somewhere; manual_confirm is the human sign-off before the box that # watches production takes it. gates = [ { kind = "node_health" }, { kind = "burn_in", hours = 24 }, { kind = "manual_confirm" }, ] [[tier.node]] name = "astra" platform = "linux/aarch64" # Tailnet name, reached over Tailscale SSH. The connection is made by the # `sando` user on fw13, so astra's host key has to be in /srv/sando/.ssh/ # known_hosts; without it the deploy fails at `Host key verification failed` # before it has done anything, which is where it stood on 2026-08-15. ssh_target = "max@astra" release_root = "/opt/pom" service_name = "pom.service" # Readiness on top of `systemctl is-active`: pom binds its API on the tailnet # address, and the dashboard is on. A crash-looping binary satisfies is-active # between restarts, which is the failure this closes. # # The tailnet name, NOT 127.0.0.1, and the difference is the gate working or # only appearing to. The probe runs on the node, astra's pom binds # 100.106.221.39:9100 (its `listen`), and 127.0.0.1:9100 there is # prometheus-node-exporter, which answers 200 with its index page for ANY path. # So the loopback URL passed this gate whether or not pom was running at all. # Measured 2026-08-15, before the first promote to this node. The Hetzner # instance binds 0.0.0.0 and is not affected. health_url = "http://astra:9100/api/health" # ---- hetzner: x86_64, watching production from production ---- [[tier]] name = "hetzner" provisioned = true canary = "sequential" gates = [ { kind = "node_health" }, { kind = "manual_confirm" }, ] [[tier.node]] name = "hetzner" platform = "linux/x86_64" # `pom@alpha-west-1` as of 2026-08-16, and the account it names now exists. # This was `root@alpha-west-1`, which never worked: sandod runs as the `sando` # user on fw13 and that user's ~/.ssh/config maps `alpha-west-1` to port 2200, # the box's own sshd. Tailscale SSH is 22 and never saw the connection, so # reaching root that way would have meant sando's key in root's authorized_keys # on a production box. # # The account matches `makenotwork@alpha-west-1` on the same machine, which is # the house pattern: the service user the unit already runs as, holding sando's # key and a sudoers grant scoped to the three systemctl verbs sandod uses. It # was a nologin system account until the cutover gave it a home and a shell. # Sando's key is pinned there with `restrict,from="100.103.89.95"`, which is # tighter than makenotwork's bare key line and still allows the rsync leg. # # Node half done by `bootstrap-pom-node.sh DEPLOY_USER=pom`: /opt/pom owned by # pom, the running 0.4.1 seeded as releases/preexisting-0.4.1 with `current` # pointing at it, ExecStart moved by /etc/systemd/system/pom.service.d/ # 20-release-root.conf. The node came out of it on the version it went in on. ssh_target = "pom@alpha-west-1" release_root = "/opt/pom" service_name = "pom.service" # Loopback is honest here, unlike astra's: this instance binds 0.0.0.0, and # 127.0.0.1:9100 on the node answers with pom's own health JSON rather than # some other daemon's index page. Verified 2026-08-16, before the first promote. health_url = "http://127.0.0.1:9100/api/health"