25. Change an app by asking
A model changes your app and shows it running on a preview address, and nothing reaches staging or live until you tap ship.
The builder app: say what should change; it edits a clone, pushes preview/run-<id> (its key can push nothing else), and the creator reviews the diff and the preview, then ships through root's ship request.
This is the code serving the zoo right now: read from disk for this request, from commit
77ff74344d (staging). The zoo's own self-check fetches this
page and compares it byte for byte with the file it runs.
exhibits/25-change-by-asking.php sha256 9dfa0779fa65 · raw
1<?php 2// Exhibit 25. The builder (its own app) changes an app on request: a model works in a clone, pushes 3// a preview/* branch with a key root granted for preview/* only, and nothing reaches staging or live 4// until the app's creator taps Ship. Every night the builder runs it for real on the sample app 5// `zoo-scratch` (asking /ping to answer a fresh word, charged to its own ai budget) and publishes what 6// it saw at its /proof.json; this card reads that over HTTPS and checks again, from outside, that 7// staging and live do not answer the word. (Staging's builder runs the same, 15 minutes later.) 8return [ 9 "n" => 25, "wing" => "Building", 10 "title" => "Change an app by asking", 11 "promise" => "A model changes your app and shows it running on a preview address, and nothing reaches staging or live until you tap ship.", 12 "block" => "The builder app: say what should change; it edits a clone, pushes preview/run-<id> (its key can push nothing else), and the creator reviews the diff and the preview, then ships through root's ship request.", 13 "show" => function (?array $me): string { 14 $p = stored(25)["data"] ?? null; 15 $b = rc_app_url("builder"); 16 if (!$p) return '<p>The <a href="' . h($b) . '/">builder</a> has not been checked yet.</p>'; 17 $line = fn($k, $label) => '<li>' . $label . ': <a href="' . h($p[$k]["url"] ?? "") . '"><code>/ping</code></a> answered ' . (int)($p[$k]["status"] ?? 0) 18 . ' <code>' . h(mb_strimwidth((string)($p[$k]["body"] ?? ""), 0, 40, "…")) . '</code>' . (!empty($p[$k]["has_word"]) ? " (has the word)" : " (without it)") . "</li>"; 19 return '<p>Last night the <a href="' . h($b) . '/">builder</a>\'s robot asked, on the sample app <code>zoo-scratch</code>:</p>' 20 . '<blockquote>' . h($p["request"] ?? "") . '</blockquote><ul>' 21 . $line("preview", "preview") . $line("staging", "staging") . $line("live", "live") 22 . '<li>the builder\'s key pushing <code>main</code>: ' . (!empty($p["main_push"]["refused"]) ? "refused" : "<b>accepted</b>") . "</li></ul>" 23 . '<p class="muted">' . h((string)($p["turns"] ?? "?")) . " turns, " . h((string)round(($p["tokens"] ?? 0) / 1000)) . "k tokens from the builder's own budget, " . h($p["at"] ?? "") . ".</p>"; 24 }, 25 "check" => function (): array { 26 $b = rc_app_url("builder"); 27 [$code, , $p] = (new Browser())->get("$b/proof.json"); 28 if ($code !== 200 || !is_array($p)) return [false, "the builder's /proof.json did not load ($code)"]; 29 if (($p["ok"] ?? null) === null) return [false, "the builder has not run its nightly exhibit yet", $p]; 30 $age = time() - (strtotime((string)($p["at"] ?? "")) ?: 0); 31 if ($age > 26 * 3600) return [false, "the builder's last nightly run is " . round($age / 3600) . " h old", $p]; 32 if (!$p["ok"]) return [false, (string)($p["detail"] ?? "the nightly run failed"), $p]; 33 // From outside, now: staging and live still do not answer last night's word. 34 foreach (["staging", "live"] as $e) { 35 [, , $body] = (new Browser())->get((string)($p[$e]["url"] ?? "")); 36 if (str_contains(is_string($body) ? $body : json_encode($body), (string)$p["word"])) return [false, "$e answers the preview's word: a change reached $e without a ship", $p]; 37 } 38 return [true, (string)$p["detail"], $p]; 39 }, 40];