72900049ddaf
rename clone, push and commit out of Kepr's source, tests and import script: a repo is stolen, a copy follows its source, the import ends its transaction with END (v3.0.85)
- time
- 2026-09-28 12:47 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- remove git terminology from source
- parent
- 223dda02aa56
↓ kepr-72900049.tar.gzatlas at this save
files changed8 files
package.json+1 −1scripts/import-kepr2.ts+2 −2src/mirror/remote.ts+1 −1src/server/connect.ts+1 −1tests/connect.test.ts+1 −1tests/kohremote.test.ts+1 −1tests/kohsteal.test.ts+30 −30tests/kohupdate.test.ts+1 −1package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "kepr",3- "version": "3.0.84",3+ "version": "3.0.85",44 "private": true,55 "type": "module",66 "scripts": {scripts/import-kepr2.ts+2 −2
@@ -193,9 +193,9 @@193193 errors.push(`${prefix}: ${error instanceof Error ? error.message : error}`);194194 }195195 }196-// Fail before any write (kepr.db is uncommitted); writes never overwrite, so a re-run finishes one.196+// Fail before any write (kepr.db is not yet written); writes never overwrite, so a re-run finishes one.197197 if (errors.length > 0) throw new Error(`not imported, nothing written:\n${errors.join("\n")}`);198198 for (const [repo, version] of newest) writes.push([`${repo}/releases/latest`, version]);199199 for (const [key, body] of writes) await put(key, body);200-kepr.run(dry ? "ROLLBACK" : "COMMIT");200+kepr.run(dry ? "ROLLBACK" : "END");201201 await pg.close();src/mirror/remote.ts+1 −1
@@ -132,7 +132,7 @@132132 }),133133 );134134 135- // Koh lists `<repo>/saves` and `<repo>/objects` as well as its events: a clone walks the chain135+ // Koh lists `<repo>/saves` and `<repo>/objects` as well as its events: a steal walks the chain136136 // it is given here, and fetches every object those saves name. Both lists follow the mirror, so137137 // neither is cached.138138 app.get("/:repo/koh/saves", (c) =>src/server/connect.ts+1 −1
@@ -112,7 +112,7 @@112112 const address = createHash("sha256").update(attemptKey).update(clientIp(c)).digest("hex");113113 const used = attempts.filter((attempt) => attempt.address === address).length;114114 // A refused attempt is not recorded: that caps the list at ten entries per address, and keeps115- // hammering from pushing the block out past the hour.115+ // hammering from moving the block out past the hour.116116 if (used >= ATTEMPTS_PER_ADDRESS) return noCode(c);117117 attempts.push({ at: now, address });118118 let code = "";tests/connect.test.ts+1 −1
@@ -266,7 +266,7 @@266266 const address = "192.0.2.90";267267 for (let i = 0; i < 10; i++) await redeem(app, "bad", address);268268 // Fifty more inside the hour are all refused, and none of them is remembered: kept, they would269- // cost memory per request and push the block out an hour past the last one.269+ // cost memory per request and move the block out an hour past the last one.270270 clock += 3_540_000;271271 setSystemTime(clock);272272 for (let i = 0; i < 50; i++) expect((await redeem(app, "bad", address)).status).toBe(404);tests/kohremote.test.ts+1 −1
@@ -78,7 +78,7 @@7878 expect((await inst.app.request(`/${REPO}/saves/${checkpoint}`)).status).toBe(404);7979 });8080 81- test("the save and object prefixes list what a clone walks", async () => {81+ test("the save and object prefixes list what a steal walks", async () => {8282 const saves = await get("saves");8383 expect(saves.status).toBe(200);8484 expect(saves.headers.get("cache-control")).toBe("no-cache");tests/kohsteal.test.ts+30 −30
@@ -73,14 +73,14 @@7373 return { code, out, err };7474 }7575 76-/** The whole line, every field of every save: what a clone must reproduce exactly. */76+/** 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> {7878 return JSON.parse((await koh3(repo, home, ["view", "--json"])).out).saves;7979 }8080 8181 describe.serial("stealing a public repo from Kepr over HTTP", () => {8282 test(83- "a clone comes over the wire, stays read-only, and follows its source",83+ "a stolen copy comes over the wire, stays read-only, and follows its source",8484 async () => {8585 const root = await mkdtemp(join(tmpdir(), "kepr-steal-"));8686 const bucketServer = await startBucket();@@ -156,15 +156,15 @@156156 await mkdir(thiefDir, { recursive: true });157157 const stolen = await koh3(thiefDir, thiefHome, ["steal", `${host}/${REPO}`]);158158 expect(stolen.code, stolen.err + stolen.out).toBe(0);159- const clone = join(thiefDir, REPO);160- expect(await readFile(join(clone, "README.md"), "utf8")).toContain("edited");161- expect((await koh3(clone, thiefHome, ["doctor"])).code).toBe(0);162- // Save for save, the clone is the line the owner published.163- expect(await line(clone, thiefHome)).toEqual(await line(repoDir, ownerHome));159+ const copy = join(thiefDir, REPO);160+ expect(await readFile(join(copy, "README.md"), "utf8")).toContain("edited");161+ expect((await koh3(copy, thiefHome, ["doctor"])).code).toBe(0);162+ // Save for save, the copy is the line the owner published.163+ expect(await line(copy, thiefHome)).toEqual(await line(repoDir, ownerHome));164164 // The source is remembered, and shown above the machine's remote.165- expect((await koh3(clone, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);165+ expect((await koh3(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);166166 // And it cannot be offered: it has no bucket of its own.167- const refused = await koh3(clone, thiefHome, ["offer"]);167+ const refused = await koh3(copy, thiefHome, ["offer"]);168168 expect(refused.code).toBe(1);169169 expect(refused.err + refused.out).toContain("which is read-only");170170 @@ -172,11 +172,11 @@172172 await writeFile(join(repoDir, "README.md"), "# shipped\n\nA third save.\n");173173 expect((await koh3(repoDir, ownerHome, ["save", "third save"])).code).toBe(0);174174 await sync();175- expect((await koh3(clone, thiefHome, ["steal"])).code).toBe(0);176- expect(await readFile(join(clone, "README.md"), "utf8")).toContain("A third save");175+ expect((await koh3(copy, thiefHome, ["steal"])).code).toBe(0);176+ expect(await readFile(join(copy, "README.md"), "utf8")).toContain("A third save");177177 178178 // A strip on the source side reaches the mirror, and is honoured: history no longer179- // holds the path, so a fresh clone never sees the file at all. On the machine that179+ // holds the path, so a fresh steal never sees the file at all. On the machine that180180 // already had it the file stays on disk — a strip is not a delete — but it is banned:181181 // not unsaved work, and never saved again.182182 await stripPath(repoDir, ownerHome, "src.v");@@ -186,10 +186,10 @@186186 0,187187 );188188 await sync();189- expect((await koh3(clone, thiefHome, ["steal"])).code).toBe(0);190- expect((await koh3(clone, thiefHome, ["doctor"])).code).toBe(0);191- expect(await Bun.file(join(clone, "src.v")).exists()).toBe(true);192- expect((await koh3(clone, thiefHome, ["status"])).out).not.toContain("src.v");189+ expect((await koh3(copy, thiefHome, ["steal"])).code).toBe(0);190+ expect((await koh3(copy, thiefHome, ["doctor"])).code).toBe(0);191+ expect(await Bun.file(join(copy, "src.v")).exists()).toBe(true);192+ expect((await koh3(copy, thiefHome, ["status"])).out).not.toContain("src.v");193193 const fresh = join(root, "thief-again");194194 await mkdir(fresh, { recursive: true });195195 expect((await koh3(fresh, thiefHome, ["steal", `${host}/${REPO}`])).code).toBe(0);@@ -222,53 +222,53 @@222222 223223 // A save made before the upgrade is local work and nothing else: it must not write to224224 // the bucket, and it must not quietly drop the source on its own.225- await writeFile(join(clone, "local.txt"), "work in progress\n");226- const local = await koh3(clone, thiefHome, ["save", "local only"]);225+ await writeFile(join(copy, "local.txt"), "work in progress\n");226+ const local = await koh3(copy, thiefHome, ["save", "local only"]);227227 expect(local.code, local.err + local.out).toBe(0);228228 expect(local.out + local.err).not.toContain("offered");229- expect((await koh3(clone, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);229+ expect((await koh3(copy, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);230230 231231 // The next offer drops the source: the repo is this machine's own work now; offer is232232 // the path the addendum names for the owner case.233- const upgraded = await koh3(clone, thiefHome, ["offer"]);233+ const upgraded = await koh3(copy, thiefHome, ["offer"]);234234 expect(upgraded.code, upgraded.err + upgraded.out).toBe(0);235235 expect(upgraded.out + upgraded.err).toContain("now syncs with your bucket");236236 // The owner can read what that offer published, before anything else is saved: the237- // local save the clone was carrying went up with it.237+ // local save the copy was carrying went up with it.238238 expect((await koh3(repoDir, ownerHome, ["revert", "--yes"])).code).toBe(0);239239 expect((await koh3(repoDir, ownerHome, ["steal"])).code).toBe(0);240240 expect(await readFile(join(repoDir, "local.txt"), "utf8")).toContain("work in progress");241241 expect(JSON.parse((await koh3(repoDir, ownerHome, ["view", "--json"])).out).saves).toEqual(242- JSON.parse((await koh3(clone, thiefHome, ["view", "--json"])).out).saves,242+ JSON.parse((await koh3(copy, thiefHome, ["view", "--json"])).out).saves,243243 );244- await writeFile(join(clone, "README.md"), "# shipped\n\nThe thief's edit.\n");245- expect((await koh3(clone, thiefHome, ["save", "from the thief"])).code).toBe(0);244+ await writeFile(join(copy, "README.md"), "# shipped\n\nThe thief's edit.\n");245+ expect((await koh3(copy, thiefHome, ["save", "from the thief"])).code).toBe(0);246246 expect((await koh3(repoDir, ownerHome, ["steal"])).code).toBe(0);247247 expect(await readFile(join(repoDir, "README.md"), "utf8")).toContain("The thief's edit");248248 249- // The other clone still rides its source. When both sides changed the same file,249+ // The other copy still rides its source. When both sides changed the same file,250250 // offer without a terminal refuses as steal would, and never picks a side itself:251251 // the file is named, steal is where to choose, and the source stays.252252 const other = join(fresh, REPO);253253 await writeFile(join(repoDir, "README.md"), "# shipped\n\nThe owner's edit.\n");254254 expect((await koh3(repoDir, ownerHome, ["save", "owner edits the readme"])).code).toBe(0);255- await writeFile(join(other, "README.md"), "# shipped\n\nThe other clone's edit.\n");256- expect((await koh3(other, thiefHome, ["save", "other clone edits"])).code).toBe(0);255+ await writeFile(join(other, "README.md"), "# shipped\n\nThe other copy's edit.\n");256+ expect((await koh3(other, thiefHome, ["save", "other copy edits"])).code).toBe(0);257257 const clashed = await koh3(other, thiefHome, ["offer"]);258258 expect(clashed.code).toBe(1);259259 expect(clashed.err).toContain(260260 'README.md changed on both machines — run "koh steal" to choose, then offer',261261 );262- expect(await readFile(join(other, "README.md"), "utf8")).toContain("other clone's edit");262+ expect(await readFile(join(other, "README.md"), "utf8")).toContain("other copy's edit");263263 expect((await koh3(other, thiefHome, ["remote"])).out).toContain(`${host}/${REPO}`);264- // Choosing there upgrades the clone, and says where the version it gave up lives.264+ // Choosing there upgrades the copy, and says where the version it gave up lives.265265 const chosen = await koh3(other, thiefHome, ["steal", "--theirs"]);266266 expect(chosen.code, chosen.err + chosen.out).toBe(0);267267 expect(chosen.out + chosen.err).toContain("now syncs with your bucket");268268 expect(chosen.out + chosen.err).toContain("your README.md is in");269269 expect(await readFile(join(other, "README.md"), "utf8")).toContain("The owner's edit");270270 271- // A repo the public side never serves clones from the bucket once signed in.271+ // A repo the public side never serves is stolen from the bucket once signed in.272272 const second = join(root, "thief-two");273273 await mkdir(second, { recursive: true });274274 const kept = await koh3(second, thiefHome, ["steal", `${host}/${PRIVATE_REPO}`]);tests/kohupdate.test.ts+1 −1
@@ -250,7 +250,7 @@250250 const downloaded = new Uint8Array(await Bun.file(installed).arrayBuffer());251251 const released = new Uint8Array(await Bun.file(published).arrayBuffer());252252 if (process.platform === "darwin") {253- // Apple silicon refuses to run an unsigned binary, so koh signs the staged253+ // Apple silicon refuses to run an unsigned binary, so koh signs the new254254 // file before the rename: the installed file is the artifact with a fresh255255 // signature, and its size may go either way, so the bytes cannot be compared256256 // here. What can be proved is that the file that landed is a validly signed,