dbc8eb3d4b8a
cache per-save pages, raw files and tarballs as private for a year so no shared cache keeps a repo after it turns private, and note why a backup cannot interleave with a signature change (v3.0.40)
- time
- 2026-09-26 20:34 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- kepr 3 implementation
- parent
- 4538c2a89e9a
↓ kepr-dbc8eb3d.tar.gzatlas at this save
files changed6 files
package.json+1 −1src/pages/layout.ts+3 −2src/server/backup.ts+1 −0src/tarball/archive.ts+3 −2tests/pages.test.ts+1 −1tests/tarball.test.ts+1 −1package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "kepr",3- "version": "3.0.39",3+ "version": "3.0.40",44 "private": true,55 "type": "module",66 "scripts": {src/pages/layout.ts+3 −2
@@ -9,8 +9,9 @@99 export type PublicRepo = { settings: RepoSettings; mirror: Database };1010 1111 const HEAD_CACHE = "public, max-age=60";12-// Content at a save never changes, but strip can remove it, so it is long-lived rather than immutable.13-const SAVE_CACHE = "public, max-age=31536000";12+// Content at a save never changes, but strip can remove it, so it is long-lived rather than immutable,13+// and `private`, so no shared cache keeps it after the repo turns private (§20).14+const SAVE_CACHE = "private, max-age=31536000";1415 const FONTS =1516 "https://fonts.bunny.net/css?family=bellota:700|comfortaa:700|martian-mono:400|quicksand:400,600|sacramento:400&display=swap";1617 src/server/backup.ts+1 −0
@@ -42,6 +42,7 @@4242 rmSync(tmp, { recursive: true, force: true });4343 // It holds reporters' contact emails, so only Kepr's user may read it.4444 mkdirSync(tmp, { recursive: true, mode: 0o700 });45+ // Synchronous from here to the rename: no request can change kepr.db or assets/ in between.4546 kepr.query("VACUUM INTO ?").run(join(tmp, "kepr.db"));4647 // A restored backup must not bring live sign-ins with it.4748 const copy = new Database(join(tmp, "kepr.db"));src/tarball/archive.ts+3 −2
@@ -4,8 +4,9 @@44 import { countInstall } from "../releases/releases.ts";55 import { cachedTarball } from "./cache.ts";66 7-// Long-lived but never `immutable` (§18): a strip can change what a save's tarball holds.8-const TARBALL_CACHE = "public, max-age=31536000";7+// Long-lived but never `immutable` (§18): a strip can change what a save's tarball holds. `private`,8+// so no shared cache keeps it after the repo turns private (§20).9+const TARBALL_CACHE = "private, max-age=31536000";910 1011 export function registerTarballRoutes(app: Hono, ctx: PageContext, tarballDir: string): void {1112 app.get("/:repo/archive/HEAD.tar.gz", (c) => {tests/pages.test.ts+1 −1
@@ -133,7 +133,7 @@133133 expect(nowPage).toContain('class="tk-');134134 135135 const then = await get(`/demo/files/src/main.ts?at=${second().slice(0, 6)}`);136- expect(then.headers.get("cache-control")).toBe("public, max-age=31536000");136+ expect(then.headers.get("cache-control")).toBe("private, max-age=31536000");137137 const thenPage = await then.text();138138 expect(thenPage).toContain("'hi'");139139 expect(thenPage).not.toContain("hello");tests/tarball.test.ts+1 −1
@@ -179,7 +179,7 @@179179 const response = await get(`/tarry/archive/${head}.tar.gz`);180180 expect(response.status).toBe(200);181181 expect(response.headers.get("content-type")).toBe("application/gzip");182- expect(response.headers.get("cache-control")).toBe("public, max-age=31536000");182+ expect(response.headers.get("cache-control")).toBe("private, max-age=31536000");183183 expect(response.headers.get("content-disposition")).toBe(184184 `attachment; filename="tarry-${head.slice(0, 8)}.tar.gz"`,185185 );