/ an exhibit's JSON (check: run its check now; seen: "this worked for me") * /how/ the exhibit's source, read from the running checkout (exhibit 19) * /lights.json every light, for tests/exhibits.php and anyone curious * /index.txt one line per exhibit (promise and building block), for a model's brief * /how/.txt exhibit n's running files as plain text (the builder's example(n)) * /platform.md what an app gets: root's PLATFORM.md, the running version, for anyone * * nginx also calls this file to ask whether a browser may listen on an `auth` channel; then * RC_CHANNEL and RC_CHANNEL_ID are set and the answer is 204 (yes) or 403 (no). */ require __DIR__ . "/../lib/zoo.php"; $channel = getenv("RC_CHANNEL"); if ($channel !== false && $channel !== "") { $ok = false; foreach (exhibits() as $e) { if (isset($e["channels"][$channel])) $ok = ($e["channels"][$channel])((string)getenv("RC_CHANNEL_ID"), auth_user()); } http_response_code($ok ? 204 : 403); exit; } $path = (string)parse_url($_SERVER["REQUEST_URI"] ?? "/", PHP_URL_PATH); $me = auth_user(); function reply(array $j, int $code = 200): never { http_response_code($code); header("Content-Type: application/json"); header("Cache-Control: no-store"); echo json_encode($j, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), "\n"; exit; } if (preg_match('#^/api/(\d+)/([a-z-]+)$#D', $path, $m)) { $e = exhibits()[(int)$m[1]] ?? reply(["error" => "no such exhibit"], 404); $do = $m[2]; if (!rc_csrf_ok()) reply(["error" => "refused: not from the zoo's own page"], 403); $post = ($_SERVER["REQUEST_METHOD"] ?? "GET") === "POST"; $in = $post ? (json_decode((string)file_get_contents("php://input"), true) ?: []) : $_GET; try { if ($do === "check" && $e["check"]) { // POST runs it now, unless it ran (or started) in the last 15 seconds: anyone can press // this, and a run costs a robot sign-in, a model call, B2 and push. The slot is taken // atomically, so a burst runs it once. GET (an anywhere) only reads the light. if ($post && claim_check($e["n"], 15)) run_check($e); $s = stored($e["n"]); if (!$s) reply(["n" => $e["n"], "title" => $e["title"], "ok" => false, "detail" => "not checked yet", "at" => null, "data" => null]); reply(["n" => $e["n"], "title" => $e["title"], "ok" => (bool)$s["ok"], "detail" => $s["detail"], "at" => gmdate("c", $s["at"]), "data" => $s["data"]]); } if ($do === "seen" && $post && $e["kind"] === "human") { if (!$me) reply(["error" => "sign in first, so the zoo can say who saw it work"], 401); seen($e["n"], $me); reply(["ok" => true, "light" => light($e)]); } if ($e["api"]) { $r = ($e["api"])($do, $me, $in, $post); if ($r !== null) reply($r, (int)($r["status"] ?? 200)); } } catch (Throwable $x) { error_log("zoo: /api/{$e['n']}/$do: " . $x->getMessage()); reply(["error" => $x->getMessage()], 500); } reply(["error" => "exhibit {$e['n']} has no '$do'"], 404); } if (preg_match('#^/how/(\d+)$#D', $path, $m)) { require ZOO_ROOT . "/lib/how.php"; how((int)$m[1]); exit; } if ($path === "/lights.json") { $out = ["env" => env("RC_ENV"), "checked" => ($t = meta("checked_at")) ? gmdate("c", (int)$t) : null, "lights" => []]; foreach (exhibits() as $n => $e) { $l = light($e); $out["lights"][] = ["n" => $n, "title" => $e["title"], "kind" => $e["kind"], "color" => $l["color"], "text" => $l["text"], "at" => isset($l["at"]) ? gmdate("c", $l["at"]) : null]; } require_once ZOO_ROOT . "/lib/alerts.php"; $out["alerts"] = array_map(fn($a) => ["at" => gmdate("c", (int)$a["at"]), "kind" => $a["kind"], "title" => $a["title"], "body" => $a["body"], "results" => $a["results"]], alerts_recent(10)); reply($out); } // Root publishes PLATFORM.md into every app's RC_FACTS; the zoo, being the documentation, serves // its copy so a model working in a clone (no RC_FACTS there) reads the version that is running. // The builder's brief lists the exhibits, and its model reads one's running source as an example: // plain text, from the same checkout "how" shows (exhibit 19), so it is the code that is live. if ($path === "/index.txt") { header("Content-Type: text/plain; charset=utf-8"); header("Cache-Control: max-age=300"); foreach (exhibits() as $n => $e) { echo "$n. {$e["title"]} ({$e["wing"]}" . ($e["kind"] === "waiting" ? ", not built yet" : "") . "): {$e["block"]}\n"; } exit; } if (preg_match('#^/how/(\d+)\.txt$#D', $path, $m)) { require ZOO_ROOT . "/lib/how.php"; $e = exhibits()[(int)$m[1]] ?? null; header("Content-Type: text/plain; charset=utf-8"); header("Cache-Control: no-store"); if (!$e) { http_response_code(404); exit("no such exhibit\n"); } echo "Exhibit {$e["n"]}: {$e["title"]}\nPromise: {$e["promise"]}\nBlock: {$e["block"]}\n"; foreach (how_files($e) as $f) echo "\n=== $f ===\n", file_get_contents(ZOO_ROOT . "/$f"); exit; } if ($path === "/platform.md") { $doc = @file_get_contents(env("RC_FACTS") . "/PLATFORM.md"); http_response_code($doc === false ? 404 : 200); header("Content-Type: text/markdown; charset=utf-8"); header("Cache-Control: max-age=300"); echo $doc === false ? "PLATFORM.md is not published here yet.\n" : $doc; exit; } if ($path === "/") { require ZOO_ROOT . "/lib/page.php"; page($me); exit; } http_response_code(404); header("Content-Type: text/html; charset=utf-8"); header("Cache-Control: no-store"); echo '', 'Not found ยท Zoo', rc_head(), '', rc_header("Zoo", $me, "/api/3/signout", false), '
Nothing here

The zoo has no page at this address.

', 'Go to the zoo
';