Zoo

← back to the zoo

7. The branch decides

main goes live, staging goes to a twin with its own data, and any other branch goes nowhere.

Push main for live, staging for https://<app>-staging. Each environment has its own RC_DATA, so its own databases; the same code, never the same data.

This is the code serving the zoo right now: read from disk for this request, from commit 0c1c124a02 (live). The zoo's own self-check fetches this page and compares it byte for byte with the file it runs.

exhibits/07-the-branch-decides.php sha256 1efa47dc780a · raw

1<?php
2// Exhibit 7. main goes live, staging goes to a twin with its own data, any other branch goes
3// nowhere. RC_FACTS/pushes.json lists recent pushes with where each one went. The staging zoo
4// has its own RC_DATA, so its own zoo.db and guestbook; the check compares the two over HTTPS.
5return [
6    "n" => 7, "wing" => "Deploy",
7    "title" => "The branch decides",
8    "promise" => "main goes live, staging goes to a twin with its own data, and any other branch goes nowhere.",
9    "block" => "Push main for live, staging for https://<app>-staging. Each environment has its own RC_DATA, so its own databases; the same code, never the same data.",
10    "show" => function (?array $me): string {
11        // The latest push to each place: live, staging and nowhere.
12        $latest = [];
13        foreach (fact("pushes.json") ?? [] as $p) $latest[$p["env"]] ??= $p;
14        $out = '<ul class="list">';
15        foreach (array_filter([$latest["live"] ?? null, $latest["staging"] ?? null, $latest["preview"] ?? null, $latest["nowhere"] ?? null]) as $p) {
16            $out .= '<li><span class="tag ' . h($p["env"]) . '">' . h($p["env"]) . "</span><span><code>" . h(preg_replace('#^refs/heads/#', "", $p["ref"]))
17                  . "</code> " . h(substr($p["commit"], 0, 7)) . " · " . h($p["result"]) . " · " . ago(strtotime($p["at"])) . "</span></li>";
18        }
19        $s = stored(7)["data"] ?? null;
20        $other = $s ? "<p>This guestbook has " . (int)$s["mine"] . " signature" . ((int)$s["mine"] === 1 ? "" : "s") . "; the <a href=\"https://" . h(other_env_host()) . "/#e4\">" . h($s["other_env"]) . " zoo's</a> has " . (int)$s["theirs"] . ", and they are different books.</p>" : "";
21        return $out . "</ul>" . $other;
22    },
23    "check" => function (): array {
24        $pushes = fact("pushes.json") ?? [];
25        foreach ($pushes as $p) {
26            // preview/* goes to <app>-preview (the builder's loop, DECISIONS.md); anything else nowhere.
27            $want = match (true) { $p["ref"] === "refs/heads/main" => "live", $p["ref"] === "refs/heads/staging" => "staging",
28                str_starts_with($p["ref"], "refs/heads/preview/") => "preview", default => "nowhere" };
29            if ($p["env"] !== $want) return [false, "{$p['ref']} was sent to {$p['env']}"];
30        }
31        $live = fact("deploy.json")["live"]["commit"] ?? "";
32        foreach ($pushes as $p) if ($p["env"] === "nowhere" && $p["commit"] === $live && !in_array($live, array_column(array_filter($pushes, fn($q) => $q["env"] === "live"), "commit"), true))
33            return [false, "a work-in-progress commit ({$p['ref']}) is what live runs"];
34        // Ours against the other environment's, each read through its own web address.
35        [, , $mine] = (new Browser())->get("https://" . env("RC_HOST") . "/api/4/list");
36        [$code, , $theirs] = (new Browser())->get("https://" . other_env_host() . "/api/4/list");
37        if ($code !== 200 || !isset($theirs["instance"])) return [false, "the other environment's zoo did not answer ($code)"];
38        if ($theirs["instance"] === $mine["instance"]) return [false, "live and staging are the same database"];
39        $sig = fn($g) => json_encode(array_map(fn($x) => [$x["user"]["username"], $x["note"], $x["at"]], $g));
40        if ($sig($mine["guestbook"]) === $sig($theirs["guestbook"])) return [false, "the live and staging guestbooks match"];
41        $nowhere = count(array_filter($pushes, fn($p) => $p["env"] === "nowhere"));
42        return [true, count($pushes) . " pushes each went where its branch says ($nowhere nowhere); " . env("RC_ENV") . " and {$theirs['env']} keep different guestbooks",
43                ["mine" => count($mine["guestbook"]), "theirs" => count($theirs["guestbook"]), "other_env" => $theirs["env"]]];
44    },
45];