4976de193715

use koh throughout fixtures and current project guidance (v3.0.204)

time
2026-10-03 14:06 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
gpt-6.1-sol
session
review declared repo version
parent
57c99012f4b4
14 files package.json+1 −1tests/bucket.test.ts+4 −4tests/fake-s3.ts+3 −3tests/fixture.ts+14 −14tests/kohpaths.test.ts+6 −6tests/kohrelease.test.ts+3 −3tests/kohsteal.test.ts+52 −54tests/kohupdate.test.ts+14 −14tests/pages.test.ts+3 −3tests/releases.test.ts+1 −1tests/sync.test.ts+8 −8tests/tarball.test.ts+2 −2tests/timeout.ts+1 −1tests/worker.test.ts+1 −1
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.203",3+	"version": "3.0.204",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

tests/bucket.test.ts+4 −4
@@ -22,7 +22,7 @@2222 2323 const bucket = () => openBucket({ kind: "folder", path: fx.bucketDir });2424 25-describe("reading a koh3-produced folder remote", () => {25+describe("reading a koh-produced folder remote", () => {2626 	test("lists the repo", async () => {2727 		expect(await listRepos(bucket())).toEqual([fx.repo]);2828 	});@@ -107,7 +107,7 @@107107 		expect(sizes).toEqual(await bucket().sizes(`${fx.repo}/`));108108 		expect(sizes.get(`${fx.repo}/head`)).toBe(12);109109 		expect(await readHead(s3, fx.repo)).toBe(fx.saveIds[0] as string);110-		// koh3 writes `<repo>/format` on its first offer (§9.2), so it is present;110+		// koh writes `<repo>/format` on its first offer (§9.2), so it is present;111111 		// a key that is not there is what reads as null.112112 		const format = await s3.get(`${fx.repo}/format`);113113 		expect(format === null ? null : new TextDecoder().decode(format)).toBe("koh-remote 1");@@ -153,7 +153,7 @@153153 });154154 155155 describe("untrusted input", () => {156-	// Starts from a genuine koh3 record and corrupts one field, as a tampered bucket would.156+	// Starts from a genuine koh record and corrupts one field, as a tampered bucket would.157157 	async function readTampered(change: (record: Record<string, unknown>) => void) {158158 		const id = fx.saveIds[0] as string;159159 		const real = await bucket().get(`${fx.repo}/saves/${id}`);@@ -290,7 +290,7 @@290290 });291291 292292 describe("event records", () => {293-	test("reads a genuine koh3 flag event and refuses a tampered one", async () => {293+	test("reads a genuine koh flag event and refuses a tampered one", async () => {294294 		const b = bucket();295295 		const [id] = await listEventIds(b, fx.repo);296296 		const event = await readEvent(b, fx.repo, id as string);

view file

tests/fake-s3.ts+3 −3
@@ -1,8 +1,8 @@11 /**2- * A bucket in memory, served over HTTP for the joint public-steal test: koh3's own S3 backend and3- * Bun's S3Client both talk to it, so Kepr and the two koh3 machines share one bucket.2+ * A bucket in memory, served over HTTP for the joint public-steal test: koh's own S3 backend and3+ * Bun's S3Client both talk to it, so Kepr and the two koh machines share one bucket.44  *5- * It runs as its own process because the test drives koh3 with `Bun.spawnSync`, which would block5+ * It runs as its own process because the test drives koh with `Bun.spawnSync`, which would block66  * the event loop this server needs. It prints its endpoint on the first line of stdout.77  *88  * Only what the two clients use is implemented: GET, HEAD, PUT (with `If-None-Match: *` for the

view file

tests/fixture.ts+14 −14
@@ -6,21 +6,21 @@66 import type { ReleaseFile } from "../src/bucket/repo.ts";77 88 /**9- * §4.3 of the resolutions: these tests are pinned to one koh3, so a stale binary cannot make a pass9+ * §4.3 of the resolutions: these tests are pinned to one koh, so a stale binary cannot make a pass1010  * look real. The version is checked before any fixture runs; the message says how to fix it.1111  */1212 const PINNED_KOH = (await Bun.file(join(import.meta.dir, "koh-version.txt")).text()).trim();1313 const RUNNING_KOH = (() => {1414 	try {15-		return Bun.spawnSync(["koh3", "--version"]).stdout.toString().trim();15+		return Bun.spawnSync(["koh", "--version"]).stdout.toString().trim();1616 	} catch {1717 		return "";1818 	}1919 })();2020 if (RUNNING_KOH !== `koh ${PINNED_KOH}`) {2121 	throw new Error(22-		`these tests are pinned to koh ${PINNED_KOH}, but koh3 on PATH reports "${RUNNING_KOH}" — ` +23-			"install the matching koh3 (see koh3's AGENTS.md), or update tests/koh-version.txt " +22+		`these tests are pinned to koh ${PINNED_KOH}, but koh on PATH reports "${RUNNING_KOH}" — ` +23+			"install the matching koh (see koh's AGENTS.md), or update tests/koh-version.txt " +2424 			"once you have verified the new one",2525 	);2626 }@@ -31,7 +31,7 @@3131 	repoDir: string;3232 	/** Every save, newest first, checkpoint included. */3333 	saveIds: string[];34-	/** Strips a path from every save with koh3, so tests can sync before and after. */34+	/** Strips a path from every save with koh, so tests can sync before and after. */3535 	strip(path: string): Promise<void>;3636 };3737 @@ -59,7 +59,7 @@5959 `;6060 6161 /**62- * Builds a genuine Koh 3 folder remote with koh3 itself, under a throwaway HOME. The line is:62+ * Builds a genuine Koh 3 folder remote with koh itself, under a throwaway HOME. The line is:6363  * "add docs, a secret and a deep helper" (flagged) → a second later "add readme and entry point"6464  * (agent and session) → a checkpoint holding an edit to the guide → "say hello" (head).6565  */@@ -110,10 +110,10 @@110110 }111111 112112 function run(cwd: string, home: string, args: string[]): string {113-	const result = Bun.spawnSync(["koh3", ...args], { cwd, env: env(home) });113+	const result = Bun.spawnSync(["koh", ...args], { cwd, env: env(home) });114114 	if (result.exitCode !== 0) {115115 		throw new Error(116-			`koh3 ${args.join(" ")} failed: ${result.stderr.toString()}${result.stdout.toString()}`,116+			`koh ${args.join(" ")} failed: ${result.stderr.toString()}${result.stdout.toString()}`,117117 		);118118 	}119119 	return result.stdout.toString();@@ -126,7 +126,7 @@126126 /** `koh strip` insists on a terminal and a typed confirmation, so it runs under a Bun PTY. */127127 export async function stripPath(cwd: string, home: string, path: string): Promise<void> {128128 	let output = "";129-	const proc = Bun.spawn(["koh3", "strip", path], {129+	const proc = Bun.spawn(["koh", "strip", path], {130130 		cwd,131131 		env: env(home),132132 		terminal: { cols: 120, rows: 24, data: (_term, data) => (output += data.toString()) },@@ -134,14 +134,14 @@134134 	proc.terminal?.write(`y\n${path}\n`);135135 	const code = await proc.exited;136136 	proc.terminal?.close();137-	if (code !== 0) throw new Error(`koh3 strip ${path} failed: ${output}`);137+	if (code !== 0) throw new Error(`koh strip ${path} failed: ${output}`);138138 }139139 140-/** Repos with one koh3 save each, all offered to one folder remote: enough history for releases. */140+/** Repos with one koh save each, all offered to one folder remote: enough history for releases. */141141 export async function makeRepos(names: string[]): Promise<{142142 	bucketDir: string;143143 	saves: Record<string, string>;144-	/** Runs koh3 in one of the repos, under the fixture's HOME. */144+	/** Runs koh in one of the repos, under the fixture's HOME. */145145 	koh(repo: string, ...args: string[]): string;146146 }> {147147 	const root = await mkdtemp(join(tmpdir(), "kepr-repos-"));@@ -165,7 +165,7 @@165165 	};166166 }167167 168-/** A throwaway HOME, a repo folder and a bucket, with koh3 runnable inside (§4.3's pinned binary). */168+/** A throwaway HOME, a repo folder and a bucket, with koh runnable inside (§4.3's pinned binary). */169169 export async function kohWorkspace(repo = "shipped"): Promise<{170170 	root: string;171171 	home: string;@@ -195,7 +195,7 @@195195 196196 /**197197  * Publishes a release straight into a folder remote, in §11.2's order: the files, then release.json,198- * then `releases/latest`. `kohWorkspace` plus `koh3 offer --builds` is the real thing198+ * then `releases/latest`. `kohWorkspace` plus `koh offer --builds` is the real thing199199  * (tests/kohrelease.test.ts); this writes them by hand for fixtures that need several versions, an200200  * old release, or hashes that no build produced.201201  */

view file

tests/kohpaths.test.ts+6 −6
@@ -12,7 +12,7 @@1212 // §5.5 is a contract between the repos: every path either side drops, the other drops too.1313 const UNSAFE = [1414 	"../joint-escape.txt",15-	"/tmp/koh3-joint-absolute.txt",15+	"/tmp/koh-joint-absolute.txt",1616 	"a/../../joint-nested.txt",1717 	"back\\slash.txt",1818 	"ctl\u0001name.txt",@@ -47,13 +47,13 @@4747 	const work = join(root, "second-work");4848 	await mkdir(home);4949 	await mkdir(work);50-	const koh3 = (cwd: string, args: string[]) =>51-		Bun.spawnSync(["koh3", ...args], {50+	const runKoh = (cwd: string, args: string[]) =>51+		Bun.spawnSync(["koh", ...args], {5252 			cwd,5353 			env: { PATH: process.env.PATH, HOME: home, NO_COLOR: "1" },5454 		});55-	expect(koh3(work, ["remote", "folder", fx.bucketDir]).exitCode).toBe(0);56-	const stolen = koh3(work, ["steal", fx.repo, "--json"]);55+	expect(runKoh(work, ["remote", "folder", fx.bucketDir]).exitCode).toBe(0);56+	const stolen = runKoh(work, ["steal", fx.repo, "--json"]);5757 	expect(stolen.exitCode, stolen.stderr.toString()).toBe(0);5858 	const dropped: string[] = JSON.parse(stolen.stdout.toString()).unsafe_paths;5959 @@ -83,7 +83,7 @@8383 	for (const outside of [8484 		join(work, "joint-escape.txt"),8585 		join(root, "joint-nested.txt"),86-		"/tmp/koh3-joint-absolute.txt",86+		"/tmp/koh-joint-absolute.txt",8787 	]) {8888 		expect(existsSync(outside), outside).toBe(false);8989 	}

view file

tests/kohrelease.test.ts+3 −3
@@ -12,8 +12,8 @@1212 import { kohWorkspace, platform } from "./fixture.ts";1313 1414 /**15- * The joint contract (§3 item 4): `koh3 offer --builds` publishes a release into a folder remote16- * with the real koh3 binary, Kepr syncs it, and every route that reads a release agrees with the15+ * The joint contract (§3 item 4): `koh offer --builds` publishes a release into a folder remote16+ * with the real koh binary, Kepr syncs it, and every route that reads a release agrees with the1717  * bytes the build produced.1818  */1919 @@ -39,7 +39,7 @@3939 	>;4040 };4141 42-describe("a release published by koh3 offer --builds", () => {42+describe("a release published by koh offer --builds", () => {4343 	let app: Hono;4444 	let bucketDir: string;4545 	let expected = { blake3: "", sha256: "" };

view file

tests/kohsteal.test.ts+52 −54
@@ -1,10 +1,10 @@11 /**22  * The joint public-steal contract (addendum A2, item 8d): one machine publishes a repo to a bucket,3- * Kepr serves it read-only over HTTP in Koh's own layout, a second `koh3` steals it — and later,3+ * Kepr serves it read-only over HTTP in Koh's own layout, a second `koh` steals it — and later,44  * once that machine has signed in, offers its work back to the bucket.55  *6- * There is no S3 on a test host, so the bucket is a fake one served here; koh3 is run as the real7- * installed binary (`koh3` on PATH, the same one this suite is pinned to). The account of what the6+ * There is no S3 on a test host, so the bucket is a fake one served here; koh is run as the real7+ * installed binary (`koh` on PATH, the same one this suite is pinned to). The account of what the88  * fake bucket accepts is in its own class: signatures are ignored rather than verified, which is99  * the one way this test is easier than the real thing.1010  */@@ -25,11 +25,11 @@2525 const BUCKET = "kohbucket";2626 const ACCESS = "fake-access";2727 const SECRET = "fake-secret";28-/** The command deadline: two koh3 runs talk to a local server, so this is generous. */28+/** The command deadline: two koh runs talk to a local server, so this is generous. */2929 const RUN = 60_000;3030 3131 /**32- * The fake bucket runs as its own process: the koh3 calls below are synchronous (`koh3` blocks the32+ * The fake bucket runs as its own process: the koh calls below are synchronous (`koh` blocks the3333  * event loop), so an in-process server would never answer them. `tests/fake-s3.ts` prints its3434  * endpoint on stdout.3535  */@@ -52,14 +52,14 @@5252 5353 type Ran = { code: number; out: string; err: string };5454 55-/** The real koh3, with a throwaway HOME so no test ever touches the operator's config. */56-async function koh3(55+/** The real koh, with a throwaway HOME so no test ever touches the operator's config. */56+async function koh(5757 	cwd: string,5858 	home: string,5959 	args: string[],6060 	env: Record<string, string> = {},6161 ): Promise<Ran> {62-	const proc = Bun.spawn(["koh3", ...args], {62+	const proc = Bun.spawn(["koh", ...args], {6363 		cwd,6464 		env: { PATH: process.env.PATH, HOME: home, NO_COLOR: "1", ...env },6565 		stdout: "pipe",@@ -75,7 +75,7 @@7575 7676 /** The whole line, every field of every save: what a stolen copy must reproduce exactly. */7777 async function line(repo: string, home: string): Promise<unknown> {78-	return JSON.parse((await koh3(repo, home, ["view", "--json"])).out).saves;78+	return JSON.parse((await koh(repo, home, ["view", "--json"])).out).saves;7979 }8080 8181 describe.serial("stealing a public repo from Kepr over HTTP", () => {@@ -95,22 +95,22 @@9595 				await mkdir(ownerHome, { recursive: true });9696 				await mkdir(repoDir, { recursive: true });9797 				await mkdir(keptDir, { recursive: true });98-				expect((await koh3(repoDir, ownerHome, ["init"], keys)).code).toBe(0);98+				expect((await koh(repoDir, ownerHome, ["init"], keys)).code).toBe(0);9999 				expect(100-					(await koh3(repoDir, ownerHome, ["remote", "s3", endpoint, BUCKET, "fr-par"], keys)).code,100+					(await koh(repoDir, ownerHome, ["remote", "s3", endpoint, BUCKET, "fr-par"], keys)).code,101101 				).toBe(0);102102 				await writeFile(join(repoDir, "README.md"), "# shipped\n\nA repo to steal.\n");103103 				await writeFile(join(repoDir, "src.v"), "fn main() {}\n");104-				expect((await koh3(repoDir, ownerHome, ["save", "first save"])).code).toBe(0);104+				expect((await koh(repoDir, ownerHome, ["save", "first save"])).code).toBe(0);105105 				await writeFile(join(repoDir, "README.md"), "# shipped\n\nA repo to steal, edited.\n");106-				expect((await koh3(repoDir, ownerHome, ["save", "second save"])).code).toBe(0);106+				expect((await koh(repoDir, ownerHome, ["save", "second save"])).code).toBe(0);107107 				// A second repo, never made public on Kepr: only a signed-in machine may have it.108-				expect((await koh3(keptDir, ownerHome, ["init"], keys)).code).toBe(0);108+				expect((await koh(keptDir, ownerHome, ["init"], keys)).code).toBe(0);109109 				expect(110-					(await koh3(keptDir, ownerHome, ["remote", "s3", endpoint, BUCKET, "fr-par"], keys)).code,110+					(await koh(keptDir, ownerHome, ["remote", "s3", endpoint, BUCKET, "fr-par"], keys)).code,111111 				).toBe(0);112112 				await writeFile(join(keptDir, "secret.txt"), "kept in the bucket\n");113-				expect((await koh3(keptDir, ownerHome, ["save", "a private repo"])).code).toBe(0);113+				expect((await koh(keptDir, ownerHome, ["save", "a private repo"])).code).toBe(0);114114 115115 				// Kepr, reading that bucket: the shipped repo is public, the kept one is not.116116 				const dataDir = await mkdtemp(join(tmpdir(), "kepr-steal-kepr-"));@@ -153,25 +153,25 @@153153 				const thiefDir = join(root, "thief");154154 				await mkdir(thiefHome, { recursive: true });155155 				await mkdir(thiefDir, { recursive: true });156-				const stolen = await koh3(thiefDir, thiefHome, ["steal", `${host}/${REPO}`]);156+				const stolen = await koh(thiefDir, thiefHome, ["steal", `${host}/${REPO}`]);157157 				expect(stolen.code, stolen.err + stolen.out).toBe(0);158158 				const copy = join(thiefDir, REPO);159159 				expect(await readFile(join(copy, "README.md"), "utf8")).toContain("edited");160-				expect((await koh3(copy, thiefHome, ["doctor"])).code).toBe(0);160+				expect((await koh(copy, thiefHome, ["doctor"])).code).toBe(0);161161 				// Save for save, the copy is the line the owner published.162162 				expect(await line(copy, thiefHome)).toEqual(await line(repoDir, ownerHome));163163 				// The source is remembered, and shown above the machine's remote.164-				expect((await koh3(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);164+				expect((await koh(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);165165 				// And it cannot be offered: it has no bucket of its own.166-				const refused = await koh3(copy, thiefHome, ["offer"]);166+				const refused = await koh(copy, thiefHome, ["offer"]);167167 				expect(refused.code).toBe(1);168168 				expect(refused.err + refused.out).toContain("which is read-only");169169 170170 				// A later public save arrives on a bare `koh steal`.171171 				await writeFile(join(repoDir, "README.md"), "# shipped\n\nA third save.\n");172-				expect((await koh3(repoDir, ownerHome, ["save", "third save"])).code).toBe(0);172+				expect((await koh(repoDir, ownerHome, ["save", "third save"])).code).toBe(0);173173 				await sync();174-				expect((await koh3(copy, thiefHome, ["steal"])).code).toBe(0);174+				expect((await koh(copy, thiefHome, ["steal"])).code).toBe(0);175175 				expect(await readFile(join(copy, "README.md"), "utf8")).toContain("A third save");176176 177177 				// A strip on the source side reaches the mirror, and is honoured: history no longer@@ -181,29 +181,27 @@181181 				await stripPath(repoDir, ownerHome, "src.v");182182 				// The strip wrote `/src.v` into `.kohignore`; the next save carries the rule everywhere.183183 				expect(await readFile(join(repoDir, ".kohignore"), "utf8")).toContain("/src.v\n");184-				expect((await koh3(repoDir, ownerHome, ["save", "ignore the stripped src.v"])).code).toBe(185-					0,186-				);184+				expect((await koh(repoDir, ownerHome, ["save", "ignore the stripped src.v"])).code).toBe(0);187185 				await sync();188-				expect((await koh3(copy, thiefHome, ["steal"])).code).toBe(0);189-				expect((await koh3(copy, thiefHome, ["doctor"])).code).toBe(0);186+				expect((await koh(copy, thiefHome, ["steal"])).code).toBe(0);187+				expect((await koh(copy, thiefHome, ["doctor"])).code).toBe(0);190188 				expect(await Bun.file(join(copy, "src.v")).exists()).toBe(true);191189 				// Not listed as a change; the status line may still quote a save message naming it.192-				const status = (await koh3(copy, thiefHome, ["status"])).out;190+				const status = (await koh(copy, thiefHome, ["status"])).out;193191 				expect(status).not.toContain("+ src.v");194192 				expect(status).toContain("no changes");195193 				const fresh = join(root, "thief-again");196194 				await mkdir(fresh, { recursive: true });197-				expect((await koh3(fresh, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);195+				expect((await koh(fresh, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);198196 				expect(await Bun.file(join(fresh, REPO, "src.v")).exists()).toBe(false);199197 				// A third copy stays stolen to the end, for `offer --json` to upgrade.200198 				const third = join(root, "thief-three");201199 				await mkdir(third, { recursive: true });202-				expect((await koh3(third, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);200+				expect((await koh(third, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);203201 				// And a fourth, for `offer --builds --json` to upgrade.204202 				const fourth = join(root, "thief-four");205203 				await mkdir(fourth, { recursive: true });206-				expect((await koh3(fourth, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);204+				expect((await koh(fourth, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);207205 208206 				// Signing in: a code from the connect page, read the way `koh` reads it.209207 				// This Kepr's domain is the loopback address it is served on, so the same-origin@@ -223,33 +221,33 @@223221 				const shown = await browser.post("/connect", { machine }, origin);224222 				const code = /class="mono code">([^<]+)</.exec(await shown.text())?.[1]?.trim() ?? "";225223 				expect(code).toMatch(/^[A-Z2-9]{4}-[A-Z2-9]{4}$/);226-				const signedIn = await koh3(thiefDir, thiefHome, ["remote", host, "--code", code]);224+				const signedIn = await koh(thiefDir, thiefHome, ["remote", host, "--code", code]);227225 				expect(signedIn.code, signedIn.err + signedIn.out).toBe(0);228226 229227 				// A save made before the upgrade is local work and nothing else: it must not write to230228 				// the bucket, and it must not quietly drop the source on its own.231229 				await writeFile(join(copy, "local.txt"), "work in progress\n");232-				const local = await koh3(copy, thiefHome, ["save", "local only"]);230+				const local = await koh(copy, thiefHome, ["save", "local only"]);233231 				expect(local.code, local.err + local.out).toBe(0);234232 				expect(local.out + local.err).not.toContain("offered");235-				expect((await koh3(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);233+				expect((await koh(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);236234 237235 				// The next offer drops the source: the repo is this machine's own work now; offer is238236 				// the path the addendum names for the owner case.239-				const upgraded = await koh3(copy, thiefHome, ["offer"]);237+				const upgraded = await koh(copy, thiefHome, ["offer"]);240238 				expect(upgraded.code, upgraded.err + upgraded.out).toBe(0);241239 				expect(upgraded.out + upgraded.err).toContain("now syncs with your bucket");242240 				// The owner can read what that offer published, before anything else is saved: the243241 				// local save the copy was carrying went up with it.244-				expect((await koh3(repoDir, ownerHome, ["revert", "--yes"])).code).toBe(0);245-				expect((await koh3(repoDir, ownerHome, ["steal"])).code).toBe(0);242+				expect((await koh(repoDir, ownerHome, ["revert", "--yes"])).code).toBe(0);243+				expect((await koh(repoDir, ownerHome, ["steal"])).code).toBe(0);246244 				expect(await readFile(join(repoDir, "local.txt"), "utf8")).toContain("work in progress");247-				expect(JSON.parse((await koh3(repoDir, ownerHome, ["view", "--json"])).out).saves).toEqual(248-					JSON.parse((await koh3(copy, thiefHome, ["view", "--json"])).out).saves,245+				expect(JSON.parse((await koh(repoDir, ownerHome, ["view", "--json"])).out).saves).toEqual(246+					JSON.parse((await koh(copy, thiefHome, ["view", "--json"])).out).saves,249247 				);250248 				await writeFile(join(copy, "README.md"), "# shipped\n\nThe thief's edit.\n");251-				expect((await koh3(copy, thiefHome, ["save", "from the thief"])).code).toBe(0);252-				expect((await koh3(repoDir, ownerHome, ["steal"])).code).toBe(0);249+				expect((await koh(copy, thiefHome, ["save", "from the thief"])).code).toBe(0);250+				expect((await koh(repoDir, ownerHome, ["steal"])).code).toBe(0);253251 				expect(await readFile(join(repoDir, "README.md"), "utf8")).toContain("The thief's edit");254252 255253 				// The other copy still rides its source. When both sides changed the same file,@@ -257,18 +255,18 @@257255 				// the file is named, steal is where to choose, and the source stays.258256 				const other = join(fresh, REPO);259257 				await writeFile(join(repoDir, "README.md"), "# shipped\n\nThe owner's edit.\n");260-				expect((await koh3(repoDir, ownerHome, ["save", "owner edits the readme"])).code).toBe(0);258+				expect((await koh(repoDir, ownerHome, ["save", "owner edits the readme"])).code).toBe(0);261259 				await writeFile(join(other, "README.md"), "# shipped\n\nThe other copy's edit.\n");262-				expect((await koh3(other, thiefHome, ["save", "other copy edits"])).code).toBe(0);263-				const clashed = await koh3(other, thiefHome, ["offer"]);260+				expect((await koh(other, thiefHome, ["save", "other copy edits"])).code).toBe(0);261+				const clashed = await koh(other, thiefHome, ["offer"]);264262 				expect(clashed.code).toBe(1);265263 				expect(clashed.err).toContain(266264 					'README.md changed on both machines — run "koh steal" to choose, then offer',267265 				);268266 				expect(await readFile(join(other, "README.md"), "utf8")).toContain("other copy's edit");269-				expect((await koh3(other, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);267+				expect((await koh(other, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);270268 				// Choosing there upgrades the copy, and says where the version it gave up lives.271-				const chosen = await koh3(other, thiefHome, ["steal", "--theirs"]);269+				const chosen = await koh(other, thiefHome, ["steal", "--theirs"]);272270 				expect(chosen.code, chosen.err + chosen.out).toBe(0);273271 				expect(chosen.out + chosen.err).toContain("now syncs with your bucket");274272 				expect(chosen.out + chosen.err).toContain("your README.md is in");@@ -277,14 +275,14 @@277275 				// A repo the public side never serves is stolen from the bucket once signed in.278276 				const second = join(root, "thief-two");279277 				await mkdir(second, { recursive: true });280-				const kept = await koh3(second, thiefHome, ["steal", `${host}/${PRIVATE_REPO}`]);278+				const kept = await koh(second, thiefHome, ["steal", `${host}/${PRIVATE_REPO}`]);281279 				expect(kept.code, kept.err + kept.out).toBe(0);282280 				expect(await readFile(join(second, PRIVATE_REPO, "secret.txt"), "utf8")).toContain("kept");283281 				// And it is a normal repo: the machine's own remote is untouched by any of this.284-				expect((await koh3(second, thiefHome, ["remote"])).out).toContain(`s3 ${endpoint}`);282+				expect((await koh(second, thiefHome, ["remote"])).out).toContain(`s3 ${endpoint}`);285283 286284 				// Saves that come in flagged during offer's upgrade are named in its JSON, as steal's are.287-				expect((await koh3(repoDir, ownerHome, ["steal"])).code).toBe(0);285+				expect((await koh(repoDir, ownerHome, ["steal"])).code).toBe(0);288286 				await writeFile(join(repoDir, "risky.txt"), "risky\n");289287 				// The same save brings a build file, so the fourth copy can publish a release.290288 				await writeFile(join(repoDir, "VERSION"), "1.0.0\n");@@ -298,15 +296,15 @@298296 					].join("\n"),299297 				);300298 				const risky = JSON.parse(301-					(await koh3(repoDir, ownerHome, ["save", "risky", "--json"])).out,299+					(await koh(repoDir, ownerHome, ["save", "risky", "--json"])).out,302300 				).id;303-				const flag = await koh3(repoDir, ownerHome, ["flag", risky, "--reason", "broke the build"]);301+				const flag = await koh(repoDir, ownerHome, ["flag", risky, "--reason", "broke the build"]);304302 				expect(flag.code, flag.err + flag.out).toBe(0);305-				expect((await koh3(repoDir, ownerHome, ["offer"])).code).toBe(0);306-				const flaggedOffer = await koh3(join(third, REPO), thiefHome, ["offer", "--json"]);303+				expect((await koh(repoDir, ownerHome, ["offer"])).code).toBe(0);304+				const flaggedOffer = await koh(join(third, REPO), thiefHome, ["offer", "--json"]);307305 				expect(flaggedOffer.code, flaggedOffer.err + flaggedOffer.out).toBe(0);308306 				expect(JSON.parse(flaggedOffer.out).flagged).toEqual([risky]);309-				const flaggedBuilds = await koh3(join(fourth, REPO), thiefHome, [307+				const flaggedBuilds = await koh(join(fourth, REPO), thiefHome, [310308 					"offer",311309 					"--builds",312310 					"--json",

view file

tests/kohupdate.test.ts+14 −14
@@ -10,8 +10,8 @@1010 import { kohWorkspace } from "./fixture.ts";1111 1212 /**13- * The joint update contract (§3 item 8): koh3 publishes a newer koh into a folder remote, Kepr14- * syncs and serves it, and a second koh3 — a copy of the installed binary, never the installed one13+ * The joint update contract (§3 item 8): koh publishes a newer koh into a folder remote, Kepr14+ * syncs and serves it, and a second koh — a copy of the installed binary, never the installed one1515  * — replaces itself with what it downloads.1616  *1717  * Two limits are deliberate, and named here so nobody reads more into a green run:@@ -20,7 +20,7 @@2020  *   documents it serves for the download point at the `file://` path a folder-bucket Kepr hands2121  *   out. The HTTPS hop and Kepr's own redirect are therefore not exercised; June's staging check2222  *   (§6.4) covers them.23- * - The artifact is a copy of the koh3 binary, so on macOS the installed file is that artifact plus23+ * - The artifact is a copy of the koh binary, so on macOS the installed file is that artifact plus2424  *   the ad-hoc signature koh writes into it, and the bytes cannot be compared. There the proof is2525  *   that the file grew, is validly signed and runs; on Linux, where nothing is signed, the bytes2626  *   are compared exactly.@@ -33,20 +33,20 @@3333 /** Long enough for a download on a slow machine, short enough not to hang a suite. */3434 const RUN_TIMEOUT_MS = 30_000;3535 36-/** What koh3 reads: the release document (§22.4). */36+/** What koh reads: the release document (§22.4). */3737 type Feed = {3838 	version: string;3939 	notes?: string;4040 	platforms: Record<string, { url: string; blake3: string; sha256: string }>;4141 };4242 43-/** The record koh3 writes, as far as this test reads it (§11.2). */43+/** The record koh writes, as far as this test reads it (§11.2). */4444 type ReleaseRecord = { files: { platform: string; name: string }[] };4545 46-/** The CLI's own `--json` document, `UpdateJson` in koh3's src/cli/cmd_update.v. */46+/** The CLI's own `--json` document, `UpdateJson` in koh's src/cli/cmd_update.v. */4747 type UpdateJson = { available?: boolean; installed?: string };4848 49-describe("koh3 updating itself through Kepr", () => {49+describe("koh updating itself through Kepr", () => {5050 	let server: Server<undefined>;5151 	let cleanup: string[] = [];5252 	let checkSource = "";@@ -70,10 +70,10 @@7070 7171 	beforeAll(async () => {7272 		const space = await kohWorkspace(REPO);73-		const koh = Bun.which("koh3") as string;73+		const koh = Bun.which("koh") as string;7474 		const platformName = platform();75-		installed = join(space.root, "bin", "koh3");76-		published = join(space.repoDir, "dist", "koh3");75+		installed = join(space.root, "bin", "koh");76+		published = join(space.repoDir, "dist", "koh");7777 		await mkdir(join(space.root, "bin"), { recursive: true });7878 		await mkdir(join(space.repoDir, "dist"), { recursive: true });7979 		await cp(koh, installed);@@ -85,8 +85,8 @@8585 			join(space.repoDir, "koh.build"),8686 			[8787 				"# a release is one file: this copies the one the test prepared",88-				"command = mkdir -p out && cp dist/koh3 out/koh3",89-				"output = out/koh3",88+				"command = mkdir -p out && cp dist/koh out/koh",89+				"output = out/koh",9090 				`platforms = ${platformName}`,9191 				"",9292 			].join("\n"),@@ -198,7 +198,7 @@198198 	});199199 200200 	/**201-	 * Runs a koh3 with the update source pointed at this test's Kepr. Asynchronous on purpose:201+	 * Runs a koh with the update source pointed at this test's Kepr. Asynchronous on purpose:202202 	 * `Bun.spawnSync` would block the event loop this test's own HTTP server runs on, and the two203203 	 * would wait for each other forever. A slow or wedged updater fails the test rather than204204 	 * hanging the suite.@@ -219,7 +219,7 @@219219 		// apply. Without it a wedged updater hangs the suite instead of failing it.220220 		const late = Bun.sleep(RUN_TIMEOUT_MS).then((): [string, string, number] => {221221 			proc.kill();222-			throw new Error(`koh3 ${args.join(" ")} did not finish in ${RUN_TIMEOUT_MS} ms`);222+			throw new Error(`koh ${args.join(" ")} did not finish in ${RUN_TIMEOUT_MS} ms`);223223 		});224224 		const [stdout, stderr, exitCode] = await Promise.race([finished, late]);225225 		return { stdout, stderr, exitCode };

view file

tests/pages.test.ts+3 −3
@@ -34,7 +34,7 @@3434 		`${"a".repeat(1024 * 1024 - 1)}é${"b".repeat(1000)}\n`,3535 	);3636 	await writeFile(join(large.repoDir, "big.bin"), new Uint8Array(1024 * 1024 + 500).fill(0xff));37-	Bun.spawnSync(["koh3", "save", "add large files"], {37+	Bun.spawnSync(["koh", "save", "add large files"], {3838 		cwd: large.repoDir,3939 		env: { PATH: process.env.PATH, HOME: join(large.repoDir, "../../home"), NO_COLOR: "1" },4040 	});@@ -47,10 +47,10 @@4747 		["a\nb", "drop the newline"],4848 	] as const) {4949 		await writeFile(join(tail.repoDir, "tail.txt"), text);50-		Bun.spawnSync(["koh3", "save", message], { cwd: tail.repoDir, env: tailEnv });50+		Bun.spawnSync(["koh", "save", message], { cwd: tail.repoDir, env: tailEnv });5151 	}5252 	tailSaves = JSON.parse(53-		Bun.spawnSync(["koh3", "view", "--json"], {53+		Bun.spawnSync(["koh", "view", "--json"], {5454 			cwd: tail.repoDir,5555 			env: tailEnv,5656 		}).stdout.toString(),

view file

tests/releases.test.ts+1 −1
@@ -250,7 +250,7 @@250250 			save: repos.saves.hush as string,251251 			files: [{ platform: "linux-x86_64", name: "h", bytes: bytes("h") }],252252 		});253-		// cli's head is a checkpoint made after its releases (koh3 offers it on its own).253+		// cli's head is a checkpoint made after its releases (koh offers it on its own).254254 		repos.koh("cli", "checkpoint", "after the releases");255255 		checkpoint = JSON.parse(repos.koh("cli", "view", "--json")).head_id;256256 		app = await releaseApp(bucketDir, (db) => {

view file

tests/sync.test.ts+8 −8
@@ -35,7 +35,7 @@3535 	return { ...target, mirror: () => openMirror(target.mirrorDir, fx.repo) };3636 }3737 38-describe("a first sync of a koh3 bucket", () => {38+describe("a first sync of a koh bucket", () => {3939 	let fx: Fixture;4040 	let t: Awaited<ReturnType<typeof syncTarget>>;4141 	beforeAll(async () => {@@ -281,7 +281,7 @@281281 	const t = await syncTarget(fx);282282 	await syncAll(t);283283 	await writeFile(join(fx.repoDir, "new.txt"), "new\n");284-	Bun.spawnSync(["koh3", "save", "add new"], {284+	Bun.spawnSync(["koh", "save", "add new"], {285285 		cwd: fx.repoDir,286286 		env: { PATH: process.env.PATH, HOME: join(fx.repoDir, "../../home"), NO_COLOR: "1" },287287 	});@@ -381,11 +381,11 @@381381 		await syncAll(t);382382 		await writeFile(join(fx.repoDir, "src/main.ts"), "// Entry point.\nconsole.log('again');\n");383383 		await writeFile(join(fx.repoDir, "added.txt"), "new\n");384-		Bun.spawnSync(["koh3", "save", "change main, add a file"], {384+		Bun.spawnSync(["koh", "save", "change main, add a file"], {385385 			cwd: fx.repoDir,386386 			env: { PATH: process.env.PATH, HOME: join(fx.repoDir, "../../home"), NO_COLOR: "1" },387387 		});388-		Bun.spawnSync(["koh3", "offer"], {388+		Bun.spawnSync(["koh", "offer"], {389389 			cwd: fx.repoDir,390390 			env: { PATH: process.env.PATH, HOME: join(fx.repoDir, "../../home"), NO_COLOR: "1" },391391 		});@@ -481,8 +481,8 @@481481 482482 	await writeFile(join(fx.repoDir, "later.txt"), "later\n");483483 	const env = { PATH: process.env.PATH, HOME: join(fx.repoDir, "../../home"), NO_COLOR: "1" };484-	Bun.spawnSync(["koh3", "save", "a later save"], { cwd: fx.repoDir, env });485-	Bun.spawnSync(["koh3", "offer"], { cwd: fx.repoDir, env });484+	Bun.spawnSync(["koh", "save", "a later save"], { cwd: fx.repoDir, env });485+	Bun.spawnSync(["koh", "offer"], { cwd: fx.repoDir, env });486486 	await syncAll({487487 		bucket: openBucket({ kind: "folder", path: fx.bucketDir }),488488 		kepr: inst.kepr,@@ -544,8 +544,8 @@544544 	const before = repoStats(t.mirror()).lines;545545 	await writeFile(join(fx.repoDir, "big.txt"), "x\n".repeat(4.5 * 1024 * 1024));546546 	const env = { PATH: process.env.PATH, HOME: join(fx.repoDir, "../../home"), NO_COLOR: "1" };547-	Bun.spawnSync(["koh3", "save", "add a big text file"], { cwd: fx.repoDir, env });548-	Bun.spawnSync(["koh3", "offer"], { cwd: fx.repoDir, env });547+	Bun.spawnSync(["koh", "save", "add a big text file"], { cwd: fx.repoDir, env });548+	Bun.spawnSync(["koh", "offer"], { cwd: fx.repoDir, env });549549 	await syncAll(t);550550 	expect(history(t.mirror(), 1, 0)[0]?.message).toBe("add a big text file");551551 	expect(repoStats(t.mirror()).lines).toBe(before);

view file

tests/tarball.test.ts+2 −2
@@ -53,9 +53,9 @@5353 	await chmod(join(fx.repoDir, "run.sh"), 0o755);5454 	const home = join(fx.repoDir, "../../home");5555 	const env = { PATH: process.env.PATH, HOME: home, NO_COLOR: "1" };56-	const save = Bun.spawnSync(["koh3", "save", "add build files"], { cwd: fx.repoDir, env });56+	const save = Bun.spawnSync(["koh", "save", "add build files"], { cwd: fx.repoDir, env });5757 	expect(save.exitCode).toBe(0);58-	const log = Bun.spawnSync(["koh3", "view", "--json"], { cwd: fx.repoDir, env });58+	const log = Bun.spawnSync(["koh", "view", "--json"], { cwd: fx.repoDir, env });5959 	head = JSON.parse(log.stdout.toString()).head_id;6060 	dataDir = await mkdtemp(join(tmpdir(), "kepr-tarball-"));6161 	const kepr = openKeprDb(dataDir);

view file

tests/timeout.ts+1 −1
@@ -1,6 +1,6 @@11 import { setDefaultTimeout } from "bun:test";22 3-// The fixture hooks run the real koh3 many times over, and on a busy machine (a Koh suite3+// The fixture hooks run the real koh many times over, and on a busy machine (a Koh suite44 // compiling alongside) one hook or test can pass Bun's 5-second default and skip a whole file.55 // bunfig.toml's own `timeout` key is ignored by this Bun, so the default is set here, preloaded.66 setDefaultTimeout(30_000);

view file

tests/worker.test.ts+1 −1
@@ -1,4 +1,4 @@1-// Covers the sync on its own thread (2026-10-02): a real worker syncs a koh3-made bucket, holds1+// Covers the sync on its own thread (2026-10-02): a real worker syncs a koh-made bucket, holds22 // syncs off while the app works, and applies hidden lists.33 44 import { expect, test } from "bun:test";

view file