SoCal, the whole bay, a moon, and gates that actually run
gates / clean-clone (push) Successful in 15s
gates / zero-config-boot (push) Successful in 9s
gates / no-binary-art (push) Successful in 4s

Six agents in parallel, and the two city packs independently reported the same
blocker: `focusRegions` and `coarseFactor` existed on the `City` type and
nothing implemented them. Uniform lattices would have been 2.9M points for
Southern California and 3.7M for the expanded bay. Both packs were unloadable
as written.

`buildAxis` is the answer, and it is honest about its limits: refinement is per
axis, not per rectangle, so a focus region sharpens its whole row *and* its
whole column. Two regions at opposite corners refine nearly everything between
them. Measured, not guessed — the bay went 0.53M points with one region and
1.64M with three, for detail nobody is looking at from a board this wide. One
region each, coarse factor ten, and the builds land at 3.8 s and 2.3 s.

Then three things that were only ever right because San Francisco was the only
city. `maxDistance: 340` and a 170-unit shadow box were constants tuned for a
230-unit board; the bay is 1003 units across and the camera physically could
not retreat far enough to frame it. Fog distances were scene units pinned to
the same assumption. And `minVisibilityM` defaulted to 4.5 km of honest
weather, which over ninety-four kilometres of bay correctly hides three
quarters of it — the night view was a black rectangle for a completely
reasonable reason. All three now derive from the board.

The moon is a real ephemeris and its light is a deliberate lie: 1.15, against a
physical ratio of one to four hundred thousand. What is being reproduced is
what a moonlit night looks like on a screen in a lit room.

The CI gate caught itself, which is the part worth keeping. Port 8431 was
already held by a server from an earlier session, so the boot check polled a
healthy stranger while the process it started died on EADDRINUSE. It now
refuses to run rather than pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Karti Tripathi
2026-08-05 03:13:32 -07:00
parent 8bcb391455
commit 44c5a79424
25 changed files with 9246 additions and 220 deletions
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* The no-binary-art gate: `src/**` holds no committed binary art, ever.
*
* This is the check that backs the licensing argument in ARCHITECTURE.md §3 —
* every mesh is a function composing cached unit primitives and every texture is
* drawn on a 2D canvas from seeded noise, which is what gives the repo zero
* asset-licensing exposure. A committed `.glb` or `.png` is a claim about
* provenance that nobody in CI can verify, so the answer is that they do not
* arrive at all.
*
* Two things about the scope, both of them corrections of an earlier design that
* CONTRACT.md §7 calls out by name.
*
* The enumeration is `git ls-files` and never a filesystem walk. A self-hoster
* is told, by CONTRIBUTING.md and by the office packs, to drop their own
* legally-clean art into `public/props/` and friends; a walk would find it and
* fail their build over files this repo does not distribute and has no opinion
* about. Tracked files are the only files a licence claim can be made about, so
* tracked files are the only files enumerated. If git is missing, this exits
* non-zero rather than falling back to a walk — a gate that quietly changes what
* it measures is worse than one that stops.
*
* And it is strict over `src/**` only. That is where the procedural-assets
* promise lives. `public/props/`, `public/kits/`, `public/offices/` and `docs/`
* are hard-exempt below and git-ignored besides: they are self-hoster space.
*
* Runs by hand as `node scripts/check-no-binaries.mjs` from anywhere in the
* working tree.
*/
import { execFileSync } from "node:child_process";
// ---- The rule ----
/**
* Art and binary-asset extensions. Lowercased at the comparison, so a `.PNG`
* off a Windows checkout is caught too.
*/
const DENIED_EXTENSIONS = new Set([
".glb",
".gltf",
".png",
".jpg",
".jpeg",
".webp",
".hdr",
".exr",
".ttf",
".otf",
".woff",
".woff2",
".mp3",
".wav",
".fbx",
".obj",
]);
/** The pathspec handed to `git ls-files`. Everything outside it is unexamined. */
const SCOPE = "src";
/**
* Self-hoster space, exempt even if something puts it inside the scope later.
* These are repo-relative prefixes, matched against the paths git reports.
*/
const EXEMPT_PREFIXES = [
"public/props/",
"public/kits/",
"public/offices/",
"docs/",
];
// ---- Enumeration ----
function repoRoot() {
try {
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
}).trim();
} catch {
fail(
"could not ask git for the repo root.",
"This check enumerates tracked files with `git ls-files` and deliberately has no",
"filesystem-walk fallback, because a walk would fail a self-hoster's build over",
"their own untracked art. Run it inside a git checkout.",
);
}
}
function trackedFiles(root, pathspec) {
const out = execFileSync("git", ["ls-files", "-z", "--", pathspec], {
cwd: root,
encoding: "utf8",
// A NUL-separated listing of a repo this size is tiny, but the default 1 MB
// ceiling is close enough to be worth not thinking about again.
maxBuffer: 64 * 1024 * 1024,
});
return out.split("\0").filter((path) => path !== "");
}
/**
* The extension, lowercased, or the empty string. A leading dot with no stem —
* `src/.keep` — is a dotfile and has no extension, which is why this compares
* against the last slash rather than reaching for `path.extname`.
*/
function extensionOf(path) {
const dot = path.lastIndexOf(".");
const slash = path.lastIndexOf("/");
if (dot <= slash + 1) return "";
return path.slice(dot).toLowerCase();
}
function isExempt(path) {
return EXEMPT_PREFIXES.some((prefix) => path.startsWith(prefix));
}
// ---- Reporting ----
function fail(...lines) {
console.error(`\ncheck-no-binaries: FAIL — ${lines[0]}\n`);
for (const line of lines.slice(1)) console.error(line);
console.error("");
process.exit(1);
}
// ---- Run ----
const root = repoRoot();
const tracked = trackedFiles(root, SCOPE);
const offenders = tracked
.filter((path) => !isExempt(path))
.filter((path) => DENIED_EXTENSIONS.has(extensionOf(path)));
if (offenders.length > 0) {
const listed = offenders.map((path) => ` ${path} (${extensionOf(path)})`);
fail(
`${offenders.length} binary asset${offenders.length === 1 ? "" : "s"} tracked under ${SCOPE}/.`,
...listed,
"",
"Apache 2.0 is a promise that everything in this repo is ours to give away, and a",
"committed binary asset is a provenance claim no reviewer can check. Assets here are",
"procedural TypeScript: meshes compose cached unit primitives, textures are drawn on a",
"2D canvas from seeded noise. See ARCHITECTURE.md §3 and CONTRACT.md §3.",
"",
"If this is your own art for your own deployment, it does not belong in src/ at all —",
`put it under ${EXEMPT_PREFIXES.join(", ")}, which are git-ignored self-hoster space, and`,
"this check will never see it.",
);
}
console.log(
`check-no-binaries: ok — ${tracked.length} tracked file${tracked.length === 1 ? "" : "s"} under ` +
`${SCOPE}/, none matching ${DENIED_EXTENSIONS.size} denied extensions.`,
);
console.log(
` enumerated with \`git ls-files\`, so untracked local art is invisible to this check.`,
);
console.log(` denied: ${[...DENIED_EXTENSIONS].join(" ")}`);
console.log(` exempt: ${EXEMPT_PREFIXES.join(" ")}`);
+346
View File
@@ -0,0 +1,346 @@
#!/usr/bin/env node
/**
* The zero-config boot gate: hand the API nothing at all and it still answers.
*
* This exists because two independent server designs made the same mistake —
* defaulting the weather source to a provider that requires a contact string,
* then failing hard when the contact was absent — which breaks the one
* acceptance test the whole repo is built around: a stranger clones this, runs
* one command, and gets a working box with no account, no key and no network.
* CONTRACT.md §5.1 resolves it (a source configured without what it needs is
* demoted, not fatal) and this script is what keeps the resolution honest.
*
* The environment handed to the server is genuinely empty — `env: {}`, the
* in-process equivalent of `env -i` — which is possible only because the child
* is launched by absolute path (`process.execPath`) and so needs no PATH to find
* itself. Nothing is stubbed and no config object is constructed by hand: this
* starts the real entry point and asks the real socket, which is the difference
* between this and `server/src/test/boot.test.ts`, which asserts the same
* property one layer down.
*
* node scripts/check-zero-config-boot.mjs
* node scripts/check-zero-config-boot.mjs --compose
*
* The second form is CONTRACT.md §0's literal wording — `docker compose up` under
* an empty environment — and needs a working Docker. CI runs the first form,
* because a gate that depends on docker-in-docker being present on the runner is
* measuring the runner rather than the repo. The two assert the same thing: the
* compose file adds only `TERA_HOST` and container hardening, and every other
* variable in it carries a `:-` default precisely so that an empty environment
* resolves it to the empty string.
*/
import { spawn } from "node:child_process";
import { connect } from "node:net";
import { fileURLToPath } from "node:url";
// ---- Where and what ----
const REPO_ROOT = fileURLToPath(new URL("..", import.meta.url));
const HEALTH_HOST = "127.0.0.1";
const HEALTH_PORT = 8431;
const HEALTH_URL = `http://${HEALTH_HOST}:${HEALTH_PORT}/api/v1/health`;
/** How long the server gets to bind and answer before this gives up. */
const PROCESS_DEADLINE_MS = 20_000;
/** Compose has to build an image first, which is a different order of patience. */
const COMPOSE_DEADLINE_MS = 300_000;
const useCompose = process.argv.slice(2).includes("--compose");
// ---- Reporting ----
function fail(headline, detail = [], log = []) {
console.error(`\ncheck-zero-config-boot: FAIL — ${headline}\n`);
for (const line of detail) console.error(line);
if (log.length > 0) {
console.error("\n--- what the server said ---");
console.error(log.join("").trimEnd());
console.error("--- end ---");
}
console.error("");
process.exitCode = 1;
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// ---- Making sure we are testing our own server ----
/**
* Is anything already listening on the port we are about to claim?
*
* This guard is here because the check silently passed without it. The port was
* occupied by an unrelated process, the server we spawned died on `EADDRINUSE`
* within a second, and the poll cheerfully collected a 200 from the stranger —
* a green gate asserting nothing at all. A check that can pass against a server
* it did not start is worse than no check, so an occupied port is a hard stop.
*/
function portInUse() {
return new Promise((resolve) => {
const socket = connect({ host: HEALTH_HOST, port: HEALTH_PORT });
const settle = (answer) => {
socket.destroy();
resolve(answer);
};
socket.setTimeout(1_500);
socket.once("connect", () => settle(true));
socket.once("timeout", () => settle(false));
socket.once("error", () => settle(false));
});
}
async function assertPortFree() {
if (!(await portInUse())) return true;
fail(`something is already listening on ${HEALTH_HOST}:${HEALTH_PORT}.`, [
"This check has to own that port, because otherwise it polls whatever is there and",
"reports a healthy stranger while the server it started is dead in a ditch. It cannot",
"move to another port either: choosing one would mean setting TERA_PORT, and the empty",
"environment is the thing under test.",
"",
"Stop whatever is on the port and run it again.",
]);
return false;
}
/**
* A second line of defence against answering for somebody else's process: if the
* server on the other end has been up longer than this check has been running,
* it is not the one we started.
*/
function uptimeLooksLikeOurs(body, startedAt) {
const allowed = Math.ceil((Date.now() - startedAt) / 1000) + 2;
return typeof body.uptimeSeconds !== "number" || body.uptimeSeconds <= allowed;
}
// ---- The assertion ----
/**
* Poll until health answers or the deadline passes. `stillRunning` lets the
* caller abort early when the thing under test has already died, so a server
* that exits on boot reports its own error instead of a twenty-second timeout.
*/
async function waitForHealth(deadlineMs, stillRunning) {
const deadline = Date.now() + deadlineMs;
let lastError = "nothing was listening";
while (Date.now() < deadline) {
if (!stillRunning()) return { dead: true, lastError };
try {
const response = await fetch(HEALTH_URL, {
signal: AbortSignal.timeout(2_000),
});
const text = await response.text();
return { status: response.status, text };
} catch (err) {
lastError = err instanceof Error ? err.message : String(err);
}
await sleep(250);
}
return { timedOut: true, lastError };
}
/**
* Everything the health body has to say on a box that was handed nothing.
*
* The source and mode checks are not padding. A default that needs configuration
* is exactly the bug this gate was written for, and it would still return 200
* while being wrong — the failure showed up as an empty sky, not as a dead
* process. `degraded` being non-empty means the config layer demoted something,
* which on an empty environment means a default asked for something it was never
* going to be given.
*/
function checkPosture(body) {
const problems = [];
if (body.ok !== true) {
problems.push(` health reported ok: ${JSON.stringify(body.ok)}, expected true`);
}
if (body.sources?.weather !== "none") {
problems.push(
` weather source defaulted to ${JSON.stringify(body.sources?.weather)}; CONTRACT.md §5.1`,
` requires "none", because every other source wants a contact string or a key.`,
);
}
if (body.auth?.mode !== "none") {
problems.push(
` auth mode defaulted to ${JSON.stringify(body.auth?.mode)}; CONTRACT.md §6 requires "none"`,
` so that a self-hoster never creates an account anywhere.`,
);
}
if (!Array.isArray(body.degraded)) {
problems.push(` health body has no degraded array; it is how demotions become visible.`);
} else if (body.degraded.length > 0) {
problems.push(
` the config layer demoted ${body.degraded.length} source(s) on an empty environment:`,
...body.degraded.map((line) => ` ${line}`),
` A demotion here means a default was chosen that needs configuration to work.`,
);
}
return problems;
}
function report(body) {
console.log("check-zero-config-boot: ok — 200 from /api/v1/health on an empty environment.");
console.log(` service: ${body.service} ${body.version}`);
console.log(
` sources: weather=${body.sources?.weather} flights=${body.sources?.flights} ` +
`markers=${body.sources?.markers}`,
);
console.log(` auth: ${body.auth?.mode}`);
console.log(` degraded: none`);
}
/** Shared tail of both modes: parse, assert, print. */
function finish(result, log, startedAt) {
if (result.dead) {
fail(
"the server exited before it ever answered.",
[
"It was started with a genuinely empty environment, which is the whole point: a box",
"that needs a variable set before it will boot is not self-hostable. See CONTRACT.md §5.1.",
` last connection attempt: ${result.lastError}`,
],
log,
);
return;
}
if (result.timedOut) {
fail(
`nothing answered ${HEALTH_URL} before the deadline.`,
[` last connection attempt: ${result.lastError}`],
log,
);
return;
}
if (result.status !== 200) {
fail(
`${HEALTH_URL} returned ${result.status}, expected 200.`,
[
"Health touches no upstream and reads no file by design, so a non-200 here is the",
"server refusing to be healthy without configuration it should not need.",
` body: ${result.text.slice(0, 500)}`,
],
log,
);
return;
}
let body;
try {
body = JSON.parse(result.text);
} catch {
fail("health answered 200 but the body was not JSON.", [` body: ${result.text.slice(0, 500)}`], log);
return;
}
if (!uptimeLooksLikeOurs(body, startedAt)) {
fail(
`${HEALTH_URL} answered, but from a server this check did not start.`,
[
` it reports ${body.uptimeSeconds}s of uptime; this check has been running for`,
` ${Math.ceil((Date.now() - startedAt) / 1000)}s. Something else claimed the port first.`,
],
log,
);
return;
}
const problems = checkPosture(body);
if (problems.length > 0) {
fail("health answered 200, but not from a zero-config box.", problems, log);
return;
}
report(body);
}
// ---- Mode: the real entry point under an empty environment ----
async function runProcessMode() {
if (!(await assertPortFree())) return;
console.log("check-zero-config-boot: starting server/src/index.ts with env -i (no variables at all)");
const startedAt = Date.now();
const child = spawn(process.execPath, ["server/src/index.ts"], {
cwd: REPO_ROOT,
// The empty environment is the test. `process.execPath` is absolute, so the
// child needs no PATH to exist, and Node needs nothing else to run.
env: {},
stdio: ["ignore", "pipe", "pipe"],
});
const log = [];
child.stdout.on("data", (chunk) => log.push(String(chunk)));
child.stderr.on("data", (chunk) => log.push(String(chunk)));
let alive = true;
let exitInfo = "";
child.on("exit", (code, signal) => {
alive = false;
exitInfo = signal ? `killed by ${signal}` : `exited with code ${code}`;
});
child.on("error", (err) => {
alive = false;
exitInfo = `could not spawn: ${err.message}`;
});
try {
const result = await waitForHealth(PROCESS_DEADLINE_MS, () => alive);
if (result.dead) log.push(`\n(process ${exitInfo})\n`);
finish(result, log, startedAt);
} finally {
if (alive) {
child.kill("SIGTERM");
// The entry point closes on SIGTERM; if it does not, this is a check, not a
// supervisor, and a lingering child would hang CI.
for (let waited = 0; alive && waited < 5_000; waited += 100) await sleep(100);
if (alive) child.kill("SIGKILL");
}
}
}
// ---- Mode: docker compose, for whoever has a Docker ----
function docker(args, timeoutMs) {
return new Promise((resolve) => {
const child = spawn("docker", args, {
cwd: REPO_ROOT,
// PATH and HOME are for the Docker CLI itself — finding its binary and its
// config — and reach the container through nothing. The compose file
// interpolates only TERA_* variables, all with `:-` defaults, so the
// container's own environment is empty either way.
env: { PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", HOME: process.env.HOME ?? "/tmp" },
stdio: ["ignore", "pipe", "pipe"],
timeout: timeoutMs,
});
const out = [];
child.stdout.on("data", (chunk) => out.push(String(chunk)));
child.stderr.on("data", (chunk) => out.push(String(chunk)));
child.on("close", (code) => resolve({ code, out: out.join("") }));
child.on("error", (err) => resolve({ code: -1, out: `could not run docker: ${err.message}` }));
});
}
async function runComposeMode() {
if (!(await assertPortFree())) return;
const compose = ["compose", "-f", "deploy/docker-compose.yml"];
console.log("check-zero-config-boot: docker compose up, with no .env file and no TERA_* set");
const up = await docker([...compose, "up", "-d", "--build"], COMPOSE_DEADLINE_MS);
if (up.code !== 0) {
fail("`docker compose up` failed.", [" " + up.out.trim().split("\n").join("\n ")]);
return;
}
// After the build, not before it: `up -d` returns with the container just
// started, so the uptime it reports is measured from about here.
const startedAt = Date.now();
try {
const result = await waitForHealth(60_000, () => true);
const logs = await docker([...compose, "logs", "--no-color"], 30_000);
finish(result, [logs.out], startedAt);
} finally {
await docker([...compose, "down", "-v"], 60_000);
}
}
// ---- Run ----
await (useCompose ? runComposeMode() : runProcessMode());