@usecontextlayer/pggit 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +14 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1119 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["ZERO_OID","GITLINK_MODE"],"sources":["../src/instrument.ts","../src/protocol/errors.ts","../src/protocol/pkt-line.ts","../src/protocol/capabilities.ts","../src/protocol/sideband.ts","../src/protocol/receive-pack.ts","../src/protocol/v2.ts","../src/protocol/upload-pack.ts","../src/object/format-error.ts","../src/object/object.ts","../src/repo-view/build-file-list.ts","../src/repo-view/config.ts","../src/repo-view/rebuild.ts","../src/database/postgres.ts","../src/database/copy-insert.ts","../src/object/edges.ts","../src/pack/object-header.ts","../src/store/reachability.ts","../src/store/repo-resolver.ts","../src/store/gc.ts","../src/gc-scheduler.ts","../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\"\nimport { performance } from \"node:perf_hooks\"\n\n/**\n * Request-scoped performance instrumentation. The perf harness activates a\n * per-request {@link Collector} via {@link runRequest}; everything else\n * (`withPhase`/`count`/`label`/`recordQuery`) reads the active collector from an\n * AsyncLocalStorage and is a **no-op when none is active**. Production and the\n * oracle tests never call `runRequest`, so they pay nothing but a `Map.get`.\n *\n * Concurrency note: phase attribution uses a single mutable `current` per\n * collector. That is correct because a single git request runs its phases\n * (graph-walk → read-objects → write-pack) sequentially, never overlapping.\n */\n\nexport type QueryRecord = { sql: string; durationMs: number; phase: string }\n\nexport type Collector = {\n\t/** What the request turned out to be (`fetch` / `ls-refs`), set by the handler. */\n\tlabel: string\n\tmethod: string\n\tpath: string\n\t/** The phase a query/counter is currently attributed to. */\n\tcurrent: string\n\t/** Phase name → total wall ms spent in that phase. */\n\tphaseMs: Map<string, number>\n\t/** Counter name → accumulated value. */\n\tcounters: Map<string, number>\n\tqueries: QueryRecord[]\n}\n\nconst als = new AsyncLocalStorage<Collector>()\nconst collected: Collector[] = []\n\n/** Every collector recorded since the last {@link resetCollected}. */\nexport function collectedRuns(): readonly Collector[] {\n\treturn collected\n}\n\nexport function resetCollected(): void {\n\tcollected.length = 0\n}\n\nfunction newCollector(method: string, path: string): Collector {\n\treturn {\n\t\tcounters: new Map(),\n\t\tcurrent: \"request\",\n\t\tlabel: \"\",\n\t\tmethod,\n\t\tpath,\n\t\tphaseMs: new Map(),\n\t\tqueries: [],\n\t}\n}\n\n/** Run `fn` inside a fresh per-request collector; record the collector when done. */\nexport async function runRequest<T>(\n\tmeta: { method: string; path: string },\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst collector = newCollector(meta.method, meta.path)\n\treturn als.run(collector, async () => {\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tcollected.push(collector)\n\t\t}\n\t})\n}\n\n/** Measure `fn`'s wall time into the active collector under `name`; no-op when inactive. */\nexport async function withPhase<T>(name: string, fn: () => Promise<T>): Promise<T> {\n\tconst collector = als.getStore()\n\tif (!collector) return fn()\n\tconst previous = collector.current\n\tcollector.current = name\n\tconst start = performance.now()\n\ttry {\n\t\treturn await fn()\n\t} finally {\n\t\tconst elapsed = performance.now() - start\n\t\tcollector.phaseMs.set(name, (collector.phaseMs.get(name) ?? 0) + elapsed)\n\t\tcollector.current = previous\n\t}\n}\n\nexport function count(metric: string, n = 1): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.counters.set(metric, (collector.counters.get(metric) ?? 0) + n)\n}\n\nexport function label(name: string): void {\n\tconst collector = als.getStore()\n\tif (collector) collector.label = name\n}\n\nexport function recordQuery(sql: string, durationMs: number): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.queries.push({ durationMs, phase: collector.current, sql })\n}\n","/**\n * A malformed-request / unsupported-capability error detected at the git wire\n * boundary (bad command list, unknown command, unsupported object-format or\n * filter, a request body in an encoding we don't accept). It is the CLIENT's\n * fault, so the HTTP layer maps it to a 400 with the message — distinct from an\n * internal failure (a missing object mid-serve, a DB error), which stays a 500.\n * Validate at the boundary, fail loud, and let the type carry the status.\n */\nexport class GitProtocolError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitProtocolError\"\n\t}\n}\n\n/**\n * A fetch `want` names an object this repo does not have — a CLIENT condition (a\n * stale/force-pushed tip, a lost promisor blob), not an internal failure. Real git\n * upload-pack answers it IN-BAND with `ERR upload-pack: not our ref <oid>` (an HTTP\n * 200 protocol error the client reads), so it must NOT escape as a 500. Carries the\n * absent OIDs; `handleFetch` maps it to the ERR pkt-line. Distinct from a generic\n * `Error` out of the serve path (a real backend fault), which still propagates → 500.\n */\nexport class WantNotFoundError extends Error {\n\tconstructor(readonly oids: string[]) {\n\t\tsuper(`upload-pack: not our ref ${oids.join(\" \")}`)\n\t\tthis.name = \"WantNotFoundError\"\n\t}\n}\n","/**\n * pkt-line framing (git wire protocol). A pkt-line is a 4-byte hex length prefix\n * (the length INCLUDES the 4 prefix bytes) followed by `length - 4` payload bytes.\n * Three special zero-payload packets: flush `0000`, delim `0001`, response-end\n * `0002`. See gitprotocol-common + design spec §5.\n */\n\nimport { GitProtocolError } from \"@/protocol/errors\"\n\nexport type Pkt =\n\t| { type: \"data\"; payload: Buffer }\n\t| { type: \"flush\" }\n\t| { type: \"delim\" }\n\t| { type: \"response-end\" }\n\nconst FLUSH_PKT = Buffer.from(\"0000\", \"latin1\")\nconst DELIM_PKT = Buffer.from(\"0001\", \"latin1\")\nconst RESPONSE_END_PKT = Buffer.from(\"0002\", \"latin1\")\n\n/** Largest payload we will emit (git's conservative writer cap). */\nexport const WRITER_MAX_PAYLOAD = 65515\n/** Largest payload we will accept on read (git's LARGE_PACKET_DATA_MAX). */\nexport const READER_MAX_PAYLOAD = 65516\n\n/** Frame a data payload as a pkt-line: `<4-hex len><payload>`. */\nexport function encodePktLine(payload: Buffer): Buffer {\n\tif (payload.length > WRITER_MAX_PAYLOAD) {\n\t\tthrow new Error(\n\t\t\t`pkt-line: payload ${payload.length} exceeds writer cap ${WRITER_MAX_PAYLOAD}`,\n\t\t)\n\t}\n\tconst len = payload.length + 4\n\tconst prefix = len.toString(16).padStart(4, \"0\")\n\treturn Buffer.concat([Buffer.from(prefix, \"latin1\"), payload])\n}\n\n/** Frame any packet — data or one of the three special zero-payload markers. */\nexport function encodePkt(pkt: Pkt): Buffer {\n\tswitch (pkt.type) {\n\t\tcase \"data\":\n\t\t\treturn encodePktLine(pkt.payload)\n\t\tcase \"flush\":\n\t\t\treturn FLUSH_PKT\n\t\tcase \"delim\":\n\t\t\treturn DELIM_PKT\n\t\tcase \"response-end\":\n\t\t\treturn RESPONSE_END_PKT\n\t}\n}\n\nfunction parseLen(buf: Buffer, offset: number): number {\n\tconst hex = buf.toString(\"latin1\", offset, offset + 4)\n\tif (!/^[0-9a-f]{4}$/i.test(hex)) {\n\t\t// Malformed framing in a client request body — a wire-boundary fault (400),\n\t\t// not a server error.\n\t\tthrow new GitProtocolError(`pkt-line: invalid length prefix ${JSON.stringify(hex)}`)\n\t}\n\treturn Number.parseInt(hex, 16)\n}\n\n/**\n * Decode a buffer into a sequence of packets. Streaming-safe: a trailing\n * partial packet is left in `rest` for the caller to prepend to the next chunk.\n *\n * With `stopAtFlush`, decoding returns at the first flush (which is NOT included\n * in `packets`), leaving the bytes after it in `rest`. The receive-pack request\n * splits here: a pkt-line command list, a flush, then the raw (un-framed) pack.\n *\n * `flushed` reports whether a flush actually terminated the stream in\n * `stopAtFlush` mode — the parser uses it to reject an unterminated command list\n * on a COMPLETE request body (where \"more bytes coming\" is not an option).\n */\nexport function decodePktStream(\n\tbuf: Buffer,\n\topts: { stopAtFlush?: boolean } = {},\n): { packets: Pkt[]; rest: Buffer; flushed: boolean } {\n\tconst packets: Pkt[] = []\n\tlet offset = 0\n\twhile (offset + 4 <= buf.length) {\n\t\tconst len = parseLen(buf, offset)\n\t\tif (len === 0) {\n\t\t\toffset += 4\n\t\t\tif (opts.stopAtFlush) return { flushed: true, packets, rest: buf.subarray(offset) }\n\t\t\tpackets.push({ type: \"flush\" })\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 1) {\n\t\t\tpackets.push({ type: \"delim\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 2) {\n\t\t\tpackets.push({ type: \"response-end\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 3) {\n\t\t\tthrow new GitProtocolError(\"pkt-line: reserved length 0003\")\n\t\t}\n\t\tconst payloadLen = len - 4\n\t\tif (payloadLen > READER_MAX_PAYLOAD) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`pkt-line: declared payload ${payloadLen} exceeds reader bound ${READER_MAX_PAYLOAD}`,\n\t\t\t)\n\t\t}\n\t\t// Incomplete data packet — leave it (and everything after) in `rest`.\n\t\tif (offset + len > buf.length) break\n\t\tconst payload = buf.subarray(offset + 4, offset + len)\n\t\tpackets.push({ payload, type: \"data\" })\n\t\toffset += len\n\t}\n\treturn { flushed: false, packets, rest: buf.subarray(offset) }\n}\n","import { GitProtocolError } from \"@/protocol/errors\"\n\n// Protocol capabilities shared by both services — the agent string and the\n// object-format guard are version-agnostic (push is v0, fetch is v2), so they\n// live here rather than in the v2-named module.\n\nexport const AGENT = \"pggit/0.0.0\"\n\n/**\n * Reject a client negotiating a non-sha1 object hash. pggit is SHA-1 only (the\n * charter) and assumes 40-hex / 20-byte OIDs everywhere; a sha256 client would\n * otherwise fail deep in the parser on a 64-hex OID. Catch it at the boundary\n * with a clear message. An absent `object-format` cap defaults to sha1 (git's\n * default), so it is accepted.\n */\nexport function assertSupportedObjectFormat(caps: string[]): void {\n\tconst fmt = caps.find((c) => c.startsWith(\"object-format=\"))\n\tif (fmt !== undefined && fmt !== \"object-format=sha1\") {\n\t\tthrow new GitProtocolError(\n\t\t\t`unsupported ${fmt} — only object-format=sha1 is supported`,\n\t\t)\n\t}\n}\n","import { encodePktLine } from \"@/protocol/pkt-line\"\n\n/** The sideband-64k data channel (band 1) — carries pack/report payload. */\nexport const SIDEBAND_DATA = 0x01\n\n// One band pkt-line is a band byte + payload, together within the pkt-line writer\n// cap (65515) — so a single payload slice is at most 65514 bytes.\nconst MAX_BAND_DATA = 65514\n\n/**\n * Multiplex `data` onto sideband `band`: each ≤MAX_BAND_DATA slice becomes a\n * pkt-line of `[band byte | slice]`. Returns the concatenated band pkt-lines with\n * NO trailing flush — the caller owns the section framing (the `packfile\\n` header\n * for fetch, the bare report for push) and appends its own flush.\n */\nexport function encodeSideband(band: number, data: Buffer): Buffer {\n\tconst parts: Buffer[] = []\n\tfor (let i = 0; i < data.length; i += MAX_BAND_DATA) {\n\t\tconst chunk = data.subarray(i, i + MAX_BAND_DATA)\n\t\tparts.push(encodePktLine(Buffer.concat([Buffer.from([band]), chunk])))\n\t}\n\treturn Buffer.concat(parts)\n}\n","import { AGENT, assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\n/** A ref name longer than this (bytes) is rejected at the boundary: `git_ref`'s PK is\n * a btree on (repo_id, name) whose index entry overflows past ~2704 bytes, which\n * Postgres raises as an opaque storage error. The cap sits far above any real ref name\n * and safely under the btree limit, so a too-long name fails loud + in-band (`ng`),\n * never as an HTTP 500 that has already orphaned the ingested pack. */\nconst MAX_REF_NAME_BYTES = 2000\n\n// The push capabilities we advertise AND honor (spec §4): report-status over\n// side-band, ref deletion, atomic mode, sha1. We pick plain `report-status`\n// (not `-v2`) — we do not emit its extra option lines.\nconst RECEIVE_CAPS = [\n\t\"report-status\",\n\t\"delete-refs\",\n\t\"side-band-64k\",\n\t\"atomic\",\n\t\"object-format=sha1\",\n\t`agent=${AGENT}`,\n]\n\nexport type RefCommand = { oldOid: string; newOid: string; ref: string }\nexport type ReceiveRequest = { commands: RefCommand[]; caps: string[]; pack: Buffer }\nexport type CommandResult = { ref: string; ok: boolean; reason?: string }\n\n/**\n * v0 ref advertisement for receive-pack (push). An empty repo — the dominant\n * first-push state — emits the synthetic `0{40} capabilities^{}` line so the\n * client has somewhere to read the push capabilities.\n */\nexport function encodeReceivePackAdvertisement(\n\trefs: { name: string; oid: string }[],\n): Buffer {\n\tconst capStr = RECEIVE_CAPS.join(\" \")\n\tconst lines: Buffer[] = []\n\tif (refs.length === 0) {\n\t\tlines.push(encodePktLine(Buffer.from(`${ZERO_OID} capabilities^{}\\0${capStr}\\n`)))\n\t} else {\n\t\trefs.forEach((r, i) => {\n\t\t\tconst base = `${r.oid} ${r.name}`\n\t\t\tlines.push(\n\t\t\t\tencodePktLine(Buffer.from(i === 0 ? `${base}\\0${capStr}\\n` : `${base}\\n`)),\n\t\t\t)\n\t\t})\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\treturn Buffer.concat(lines)\n}\n\n/**\n * Parse the receive-pack POST body: a pkt-line command list (`<old> <new> <ref>`,\n * caps after a NUL on the first line), a flush, then the raw packfile.\n */\nexport function parseReceivePack(body: Buffer): ReceiveRequest {\n\tconst { packets, rest, flushed } = decodePktStream(body, { stopAtFlush: true })\n\t// A non-empty command list MUST be terminated by a flush before the pack. Without\n\t// it, decodePktStream falls off the end (a truncated/length-overrunning command\n\t// pkt-line) and hands the framing garbage back as `rest` — which would otherwise\n\t// be mis-fed to the pack reader. Reject the framing fault loudly. An empty body is\n\t// the legitimate zero-command no-op and is left alone.\n\tif (!flushed && body.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"receive-pack: command list not terminated by a flush (truncated or length-overrunning pkt-line)\",\n\t\t)\n\t}\n\tconst commands: RefCommand[] = []\n\tlet caps: string[] = []\n\tfor (const p of packets) {\n\t\tif (p.type !== \"data\") continue\n\t\tlet line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tconst nul = line.indexOf(\"\\0\")\n\t\tif (nul >= 0) {\n\t\t\tcaps = line\n\t\t\t\t.slice(nul + 1)\n\t\t\t\t.split(\" \")\n\t\t\t\t.filter(Boolean)\n\t\t\tline = line.slice(0, nul)\n\t\t}\n\t\t// Fail loud: a command line is exactly `<old> <new> <ref>`. Anything else is\n\t\t// malformed — reject it rather than silently drop it (which would apply a\n\t\t// partial command set with no diagnostic).\n\t\tconst parts = line.split(\" \")\n\t\tconst [oldOid, newOid, ref] = parts\n\t\tif (parts.length !== 3 || !oldOid || !newOid || !ref) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`receive-pack: malformed command line ${JSON.stringify(line)}`,\n\t\t\t)\n\t\t}\n\t\tcommands.push({ newOid, oldOid, ref })\n\t}\n\treturn { caps, commands, pack: rest }\n}\n\n/**\n * report-status: `unpack <status>` then `ok <ref>` / `ng <ref> <reason>` per\n * command, flush. When side-band-64k is negotiated the whole stream rides band 1.\n */\nexport function encodeReportStatus(\n\tunpack: string,\n\tresults: CommandResult[],\n\tuseSideband: boolean,\n): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(`unpack ${unpack}\\n`))]\n\tfor (const r of results) {\n\t\tconst line = r.ok ? `ok ${r.ref}\\n` : `ng ${r.ref} ${r.reason ?? \"failed\"}\\n`\n\t\tlines.push(encodePktLine(Buffer.from(line)))\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\tconst report = Buffer.concat(lines)\n\tif (!useSideband) return report\n\treturn Buffer.concat([\n\t\tencodeSideband(SIDEBAND_DATA, report),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\n/** Everything receive-pack needs from a single repo's storage. */\nexport type ReceiveBackend = {\n\tingest: (pack: Buffer) => Promise<void>\n\t/** Apply ref CAS updates; `atomic` ⇒ all-or-nothing. Per-command success flags. */\n\tapplyRefUpdates: (commands: RefCommand[], atomic: boolean) => Promise<boolean[]>\n\t/** Is every object reachable from `oid` present? (connectivity, spec §10). */\n\tisConnected: (oid: string) => Promise<boolean>\n\t/** Refresh the queryable file projection for a just-applied ref. Present only\n\t * when the (optional) queryable-view layer is wired; a plain remote omits it. */\n\tsyncRefSnapshot?: (ref: string, newOid: string) => Promise<void>\n}\n\n/**\n * Handle a receive-pack POST: ingest the pack (if any), then apply the ref\n * commands under CAS — atomically when the client negotiated `atomic` — and\n * report status. A failed unpack fails every ref; an atomic failure ng's every\n * ref (none applied). Non-ff is accepted by default (CAS guards concurrency, not\n * ancestry — spec §3.6).\n */\nexport async function handleReceivePack(\n\tbody: Buffer,\n\tbackend: ReceiveBackend,\n): Promise<Buffer> {\n\tconst { commands, caps, pack } = parseReceivePack(body)\n\tassertSupportedObjectFormat(caps)\n\tconst useSideband = caps.includes(\"side-band-64k\")\n\tconst atomic = caps.includes(\"atomic\")\n\n\t// rc3 boundary check: a ref name too long to store is rejected BEFORE ingest — so\n\t// an all-unstorable push never ingests a pack (no orphaned objects), and the raw\n\t// btree error never escapes as a 500.\n\tconst nameTooLong = commands.map(\n\t\t(c) => Buffer.byteLength(c.ref, \"utf8\") > MAX_REF_NAME_BYTES,\n\t)\n\tconst anyApplicable = nameTooLong.length === 0 || nameTooLong.some((t) => !t)\n\n\tlet unpackStatus = \"ok\"\n\tif (pack.length > 0 && anyApplicable) {\n\t\ttry {\n\t\t\tawait backend.ingest(pack)\n\t\t} catch (e) {\n\t\t\tunpackStatus = (e instanceof Error ? e.message : \"unpack failed\").replace(\n\t\t\t\t/\\n/g,\n\t\t\t\t\" \",\n\t\t\t)\n\t\t}\n\t}\n\n\tif (unpackStatus !== \"ok\") {\n\t\tconst failed = commands.map((c) => ({\n\t\t\tok: false,\n\t\t\treason: \"unpacker error\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Connectivity (spec §10): a create/update must leave its new tip fully reachable\n\t// in the store; a delete (newOid zero) needs no objects. A too-long ref is already\n\t// disqualified, so it skips the closure walk.\n\tconst connected = await Promise.all(\n\t\tcommands.map((c, i) =>\n\t\t\tnameTooLong[i] || c.newOid === ZERO_OID\n\t\t\t\t? Promise.resolve(true)\n\t\t\t\t: backend.isConnected(c.newOid),\n\t\t),\n\t)\n\t// Per-command disqualification reason (null ⇒ applicable): a too-long name fails\n\t// the storage boundary, a disconnected tip fails connectivity. A disqualified\n\t// command never touches a ref.\n\tconst reasons = commands.map((_, i) =>\n\t\tnameTooLong[i]\n\t\t\t? \"funny refname (too long to store)\"\n\t\t\t: connected[i]\n\t\t\t\t? null\n\t\t\t\t: \"missing necessary objects\",\n\t)\n\tif (atomic && reasons.some((r) => r !== null)) {\n\t\tconst failed = commands.map((c, i) => ({\n\t\t\tok: false,\n\t\t\treason: reasons[i] ?? \"atomic transaction failed\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Apply only the applicable commands; a disqualified one never touches a ref.\n\tconst oks = await backend.applyRefUpdates(\n\t\tcommands.filter((_, i) => reasons[i] === null),\n\t\tatomic,\n\t)\n\tlet applied = 0\n\tconst results: CommandResult[] = commands.map((c, i) => {\n\t\tconst reason = reasons[i]\n\t\tif (reason !== null) return { ok: false, reason, ref: c.ref }\n\t\treturn oks[applied++]\n\t\t\t? { ok: true, ref: c.ref }\n\t\t\t: {\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: atomic\n\t\t\t\t\t\t? \"atomic transaction failed\"\n\t\t\t\t\t\t: \"stale ref (compare-and-swap failed)\",\n\t\t\t\t\tref: c.ref,\n\t\t\t\t}\n\t})\n\n\t// Post-commit: refresh the queryable file projection for each applied ref. The\n\t// view layer decides branch-filtering and build-vs-drop. Sequential — same-repo\n\t// rebuilds must not race the shared-blob reaper.\n\tfor (const [i, c] of commands.entries()) {\n\t\tif (!results[i]?.ok) continue\n\t\t// rc2: the queryable view is a DERIVED projection — a rebuild failure (e.g. a\n\t\t// tip that is not a commit, which buildFileList cannot walk) must NEVER roll\n\t\t// back or 500 an already-applied push (rebuild.ts's standing contract). Absorb\n\t\t// it loudly to the log; the projection is rebuilt on the next push to the ref.\n\t\ttry {\n\t\t\tawait backend.syncRefSnapshot?.(c.ref, c.newOid)\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit: snapshot refresh failed for ${c.ref} (the push is already applied):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\treturn encodeReportStatus(unpackStatus, results, useSideband)\n}\n","import { AGENT } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\n/**\n * The v2 capability advertisement (GET info/refs body, minus HTTP framing).\n * We advertise ONLY what we honor (spec §4): ls-refs (with `unborn`) and fetch\n * with the `filter` (partial clone) and `include-tag` (auto-follow annotated tags)\n * features. No shallow / ref-in-want — those have no milestone owner and\n * advertising them flips clients onto unimplemented paths.\n */\nexport function encodeAdvertisement(): Buffer {\n\tconst caps = [\n\t\t\"version 2\",\n\t\t`agent=${AGENT}`,\n\t\t\"ls-refs=unborn\",\n\t\t\"fetch=filter include-tag\",\n\t\t\"object-format=sha1\",\n\t]\n\treturn Buffer.concat([\n\t\t...caps.map((c) => encodePktLine(Buffer.from(`${c}\\n`))),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\nexport type V2Request = {\n\tcommand: string\n\tcapabilities: string[]\n\targs: string[]\n}\n\n/** Decode a `command=… <caps> 0001 <args> 0000` v2 request body. */\nexport function parseV2Request(body: Buffer): V2Request {\n\tconst { packets, rest } = decodePktStream(body)\n\t// The body is a COMPLETE request: any leftover bytes are an incomplete or\n\t// length-overrunning packet (decodePktStream leaves a partial packet in `rest`).\n\t// On a complete body that is a framing fault, not a streaming boundary — reject\n\t// it loudly rather than silently dropping the truncated args.\n\tif (rest.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t`pkt-line: ${rest.length} trailing bytes after the request — incomplete or length-overrunning packet`,\n\t\t)\n\t}\n\tlet command = \"\"\n\tconst capabilities: string[] = []\n\tconst args: string[] = []\n\tlet afterDelim = false\n\tfor (const p of packets) {\n\t\tif (p.type === \"delim\") {\n\t\t\tafterDelim = true\n\t\t\tcontinue\n\t\t}\n\t\tif (p.type !== \"data\") continue\n\t\tconst line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tif (afterDelim) args.push(line)\n\t\telse if (line.startsWith(\"command=\")) command = line.slice(\"command=\".length)\n\t\telse capabilities.push(line)\n\t}\n\treturn { args, capabilities, command }\n}\n\nexport type FetchRequest = {\n\twants: string[]\n\thaves: string[]\n\tdone: boolean\n\t/** Partial-clone filter spec (e.g. `blob:none`), if the client sent one. */\n\tfilter?: string\n\t/** Client asked the server to auto-include annotated tags pointing into the\n\t * fetched set (the `include-tag` capability). */\n\tincludeTag: boolean\n}\n\n/** Fetch features pggit deliberately does NOT advertise (encodeAdvertisement): a\n * client that drives one anyway must FAIL LOUDLY, never be silently dropped to an\n * empty result (the charter). `ref-in-want` (`want-ref`) and the `shallow`/`deepen`\n * family are the unimplemented ones. */\nconst UNSUPPORTED_FETCH_ARG = /^(want-ref|deepen|shallow)\\b/\n\nconst OID = /^[0-9a-f]{40}$/\n\nexport function parseFetch(req: V2Request): FetchRequest {\n\tconst wants: string[] = []\n\tconst haves: string[] = []\n\tlet done = false\n\tlet filter: string | undefined\n\tlet includeTag = false\n\tfor (const arg of req.args) {\n\t\t// Reject an unadvertised feature request loudly before parsing wants — else a\n\t\t// `want-ref` line falls through every branch below and silently leaves wants=[]\n\t\t// (a no-op empty pack the client misreads as a successful empty clone).\n\t\tif (UNSUPPORTED_FETCH_ARG.test(arg)) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`fetch: unsupported feature ${JSON.stringify(arg.split(\" \")[0])} — pggit does not advertise it`,\n\t\t\t)\n\t\t}\n\t\tif (arg.startsWith(\"want \")) {\n\t\t\tconst oid = arg.slice(5)\n\t\t\t// A want OID is coerced to `bytea` downstream via Buffer.from(oid, \"hex\"),\n\t\t\t// which SILENTLY yields a short/empty buffer for a non-hex value and then\n\t\t\t// fails deep in buildPack. Validate the wire shape at the boundary instead.\n\t\t\tif (!OID.test(oid)) {\n\t\t\t\tthrow new GitProtocolError(\n\t\t\t\t\t`fetch: malformed want object id ${JSON.stringify(oid)}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\twants.push(oid)\n\t\t} else if (arg.startsWith(\"have \")) haves.push(arg.slice(5))\n\t\telse if (arg.startsWith(\"filter \")) filter = arg.slice(\"filter \".length)\n\t\telse if (arg === \"include-tag\") includeTag = true\n\t\telse if (arg === \"done\") done = true\n\t}\n\treturn { done, filter, haves, includeTag, wants }\n}\n\n/**\n * One ls-refs line. A normal ref leads with its oid; an `unborn` ref (an empty\n * repo's HEAD, which has no commit yet) leads with the literal `unborn` token\n * instead — exactly git's `ls-refs.c send_ref` shape.\n */\nexport type LsRefEntry =\n\t| { name: string; oid: string; symrefTarget?: string; peeled?: string }\n\t| { name: string; unborn: true; symrefTarget?: string }\n\n/** ls-refs response: one line per ref (+ symref-target / peeled), then flush. */\nexport function encodeLsRefsResponse(entries: LsRefEntry[]): Buffer {\n\tconst lines = entries.map((e) => {\n\t\tlet line = \"unborn\" in e ? `unborn ${e.name}` : `${e.oid} ${e.name}`\n\t\tif (e.symrefTarget) line += ` symref-target:${e.symrefTarget}`\n\t\tif (\"peeled\" in e && e.peeled) line += ` peeled:${e.peeled}`\n\t\treturn encodePktLine(Buffer.from(`${line}\\n`))\n\t})\n\treturn Buffer.concat([...lines, encodePkt({ type: \"flush\" })])\n}\n\n/** The `acknowledgments` section lines: header, ACKs / NAK, optional `ready`. */\nfunction acknowledgmentLines(common: string[], ready: boolean): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(\"acknowledgments\\n\"))]\n\tif (common.length === 0 && !ready) {\n\t\tlines.push(encodePktLine(Buffer.from(\"NAK\\n\")))\n\t} else {\n\t\tfor (const oid of common) lines.push(encodePktLine(Buffer.from(`ACK ${oid}\\n`)))\n\t\tif (ready) lines.push(encodePktLine(Buffer.from(\"ready\\n\")))\n\t}\n\treturn Buffer.concat(lines)\n}\n\n/**\n * fetch `acknowledgments` response for a negotiation round that is NOT yet ready\n * (no `done`): the section + flush, no pack. The client sends more haves or\n * `done` (spec §4 shape b).\n */\nexport function encodeAcknowledgments(common: string[], ready: boolean): Buffer {\n\treturn Buffer.concat([acknowledgmentLines(common, ready), encodePkt({ type: \"flush\" })])\n}\n\n/**\n * fetch response when the server becomes `ready` mid-negotiation: the\n * acknowledgments section (with `ready`), a delim-pkt, then the packfile — git\n * requires the pack to follow `ready` in the same response (not a later round).\n */\nexport function encodeReadyWithPack(common: string[], pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tacknowledgmentLines(common, true),\n\t\tencodePkt({ type: \"delim\" }),\n\t\tencodePackfileResponse(pack),\n\t])\n}\n\n/**\n * A v2 error response: a single `ERR <message>` pkt-line. git's packet reader\n * recognizes the `ERR ` prefix and the client dies with `remote error: <message>`\n * — the in-band channel for a request that cannot be served (e.g. a `want` the repo\n * does not have): an HTTP-200 protocol error the client can read, NOT a transport 500.\n */\nexport function encodeErr(message: string): Buffer {\n\treturn encodePktLine(Buffer.from(`ERR ${message}\\n`))\n}\n\n/**\n * fetch response for the clone path (client sent `done`, no haves): the\n * `packfile` section header, the pack multiplexed over sideband band-1, flush.\n */\nexport function encodePackfileResponse(pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"packfile\\n\")),\n\t\tencodeSideband(SIDEBAND_DATA, pack),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n","import { label, withPhase } from \"@/instrument\"\nimport { assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError, WantNotFoundError } from \"@/protocol/errors\"\nimport {\n\tencodeAcknowledgments,\n\tencodeErr,\n\tencodeLsRefsResponse,\n\tencodePackfileResponse,\n\tencodeReadyWithPack,\n\ttype LsRefEntry,\n\tparseFetch,\n\tparseV2Request,\n\ttype V2Request,\n} from \"@/protocol/v2\"\n\n/** A ref advertised by the store: its oid, plus the peeled tag target when it is\n * an annotated tag (computed at ref-write, §5.3). */\nexport type AdvertisedRef = { name: string; oid: string; peeled?: string }\n\n/**\n * Everything the upload-pack service needs from a single repo's storage. The graph\n * logic lives in the store now (set-based SQL over the row+edge model), so this is\n * a thin set-oriented interface — no object-at-a-time walk. Tag peeling is read\n * straight off the ref (`peeled`), so there is no object-fetch on the serve path.\n */\nexport type RepoBackend = {\n\tlistRefs: () => Promise<AdvertisedRef[]>\n\tgetSymref: (name: string) => Promise<string | null>\n\t/** The subset of `haves` the repo has — the negotiation common set. */\n\tcommonHaves: (haves: string[]) => Promise<string[]>\n\t/** git's ok_to_give_up: does every want reach a common have by ancestry? */\n\treadyToGiveUp: (wants: string[], common: string[]) => Promise<boolean>\n\t/** The served pack: want-closure minus have-closure, plus the explicit wants\n\t * (and, when `includeTag`, annotated tags pointing into the served set). */\n\tbuildPack: (\n\t\twants: string[],\n\t\thaves: string[],\n\t\tomitBlobs: boolean,\n\t\tincludeTag: boolean,\n\t) => Promise<Buffer>\n}\n\nasync function handleLsRefs(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"ls-refs\")\n\treturn withPhase(\"ref-advertise\", async () => {\n\t\tconst wantPeel = req.args.includes(\"peel\")\n\t\tconst wantSymrefs = req.args.includes(\"symrefs\")\n\t\tconst prefixes = req.args\n\t\t\t.filter((a) => a.startsWith(\"ref-prefix \"))\n\t\t\t.map((a) => a.slice(\"ref-prefix \".length))\n\t\tconst matches = (name: string) =>\n\t\t\tprefixes.length === 0 || prefixes.some((p) => name.startsWith(p))\n\n\t\tconst refs = await backend.listRefs()\n\t\tconst byName = new Map(refs.map((r) => [r.name, r.oid]))\n\t\tconst entries: LsRefEntry[] = []\n\n\t\tconst wantUnborn = req.args.includes(\"unborn\")\n\t\tconst headTarget = await backend.getSymref(\"HEAD\")\n\t\tif (headTarget && matches(\"HEAD\")) {\n\t\t\tconst headOid = byName.get(headTarget)\n\t\t\tif (headOid) {\n\t\t\t\tentries.push({\n\t\t\t\t\tname: \"HEAD\",\n\t\t\t\t\toid: headOid,\n\t\t\t\t\tsymrefTarget: wantSymrefs ? headTarget : undefined,\n\t\t\t\t})\n\t\t\t} else if (wantUnborn && wantSymrefs) {\n\t\t\t\t// Empty repo: HEAD points at a branch with no commit yet. git emits the\n\t\t\t\t// unborn HEAD only when the client asked for both `unborn` and `symrefs`\n\t\t\t\t// (ls-refs.c send_possibly_unborn_head), propagating the default branch.\n\t\t\t\tentries.push({ name: \"HEAD\", symrefTarget: headTarget, unborn: true })\n\t\t\t}\n\t\t}\n\n\t\tfor (const ref of refs) {\n\t\t\tif (!matches(ref.name)) continue\n\t\t\tconst entry: LsRefEntry = { name: ref.name, oid: ref.oid }\n\t\t\t// Peeled target is precomputed at ref-write (§5.3) — no per-request walk.\n\t\t\tif (wantPeel && ref.peeled) entry.peeled = ref.peeled\n\t\t\tentries.push(entry)\n\t\t}\n\n\t\treturn encodeLsRefsResponse(entries)\n\t})\n}\n\n/**\n * Translate the wire filter spec to a walk option. We optimize the common\n * `blob:none` (blobless partial clone) by omitting blobs; any other filter\n * (`tree:0`, `blob:limit=…`, …) serves the FULL closure. The protocol lets a\n * server send more than a filter requests — the client accepts the superset and\n * has nothing to lazily fetch — so over-serving completes the clone that a hard\n * rejection would abort, without implementing every filter spec.\n */\nfunction filterOmitsBlobs(filter: string | undefined): boolean {\n\treturn filter === \"blob:none\"\n}\n\nasync function handleFetch(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"fetch\")\n\t// parseFetch validates wire shape (malformed/unsupported args → GitProtocolError\n\t// → 400) BEFORE the serve attempt — kept outside the try so it stays a 400.\n\tconst { wants, haves, done, filter, includeTag } = parseFetch(req)\n\t// A zero-want fetch is NOT an error: git's upload-pack treats it as a no-op\n\t// (upload-pack.c) and returns an empty pack — buildPack produces one, so we let\n\t// it fall through rather than rejecting.\n\tconst omitBlobs = filterOmitsBlobs(filter)\n\tconst common = await backend.commonHaves(haves)\n\n\ttry {\n\t\tif (!done) {\n\t\t\t// Negotiation round (spec §4 shape b): until the haves cut every want we\n\t\t\t// ACK/NAK and flush, no pack — the client sends more haves. Once ready, git\n\t\t\t// requires the pack in this same response, after the `ready` line.\n\t\t\tif (!(await backend.readyToGiveUp(wants, common))) {\n\t\t\t\treturn encodeAcknowledgments(common, false)\n\t\t\t}\n\t\t\treturn encodeReadyWithPack(\n\t\t\t\tcommon,\n\t\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t\t)\n\t\t}\n\n\t\t// `done` (spec §4 shapes a/c): pack the delta directly. A clone has no haves, so\n\t\t// the subtrahend is empty and we pack the whole want-closure.\n\t\treturn encodePackfileResponse(\n\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t)\n\t} catch (err) {\n\t\t// A `want` the repo does not have is a client condition, not a server fault:\n\t\t// answer it in-band like canonical upload-pack (`ERR … not our ref`) so the\n\t\t// client reads a clean protocol error, not an HTTP 500. A genuine serve failure\n\t\t// (any other error) still propagates → 500 (§10, no partial pack is emitted).\n\t\tif (err instanceof WantNotFoundError) return encodeErr(err.message)\n\t\tthrow err\n\t}\n}\n\n/** Dispatch a v2 upload-pack POST body to ls-refs or fetch. */\nexport async function handleUploadPack(\n\tbody: Buffer,\n\tbackend: RepoBackend,\n): Promise<Buffer> {\n\tconst req = parseV2Request(body)\n\tassertSupportedObjectFormat(req.capabilities)\n\tif (req.command === \"ls-refs\") return handleLsRefs(req, backend)\n\tif (req.command === \"fetch\") return handleFetch(req, backend)\n\tthrow new GitProtocolError(\n\t\t`upload-pack: unsupported command ${JSON.stringify(req.command)}`,\n\t)\n}\n","/**\n * A \"the git data we were handed is not well-formed\" error — raised while parsing\n * a packfile, applying a delta, or reading a git object's bytes. It is distinct\n * from `GitProtocolError` (a malformed *request* at the wire boundary, → HTTP\n * 400): a `GitFormatError` is about the *content* (a corrupt/truncated pack or\n * object), which on push surfaces as the `unpack <reason>` report-status line and\n * otherwise propagates as an internal failure.\n *\n * The `code` is the stable, assertable identity of the failure; the `message` is\n * free-form prose for humans (the `unpack` line, logs) and may be reworded\n * without breaking callers or tests. Tests assert `code`, never the message text.\n */\nexport type GitFormatErrorCode =\n\t// packfile framing / codec (read-pack)\n\t| \"bad-magic\"\n\t| \"unsupported-version\"\n\t| \"trailer-mismatch\"\n\t| \"unknown-object-type\"\n\t| \"size-mismatch\"\n\t| \"trailing-bytes\"\n\t| \"unresolved-base\"\n\t| \"inflate-failed\"\n\t// delta application\n\t| \"delta-base-size-mismatch\"\n\t| \"delta-reserved-opcode\"\n\t| \"delta-target-size-mismatch\"\n\t// git object content\n\t| \"malformed-tree\"\n\t| \"malformed-oid\"\n\t| \"missing-tree-header\"\n\t| \"multiple-tree-headers\"\n\t| \"missing-tag-object\"\n\t| \"multiple-tag-objects\"\n\nexport class GitFormatError extends Error {\n\treadonly code: GitFormatErrorCode\n\n\tconstructor(code: GitFormatErrorCode, message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitFormatError\"\n\t\tthis.code = code\n\t}\n}\n","import { createHash } from \"node:crypto\"\nimport { GitFormatError } from \"@/object/format-error\"\n\n/** The four addressable git object types (deltas resolve into one of these). */\nexport type GitObjectType = \"blob\" | \"commit\" | \"tree\" | \"tag\"\n\n/**\n * The git object ID: SHA-1 of the loose-object representation\n * `\"<type> <byteLength>\\0\" + content`. Returns the 40-char lowercase hex digest.\n */\nexport function computeOid(type: GitObjectType, content: Buffer): string {\n\tconst header = Buffer.from(`${type} ${content.length}\\0`, \"latin1\")\n\treturn createHash(\"sha1\").update(header).update(content).digest(\"hex\")\n}\n\n/** OIDs in the leading `key <oid>` headers (up to the blank line) for given keys. */\nfunction headerOids(content: Buffer, keys: Set<string>): string[] {\n\tconst oids: string[] = []\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tconst sp = line.indexOf(\" \")\n\t\tif (sp > 0 && keys.has(line.slice(0, sp))) oids.push(line.slice(sp + 1))\n\t}\n\treturn oids\n}\n\n/** One entry of a tree. `mode` is the raw stored value (`\"40000\"` for a subtree —\n * git zero-pads to `\"040000\"` only for display); `name` is the entry's own path\n * segment, not a full path. */\nexport type TreeEntry = { mode: string; name: string; oid: string }\n\n/** A tree's entries — `<mode> <name>\\0<20-byte oid>` repeated. */\nexport function treeEntries(content: Buffer): TreeEntry[] {\n\tconst entries: TreeEntry[] = []\n\tlet pos = 0\n\twhile (pos < content.length) {\n\t\tconst space = content.indexOf(0x20, pos)\n\t\tconst nul = content.indexOf(0x00, pos)\n\t\t// Fail loud: a tree is `<mode> <name>\\0<20-byte oid>` repeated exactly. Any\n\t\t// missing separator or a trailing OID shorter than 20 bytes is corruption —\n\t\t// throw rather than return a short list (which would let `isConnected` report\n\t\t// a truncated object as connected and silently accept bad data).\n\t\tif (space < 0 || nul < 0 || space > nul || nul + 21 > content.length) {\n\t\t\tthrow new GitFormatError(\"malformed-tree\", `tree: malformed entry at offset ${pos}`)\n\t\t}\n\t\tconst mode = content.subarray(pos, space).toString(\"latin1\")\n\t\tconst name = content.subarray(space + 1, nul).toString(\"utf8\")\n\t\tconst oid = content.subarray(nul + 1, nul + 21).toString(\"hex\")\n\t\tentries.push({ mode, name, oid })\n\t\tpos = nul + 21\n\t}\n\treturn entries\n}\n\n/** A tree entry's mode marks a subtree (directory), not a blob or gitlink. */\nexport function isTreeEntryMode(mode: string): boolean {\n\treturn mode === \"40000\"\n}\n\n/** OIDs of a tree's entries (all kinds), in tree order. */\nfunction treeEntryOids(content: Buffer): string[] {\n\treturn treeEntries(content).map((e) => e.oid)\n}\n\n/** A commit's parent OIDs only (ancestry walk; excludes its tree). */\nexport function commitParents(content: Buffer): string[] {\n\treturn headerOids(content, new Set([\"parent\"]))\n}\n\n/** A commit's root tree OID. Every commit has exactly one `tree` header. */\nexport function commitTreeOid(content: Buffer): string {\n\tconst [tree] = headerOids(content, new Set([\"tree\"]))\n\tif (!tree) {\n\t\tthrow new GitFormatError(\n\t\t\t\"missing-tree-header\",\n\t\t\t\"commitTreeOid: commit has no tree header\",\n\t\t)\n\t}\n\treturn tree\n}\n\n/**\n * The OIDs an object directly references: a commit → its tree + parents, a tree\n * → its entries, a tag → its target, a blob → nothing. The basis of reachability\n * enumeration (fetch, connectivity).\n */\nexport function referencedOids(type: GitObjectType, content: Buffer): string[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn headerOids(content, new Set([\"tree\", \"parent\"]))\n\t\tcase \"tag\":\n\t\t\treturn headerOids(content, new Set([\"object\"]))\n\t\tcase \"tree\":\n\t\t\treturn treeEntryOids(content)\n\t}\n}\n","import {\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/** Reads a stored object's type + content by OID — the snapshot builder's view of\n * the object store. */\nexport type ObjectReader = (\n\toid: string,\n) => Promise<{ type: GitObjectType; content: Buffer }>\n\nexport type FileEntry = { path: string; mode: string; blobOid: string }\nexport type FileList = { files: FileEntry[] }\n\n/** Gitlink/submodule entries point at a commit in another repo — no blob here. */\nconst GITLINK_MODE = \"160000\"\n\n/**\n * The flat path→blob index of a commit's tree (the `git ls-tree -r` of a commit,\n * read straight from the object store): one FileEntry per blob — full path from the\n * root, raw mode, blob oid. Subtrees are recursed; gitlinks (submodules) are skipped\n * (no blob in this repo). Blob CONTENT is NOT read — it lives in git_object and is\n * joined at query time (§4.5 collapse), so this walk touches only commits + trees.\n */\nexport async function buildFileList(\n\tread: ObjectReader,\n\tcommitOid: string,\n): Promise<FileList> {\n\tconst commit = await read(commitOid)\n\tconst files: FileEntry[] = []\n\n\tconst walk = async (treeOid: string, prefix: string): Promise<void> => {\n\t\tconst tree = await read(treeOid)\n\t\tfor (const entry of treeEntries(tree.content)) {\n\t\t\tconst path = prefix + entry.name\n\t\t\tif (isTreeEntryMode(entry.mode)) {\n\t\t\t\tawait walk(entry.oid, `${path}/`)\n\t\t\t} else if (entry.mode !== GITLINK_MODE) {\n\t\t\t\tfiles.push({ blobOid: entry.oid, mode: entry.mode, path })\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(commitTreeOid(commit.content), \"\")\n\treturn { files }\n}\n","/**\n * Which refs get a queryable file snapshot. Branches only — tags, notes, and\n * `refs/pull/*` are skipped. One edit to widen the projection later.\n */\nexport const SNAPSHOT_REFS = (refName: string): boolean =>\n\trefName.startsWith(\"refs/heads/\")\n","import { buildFileList } from \"@/repo-view/build-file-list\"\nimport { SNAPSHOT_REFS } from \"@/repo-view/config\"\nimport type { RepoFileProjection } from \"@/repo-view/repo-file-projection\"\nimport type { ObjectStore } from \"@/store/object-store\"\nimport type { RefStore } from \"@/store/refs-store\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\nexport type SnapshotDeps = {\n\tobjects: ObjectStore\n\tsnapshots: RepoFileProjection\n}\n\n/**\n * Refresh `refName`'s file snapshot after a push applied it. Non-branch refs are\n * ignored (§ SNAPSHOT_REFS); a delete (zero oid) drops the snapshot; otherwise\n * the new tip's tree is walked — objects are already present post-ingest — into a\n * fresh snapshot. Runs after the push commits, so a failure here never rolls back\n * the git operation (the projection is rebuildable from the packs).\n */\nexport async function syncRefSnapshot(\n\tdeps: SnapshotDeps,\n\trepoId: string,\n\trefName: string,\n\tnewOid: string,\n): Promise<void> {\n\tif (!SNAPSHOT_REFS(refName)) return\n\tif (newOid === ZERO_OID) {\n\t\tawait deps.snapshots.dropRefSnapshot(repoId, refName)\n\t\treturn\n\t}\n\tconst read = async (oid: string) => {\n\t\tconst obj = await deps.objects.getObject(repoId, oid)\n\t\tif (!obj)\n\t\t\tthrow new Error(`repo-view: object ${oid} missing while building ${refName}`)\n\t\treturn obj\n\t}\n\tawait deps.snapshots.rebuildRefSnapshot(\n\t\trepoId,\n\t\trefName,\n\t\tawait buildFileList(read, newOid),\n\t)\n}\n\n/**\n * Rebuild a repo's entire projection from its current branch tips — the backfill\n * for an existing repo, and the \"nuke and rebuild\" backstop if the cache ever\n * drifts. Everything is re-derived from the canonical packs.\n */\nexport async function rebuildAllSnapshots(\n\tdeps: SnapshotDeps & { refs: RefStore },\n\trepoId: string,\n): Promise<void> {\n\tawait deps.snapshots.clearRepo(repoId)\n\tfor (const ref of await deps.refs.listRefs(repoId)) {\n\t\tawait syncRefSnapshot(deps, repoId, ref.name, ref.oid)\n\t}\n}\n","import { Kysely } from \"kysely\"\nimport { PostgresJSDialect } from \"kysely-postgres-js\"\nimport type { Sql } from \"postgres\"\nimport { recordQuery } from \"@/instrument\"\n\n// Mirrors web/postgres.ts: Kysely over the porsager `postgres` driver via\n// PostgresJSDialect. Unlike web, pggit does NOT keep a module-level singleton —\n// the caller owns the porsager client and injects it; each store builds its own\n// Kysely from it (so the app stays a mountable sub-app and per-schema test\n// isolation works), and keeps the raw client for the COPY ingest path.\nconst EVENT_SIGNS = { error: \"🔴\", query: \"🟢\" } as const\n\n/** Wrap a porsager client in a typed Kysely. Dev builds log query/error events. */\nexport function initKysely<T>(pg: Sql): Kysely<T> {\n\treturn new Kysely<T>({\n\t\tdialect: new PostgresJSDialect({ postgres: pg }),\n\t\tlog(event) {\n\t\t\tif (event.level === \"query\" || event.level === \"error\") {\n\t\t\t\trecordQuery(event.query.sql, event.queryDurationMillis)\n\t\t\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\t\t\tconsole.debug(\n\t\t\t\t\t\t`${EVENT_SIGNS[event.level]} ${event.queryDurationMillis}ms ${event.query.sql}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t})\n}\n","import type { TransactionSql } from \"postgres\"\n\n/**\n * Bulk binary insert via `COPY … FROM STDIN (FORMAT binary)` into a staging temp\n * table, then `INSERT … SELECT … ON CONFLICT DO NOTHING`. This is the one bulk-row\n * insert path for the ingest spine, and it exists because a multi-row `INSERT`\n * cannot carry git's data correctly:\n *\n * - The porsager driver serializes a `bytea` parameter as the text `'\\x'+hex`,\n * DOUBLING the byte length; a blob over ~256MiB therefore overruns V8's max\n * string length and the insert throws. COPY binary streams the bytea as RAW\n * bytes — content never lands on the JS string heap, so blob size is bounded\n * only by Postgres (`bytea` ~1GB), not by the string cap.\n * - The wire protocol caps a statement at 65534 bind parameters (≈13k object rows\n * at 5 columns); COPY binds none, so any row count goes in a single statement —\n * no chunking, whatever the column count.\n *\n * The staging hop keeps ingest idempotent (a re-sent object skips on the primary\n * key), exactly as the prior `onConflict().doNothing()` did. Caller runs it on a\n * transaction-scoped `Sql` so the staging COPY and the final insert commit\n * together; the temp table drops on commit.\n */\n\n/** One COPY field, tagged with the destination column's Postgres type so it\n * encodes to the correct binary wire form. */\nexport type CopyValue =\n\t| { t: \"int2\"; v: number }\n\t| { t: \"int4\"; v: number }\n\t| { t: \"int8\"; v: number | bigint | string }\n\t| { t: \"bytea\"; v: Buffer }\n\t| { t: \"text\"; v: string }\n\n// PGCOPY binary signature + the two zero header words (flags, header extension).\nconst HEADER = Buffer.concat([\n\tBuffer.from([0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00]),\n\tBuffer.alloc(8),\n])\n// File trailer: a -1 field count.\nconst TRAILER = (() => {\n\tconst b = Buffer.alloc(2)\n\tb.writeInt16BE(-1)\n\treturn b\n})()\n\nfunction encodeValue(field: CopyValue): Buffer {\n\tswitch (field.t) {\n\t\tcase \"int2\": {\n\t\t\tconst b = Buffer.alloc(2)\n\t\t\tb.writeInt16BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int4\": {\n\t\t\tconst b = Buffer.alloc(4)\n\t\t\tb.writeInt32BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int8\": {\n\t\t\tconst b = Buffer.alloc(8)\n\t\t\tb.writeBigInt64BE(BigInt(field.v))\n\t\t\treturn b\n\t\t}\n\t\tcase \"bytea\":\n\t\t\treturn field.v\n\t\tcase \"text\":\n\t\t\treturn Buffer.from(field.v, \"utf8\")\n\t}\n}\n\n/** Encode rows as one PGCOPY binary payload: header, then per row a field count\n * and each field as `<int32 length><raw bytes>`, then the trailer. */\nfunction encodeBinaryCopy(rows: CopyValue[][]): Buffer {\n\tconst parts: Buffer[] = [HEADER]\n\tfor (const row of rows) {\n\t\tconst fieldCount = Buffer.alloc(2)\n\t\tfieldCount.writeInt16BE(row.length)\n\t\tparts.push(fieldCount)\n\t\tfor (const field of row) {\n\t\t\tconst value = encodeValue(field)\n\t\t\tconst len = Buffer.alloc(4)\n\t\t\tlen.writeInt32BE(value.length)\n\t\t\tparts.push(len, value)\n\t\t}\n\t}\n\tparts.push(TRAILER)\n\treturn Buffer.concat(parts)\n}\n\n/**\n * COPY `rows` into `target` (a temp staging table shaped from `target`'s columns,\n * then `INSERT … SELECT … ON CONFLICT DO NOTHING`). `tx` must be a\n * transaction-scoped porsager `Sql`. `target` and `columns` are internal constants\n * (never client input), interpolated as SQL identifiers.\n */\nexport async function copyInsert(\n\ttx: TransactionSql,\n\ttarget: string,\n\tcolumns: readonly string[],\n\trows: CopyValue[][],\n): Promise<void> {\n\tif (rows.length === 0) return\n\tconst cols = columns.join(\", \")\n\tconst staging = `copy_stg_${target}`\n\t// `CREATE TABLE AS … WITH NO DATA` shapes the staging table from exactly the\n\t// inserted columns (their types, no NOT NULL / defaults / constraints), so COPY\n\t// fills every column it declares and the final insert lets `target` apply its\n\t// own defaults (e.g. git_object.created_at) to the unlisted ones.\n\tawait tx.unsafe(\n\t\t`create temp table ${staging} on commit drop as select ${cols} from ${target} with no data`,\n\t)\n\tconst writable =\n\t\tawait tx`copy ${tx(staging)} (${tx.unsafe(cols)}) from stdin (format binary)`.writable()\n\tawait new Promise<void>((resolve, reject) => {\n\t\twritable.on(\"error\", reject)\n\t\twritable.on(\"finish\", () => resolve())\n\t\twritable.write(encodeBinaryCopy(rows), (err) => {\n\t\t\tif (err) reject(err)\n\t\t\telse writable.end()\n\t\t})\n\t})\n\tawait tx.unsafe(\n\t\t`insert into ${target} (${cols}) select ${cols} from ${staging} on conflict do nothing`,\n\t)\n}\n","import { GitFormatError } from \"@/object/format-error\"\nimport {\n\tcommitParents,\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\treferencedOids,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/**\n * Edge kinds stored in `git_edge.kind`. tree→blob (would be `4`) is deliberately\n * NOT a kind: blobs are enumerated from tree content, never stored as edges (§4.3),\n * so `4` is reserved/unused.\n */\nexport const EDGE_KIND = {\n\tCOMMIT_PARENT: 2,\n\tCOMMIT_TREE: 1,\n\tTAG_TARGET: 5,\n\tTREE_SUBTREE: 3,\n} as const\n\nexport type DerivedEdge = { child: string; kind: number }\n\n/** A tree entry pointing at a commit in *another* repo — no blob, no edge here. */\nconst GITLINK_MODE = \"160000\"\n\nconst WELL_FORMED_OID = /^[0-9a-f]{40}$/\n\n/**\n * Validate an OID parsed from a commit/tag header. `commitParents`/`commitTreeOid`/\n * `referencedOids` take whatever follows the header key verbatim — a forged object\n * could carry a non-OID there and yield a bogus edge child — so reject it loudly at\n * the ingest boundary (§5.1). Tree-entry OIDs are exempt: `treeEntries` already\n * guarantees a 20-byte value, and the `bytea CHECK(length(child)=20)` is the\n * database-level backstop for every edge.\n */\nfunction assertOid(oid: string, context: string): string {\n\tif (!WELL_FORMED_OID.test(oid)) {\n\t\tthrow new GitFormatError(\n\t\t\t\"malformed-oid\",\n\t\t\t`${context}: not a well-formed object id: ${JSON.stringify(oid)}`,\n\t\t)\n\t}\n\treturn oid\n}\n\n/** Count the leading `key value` header lines (up to the blank line that ends a\n * commit/tag's header block). */\nfunction countHeader(content: Buffer, key: string): number {\n\tconst prefix = `${key} `\n\tlet n = 0\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tif (line.startsWith(prefix)) n++\n\t}\n\treturn n\n}\n\n/**\n * fsck-grade structural validation at the ingest boundary (§5.1, invariant §10.2):\n * reject the malformed objects that OID-wellformedness and tree parsing do not\n * catch. A commit must not carry more than one `tree` header (git fsck:\n * multipleTrees — `commitTreeOid` would otherwise silently take the first and drop\n * the rest, recording an edge to a tree the object does not actually root). An\n * annotated tag must carry exactly one `object` header (git fsck: missingObject /\n * an extra object line): zero yields no `kind=5` edge and silently breaks peeling\n * and connectivity; more than one yields multiple divergent `kind=5` edges and a\n * nondeterministic `peeled_oid`. The other structural guarantees are already\n * enforced downstream: `assertOid` on every referenced OID (below), a present root\n * `tree` (`commitTreeOid`, which also rejects a zero-tree commit), and a well-formed\n * tree body (`treeEntries` throws). Called by the store once per object before\n * derivation, in the ingest transaction, so a malformed push aborts before any row\n * lands.\n */\nexport function validateObject(type: GitObjectType, content: Buffer): void {\n\tif (type === \"commit\" && countHeader(content, \"tree\") > 1) {\n\t\tthrow new GitFormatError(\n\t\t\t\"multiple-tree-headers\",\n\t\t\t\"commit carries more than one tree header\",\n\t\t)\n\t}\n\tif (type === \"tag\") {\n\t\tconst objects = countHeader(content, \"object\")\n\t\tif (objects < 1) {\n\t\t\tthrow new GitFormatError(\"missing-tag-object\", \"annotated tag has no object header\")\n\t\t}\n\t\tif (objects > 1) {\n\t\t\tthrow new GitFormatError(\n\t\t\t\t\"multiple-tag-objects\",\n\t\t\t\t\"annotated tag carries more than one object header\",\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * The edges an object contributes to `git_edge`, with the object's own OID as the\n * parent — the §4.3 standing rule, mode-aware:\n * - commit → its tree (kind 1) then each parent (kind 2);\n * - tree → its **subtrees only** (mode `40000` → kind 3). Blobs and gitlinks\n * (`160000`, a commit living in another repo) are NOT edges — `isTreeEntryMode`\n * admits only `40000`, so both are dropped;\n * - tag → its target (kind 5);\n * - blob → nothing.\n *\n * This is the single derivation the store inserts alongside the object row, in the\n * same transaction (§10.1), so edges are a validated total function of content.\n */\nexport function deriveEdges(type: GitObjectType, content: Buffer): DerivedEdge[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tchild: assertOid(commitTreeOid(content), \"commit tree\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_TREE,\n\t\t\t\t},\n\t\t\t\t...commitParents(content).map((p) => ({\n\t\t\t\t\tchild: assertOid(p, \"commit parent\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_PARENT,\n\t\t\t\t})),\n\t\t\t]\n\t\tcase \"tag\":\n\t\t\treturn referencedOids(\"tag\", content).map((t) => ({\n\t\t\t\tchild: assertOid(t, \"tag target\"),\n\t\t\t\tkind: EDGE_KIND.TAG_TARGET,\n\t\t\t}))\n\t\tcase \"tree\":\n\t\t\treturn treeEntries(content)\n\t\t\t\t.filter((e) => isTreeEntryMode(e.mode))\n\t\t\t\t.map((e) => ({ child: e.oid, kind: EDGE_KIND.TREE_SUBTREE }))\n\t}\n}\n\n/**\n * The blob OIDs directly in a tree — the §4.3 standing rule's other half: blobs\n * are enumerated from tree content, never stored as edges. A tree entry is a blob\n * unless it is a subtree (`deriveEdges` covers those as kind-3 edges) or a gitlink\n * (`160000`, a submodule commit living in another repo — neither blob nor edge).\n * Connectivity uses this to find the blobs a present tree requires, since no\n * tree→blob edge exists to anchor a missing one.\n */\nexport function treeBlobOids(content: Buffer): string[] {\n\treturn treeEntries(content)\n\t\t.filter((e) => !isTreeEntryMode(e.mode) && e.mode !== GITLINK_MODE)\n\t\t.map((e) => e.oid)\n}\n","/**\n * Pack object header: a variable-length encoding of (type, uncompressed size)\n * that prefixes every object entry in a packfile.\n *\n * First byte: `[c|ttt|ssss]` — continuation bit `c`, 3-bit type `ttt`, low 4 bits\n * of size. Each continuation byte contributes 7 more size bits, least-significant\n * group first. See gitformat-pack.\n *\n * Size arithmetic uses `*`/`Math.floor`, NOT `<<`/`>>` — JS bitwise ops are\n * 32-bit and would corrupt object sizes ≥ 2³¹.\n */\n\nexport const PACK_OBJ_TYPE = {\n\tBLOB: 3,\n\tCOMMIT: 1,\n\tOFS_DELTA: 6,\n\tREF_DELTA: 7,\n\tTAG: 4,\n\tTREE: 2,\n} as const\n\nexport type PackObjType = (typeof PACK_OBJ_TYPE)[keyof typeof PACK_OBJ_TYPE]\n\nexport type DecodedObjectHeader = {\n\ttype: number\n\tsize: number\n\tbytesRead: number\n}\n\nexport function encodeObjectHeader(type: number, size: number): Buffer {\n\tlet rest = Math.floor(size / 16)\n\tlet first = (type << 4) | (size % 16)\n\tif (rest > 0) first |= 0x80\n\tconst bytes = [first]\n\twhile (rest > 0) {\n\t\tlet byte = rest % 128\n\t\trest = Math.floor(rest / 128)\n\t\tif (rest > 0) byte |= 0x80\n\t\tbytes.push(byte)\n\t}\n\treturn Buffer.from(bytes)\n}\n\nexport function decodeObjectHeader(buf: Buffer, offset: number): DecodedObjectHeader {\n\tlet b = buf.readUInt8(offset)\n\tlet bytesRead = 1\n\tconst type = (b >> 4) & 0x07\n\tlet size = b & 0x0f\n\tlet mult = 16\n\twhile (b & 0x80) {\n\t\tb = buf.readUInt8(offset + bytesRead)\n\t\tbytesRead++\n\t\tsize += (b & 0x7f) * mult\n\t\tmult *= 128\n\t}\n\treturn { bytesRead, size, type }\n}\n","import { type Kysely, sql } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { EDGE_KIND, treeBlobOids } from \"@/object/edges\"\nimport { PACK_OBJ_TYPE } from \"@/pack/object-header\"\n\n/** Objects looked up per round-trip when chunking tree/blob existence queries. */\nconst LOOKUP_BATCH = 1000\n\n/** Split `items` into consecutive batches of at most `size`. */\nfunction batches<T>(items: T[], size: number): T[][] {\n\tconst out: T[][] = []\n\tfor (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n\treturn out\n}\n\n/**\n * The objects reachable from `roots` over the stored DAG — the ONE reachability\n * engine shared by connectivity, clone, and incremental fetch (so they can never\n * disagree). A recursive CTE walks `git_edge` (all stored kinds 1,2,3,5) for the\n * commit/tree/tag closure; the LEFT JOIN marks which are present. Blobs are not\n * edges (§4.3), so unless `omitBlobs` they are enumerated from each present tree's\n * content (mode-aware) and their presence checked. Returns the reachable set\n * partitioned into present / missing. `::bigint`/`::bytea` casts and the\n * `VALUES (…::bytea)` seed pin types in the raw CTE (the porsager driver can't\n * bind a raw `bytea[]`, OQ-13); array lookups use Kysely's `in`-expansion.\n */\nexport async function reachableClosure(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\troots: string[],\n\tomitBlobs: boolean,\n): Promise<{ present: Set<string>; missing: Set<string> }> {\n\tconst present = new Set<string>()\n\tconst missing = new Set<string>()\n\tif (roots.length === 0) return { missing, present }\n\n\tconst seed = sql.join(roots.map((r) => sql`(${Buffer.from(r, \"hex\")}::bytea)`))\n\tconst closure = await sql<{ oid: Buffer; type: number | null }>`\n\t\twith recursive closure(oid) as (\n\t\t\tselect oid from (values ${seed}) as roots(oid)\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin closure c on e.parent = c.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t)\n\t\tselect c.oid, o.type\n\t\tfrom closure c\n\t\tleft join git_object o on o.repo_id = ${id}::bigint and o.oid = c.oid\n\t`.execute(db)\n\n\tconst treeOids: Buffer[] = []\n\tfor (const r of closure.rows) {\n\t\tconst hex = r.oid.toString(\"hex\")\n\t\tif (r.type === null) {\n\t\t\tmissing.add(hex)\n\t\t} else {\n\t\t\tpresent.add(hex)\n\t\t\tif (r.type === PACK_OBJ_TYPE.TREE) treeOids.push(r.oid)\n\t\t}\n\t}\n\tif (omitBlobs || treeOids.length === 0) return { missing, present }\n\n\tconst blobCandidates = new Set<string>()\n\tfor (const batch of batches(treeOids, LOOKUP_BATCH)) {\n\t\tconst trees = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"content\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\"oid\", \"in\", batch)\n\t\t\t.execute()\n\t\tfor (const t of trees) {\n\t\t\tfor (const blob of treeBlobOids(t.content)) blobCandidates.add(blob)\n\t\t}\n\t}\n\tif (blobCandidates.size === 0) return { missing, present }\n\n\tconst presentBlobs = new Set<string>()\n\tfor (const batch of batches([...blobCandidates], LOOKUP_BATCH)) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"oid\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\n\t\t\t\t\"oid\",\n\t\t\t\t\"in\",\n\t\t\t\tbatch.map((h) => Buffer.from(h, \"hex\")),\n\t\t\t)\n\t\t\t.execute()\n\t\tfor (const r of rows) presentBlobs.add(r.oid.toString(\"hex\"))\n\t}\n\tfor (const b of blobCandidates) (presentBlobs.has(b) ? present : missing).add(b)\n\treturn { missing, present }\n}\n\n/** Does `want`'s commit/tag ancestry (edge kinds 2,5) reach any oid in `common`?\n * The ancestry-only CTE that underpins `readyToGiveUp`. */\nexport async function ancestryReachesCommon(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\twant: string,\n\tcommonBufs: Buffer[],\n): Promise<boolean> {\n\tif (commonBufs.length === 0) return false\n\tconst commons = sql.join(commonBufs.map((b) => sql`(${b}::bytea)`))\n\tconst result = await sql<{ reached: boolean }>`\n\t\twith recursive anc(oid) as (\n\t\t\tselect ${Buffer.from(want, \"hex\")}::bytea\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin anc a on e.parent = a.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t\t\t\tand e.kind in (${EDGE_KIND.COMMIT_PARENT}, ${EDGE_KIND.TAG_TARGET})\n\t\t)\n\t\tselect exists (\n\t\t\tselect 1 from anc join (values ${commons}) as c(oid) on c.oid = anc.oid\n\t\t) as reached\n\t`.execute(db)\n\treturn result.rows[0]?.reached ?? false\n}\n","import type { Kysely } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\n\nexport type RepoResolver = ReturnType<typeof createRepoResolver>\n\n/**\n * Resolves a wire repo name to its `repos.id` surrogate, memoized. The object and\n * ref stores both key on the bigint `repo_id`, so each builds one of these as its\n * name→id boundary.\n *\n * The mapping is immutable once a repo exists (ids are `generated always`, names\n * are unique), so a found id is cached for the resolver's lifetime — keeping the\n * per-object hot path (getObject) at one point-read, not a join. Misses are NEVER\n * cached: a name the lookup didn't find may be created by a later push, and a\n * cached `null` would mask it.\n *\n * Reads resolve (lookup; `null` ⇒ the repo has never been written, i.e. empty).\n * Writes ensure (race-safe get-or-create).\n */\nexport function createRepoResolver(db: Kysely<Database>) {\n\tconst cache = new Map<string, ReposId>()\n\n\treturn {\n\t\t/** The repo's id, creating the row if absent. Race-safe under concurrent\n\t\t * first-pushes, and avoids a no-op UPDATE on the common (exists) path. */\n\t\tasync ensureRepoId(name: string): Promise<ReposId> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (existing) {\n\t\t\t\tcache.set(name, existing.id)\n\t\t\t\treturn existing.id\n\t\t\t}\n\t\t\tconst inserted = await db\n\t\t\t\t.insertInto(\"repos\")\n\t\t\t\t.values({ name })\n\t\t\t\t.onConflict((oc) => oc.doNothing())\n\t\t\t\t.returning(\"id\")\n\t\t\t\t.executeTakeFirst()\n\t\t\t// `inserted` is undefined iff a concurrent push won the insert race; the\n\t\t\t// row is guaranteed present now, so re-select it.\n\t\t\tconst id =\n\t\t\t\tinserted?.id ??\n\t\t\t\t(\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t\t\t.select(\"id\")\n\t\t\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t\t\t.executeTakeFirstOrThrow()\n\t\t\t\t).id\n\t\t\tcache.set(name, id)\n\t\t\treturn id\n\t\t},\n\t\t/** The repo's id, or `null` if it has never been written to. */\n\t\tasync resolveRepoId(name: string): Promise<ReposId | null> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (!row) return null\n\t\t\tcache.set(name, row.id)\n\t\t\treturn row.id\n\t\t},\n\t}\n}\n","import type { Kysely } from \"kysely\"\nimport type { ReservedSql, Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport { copyInsert } from \"@/database/copy-insert\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { reachableClosure } from \"@/store/reachability\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\n/**\n * Per-repo reachability GC — the one piece of the Postgres-native redesign (§7)\n * that the rest of the spine deferred. See `docs/2026-06-24-force-commit-gc-design.md`\n * for the design; the observable contract is §4 of that doc, and the authoritative\n * algorithm is §7 of `internal/archived/2026-06-22-pggit-postgres-native-storage-\n * redesign.md`.\n *\n * The mechanism (data-structures-first): materialize the LIVE set — the reachable\n * closure from every ref tip — into an UNLOGGED table, then sweep `git_object` in\n * batched short transactions with a server-side anti-join (`NOT EXISTS`) against\n * that table plus a `created_at` grace cutoff. Reachability itself is NOT re-derived\n * here: it is exactly `reachableClosure(omitBlobs=false)`, the one engine clone /\n * fetch / connectivity already share, so GC can never disagree with them about what\n * is reachable.\n */\n\n/** Tunables for one GC pass. `graceSeconds` is REQUIRED — no silent default: an\n * object is reclaimed iff it is unreachable from every ref AND its `created_at`\n * is older than `graceSeconds` (0 ⇒ reclaim all unreachable; a huge value ⇒\n * retain). `batchLimit` caps the per-batch DELETE size (sweep tuning only — it\n * never changes the final observable state). `maintain` (default true) runs the\n * post-sweep VACUUM/REINDEX; the self-scheduling drain passes `false` so a\n * frequent per-repo pass never triggers a full-table VACUUM on the hot cadence\n * (autovacuum reclaims the GC churn instead). Maintenance is observable-neutral —\n * it changes dead-tuple bloat, never the row/clone state. */\nexport type GcOptions = { graceSeconds: number; batchLimit?: number; maintain?: boolean }\n\n/**\n * Internal-only test seam (NOT part of the public `GcOptions` contract): hooks the\n * GC pass at the one point §5 in-flight safety depends on. `afterLiveSet` is awaited\n * AFTER the live set is materialized and BEFORE the object sweep begins, so a test\n * can deterministically interpose a concurrent push there and assert the just-pushed\n * tip is never partially reclaimed. Test-only; do not document or use in production.\n */\ntype GcHooks = { afterLiveSet?: () => Promise<void> }\ntype InternalGcOptions = GcOptions & { _hooks?: GcHooks }\n\n/** What one GC pass reclaimed: the deleted `git_object` / `git_edge` row counts. */\nexport type GcResult = { deletedObjects: number; deletedEdges: number }\n\nexport type Gc = ReturnType<typeof createGc>\n\n/** Default per-batch DELETE cap when the caller omits `batchLimit`. Large enough to\n * sweep a typical force-commit orphan set in one or two batches, small enough to\n * bound the dead-tuple burst and lock duration per transaction (§7). */\nconst DEFAULT_BATCH_LIMIT = 10_000\n\n/** OIDs loaded per COPY round-trip into the live table (the live set can be the whole\n * reachable tree, so it streams in bounded batches, never one giant payload). */\nconst LIVE_LOAD_BATCH = 10_000\n\n/**\n * Build the GC over a porsager client (the same wire→DB boundary the object and ref\n * stores take). `gc(repo, opts)` reclaims a single repo's unreachable-and-old-enough\n * objects offline; reachable objects are always retained.\n */\nexport function createGc(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\treturn {\n\t\tasync gc(repo: string, opts: InternalGcOptions): Promise<GcResult> {\n\t\t\t// 1. Resolve the repo. A name never written has nothing to reclaim.\n\t\t\tconst id = await repos.resolveRepoId(repo)\n\t\t\tif (id === null) return { deletedEdges: 0, deletedObjects: 0 }\n\n\t\t\tconst batchLimit = opts.batchLimit ?? DEFAULT_BATCH_LIMIT\n\n\t\t\t// 2 + 3. Materialize the live set under a consistent snapshot.\n\t\t\t//\n\t\t\t// CONCURRENCY: the write/ingest path (`object-store.insertObjects`) does NOT\n\t\t\t// yet take a per-repo `pg_advisory_xact_lock` — that lock was deferred to this\n\t\t\t// GC chunk (redesign §5.4 / §12) and has no other consumer. So GC takes its\n\t\t\t// safety from two defenses (§5): (a) a REPEATABLE READ transaction, so the\n\t\t\t// ref-tip read and the closure walk see ONE consistent MVCC snapshot that\n\t\t\t// hides any push not yet committed when the snapshot opened; and (b) the\n\t\t\t// `created_at` grace below, which protects the present-but-unreachable window\n\t\t\t// (just-ingested objects a ref does not yet reach). When the write path adopts\n\t\t\t// the same per-repo advisory lock, GC should take that SAME key around this\n\t\t\t// read for full §5 mutual exclusion — DO NOT add a key here that the write path\n\t\t\t// does not also hold, or the lock would guard nothing.\n\t\t\t//\n\t\t\t// The live OIDs land in a per-repo UNLOGGED table (named by repo id, so two\n\t\t\t// DIFFERENT repos' GC passes never collide) — server-side so the sweep's\n\t\t\t// anti-join scales to a ~30k-orphan repo without pulling the orphan set through\n\t\t\t// JS. SINGLE-INSTANCE ONLY: two pggit processes GC'ing the SAME repo would share\n\t\t\t// this id-named table — B's `truncate`/`drop` could wipe A's live set mid-sweep,\n\t\t\t// and A's anti-join would then match (and delete) the whole reachable set. So the\n\t\t\t// deferred multi-instance advisory lock (redesign §5.4; scheduler design §8) MUST\n\t\t\t// wrap the ENTIRE pass (create→load→sweep→drop) AND the staging table be\n\t\t\t// instance-scoped, before a second instance is ever run.\n\t\t\tconst live = `gc_live_${id}`\n\t\t\tawait pg.unsafe(\n\t\t\t\t`create unlogged table if not exists ${live} (oid bytea primary key)`,\n\t\t\t)\n\t\t\ttry {\n\t\t\t\tawait pg.unsafe(`truncate ${live}`)\n\n\t\t\t\tconst roots = await liveSet(id)\n\t\t\t\tawait loadLive(live, roots)\n\n\t\t\t\t// TEST SEAM (§5 in-flight safety): interpose a concurrent push here, between\n\t\t\t\t// the live-set materialization and the object sweep.\n\t\t\t\tawait opts._hooks?.afterLiveSet?.()\n\n\t\t\t\t// 4. SWEEP objects: batched DELETE, each batch its own short transaction,\n\t\t\t\t// anti-join `NOT EXISTS` against the live set, `created_at` past the grace\n\t\t\t\t// cutoff. `clock_timestamp()` (not `now()`) so the cutoff advances per batch.\n\t\t\t\tconst deletedObjects = await sweepObjects(id, live, opts.graceSeconds, batchLimit)\n\n\t\t\t\t// 5. SWEEP edges: drop every edge whose PARENT object no longer survives —\n\t\t\t\t// run AFTER the object sweep, so a grace-retained object keeps its edges and a\n\t\t\t\t// surviving (reachable) parent's edges (which point only at reachable children)\n\t\t\t\t// never dangle.\n\t\t\t\tconst deletedEdges = await sweepEdges(id, batchLimit)\n\n\t\t\t\t// 6. MAINTENANCE (best-effort, not part of the counted deletion): reclaim the\n\t\t\t\t// dead tuples + reindex the walk index. VACUUM cannot run in a txn block, so\n\t\t\t\t// these are standalone statements run outside any transaction. Skipped when\n\t\t\t\t// the pass reclaimed nothing (no dead tuples to chase) or the caller opted out\n\t\t\t\t// (`maintain: false`, the drain's choice) — so a frequent per-repo drain never\n\t\t\t\t// triggers a full-table VACUUM/REINDEX on its hot cadence; the leaf partitions'\n\t\t\t\t// autovacuum (0001_init.ts) reclaims the GC churn. Observable-neutral either way.\n\t\t\t\tif (opts.maintain !== false && deletedObjects + deletedEdges > 0) {\n\t\t\t\t\tawait maintain()\n\t\t\t\t}\n\n\t\t\t\treturn { deletedEdges, deletedObjects }\n\t\t\t} finally {\n\t\t\t\tawait pg.unsafe(`drop table if exists ${live}`)\n\t\t\t}\n\t\t},\n\t}\n\n\t/**\n\t * The live set: the reachable closure from every ref tip, read under ONE\n\t * REPEATABLE READ snapshot so the ref-tip read and the multi-statement closure\n\t * walk cannot interleave with a concurrent push's ref update (§5 defense (a)).\n\t *\n\t * `reachableClosure` is the shared engine and takes a `Kysely`, but the\n\t * kysely-postgres-js dialect drives queries by calling `.reserve()` on its\n\t * `postgres` client for EACH query — so a plain pooled Kysely would scatter the\n\t * closure's statements across connections (no shared snapshot), and a\n\t * transaction-scoped `Sql` has no `.reserve()` at all. So we pin ONE porsager\n\t * connection, open a REPEATABLE READ transaction on it, and back a Kysely with a\n\t * shim whose `reserve()` always returns that pinned connection with a no-op\n\t * `release()` — every closure statement then runs on the one snapshotted\n\t * connection. The transaction is read-only; it commits (releasing the snapshot)\n\t * before the sweep's own short write transactions begin.\n\t */\n\tasync function liveSet(id: ReposId): Promise<Set<string>> {\n\t\tconst conn = await pg.reserve()\n\t\ttry {\n\t\t\tawait conn`begin isolation level repeatable read`\n\t\t\tconst pinned = pinnedKysely(conn)\n\t\t\tconst rows = await conn<{ oid: Buffer | null; peeled_oid: Buffer | null }[]>`\n\t\t\t\tselect oid, peeled_oid from git_ref where repo_id = ${id} and oid is not null\n\t\t\t`\n\t\t\t// Roots: every direct ref tip plus each annotated tag's peeled target. The\n\t\t\t// closure over kinds (1,2,3,5) already descends tag→target, so the peeled\n\t\t\t// target is redundant for the walk, but it is included to match §7 and stay\n\t\t\t// correct even if a tag ref's edge were ever absent.\n\t\t\tconst tips = new Set<string>()\n\t\t\tfor (const r of rows) {\n\t\t\t\tif (r.oid) tips.add(r.oid.toString(\"hex\"))\n\t\t\t\tif (r.peeled_oid) tips.add(r.peeled_oid.toString(\"hex\"))\n\t\t\t}\n\t\t\tconst { present } = await reachableClosure(pinned, id, [...tips], false)\n\t\t\tawait conn`commit`\n\t\t\treturn present\n\t\t} finally {\n\t\t\tconn.release()\n\t\t}\n\t}\n\n\t/** A Kysely pinned to a single porsager connection: its dialect `reserve()`s the\n\t * same connection for every statement (so a multi-statement read shares one MVCC\n\t * snapshot) and `release()` is a no-op (the caller owns the connection's lifetime).\n\t * The shim is a callable with a `reserve` property, the shape the dialect probes\n\t * for (`isPostgresJSSql`). */\n\tfunction pinnedKysely(conn: ReservedSql): Kysely<Database> {\n\t\t// The dialect only ever calls `.unsafe(sql, params)` then `.release()` on the\n\t\t// reserved connection — so hand it the real `conn` for `.unsafe` but swallow\n\t\t// `.release()` (a no-op), keeping the connection pinned across every closure\n\t\t// statement. The caller releases `conn` exactly once when the snapshot is done.\n\t\tconst nonReleasing = new Proxy(conn, {\n\t\t\tget: (target, prop) =>\n\t\t\t\tprop === \"release\" ? () => {} : Reflect.get(target, prop, target),\n\t\t})\n\t\tconst handle = Object.assign(\n\t\t\t() => {\n\t\t\t\tthrow new Error(\"pggit gc: pinned client used as a tagged template\")\n\t\t\t},\n\t\t\t{ reserve: async () => nonReleasing },\n\t\t)\n\t\treturn initKysely<Database>(handle as unknown as Sql)\n\t}\n\n\t/** Bulk-load the live OID set into the UNLOGGED `live` table via binary COPY (the\n\t * one bytea-safe bulk path, copy-insert.ts), batched so the payload stays bounded.\n\t * Each COPY runs in its own transaction so the staging temp table drops on commit. */\n\tasync function loadLive(live: string, oids: Set<string>): Promise<void> {\n\t\tif (oids.size === 0) return\n\t\tconst all = [...oids]\n\t\tfor (let i = 0; i < all.length; i += LIVE_LOAD_BATCH) {\n\t\t\tconst chunk = all.slice(i, i + LIVE_LOAD_BATCH)\n\t\t\tawait pg.begin(async (tx) => {\n\t\t\t\tawait copyInsert(\n\t\t\t\t\ttx,\n\t\t\t\t\tlive,\n\t\t\t\t\t[\"oid\"],\n\t\t\t\t\tchunk.map((hex) => [{ t: \"bytea\", v: Buffer.from(hex, \"hex\") }]),\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\t}\n\n\t/** Batched object sweep. Postgres `DELETE` has no `LIMIT`, so each batch picks a\n\t * `LIMIT`-bounded set of victim OIDs then deletes them by PRIMARY KEY `(repo_id,\n\t * oid)`. The match is on the PK — NOT `ctid`: `ctid` is per-partition-relative, so\n\t * matching `ctid` across the HASH-partitioned table would delete same-ctid rows in\n\t * OTHER partitions (other tenants). The loop ends when a batch deletes nothing.\n\t * Each batch is its own (implicit) transaction, so `clock_timestamp()` re-evaluates\n\t * per batch and the grace cutoff advances. Returns total rows deleted. */\n\tasync function sweepObjects(\n\t\tid: ReposId,\n\t\tlive: string,\n\t\tgraceSeconds: number,\n\t\tbatchLimit: number,\n\t): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect o.oid from git_object o\n\t\t\t\t\twhere o.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (select 1 from ${live} l where l.oid = o.oid)\n\t\t\t\t\t\tand o.created_at < clock_timestamp() - make_interval(secs => $2::float8)\n\t\t\t\t\tlimit $3::int\n\t\t\t\t)\n\t\t\t\tdelete from git_object o using victims v\n\t\t\t\twhere o.repo_id = $1::bigint and o.oid = v.oid returning 1 as n`,\n\t\t\t\t[String(id), String(graceSeconds), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Batched edge sweep: delete every `git_edge` row whose PARENT object no longer\n\t * exists in `git_object` (a deleted object's outgoing edges). No FK cascade exists\n\t * (0003_git_edge.ts), so dangling edges must be swept explicitly. Anti-join on the\n\t * parent only: a surviving parent is reachable, so all its children are reachable\n\t * and present — its edges never dangle. Like the object sweep, each batch picks a\n\t * `LIMIT`-bounded victim set then deletes by PRIMARY KEY `(repo_id, parent, child)`\n\t * — never `ctid`, which is per-partition and would reach into other tenants. */\n\tasync function sweepEdges(id: ReposId, batchLimit: number): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect e.parent, e.child from git_edge e\n\t\t\t\t\twhere e.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (\n\t\t\t\t\t\t\tselect 1 from git_object o where o.repo_id = e.repo_id and o.oid = e.parent\n\t\t\t\t\t\t)\n\t\t\t\t\tlimit $2::int\n\t\t\t\t)\n\t\t\t\tdelete from git_edge e using victims v\n\t\t\t\twhere e.repo_id = $1::bigint and e.parent = v.parent and e.child = v.child\n\t\t\t\treturning 1 as n`,\n\t\t\t\t[String(id), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Post-sweep maintenance (best-effort): reclaim the dead tuples GC produced in\n\t * the heap + TOAST and refresh planner stats, then reindex the walk index.\n\t * `VACUUM` cannot run inside a transaction block, so these are standalone\n\t * statements run outside any txn. */\n\tasync function maintain(): Promise<void> {\n\t\tawait pg.unsafe(`vacuum (analyze) git_object`)\n\t\tawait pg.unsafe(`vacuum (analyze) git_edge`)\n\t\tawait pg.unsafe(`reindex index git_edge_walk`)\n\t}\n}\n","import type { Sql } from \"postgres\"\nimport { createGc } from \"@/store/gc\"\n\n/**\n * Self-scheduling GC — the background drain that decides WHEN the per-repo\n * reachability GC (`store/gc.ts`) runs, off the push/fetch hot path. See\n * `docs/2026-06-24-gc-scheduler-design.md`; the observable contract is §6 of that\n * doc (SCH-1 … SCH-11 / PBT-S1).\n *\n * Mechanism (data-structures-first): every storage-mutating push stamps\n * `repos.last_pushed_at` in its own transaction (the store), so the scheduler is a\n * pure poll loop over Postgres with NO coupling to the request path. One pass\n * (`drainOnce`) selects the eligible repos — `last_pushed_at > last_gc_at`\n * (or `last_gc_at is null`) — and runs `gc()` on each (per-repo serialized,\n * bounded concurrency), then advances `last_gc_at` to the pass's start time so a\n * push landing mid-pass re-qualifies the repo next loop (no lost garbage). `start`\n * is just `drainOnce` on a `setInterval`; all correctness lives in `drainOnce`,\n * which the tests drive directly (the timer is never in a test's critical path).\n */\n\n/** One repo's outcome in a drain pass: the repo and what its GC reclaimed.\n * Emitted for EVERY repo the pass judged eligible (including zero-reclaim), so the\n * eligible set itself is observable (SCH-3). */\nexport type DrainEntry = { repo: string; deletedObjects: number; deletedEdges: number }\n\n/** What one `drainOnce()` reclaimed, one entry per eligible repo. */\nexport type DrainSummary = DrainEntry[]\n\n/** Scheduler tunables (resolved from `env` / `startServer` opts). `graceSeconds`\n * is passed straight to `gc()`; `intervalMs` is the drain cadence (the debounce\n * window); `concurrency` caps repos GC'd at once per pass so one large-orphan repo\n * cannot head-of-line-block the rest. */\nexport type GcSchedulerOptions = {\n\tgraceSeconds: number\n\tintervalMs: number\n\tconcurrency: number\n}\n\nexport type GcScheduler = ReturnType<typeof createGcScheduler>\n\n/** A candidate repo for one drain pass: its id + wire name. The pass-start\n * watermark is captured per-repo (in `drainRepo`, before that repo's GC snapshot)\n * and written back as `last_gc_at`. */\ntype Candidate = { id: string; name: string }\n\n/**\n * Build the GC scheduler over a porsager client (the same wire→DB boundary the\n * stores take). `drainOnce()` runs one poll+sweep pass; `start()`/`stop()` drive\n * it on `intervalMs`. Reachable objects are never touched — it only invokes the\n * per-repo GC primitive, which is reachability-safe.\n */\nexport function createGcScheduler(pg: Sql, opts: GcSchedulerOptions) {\n\tconst gc = createGc(pg)\n\tlet timer: ReturnType<typeof setInterval> | undefined\n\t// The in-flight pass (if any). Doubles as the overlap guard (a tick skips while a\n\t// pass runs, so two passes never touch the same repo at once) and the shutdown\n\t// barrier (`stop()` awaits it).\n\tlet inFlight: Promise<unknown> | undefined\n\n\t/** The eligible repos for this pass — the §2 predicate. */\n\tasync function selectCandidates(): Promise<Candidate[]> {\n\t\treturn pg<Candidate[]>`\n\t\t\tselect r.id::text as id, r.name\n\t\t\tfrom repos r\n\t\t\twhere r.last_pushed_at is not null\n\t\t\t\tand (r.last_gc_at is null or r.last_pushed_at > r.last_gc_at)\n\t\t`\n\t}\n\n\t/**\n\t * GC one candidate. `t0 = clock_timestamp()` is captured BEFORE `gc()` opens its\n\t * snapshot, then written as `last_gc_at` after the sweep: any push committing\n\t * after t0 re-stamps `last_pushed_at > t0` (the store stamps after commit) and\n\t * re-qualifies the repo next pass (no lost garbage). A per-repo failure is\n\t * ISOLATED — logged and skipped (the repo keeps its old `last_gc_at`, so it\n\t * re-qualifies and is retried next pass) — so one poison repo never aborts the\n\t * rest of the pass. `maintain: false`: the drain leans on autovacuum, never a\n\t * per-pass full-table VACUUM (gc.ts).\n\t */\n\tasync function drainRepo(c: Candidate): Promise<DrainEntry | null> {\n\t\ttry {\n\t\t\tconst [t] = await pg<{ t0: string }[]>`select clock_timestamp()::text as t0`\n\t\t\tif (!t) throw new Error(\"pggit gc-scheduler: clock_timestamp() returned no row\")\n\t\t\tconst { deletedObjects, deletedEdges } = await gc.gc(c.name, {\n\t\t\t\tgraceSeconds: opts.graceSeconds,\n\t\t\t\tmaintain: false,\n\t\t\t})\n\t\t\tawait pg`update repos set last_gc_at = ${t.t0}::timestamptz where id = ${c.id}::bigint`\n\t\t\treturn { deletedEdges, deletedObjects, repo: c.name }\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit gc-scheduler: GC of repo ${JSON.stringify(c.name)} failed (retried next pass):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/** One drain pass: GC every eligible repo (bounded concurrency, distinct repos so\n\t * a pass never double-GCs one). Returns an entry per repo GC'd this pass — a repo\n\t * whose GC threw is skipped (not in the summary) and retried next pass. */\n\tasync function drainOnce(): Promise<DrainSummary> {\n\t\tconst candidates = await selectCandidates()\n\t\tconst results = await mapPool(candidates, Math.max(1, opts.concurrency), drainRepo)\n\t\treturn results.filter((e): e is DrainEntry => e !== null)\n\t}\n\n\t/** Run the drain on `intervalMs`. The `inFlight` guard ensures passes never\n\t * overlap — so two passes can never touch the same repo at once — and a slow pass\n\t * simply skips the next tick. A pass failure is logged, never thrown into the\n\t * timer. The timer is `unref`'d so it alone does not keep the process alive (the\n\t * server's socket does). */\n\tfunction start(): void {\n\t\tif (timer) return\n\t\ttimer = setInterval(() => {\n\t\t\tif (inFlight) return\n\t\t\tinFlight = drainOnce()\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(\"pggit gc-scheduler: drain pass failed:\", err)\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tinFlight = undefined\n\t\t\t\t})\n\t\t}, opts.intervalMs)\n\t\ttimer.unref?.()\n\t}\n\n\t/** Halt the background drain and AWAIT any pass already in flight, so a caller may\n\t * safely tear the connection pool down afterwards (no query runs into a closed\n\t * pool). Idempotent. */\n\tasync function stop(): Promise<void> {\n\t\tif (timer) {\n\t\t\tclearInterval(timer)\n\t\t\ttimer = undefined\n\t\t}\n\t\tawait inFlight\n\t}\n\n\treturn { drainOnce, start, stop }\n}\n\n/** Run `fn` over `items` with at most `limit` concurrent, preserving result order.\n * A bounded worker pool — `limit` workers pull from a shared cursor — so one\n * large-orphan repo cannot head-of-line-block the rest of a pass. */\nasync function mapPool<T, R>(\n\titems: T[],\n\tlimit: number,\n\tfn: (item: T) => Promise<R>,\n): Promise<R[]> {\n\tconst results = new Array<R>(items.length)\n\tlet cursor = 0\n\tasync function worker(): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst i = cursor++\n\t\t\tif (i >= items.length) return\n\t\t\tresults[i] = await fn(items[i] as T)\n\t\t}\n\t}\n\tconst workers = Array.from({ length: Math.min(limit, items.length) }, worker)\n\tawait Promise.all(workers)\n\treturn results\n}\n","import { gunzipSync } from \"node:zlib\"\nimport { type Context, Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport { count, runRequest } from \"@/instrument\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport {\n\tencodeReceivePackAdvertisement,\n\thandleReceivePack,\n\ttype ReceiveBackend,\n} from \"@/protocol/receive-pack\"\nimport { handleUploadPack, type RepoBackend } from \"@/protocol/upload-pack\"\nimport { encodeAdvertisement } from \"@/protocol/v2\"\nimport { syncRefSnapshot } from \"@/repo-view/rebuild\"\nimport type { RepoFileProjection } from \"@/repo-view/repo-file-projection\"\nimport type { ObjectStore } from \"@/store/object-store\"\nimport type { RefStore } from \"@/store/refs-store\"\n\nexport type GitAppDeps = {\n\tobjects: ObjectStore\n\trefs: RefStore\n\t/** Optional queryable-view layer. When provided, push maintains a `repo_file`\n\t * path→blob index per branch; when omitted, this is a plain git remote. */\n\tsnapshots?: RepoFileProjection\n}\n\n// Smart-HTTP info/refs body: the `# service` preamble + flush, then the v2\n// capability advertisement.\nconst UPLOAD_PACK_ADVERTISEMENT = Buffer.concat([\n\tencodePktLine(Buffer.from(\"# service=git-upload-pack\\n\")),\n\tencodePkt({ type: \"flush\" }),\n\tencodeAdvertisement(),\n])\n\n// Hono's body types want an ArrayBuffer, not a Node Buffer (a Uint8Array view).\nfunction toArrayBuffer(buf: Buffer): ArrayBuffer {\n\treturn buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\nconst ADVERTISEMENT_BODY = toArrayBuffer(UPLOAD_PACK_ADVERTISEMENT)\n\n/**\n * Read a smart-HTTP POST body, honoring `Content-Encoding`. Git compresses the\n * upload-pack/receive-pack request body with gzip once it is large enough\n * (`remote-curl.c`), exactly as `git http-backend` decompresses on the server\n * side — so we must too. Any other declared encoding is a hard error, never fed\n * raw to the pkt-line parser.\n */\nasync function readRequestBody(c: Context): Promise<Buffer> {\n\tconst raw = Buffer.from(await c.req.arrayBuffer())\n\tconst encoding = c.req.header(\"content-encoding\")?.toLowerCase()\n\tif (encoding === undefined || encoding === \"identity\") return raw\n\tif (encoding === \"gzip\" || encoding === \"x-gzip\") {\n\t\t// A body that declares gzip but fails to inflate is a CLIENT wire fault, not a\n\t\t// server error — surface it on the same clean 400 path as the unsupported\n\t\t// encodings below, never let the ZlibError escape to a 500.\n\t\ttry {\n\t\t\treturn gunzipSync(raw)\n\t\t} catch (err) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`request body declared Content-Encoding ${JSON.stringify(encoding)} but failed to gunzip: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t)\n\t\t}\n\t}\n\tthrow new GitProtocolError(\n\t\t`unsupported request Content-Encoding: ${JSON.stringify(encoding)}`,\n\t)\n}\n\n/**\n * Fetch is served over git protocol v2 ONLY (the charter). git requests v2 with a\n * `Git-Protocol: version=2` header (a `:`-joined key list; git ≥ 2.26 sends it by\n * default). A v0/v1 client sends no such header and cannot parse the v2\n * advertisement — it would read the `version 2` line + flush as an empty repo and\n * silently clone nothing. So reject the unnegotiated case loudly at the boundary\n * (400) instead of handing back an advertisement it will misread.\n */\nfunction assertProtocolV2(header: string | undefined): void {\n\tconst requested = (header ?? \"\").split(\":\").map((s) => s.trim())\n\tif (!requested.includes(\"version=2\")) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"pggit serves fetch over git protocol v2 only; set protocol.version=2 (git ≥ 2.26 negotiates it by default)\",\n\t\t)\n\t}\n}\n\nfunction backendFor(deps: GitAppDeps, repoId: string): RepoBackend {\n\treturn {\n\t\tbuildPack: (wants, haves, omitBlobs, includeTag) =>\n\t\t\tdeps.objects.buildPack(repoId, wants, haves, omitBlobs, includeTag),\n\t\tcommonHaves: (haves) => deps.objects.commonHaves(repoId, haves),\n\t\tgetSymref: (name) => deps.refs.getSymref(repoId, name),\n\t\tlistRefs: () => deps.refs.listRefs(repoId),\n\t\treadyToGiveUp: (wants, common) => deps.objects.readyToGiveUp(repoId, wants, common),\n\t}\n}\n\nfunction receiveBackendFor(deps: GitAppDeps, repoId: string): ReceiveBackend {\n\tconst backend: ReceiveBackend = {\n\t\tapplyRefUpdates: (commands, atomic) =>\n\t\t\tdeps.refs.applyRefUpdates(repoId, commands, atomic),\n\t\tingest: async (pack) => {\n\t\t\tawait deps.objects.ingestPack(repoId, pack)\n\t\t},\n\t\tisConnected: (oid) => deps.objects.isConnected(repoId, oid),\n\t}\n\tif (deps.snapshots) {\n\t\tconst sdeps = { objects: deps.objects, snapshots: deps.snapshots }\n\t\tbackend.syncRefSnapshot = (ref, newOid) => syncRefSnapshot(sdeps, repoId, ref, newOid)\n\t}\n\treturn backend\n}\n\n/** v0 receive-pack ref advertisement body: the `# service` preamble + ref list. */\nasync function receivePackAdvertBody(deps: GitAppDeps, repoId: string): Promise<Buffer> {\n\tconst refs = await deps.refs.listRefs(repoId)\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"# service=git-receive-pack\\n\")),\n\t\tencodePkt({ type: \"flush\" }),\n\t\tencodeReceivePackAdvertisement(refs),\n\t])\n}\n\n/**\n * Build the git-remote Hono app (smart-HTTP, protocol v2 fetch). Mountable into\n * a host app via `host.route(\"/git\", createGitApp(deps))`; the host owns the\n * Postgres lifecycle behind `deps`.\n */\nexport function createGitApp(\n\tdeps: GitAppDeps,\n\topts: { instrument?: boolean } = {},\n): Hono {\n\tconst app = new Hono()\n\tif (opts.instrument) {\n\t\tapp.use((c, next) =>\n\t\t\trunRequest({ method: c.req.method, path: c.req.path }, () => next()),\n\t\t)\n\t}\n\tapp.use(cors())\n\n\t// A client-caused boundary error (malformed request, unsupported capability) is\n\t// a clean 400 with its message; anything else is an internal 500, logged loud.\n\tapp.onError((err, c) => {\n\t\tif (err instanceof GitProtocolError) return c.text(err.message, 400)\n\t\tconsole.error(err)\n\t\treturn c.text(\"internal server error\", 500)\n\t})\n\n\tapp.get(\"/health\", (c) => c.text(\"ok\"))\n\n\tapp.get(\"/:repo/info/refs\", async (c) => {\n\t\tconst service = c.req.query(\"service\")\n\t\tif (service === \"git-upload-pack\") {\n\t\t\tassertProtocolV2(c.req.header(\"git-protocol\"))\n\t\t\treturn c.body(ADVERTISEMENT_BODY, 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\tif (service === \"git-receive-pack\") {\n\t\t\tconst body = await receivePackAdvertBody(deps, c.req.param(\"repo\"))\n\t\t\treturn c.body(toArrayBuffer(body), 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-receive-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\treturn c.text(`unsupported service ${JSON.stringify(service)}`, 403)\n\t})\n\n\tapp.post(\"/:repo/git-upload-pack\", async (c) => {\n\t\tconst body = await readRequestBody(c)\n\t\tconst out = await handleUploadPack(body, backendFor(deps, c.req.param(\"repo\")))\n\t\t// Layer-1 wire size, measured at the boundary where bytes actually leave —\n\t\t// a no-op unless the perf harness activated a collector, so production pays\n\t\t// nothing and the metric survives any restructure of the core.\n\t\tcount(\"wireBytes\", out.length)\n\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t})\n\t})\n\n\tapp.post(\"/:repo/git-receive-pack\", async (c) => {\n\t\tconst body = await readRequestBody(c)\n\t\tconst out = await handleReceivePack(\n\t\t\tbody,\n\t\t\treceiveBackendFor(deps, c.req.param(\"repo\")),\n\t\t)\n\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\"Content-Type\": \"application/x-git-receive-pack-result\",\n\t\t})\n\t})\n\n\treturn app\n}\n\nexport type {\n\tDrainEntry,\n\tDrainSummary,\n\tGcScheduler,\n\tGcSchedulerOptions,\n} from \"@/gc-scheduler\"\n// Public GC surface: the per-repo reachability GC primitive (a host may drive it\n// directly on its own schedule) and the self-scheduling background drain that\n// decides when to run it (docs/2026-06-24-gc-scheduler-design.md §4).\nexport { createGcScheduler } from \"@/gc-scheduler\"\nexport type { Gc, GcOptions, GcResult } from \"@/store/gc\"\nexport { createGc } from \"@/store/gc\"\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["ZERO_OID","GITLINK_MODE","CODE_TO_TYPE","batches"],"sources":["../src/instrument.ts","../src/protocol/errors.ts","../src/protocol/pkt-line.ts","../src/protocol/capabilities.ts","../src/protocol/sideband.ts","../src/protocol/receive-pack.ts","../src/protocol/v2.ts","../src/protocol/upload-pack.ts","../src/object/format-error.ts","../src/object/object.ts","../src/repo-view/build-file-list.ts","../src/repo-view/config.ts","../src/repo-view/rebuild.ts","../src/database/postgres.ts","../src/database/copy-insert.ts","../src/store/repo-resolver.ts","../src/repo-view/repo-file-projection.ts","../src/object/edges.ts","../src/pack/object-header.ts","../src/pack/delta.ts","../src/pack/read-pack.ts","../src/pack/write-pack.ts","../src/store/reachability.ts","../src/store/object-store.ts","../src/store/refs-store.ts","../src/store/gc.ts","../src/gc-scheduler.ts","../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\"\nimport { performance } from \"node:perf_hooks\"\n\n/**\n * Request-scoped performance instrumentation. The perf harness activates a\n * per-request {@link Collector} via {@link runRequest}; everything else\n * (`withPhase`/`count`/`label`/`recordQuery`) reads the active collector from an\n * AsyncLocalStorage and is a **no-op when none is active**. Production and the\n * oracle tests never call `runRequest`, so they pay nothing but a `Map.get`.\n *\n * Concurrency note: phase attribution uses a single mutable `current` per\n * collector. That is correct because a single git request runs its phases\n * (graph-walk → read-objects → write-pack) sequentially, never overlapping.\n */\n\nexport type QueryRecord = { sql: string; durationMs: number; phase: string }\n\nexport type Collector = {\n\t/** What the request turned out to be (`fetch` / `ls-refs`), set by the handler. */\n\tlabel: string\n\tmethod: string\n\tpath: string\n\t/** The phase a query/counter is currently attributed to. */\n\tcurrent: string\n\t/** Phase name → total wall ms spent in that phase. */\n\tphaseMs: Map<string, number>\n\t/** Counter name → accumulated value. */\n\tcounters: Map<string, number>\n\tqueries: QueryRecord[]\n}\n\nconst als = new AsyncLocalStorage<Collector>()\nconst collected: Collector[] = []\n\n/** Every collector recorded since the last {@link resetCollected}. */\nexport function collectedRuns(): readonly Collector[] {\n\treturn collected\n}\n\nexport function resetCollected(): void {\n\tcollected.length = 0\n}\n\nfunction newCollector(method: string, path: string): Collector {\n\treturn {\n\t\tcounters: new Map(),\n\t\tcurrent: \"request\",\n\t\tlabel: \"\",\n\t\tmethod,\n\t\tpath,\n\t\tphaseMs: new Map(),\n\t\tqueries: [],\n\t}\n}\n\n/** Run `fn` inside a fresh per-request collector; record the collector when done. */\nexport async function runRequest<T>(\n\tmeta: { method: string; path: string },\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst collector = newCollector(meta.method, meta.path)\n\treturn als.run(collector, async () => {\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tcollected.push(collector)\n\t\t}\n\t})\n}\n\n/** Measure `fn`'s wall time into the active collector under `name`; no-op when inactive. */\nexport async function withPhase<T>(name: string, fn: () => Promise<T>): Promise<T> {\n\tconst collector = als.getStore()\n\tif (!collector) return fn()\n\tconst previous = collector.current\n\tcollector.current = name\n\tconst start = performance.now()\n\ttry {\n\t\treturn await fn()\n\t} finally {\n\t\tconst elapsed = performance.now() - start\n\t\tcollector.phaseMs.set(name, (collector.phaseMs.get(name) ?? 0) + elapsed)\n\t\tcollector.current = previous\n\t}\n}\n\nexport function count(metric: string, n = 1): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.counters.set(metric, (collector.counters.get(metric) ?? 0) + n)\n}\n\nexport function label(name: string): void {\n\tconst collector = als.getStore()\n\tif (collector) collector.label = name\n}\n\nexport function recordQuery(sql: string, durationMs: number): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.queries.push({ durationMs, phase: collector.current, sql })\n}\n","/**\n * A malformed-request / unsupported-capability error detected at the git wire\n * boundary (bad command list, unknown command, unsupported object-format or\n * filter, a request body in an encoding we don't accept). It is the CLIENT's\n * fault, so the HTTP layer maps it to a 400 with the message — distinct from an\n * internal failure (a missing object mid-serve, a DB error), which stays a 500.\n * Validate at the boundary, fail loud, and let the type carry the status.\n */\nexport class GitProtocolError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitProtocolError\"\n\t}\n}\n\n/**\n * A fetch `want` names an object this repo does not have — a CLIENT condition (a\n * stale/force-pushed tip, a lost promisor blob), not an internal failure. Real git\n * upload-pack answers it IN-BAND with `ERR upload-pack: not our ref <oid>` (an HTTP\n * 200 protocol error the client reads), so it must NOT escape as a 500. Carries the\n * absent OIDs; `handleFetch` maps it to the ERR pkt-line. Distinct from a generic\n * `Error` out of the serve path (a real backend fault), which still propagates → 500.\n */\nexport class WantNotFoundError extends Error {\n\tconstructor(readonly oids: string[]) {\n\t\tsuper(`upload-pack: not our ref ${oids.join(\" \")}`)\n\t\tthis.name = \"WantNotFoundError\"\n\t}\n}\n","/**\n * pkt-line framing (git wire protocol). A pkt-line is a 4-byte hex length prefix\n * (the length INCLUDES the 4 prefix bytes) followed by `length - 4` payload bytes.\n * Three special zero-payload packets: flush `0000`, delim `0001`, response-end\n * `0002`. See gitprotocol-common + design spec §5.\n */\n\nimport { GitProtocolError } from \"@/protocol/errors\"\n\nexport type Pkt =\n\t| { type: \"data\"; payload: Buffer }\n\t| { type: \"flush\" }\n\t| { type: \"delim\" }\n\t| { type: \"response-end\" }\n\nconst FLUSH_PKT = Buffer.from(\"0000\", \"latin1\")\nconst DELIM_PKT = Buffer.from(\"0001\", \"latin1\")\nconst RESPONSE_END_PKT = Buffer.from(\"0002\", \"latin1\")\n\n/** Largest payload we will emit (git's conservative writer cap). */\nexport const WRITER_MAX_PAYLOAD = 65515\n/** Largest payload we will accept on read (git's LARGE_PACKET_DATA_MAX). */\nexport const READER_MAX_PAYLOAD = 65516\n\n/** Frame a data payload as a pkt-line: `<4-hex len><payload>`. */\nexport function encodePktLine(payload: Buffer): Buffer {\n\tif (payload.length > WRITER_MAX_PAYLOAD) {\n\t\tthrow new Error(\n\t\t\t`pkt-line: payload ${payload.length} exceeds writer cap ${WRITER_MAX_PAYLOAD}`,\n\t\t)\n\t}\n\tconst len = payload.length + 4\n\tconst prefix = len.toString(16).padStart(4, \"0\")\n\treturn Buffer.concat([Buffer.from(prefix, \"latin1\"), payload])\n}\n\n/** Frame any packet — data or one of the three special zero-payload markers. */\nexport function encodePkt(pkt: Pkt): Buffer {\n\tswitch (pkt.type) {\n\t\tcase \"data\":\n\t\t\treturn encodePktLine(pkt.payload)\n\t\tcase \"flush\":\n\t\t\treturn FLUSH_PKT\n\t\tcase \"delim\":\n\t\t\treturn DELIM_PKT\n\t\tcase \"response-end\":\n\t\t\treturn RESPONSE_END_PKT\n\t}\n}\n\nfunction parseLen(buf: Buffer, offset: number): number {\n\tconst hex = buf.toString(\"latin1\", offset, offset + 4)\n\tif (!/^[0-9a-f]{4}$/i.test(hex)) {\n\t\t// Malformed framing in a client request body — a wire-boundary fault (400),\n\t\t// not a server error.\n\t\tthrow new GitProtocolError(`pkt-line: invalid length prefix ${JSON.stringify(hex)}`)\n\t}\n\treturn Number.parseInt(hex, 16)\n}\n\n/**\n * Decode a buffer into a sequence of packets. Streaming-safe: a trailing\n * partial packet is left in `rest` for the caller to prepend to the next chunk.\n *\n * With `stopAtFlush`, decoding returns at the first flush (which is NOT included\n * in `packets`), leaving the bytes after it in `rest`. The receive-pack request\n * splits here: a pkt-line command list, a flush, then the raw (un-framed) pack.\n *\n * `flushed` reports whether a flush actually terminated the stream in\n * `stopAtFlush` mode — the parser uses it to reject an unterminated command list\n * on a COMPLETE request body (where \"more bytes coming\" is not an option).\n */\nexport function decodePktStream(\n\tbuf: Buffer,\n\topts: { stopAtFlush?: boolean } = {},\n): { packets: Pkt[]; rest: Buffer; flushed: boolean } {\n\tconst packets: Pkt[] = []\n\tlet offset = 0\n\twhile (offset + 4 <= buf.length) {\n\t\tconst len = parseLen(buf, offset)\n\t\tif (len === 0) {\n\t\t\toffset += 4\n\t\t\tif (opts.stopAtFlush) return { flushed: true, packets, rest: buf.subarray(offset) }\n\t\t\tpackets.push({ type: \"flush\" })\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 1) {\n\t\t\tpackets.push({ type: \"delim\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 2) {\n\t\t\tpackets.push({ type: \"response-end\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 3) {\n\t\t\tthrow new GitProtocolError(\"pkt-line: reserved length 0003\")\n\t\t}\n\t\tconst payloadLen = len - 4\n\t\tif (payloadLen > READER_MAX_PAYLOAD) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`pkt-line: declared payload ${payloadLen} exceeds reader bound ${READER_MAX_PAYLOAD}`,\n\t\t\t)\n\t\t}\n\t\t// Incomplete data packet — leave it (and everything after) in `rest`.\n\t\tif (offset + len > buf.length) break\n\t\tconst payload = buf.subarray(offset + 4, offset + len)\n\t\tpackets.push({ payload, type: \"data\" })\n\t\toffset += len\n\t}\n\treturn { flushed: false, packets, rest: buf.subarray(offset) }\n}\n","import { GitProtocolError } from \"@/protocol/errors\"\n\n// Protocol capabilities shared by both services — the agent string and the\n// object-format guard are version-agnostic (push is v0, fetch is v2), so they\n// live here rather than in the v2-named module.\n\nexport const AGENT = \"pggit/0.0.0\"\n\n/**\n * Reject a client negotiating a non-sha1 object hash. pggit is SHA-1 only (the\n * charter) and assumes 40-hex / 20-byte OIDs everywhere; a sha256 client would\n * otherwise fail deep in the parser on a 64-hex OID. Catch it at the boundary\n * with a clear message. An absent `object-format` cap defaults to sha1 (git's\n * default), so it is accepted.\n */\nexport function assertSupportedObjectFormat(caps: string[]): void {\n\tconst fmt = caps.find((c) => c.startsWith(\"object-format=\"))\n\tif (fmt !== undefined && fmt !== \"object-format=sha1\") {\n\t\tthrow new GitProtocolError(\n\t\t\t`unsupported ${fmt} — only object-format=sha1 is supported`,\n\t\t)\n\t}\n}\n","import { encodePktLine } from \"@/protocol/pkt-line\"\n\n/** The sideband-64k data channel (band 1) — carries pack/report payload. */\nexport const SIDEBAND_DATA = 0x01\n\n// One band pkt-line is a band byte + payload, together within the pkt-line writer\n// cap (65515) — so a single payload slice is at most 65514 bytes.\nconst MAX_BAND_DATA = 65514\n\n/**\n * Multiplex `data` onto sideband `band`: each ≤MAX_BAND_DATA slice becomes a\n * pkt-line of `[band byte | slice]`. Returns the concatenated band pkt-lines with\n * NO trailing flush — the caller owns the section framing (the `packfile\\n` header\n * for fetch, the bare report for push) and appends its own flush.\n */\nexport function encodeSideband(band: number, data: Buffer): Buffer {\n\tconst parts: Buffer[] = []\n\tfor (let i = 0; i < data.length; i += MAX_BAND_DATA) {\n\t\tconst chunk = data.subarray(i, i + MAX_BAND_DATA)\n\t\tparts.push(encodePktLine(Buffer.concat([Buffer.from([band]), chunk])))\n\t}\n\treturn Buffer.concat(parts)\n}\n","import { AGENT, assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\n/** A ref name longer than this (bytes) is rejected at the boundary: `git_ref`'s PK is\n * a btree on (repo_id, name) whose index entry overflows past ~2704 bytes, which\n * Postgres raises as an opaque storage error. The cap sits far above any real ref name\n * and safely under the btree limit, so a too-long name fails loud + in-band (`ng`),\n * never as an HTTP 500 that has already orphaned the ingested pack. */\nconst MAX_REF_NAME_BYTES = 2000\n\n// The push capabilities we advertise AND honor (spec §4): report-status over\n// side-band, ref deletion, atomic mode, sha1. We pick plain `report-status`\n// (not `-v2`) — we do not emit its extra option lines.\nconst RECEIVE_CAPS = [\n\t\"report-status\",\n\t\"delete-refs\",\n\t\"side-band-64k\",\n\t\"atomic\",\n\t\"object-format=sha1\",\n\t`agent=${AGENT}`,\n]\n\nexport type RefCommand = { oldOid: string; newOid: string; ref: string }\nexport type ReceiveRequest = { commands: RefCommand[]; caps: string[]; pack: Buffer }\nexport type CommandResult = { ref: string; ok: boolean; reason?: string }\n\n/**\n * v0 ref advertisement for receive-pack (push). An empty repo — the dominant\n * first-push state — emits the synthetic `0{40} capabilities^{}` line so the\n * client has somewhere to read the push capabilities.\n */\nexport function encodeReceivePackAdvertisement(\n\trefs: { name: string; oid: string }[],\n): Buffer {\n\tconst capStr = RECEIVE_CAPS.join(\" \")\n\tconst lines: Buffer[] = []\n\tif (refs.length === 0) {\n\t\tlines.push(encodePktLine(Buffer.from(`${ZERO_OID} capabilities^{}\\0${capStr}\\n`)))\n\t} else {\n\t\trefs.forEach((r, i) => {\n\t\t\tconst base = `${r.oid} ${r.name}`\n\t\t\tlines.push(\n\t\t\t\tencodePktLine(Buffer.from(i === 0 ? `${base}\\0${capStr}\\n` : `${base}\\n`)),\n\t\t\t)\n\t\t})\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\treturn Buffer.concat(lines)\n}\n\n/**\n * Parse the receive-pack POST body: a pkt-line command list (`<old> <new> <ref>`,\n * caps after a NUL on the first line), a flush, then the raw packfile.\n */\nexport function parseReceivePack(body: Buffer): ReceiveRequest {\n\tconst { packets, rest, flushed } = decodePktStream(body, { stopAtFlush: true })\n\t// A non-empty command list MUST be terminated by a flush before the pack. Without\n\t// it, decodePktStream falls off the end (a truncated/length-overrunning command\n\t// pkt-line) and hands the framing garbage back as `rest` — which would otherwise\n\t// be mis-fed to the pack reader. Reject the framing fault loudly. An empty body is\n\t// the legitimate zero-command no-op and is left alone.\n\tif (!flushed && body.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"receive-pack: command list not terminated by a flush (truncated or length-overrunning pkt-line)\",\n\t\t)\n\t}\n\tconst commands: RefCommand[] = []\n\tlet caps: string[] = []\n\tfor (const p of packets) {\n\t\tif (p.type !== \"data\") continue\n\t\tlet line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tconst nul = line.indexOf(\"\\0\")\n\t\tif (nul >= 0) {\n\t\t\tcaps = line\n\t\t\t\t.slice(nul + 1)\n\t\t\t\t.split(\" \")\n\t\t\t\t.filter(Boolean)\n\t\t\tline = line.slice(0, nul)\n\t\t}\n\t\t// Fail loud: a command line is exactly `<old> <new> <ref>`. Anything else is\n\t\t// malformed — reject it rather than silently drop it (which would apply a\n\t\t// partial command set with no diagnostic).\n\t\tconst parts = line.split(\" \")\n\t\tconst [oldOid, newOid, ref] = parts\n\t\tif (parts.length !== 3 || !oldOid || !newOid || !ref) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`receive-pack: malformed command line ${JSON.stringify(line)}`,\n\t\t\t)\n\t\t}\n\t\tcommands.push({ newOid, oldOid, ref })\n\t}\n\treturn { caps, commands, pack: rest }\n}\n\n/**\n * report-status: `unpack <status>` then `ok <ref>` / `ng <ref> <reason>` per\n * command, flush. When side-band-64k is negotiated the whole stream rides band 1.\n */\nexport function encodeReportStatus(\n\tunpack: string,\n\tresults: CommandResult[],\n\tuseSideband: boolean,\n): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(`unpack ${unpack}\\n`))]\n\tfor (const r of results) {\n\t\tconst line = r.ok ? `ok ${r.ref}\\n` : `ng ${r.ref} ${r.reason ?? \"failed\"}\\n`\n\t\tlines.push(encodePktLine(Buffer.from(line)))\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\tconst report = Buffer.concat(lines)\n\tif (!useSideband) return report\n\treturn Buffer.concat([\n\t\tencodeSideband(SIDEBAND_DATA, report),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\n/** Everything receive-pack needs from a single repo's storage. */\nexport type ReceiveBackend = {\n\tingest: (pack: Buffer) => Promise<void>\n\t/** Apply ref CAS updates; `atomic` ⇒ all-or-nothing. Per-command success flags. */\n\tapplyRefUpdates: (commands: RefCommand[], atomic: boolean) => Promise<boolean[]>\n\t/** Is every object reachable from `oid` present? (connectivity, spec §10). */\n\tisConnected: (oid: string) => Promise<boolean>\n\t/** Refresh the queryable file projection for a just-applied ref. Present only\n\t * when the (optional) queryable-view layer is wired; a plain remote omits it. */\n\tsyncRefSnapshot?: (ref: string, newOid: string) => Promise<void>\n}\n\n/**\n * Handle a receive-pack POST: ingest the pack (if any), then apply the ref\n * commands under CAS — atomically when the client negotiated `atomic` — and\n * report status. A failed unpack fails every ref; an atomic failure ng's every\n * ref (none applied). Non-ff is accepted by default (CAS guards concurrency, not\n * ancestry — spec §3.6).\n */\nexport async function handleReceivePack(\n\tbody: Buffer,\n\tbackend: ReceiveBackend,\n): Promise<Buffer> {\n\tconst { commands, caps, pack } = parseReceivePack(body)\n\tassertSupportedObjectFormat(caps)\n\tconst useSideband = caps.includes(\"side-band-64k\")\n\tconst atomic = caps.includes(\"atomic\")\n\n\t// rc3 boundary check: a ref name too long to store is rejected BEFORE ingest — so\n\t// an all-unstorable push never ingests a pack (no orphaned objects), and the raw\n\t// btree error never escapes as a 500.\n\tconst nameTooLong = commands.map(\n\t\t(c) => Buffer.byteLength(c.ref, \"utf8\") > MAX_REF_NAME_BYTES,\n\t)\n\tconst anyApplicable = nameTooLong.length === 0 || nameTooLong.some((t) => !t)\n\n\tlet unpackStatus = \"ok\"\n\tif (pack.length > 0 && anyApplicable) {\n\t\ttry {\n\t\t\tawait backend.ingest(pack)\n\t\t} catch (e) {\n\t\t\tunpackStatus = (e instanceof Error ? e.message : \"unpack failed\").replace(\n\t\t\t\t/\\n/g,\n\t\t\t\t\" \",\n\t\t\t)\n\t\t}\n\t}\n\n\tif (unpackStatus !== \"ok\") {\n\t\tconst failed = commands.map((c) => ({\n\t\t\tok: false,\n\t\t\treason: \"unpacker error\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Connectivity (spec §10): a create/update must leave its new tip fully reachable\n\t// in the store; a delete (newOid zero) needs no objects. A too-long ref is already\n\t// disqualified, so it skips the closure walk.\n\tconst connected = await Promise.all(\n\t\tcommands.map((c, i) =>\n\t\t\tnameTooLong[i] || c.newOid === ZERO_OID\n\t\t\t\t? Promise.resolve(true)\n\t\t\t\t: backend.isConnected(c.newOid),\n\t\t),\n\t)\n\t// Per-command disqualification reason (null ⇒ applicable): a too-long name fails\n\t// the storage boundary, a disconnected tip fails connectivity. A disqualified\n\t// command never touches a ref.\n\tconst reasons = commands.map((_, i) =>\n\t\tnameTooLong[i]\n\t\t\t? \"funny refname (too long to store)\"\n\t\t\t: connected[i]\n\t\t\t\t? null\n\t\t\t\t: \"missing necessary objects\",\n\t)\n\tif (atomic && reasons.some((r) => r !== null)) {\n\t\tconst failed = commands.map((c, i) => ({\n\t\t\tok: false,\n\t\t\treason: reasons[i] ?? \"atomic transaction failed\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Apply only the applicable commands; a disqualified one never touches a ref.\n\tconst oks = await backend.applyRefUpdates(\n\t\tcommands.filter((_, i) => reasons[i] === null),\n\t\tatomic,\n\t)\n\tlet applied = 0\n\tconst results: CommandResult[] = commands.map((c, i) => {\n\t\tconst reason = reasons[i]\n\t\tif (reason !== null) return { ok: false, reason, ref: c.ref }\n\t\treturn oks[applied++]\n\t\t\t? { ok: true, ref: c.ref }\n\t\t\t: {\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: atomic\n\t\t\t\t\t\t? \"atomic transaction failed\"\n\t\t\t\t\t\t: \"stale ref (compare-and-swap failed)\",\n\t\t\t\t\tref: c.ref,\n\t\t\t\t}\n\t})\n\n\t// Post-commit: refresh the queryable file projection for each applied ref. The\n\t// view layer decides branch-filtering and build-vs-drop. Sequential — same-repo\n\t// rebuilds must not race the shared-blob reaper.\n\tfor (const [i, c] of commands.entries()) {\n\t\tif (!results[i]?.ok) continue\n\t\t// rc2: the queryable view is a DERIVED projection — a rebuild failure (e.g. a\n\t\t// tip that is not a commit, which buildFileList cannot walk) must NEVER roll\n\t\t// back or 500 an already-applied push (rebuild.ts's standing contract). Absorb\n\t\t// it loudly to the log; the projection is rebuilt on the next push to the ref.\n\t\ttry {\n\t\t\tawait backend.syncRefSnapshot?.(c.ref, c.newOid)\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit: snapshot refresh failed for ${c.ref} (the push is already applied):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\treturn encodeReportStatus(unpackStatus, results, useSideband)\n}\n","import { AGENT } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\n/**\n * The v2 capability advertisement (GET info/refs body, minus HTTP framing).\n * We advertise ONLY what we honor (spec §4): ls-refs (with `unborn`) and fetch\n * with the `filter` (partial clone) and `include-tag` (auto-follow annotated tags)\n * features. No shallow / ref-in-want — those have no milestone owner and\n * advertising them flips clients onto unimplemented paths.\n */\nexport function encodeAdvertisement(): Buffer {\n\tconst caps = [\n\t\t\"version 2\",\n\t\t`agent=${AGENT}`,\n\t\t\"ls-refs=unborn\",\n\t\t\"fetch=filter include-tag\",\n\t\t\"object-format=sha1\",\n\t]\n\treturn Buffer.concat([\n\t\t...caps.map((c) => encodePktLine(Buffer.from(`${c}\\n`))),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\nexport type V2Request = {\n\tcommand: string\n\tcapabilities: string[]\n\targs: string[]\n}\n\n/** Decode a `command=… <caps> 0001 <args> 0000` v2 request body. */\nexport function parseV2Request(body: Buffer): V2Request {\n\tconst { packets, rest } = decodePktStream(body)\n\t// The body is a COMPLETE request: any leftover bytes are an incomplete or\n\t// length-overrunning packet (decodePktStream leaves a partial packet in `rest`).\n\t// On a complete body that is a framing fault, not a streaming boundary — reject\n\t// it loudly rather than silently dropping the truncated args.\n\tif (rest.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t`pkt-line: ${rest.length} trailing bytes after the request — incomplete or length-overrunning packet`,\n\t\t)\n\t}\n\tlet command = \"\"\n\tconst capabilities: string[] = []\n\tconst args: string[] = []\n\tlet afterDelim = false\n\tfor (const p of packets) {\n\t\tif (p.type === \"delim\") {\n\t\t\tafterDelim = true\n\t\t\tcontinue\n\t\t}\n\t\tif (p.type !== \"data\") continue\n\t\tconst line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tif (afterDelim) args.push(line)\n\t\telse if (line.startsWith(\"command=\")) command = line.slice(\"command=\".length)\n\t\telse capabilities.push(line)\n\t}\n\treturn { args, capabilities, command }\n}\n\nexport type FetchRequest = {\n\twants: string[]\n\thaves: string[]\n\tdone: boolean\n\t/** Partial-clone filter spec (e.g. `blob:none`), if the client sent one. */\n\tfilter?: string\n\t/** Client asked the server to auto-include annotated tags pointing into the\n\t * fetched set (the `include-tag` capability). */\n\tincludeTag: boolean\n}\n\n/** Fetch features pggit deliberately does NOT advertise (encodeAdvertisement): a\n * client that drives one anyway must FAIL LOUDLY, never be silently dropped to an\n * empty result (the charter). `ref-in-want` (`want-ref`) and the `shallow`/`deepen`\n * family are the unimplemented ones. */\nconst UNSUPPORTED_FETCH_ARG = /^(want-ref|deepen|shallow)\\b/\n\nconst OID = /^[0-9a-f]{40}$/\n\nexport function parseFetch(req: V2Request): FetchRequest {\n\tconst wants: string[] = []\n\tconst haves: string[] = []\n\tlet done = false\n\tlet filter: string | undefined\n\tlet includeTag = false\n\tfor (const arg of req.args) {\n\t\t// Reject an unadvertised feature request loudly before parsing wants — else a\n\t\t// `want-ref` line falls through every branch below and silently leaves wants=[]\n\t\t// (a no-op empty pack the client misreads as a successful empty clone).\n\t\tif (UNSUPPORTED_FETCH_ARG.test(arg)) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`fetch: unsupported feature ${JSON.stringify(arg.split(\" \")[0])} — pggit does not advertise it`,\n\t\t\t)\n\t\t}\n\t\tif (arg.startsWith(\"want \")) {\n\t\t\tconst oid = arg.slice(5)\n\t\t\t// A want OID is coerced to `bytea` downstream via Buffer.from(oid, \"hex\"),\n\t\t\t// which SILENTLY yields a short/empty buffer for a non-hex value and then\n\t\t\t// fails deep in buildPack. Validate the wire shape at the boundary instead.\n\t\t\tif (!OID.test(oid)) {\n\t\t\t\tthrow new GitProtocolError(\n\t\t\t\t\t`fetch: malformed want object id ${JSON.stringify(oid)}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\twants.push(oid)\n\t\t} else if (arg.startsWith(\"have \")) haves.push(arg.slice(5))\n\t\telse if (arg.startsWith(\"filter \")) filter = arg.slice(\"filter \".length)\n\t\telse if (arg === \"include-tag\") includeTag = true\n\t\telse if (arg === \"done\") done = true\n\t}\n\treturn { done, filter, haves, includeTag, wants }\n}\n\n/**\n * One ls-refs line. A normal ref leads with its oid; an `unborn` ref (an empty\n * repo's HEAD, which has no commit yet) leads with the literal `unborn` token\n * instead — exactly git's `ls-refs.c send_ref` shape.\n */\nexport type LsRefEntry =\n\t| { name: string; oid: string; symrefTarget?: string; peeled?: string }\n\t| { name: string; unborn: true; symrefTarget?: string }\n\n/** ls-refs response: one line per ref (+ symref-target / peeled), then flush. */\nexport function encodeLsRefsResponse(entries: LsRefEntry[]): Buffer {\n\tconst lines = entries.map((e) => {\n\t\tlet line = \"unborn\" in e ? `unborn ${e.name}` : `${e.oid} ${e.name}`\n\t\tif (e.symrefTarget) line += ` symref-target:${e.symrefTarget}`\n\t\tif (\"peeled\" in e && e.peeled) line += ` peeled:${e.peeled}`\n\t\treturn encodePktLine(Buffer.from(`${line}\\n`))\n\t})\n\treturn Buffer.concat([...lines, encodePkt({ type: \"flush\" })])\n}\n\n/** The `acknowledgments` section lines: header, ACKs / NAK, optional `ready`. */\nfunction acknowledgmentLines(common: string[], ready: boolean): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(\"acknowledgments\\n\"))]\n\tif (common.length === 0 && !ready) {\n\t\tlines.push(encodePktLine(Buffer.from(\"NAK\\n\")))\n\t} else {\n\t\tfor (const oid of common) lines.push(encodePktLine(Buffer.from(`ACK ${oid}\\n`)))\n\t\tif (ready) lines.push(encodePktLine(Buffer.from(\"ready\\n\")))\n\t}\n\treturn Buffer.concat(lines)\n}\n\n/**\n * fetch `acknowledgments` response for a negotiation round that is NOT yet ready\n * (no `done`): the section + flush, no pack. The client sends more haves or\n * `done` (spec §4 shape b).\n */\nexport function encodeAcknowledgments(common: string[], ready: boolean): Buffer {\n\treturn Buffer.concat([acknowledgmentLines(common, ready), encodePkt({ type: \"flush\" })])\n}\n\n/**\n * fetch response when the server becomes `ready` mid-negotiation: the\n * acknowledgments section (with `ready`), a delim-pkt, then the packfile — git\n * requires the pack to follow `ready` in the same response (not a later round).\n */\nexport function encodeReadyWithPack(common: string[], pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tacknowledgmentLines(common, true),\n\t\tencodePkt({ type: \"delim\" }),\n\t\tencodePackfileResponse(pack),\n\t])\n}\n\n/**\n * A v2 error response: a single `ERR <message>` pkt-line. git's packet reader\n * recognizes the `ERR ` prefix and the client dies with `remote error: <message>`\n * — the in-band channel for a request that cannot be served (e.g. a `want` the repo\n * does not have): an HTTP-200 protocol error the client can read, NOT a transport 500.\n */\nexport function encodeErr(message: string): Buffer {\n\treturn encodePktLine(Buffer.from(`ERR ${message}\\n`))\n}\n\n/**\n * fetch response for the clone path (client sent `done`, no haves): the\n * `packfile` section header, the pack multiplexed over sideband band-1, flush.\n */\nexport function encodePackfileResponse(pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"packfile\\n\")),\n\t\tencodeSideband(SIDEBAND_DATA, pack),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n","import { label, withPhase } from \"@/instrument\"\nimport { assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError, WantNotFoundError } from \"@/protocol/errors\"\nimport {\n\tencodeAcknowledgments,\n\tencodeErr,\n\tencodeLsRefsResponse,\n\tencodePackfileResponse,\n\tencodeReadyWithPack,\n\ttype LsRefEntry,\n\tparseFetch,\n\tparseV2Request,\n\ttype V2Request,\n} from \"@/protocol/v2\"\n\n/** A ref advertised by the store: its oid, plus the peeled tag target when it is\n * an annotated tag (computed at ref-write, §5.3). */\nexport type AdvertisedRef = { name: string; oid: string; peeled?: string }\n\n/**\n * Everything the upload-pack service needs from a single repo's storage. The graph\n * logic lives in the store now (set-based SQL over the row+edge model), so this is\n * a thin set-oriented interface — no object-at-a-time walk. Tag peeling is read\n * straight off the ref (`peeled`), so there is no object-fetch on the serve path.\n */\nexport type RepoBackend = {\n\tlistRefs: () => Promise<AdvertisedRef[]>\n\tgetSymref: (name: string) => Promise<string | null>\n\t/** The subset of `haves` the repo has — the negotiation common set. */\n\tcommonHaves: (haves: string[]) => Promise<string[]>\n\t/** git's ok_to_give_up: does every want reach a common have by ancestry? */\n\treadyToGiveUp: (wants: string[], common: string[]) => Promise<boolean>\n\t/** The served pack: want-closure minus have-closure, plus the explicit wants\n\t * (and, when `includeTag`, annotated tags pointing into the served set). */\n\tbuildPack: (\n\t\twants: string[],\n\t\thaves: string[],\n\t\tomitBlobs: boolean,\n\t\tincludeTag: boolean,\n\t) => Promise<Buffer>\n}\n\nasync function handleLsRefs(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"ls-refs\")\n\treturn withPhase(\"ref-advertise\", async () => {\n\t\tconst wantPeel = req.args.includes(\"peel\")\n\t\tconst wantSymrefs = req.args.includes(\"symrefs\")\n\t\tconst prefixes = req.args\n\t\t\t.filter((a) => a.startsWith(\"ref-prefix \"))\n\t\t\t.map((a) => a.slice(\"ref-prefix \".length))\n\t\tconst matches = (name: string) =>\n\t\t\tprefixes.length === 0 || prefixes.some((p) => name.startsWith(p))\n\n\t\tconst refs = await backend.listRefs()\n\t\tconst byName = new Map(refs.map((r) => [r.name, r.oid]))\n\t\tconst entries: LsRefEntry[] = []\n\n\t\tconst wantUnborn = req.args.includes(\"unborn\")\n\t\tconst headTarget = await backend.getSymref(\"HEAD\")\n\t\tif (headTarget && matches(\"HEAD\")) {\n\t\t\tconst headOid = byName.get(headTarget)\n\t\t\tif (headOid) {\n\t\t\t\tentries.push({\n\t\t\t\t\tname: \"HEAD\",\n\t\t\t\t\toid: headOid,\n\t\t\t\t\tsymrefTarget: wantSymrefs ? headTarget : undefined,\n\t\t\t\t})\n\t\t\t} else if (wantUnborn && wantSymrefs) {\n\t\t\t\t// Empty repo: HEAD points at a branch with no commit yet. git emits the\n\t\t\t\t// unborn HEAD only when the client asked for both `unborn` and `symrefs`\n\t\t\t\t// (ls-refs.c send_possibly_unborn_head), propagating the default branch.\n\t\t\t\tentries.push({ name: \"HEAD\", symrefTarget: headTarget, unborn: true })\n\t\t\t}\n\t\t}\n\n\t\tfor (const ref of refs) {\n\t\t\tif (!matches(ref.name)) continue\n\t\t\tconst entry: LsRefEntry = { name: ref.name, oid: ref.oid }\n\t\t\t// Peeled target is precomputed at ref-write (§5.3) — no per-request walk.\n\t\t\tif (wantPeel && ref.peeled) entry.peeled = ref.peeled\n\t\t\tentries.push(entry)\n\t\t}\n\n\t\treturn encodeLsRefsResponse(entries)\n\t})\n}\n\n/**\n * Translate the wire filter spec to a walk option. We optimize the common\n * `blob:none` (blobless partial clone) by omitting blobs; any other filter\n * (`tree:0`, `blob:limit=…`, …) serves the FULL closure. The protocol lets a\n * server send more than a filter requests — the client accepts the superset and\n * has nothing to lazily fetch — so over-serving completes the clone that a hard\n * rejection would abort, without implementing every filter spec.\n */\nfunction filterOmitsBlobs(filter: string | undefined): boolean {\n\treturn filter === \"blob:none\"\n}\n\nasync function handleFetch(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"fetch\")\n\t// parseFetch validates wire shape (malformed/unsupported args → GitProtocolError\n\t// → 400) BEFORE the serve attempt — kept outside the try so it stays a 400.\n\tconst { wants, haves, done, filter, includeTag } = parseFetch(req)\n\t// A zero-want fetch is NOT an error: git's upload-pack treats it as a no-op\n\t// (upload-pack.c) and returns an empty pack — buildPack produces one, so we let\n\t// it fall through rather than rejecting.\n\tconst omitBlobs = filterOmitsBlobs(filter)\n\tconst common = await backend.commonHaves(haves)\n\n\ttry {\n\t\tif (!done) {\n\t\t\t// Negotiation round (spec §4 shape b): until the haves cut every want we\n\t\t\t// ACK/NAK and flush, no pack — the client sends more haves. Once ready, git\n\t\t\t// requires the pack in this same response, after the `ready` line.\n\t\t\tif (!(await backend.readyToGiveUp(wants, common))) {\n\t\t\t\treturn encodeAcknowledgments(common, false)\n\t\t\t}\n\t\t\treturn encodeReadyWithPack(\n\t\t\t\tcommon,\n\t\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t\t)\n\t\t}\n\n\t\t// `done` (spec §4 shapes a/c): pack the delta directly. A clone has no haves, so\n\t\t// the subtrahend is empty and we pack the whole want-closure.\n\t\treturn encodePackfileResponse(\n\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t)\n\t} catch (err) {\n\t\t// A `want` the repo does not have is a client condition, not a server fault:\n\t\t// answer it in-band like canonical upload-pack (`ERR … not our ref`) so the\n\t\t// client reads a clean protocol error, not an HTTP 500. A genuine serve failure\n\t\t// (any other error) still propagates → 500 (§10, no partial pack is emitted).\n\t\tif (err instanceof WantNotFoundError) return encodeErr(err.message)\n\t\tthrow err\n\t}\n}\n\n/** Dispatch a v2 upload-pack POST body to ls-refs or fetch. */\nexport async function handleUploadPack(\n\tbody: Buffer,\n\tbackend: RepoBackend,\n): Promise<Buffer> {\n\tconst req = parseV2Request(body)\n\tassertSupportedObjectFormat(req.capabilities)\n\tif (req.command === \"ls-refs\") return handleLsRefs(req, backend)\n\tif (req.command === \"fetch\") return handleFetch(req, backend)\n\tthrow new GitProtocolError(\n\t\t`upload-pack: unsupported command ${JSON.stringify(req.command)}`,\n\t)\n}\n","/**\n * A \"the git data we were handed is not well-formed\" error — raised while parsing\n * a packfile, applying a delta, or reading a git object's bytes. It is distinct\n * from `GitProtocolError` (a malformed *request* at the wire boundary, → HTTP\n * 400): a `GitFormatError` is about the *content* (a corrupt/truncated pack or\n * object), which on push surfaces as the `unpack <reason>` report-status line and\n * otherwise propagates as an internal failure.\n *\n * The `code` is the stable, assertable identity of the failure; the `message` is\n * free-form prose for humans (the `unpack` line, logs) and may be reworded\n * without breaking callers or tests. Tests assert `code`, never the message text.\n */\nexport type GitFormatErrorCode =\n\t// packfile framing / codec (read-pack)\n\t| \"bad-magic\"\n\t| \"unsupported-version\"\n\t| \"trailer-mismatch\"\n\t| \"unknown-object-type\"\n\t| \"size-mismatch\"\n\t| \"trailing-bytes\"\n\t| \"unresolved-base\"\n\t| \"inflate-failed\"\n\t// delta application\n\t| \"delta-base-size-mismatch\"\n\t| \"delta-reserved-opcode\"\n\t| \"delta-target-size-mismatch\"\n\t// git object content\n\t| \"malformed-tree\"\n\t| \"malformed-oid\"\n\t| \"missing-tree-header\"\n\t| \"multiple-tree-headers\"\n\t| \"missing-tag-object\"\n\t| \"multiple-tag-objects\"\n\nexport class GitFormatError extends Error {\n\treadonly code: GitFormatErrorCode\n\n\tconstructor(code: GitFormatErrorCode, message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitFormatError\"\n\t\tthis.code = code\n\t}\n}\n","import { createHash } from \"node:crypto\"\nimport { GitFormatError } from \"@/object/format-error\"\n\n/** The four addressable git object types (deltas resolve into one of these). */\nexport type GitObjectType = \"blob\" | \"commit\" | \"tree\" | \"tag\"\n\n/**\n * The git object ID: SHA-1 of the loose-object representation\n * `\"<type> <byteLength>\\0\" + content`. Returns the 40-char lowercase hex digest.\n */\nexport function computeOid(type: GitObjectType, content: Buffer): string {\n\tconst header = Buffer.from(`${type} ${content.length}\\0`, \"latin1\")\n\treturn createHash(\"sha1\").update(header).update(content).digest(\"hex\")\n}\n\n/** OIDs in the leading `key <oid>` headers (up to the blank line) for given keys. */\nfunction headerOids(content: Buffer, keys: Set<string>): string[] {\n\tconst oids: string[] = []\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tconst sp = line.indexOf(\" \")\n\t\tif (sp > 0 && keys.has(line.slice(0, sp))) oids.push(line.slice(sp + 1))\n\t}\n\treturn oids\n}\n\n/** One entry of a tree. `mode` is the raw stored value (`\"40000\"` for a subtree —\n * git zero-pads to `\"040000\"` only for display); `name` is the entry's own path\n * segment, not a full path. */\nexport type TreeEntry = { mode: string; name: string; oid: string }\n\n/** A tree's entries — `<mode> <name>\\0<20-byte oid>` repeated. */\nexport function treeEntries(content: Buffer): TreeEntry[] {\n\tconst entries: TreeEntry[] = []\n\tlet pos = 0\n\twhile (pos < content.length) {\n\t\tconst space = content.indexOf(0x20, pos)\n\t\tconst nul = content.indexOf(0x00, pos)\n\t\t// Fail loud: a tree is `<mode> <name>\\0<20-byte oid>` repeated exactly. Any\n\t\t// missing separator or a trailing OID shorter than 20 bytes is corruption —\n\t\t// throw rather than return a short list (which would let `isConnected` report\n\t\t// a truncated object as connected and silently accept bad data).\n\t\tif (space < 0 || nul < 0 || space > nul || nul + 21 > content.length) {\n\t\t\tthrow new GitFormatError(\"malformed-tree\", `tree: malformed entry at offset ${pos}`)\n\t\t}\n\t\tconst mode = content.subarray(pos, space).toString(\"latin1\")\n\t\tconst name = content.subarray(space + 1, nul).toString(\"utf8\")\n\t\tconst oid = content.subarray(nul + 1, nul + 21).toString(\"hex\")\n\t\tentries.push({ mode, name, oid })\n\t\tpos = nul + 21\n\t}\n\treturn entries\n}\n\n/** A tree entry's mode marks a subtree (directory), not a blob or gitlink. */\nexport function isTreeEntryMode(mode: string): boolean {\n\treturn mode === \"40000\"\n}\n\n/** OIDs of a tree's entries (all kinds), in tree order. */\nfunction treeEntryOids(content: Buffer): string[] {\n\treturn treeEntries(content).map((e) => e.oid)\n}\n\n/** A commit's parent OIDs only (ancestry walk; excludes its tree). */\nexport function commitParents(content: Buffer): string[] {\n\treturn headerOids(content, new Set([\"parent\"]))\n}\n\n/** A commit's root tree OID. Every commit has exactly one `tree` header. */\nexport function commitTreeOid(content: Buffer): string {\n\tconst [tree] = headerOids(content, new Set([\"tree\"]))\n\tif (!tree) {\n\t\tthrow new GitFormatError(\n\t\t\t\"missing-tree-header\",\n\t\t\t\"commitTreeOid: commit has no tree header\",\n\t\t)\n\t}\n\treturn tree\n}\n\n/**\n * The OIDs an object directly references: a commit → its tree + parents, a tree\n * → its entries, a tag → its target, a blob → nothing. The basis of reachability\n * enumeration (fetch, connectivity).\n */\nexport function referencedOids(type: GitObjectType, content: Buffer): string[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn headerOids(content, new Set([\"tree\", \"parent\"]))\n\t\tcase \"tag\":\n\t\t\treturn headerOids(content, new Set([\"object\"]))\n\t\tcase \"tree\":\n\t\t\treturn treeEntryOids(content)\n\t}\n}\n","import {\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/** Reads a stored object's type + content by OID — the snapshot builder's view of\n * the object store. */\nexport type ObjectReader = (\n\toid: string,\n) => Promise<{ type: GitObjectType; content: Buffer }>\n\nexport type FileEntry = { path: string; mode: string; blobOid: string }\nexport type FileList = { files: FileEntry[] }\n\n/** Gitlink/submodule entries point at a commit in another repo — no blob here. */\nconst GITLINK_MODE = \"160000\"\n\n/**\n * The flat path→blob index of a commit's tree (the `git ls-tree -r` of a commit,\n * read straight from the object store): one FileEntry per blob — full path from the\n * root, raw mode, blob oid. Subtrees are recursed; gitlinks (submodules) are skipped\n * (no blob in this repo). Blob CONTENT is NOT read — it lives in git_object and is\n * joined at query time (§4.5 collapse), so this walk touches only commits + trees.\n */\nexport async function buildFileList(\n\tread: ObjectReader,\n\tcommitOid: string,\n): Promise<FileList> {\n\tconst commit = await read(commitOid)\n\tconst files: FileEntry[] = []\n\n\tconst walk = async (treeOid: string, prefix: string): Promise<void> => {\n\t\tconst tree = await read(treeOid)\n\t\tfor (const entry of treeEntries(tree.content)) {\n\t\t\tconst path = prefix + entry.name\n\t\t\tif (isTreeEntryMode(entry.mode)) {\n\t\t\t\tawait walk(entry.oid, `${path}/`)\n\t\t\t} else if (entry.mode !== GITLINK_MODE) {\n\t\t\t\tfiles.push({ blobOid: entry.oid, mode: entry.mode, path })\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(commitTreeOid(commit.content), \"\")\n\treturn { files }\n}\n","/**\n * Which refs get a queryable file snapshot. Branches only — tags, notes, and\n * `refs/pull/*` are skipped. One edit to widen the projection later.\n */\nexport const SNAPSHOT_REFS = (refName: string): boolean =>\n\trefName.startsWith(\"refs/heads/\")\n","import { buildFileList } from \"@/repo-view/build-file-list\"\nimport { SNAPSHOT_REFS } from \"@/repo-view/config\"\nimport type { RepoFileProjection } from \"@/repo-view/repo-file-projection\"\nimport type { ObjectStore } from \"@/store/object-store\"\nimport type { RefStore } from \"@/store/refs-store\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\nexport type SnapshotDeps = {\n\tobjects: ObjectStore\n\tsnapshots: RepoFileProjection\n}\n\n/**\n * Refresh `refName`'s file snapshot after a push applied it. Non-branch refs are\n * ignored (§ SNAPSHOT_REFS); a delete (zero oid) drops the snapshot; otherwise\n * the new tip's tree is walked — objects are already present post-ingest — into a\n * fresh snapshot. Runs after the push commits, so a failure here never rolls back\n * the git operation (the projection is rebuildable from the packs).\n */\nexport async function syncRefSnapshot(\n\tdeps: SnapshotDeps,\n\trepoId: string,\n\trefName: string,\n\tnewOid: string,\n): Promise<void> {\n\tif (!SNAPSHOT_REFS(refName)) return\n\tif (newOid === ZERO_OID) {\n\t\tawait deps.snapshots.dropRefSnapshot(repoId, refName)\n\t\treturn\n\t}\n\tconst read = async (oid: string) => {\n\t\tconst obj = await deps.objects.getObject(repoId, oid)\n\t\tif (!obj)\n\t\t\tthrow new Error(`repo-view: object ${oid} missing while building ${refName}`)\n\t\treturn obj\n\t}\n\tawait deps.snapshots.rebuildRefSnapshot(\n\t\trepoId,\n\t\trefName,\n\t\tawait buildFileList(read, newOid),\n\t)\n}\n\n/**\n * Rebuild a repo's entire projection from its current branch tips — the backfill\n * for an existing repo, and the \"nuke and rebuild\" backstop if the cache ever\n * drifts. Everything is re-derived from the canonical packs.\n */\nexport async function rebuildAllSnapshots(\n\tdeps: SnapshotDeps & { refs: RefStore },\n\trepoId: string,\n): Promise<void> {\n\tawait deps.snapshots.clearRepo(repoId)\n\tfor (const ref of await deps.refs.listRefs(repoId)) {\n\t\tawait syncRefSnapshot(deps, repoId, ref.name, ref.oid)\n\t}\n}\n","import { Kysely } from \"kysely\"\nimport { PostgresJSDialect } from \"kysely-postgres-js\"\nimport type { Sql } from \"postgres\"\nimport { recordQuery } from \"@/instrument\"\n\n// Mirrors web/postgres.ts: Kysely over the porsager `postgres` driver via\n// PostgresJSDialect. Unlike web, pggit does NOT keep a module-level singleton —\n// the caller owns the porsager client and injects it; each store builds its own\n// Kysely from it (so the app stays a mountable sub-app and per-schema test\n// isolation works), and keeps the raw client for the COPY ingest path.\nconst EVENT_SIGNS = { error: \"🔴\", query: \"🟢\" } as const\n\n/** Wrap a porsager client in a typed Kysely. Dev builds log query/error events. */\nexport function initKysely<T>(pg: Sql): Kysely<T> {\n\treturn new Kysely<T>({\n\t\tdialect: new PostgresJSDialect({ postgres: pg }),\n\t\tlog(event) {\n\t\t\tif (event.level === \"query\" || event.level === \"error\") {\n\t\t\t\trecordQuery(event.query.sql, event.queryDurationMillis)\n\t\t\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\t\t\tconsole.debug(\n\t\t\t\t\t\t`${EVENT_SIGNS[event.level]} ${event.queryDurationMillis}ms ${event.query.sql}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t})\n}\n","import type { TransactionSql } from \"postgres\"\n\n/**\n * Bulk binary insert via `COPY … FROM STDIN (FORMAT binary)` into a staging temp\n * table, then `INSERT … SELECT … ON CONFLICT DO NOTHING`. This is the one bulk-row\n * insert path for the ingest spine, and it exists because a multi-row `INSERT`\n * cannot carry git's data correctly:\n *\n * - The porsager driver serializes a `bytea` parameter as the text `'\\x'+hex`,\n * DOUBLING the byte length; a blob over ~256MiB therefore overruns V8's max\n * string length and the insert throws. COPY binary streams the bytea as RAW\n * bytes — content never lands on the JS string heap, so blob size is bounded\n * only by Postgres (`bytea` ~1GB), not by the string cap.\n * - The wire protocol caps a statement at 65534 bind parameters (≈13k object rows\n * at 5 columns); COPY binds none, so any row count goes in a single statement —\n * no chunking, whatever the column count.\n *\n * The staging hop keeps ingest idempotent (a re-sent object skips on the primary\n * key), exactly as the prior `onConflict().doNothing()` did. Caller runs it on a\n * transaction-scoped `Sql` so the staging COPY and the final insert commit\n * together; the temp table drops on commit.\n */\n\n/** One COPY field, tagged with the destination column's Postgres type so it\n * encodes to the correct binary wire form. */\nexport type CopyValue =\n\t| { t: \"int2\"; v: number }\n\t| { t: \"int4\"; v: number }\n\t| { t: \"int8\"; v: number | bigint | string }\n\t| { t: \"bytea\"; v: Buffer }\n\t| { t: \"text\"; v: string }\n\n// PGCOPY binary signature + the two zero header words (flags, header extension).\nconst HEADER = Buffer.concat([\n\tBuffer.from([0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00]),\n\tBuffer.alloc(8),\n])\n// File trailer: a -1 field count.\nconst TRAILER = (() => {\n\tconst b = Buffer.alloc(2)\n\tb.writeInt16BE(-1)\n\treturn b\n})()\n\nfunction encodeValue(field: CopyValue): Buffer {\n\tswitch (field.t) {\n\t\tcase \"int2\": {\n\t\t\tconst b = Buffer.alloc(2)\n\t\t\tb.writeInt16BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int4\": {\n\t\t\tconst b = Buffer.alloc(4)\n\t\t\tb.writeInt32BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int8\": {\n\t\t\tconst b = Buffer.alloc(8)\n\t\t\tb.writeBigInt64BE(BigInt(field.v))\n\t\t\treturn b\n\t\t}\n\t\tcase \"bytea\":\n\t\t\treturn field.v\n\t\tcase \"text\":\n\t\t\treturn Buffer.from(field.v, \"utf8\")\n\t}\n}\n\n/** Encode rows as one PGCOPY binary payload: header, then per row a field count\n * and each field as `<int32 length><raw bytes>`, then the trailer. */\nfunction encodeBinaryCopy(rows: CopyValue[][]): Buffer {\n\tconst parts: Buffer[] = [HEADER]\n\tfor (const row of rows) {\n\t\tconst fieldCount = Buffer.alloc(2)\n\t\tfieldCount.writeInt16BE(row.length)\n\t\tparts.push(fieldCount)\n\t\tfor (const field of row) {\n\t\t\tconst value = encodeValue(field)\n\t\t\tconst len = Buffer.alloc(4)\n\t\t\tlen.writeInt32BE(value.length)\n\t\t\tparts.push(len, value)\n\t\t}\n\t}\n\tparts.push(TRAILER)\n\treturn Buffer.concat(parts)\n}\n\n/**\n * COPY `rows` into `target` (a temp staging table shaped from `target`'s columns,\n * then `INSERT … SELECT … ON CONFLICT DO NOTHING`). `tx` must be a\n * transaction-scoped porsager `Sql`. `target` and `columns` are internal constants\n * (never client input), interpolated as SQL identifiers.\n */\nexport async function copyInsert(\n\ttx: TransactionSql,\n\ttarget: string,\n\tcolumns: readonly string[],\n\trows: CopyValue[][],\n): Promise<void> {\n\tif (rows.length === 0) return\n\tconst cols = columns.join(\", \")\n\tconst staging = `copy_stg_${target}`\n\t// `CREATE TABLE AS … WITH NO DATA` shapes the staging table from exactly the\n\t// inserted columns (their types, no NOT NULL / defaults / constraints), so COPY\n\t// fills every column it declares and the final insert lets `target` apply its\n\t// own defaults (e.g. git_object.created_at) to the unlisted ones.\n\tawait tx.unsafe(\n\t\t`create temp table ${staging} on commit drop as select ${cols} from ${target} with no data`,\n\t)\n\tconst writable =\n\t\tawait tx`copy ${tx(staging)} (${tx.unsafe(cols)}) from stdin (format binary)`.writable()\n\tawait new Promise<void>((resolve, reject) => {\n\t\twritable.on(\"error\", reject)\n\t\twritable.on(\"finish\", () => resolve())\n\t\twritable.write(encodeBinaryCopy(rows), (err) => {\n\t\t\tif (err) reject(err)\n\t\t\telse writable.end()\n\t\t})\n\t})\n\tawait tx.unsafe(\n\t\t`insert into ${target} (${cols}) select ${cols} from ${staging} on conflict do nothing`,\n\t)\n}\n","import type { Kysely } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\n\nexport type RepoResolver = ReturnType<typeof createRepoResolver>\n\n/**\n * Resolves a wire repo name to its `repos.id` surrogate, memoized. The object and\n * ref stores both key on the bigint `repo_id`, so each builds one of these as its\n * name→id boundary.\n *\n * The mapping is immutable once a repo exists (ids are `generated always`, names\n * are unique), so a found id is cached for the resolver's lifetime — keeping the\n * per-object hot path (getObject) at one point-read, not a join. Misses are NEVER\n * cached: a name the lookup didn't find may be created by a later push, and a\n * cached `null` would mask it.\n *\n * Reads resolve (lookup; `null` ⇒ the repo has never been written, i.e. empty).\n * Writes ensure (race-safe get-or-create).\n */\nexport function createRepoResolver(db: Kysely<Database>) {\n\tconst cache = new Map<string, ReposId>()\n\n\treturn {\n\t\t/** The repo's id, creating the row if absent. Race-safe under concurrent\n\t\t * first-pushes, and avoids a no-op UPDATE on the common (exists) path. */\n\t\tasync ensureRepoId(name: string): Promise<ReposId> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (existing) {\n\t\t\t\tcache.set(name, existing.id)\n\t\t\t\treturn existing.id\n\t\t\t}\n\t\t\tconst inserted = await db\n\t\t\t\t.insertInto(\"repos\")\n\t\t\t\t.values({ name })\n\t\t\t\t.onConflict((oc) => oc.doNothing())\n\t\t\t\t.returning(\"id\")\n\t\t\t\t.executeTakeFirst()\n\t\t\t// `inserted` is undefined iff a concurrent push won the insert race; the\n\t\t\t// row is guaranteed present now, so re-select it.\n\t\t\tconst id =\n\t\t\t\tinserted?.id ??\n\t\t\t\t(\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t\t\t.select(\"id\")\n\t\t\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t\t\t.executeTakeFirstOrThrow()\n\t\t\t\t).id\n\t\t\tcache.set(name, id)\n\t\t\treturn id\n\t\t},\n\t\t/** The repo's id, or `null` if it has never been written to. */\n\t\tasync resolveRepoId(name: string): Promise<ReposId | null> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (!row) return null\n\t\t\tcache.set(name, row.id)\n\t\t\treturn row.id\n\t\t},\n\t}\n}\n","import type { Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport { type CopyValue, copyInsert } from \"@/database/copy-insert\"\nimport type { FileList } from \"@/repo-view/build-file-list\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\nexport type RepoFileProjection = ReturnType<typeof createRepoFileProjection>\n\n/**\n * Write-only maintainer of `repo_file`: the slim per-branch-tip `path → (mode,\n * blob_oid)` index that IS pggit's public read surface. Reads never go through this\n * module — a consumer queries `repo_file ⋈ git_object` (on `oid = blob_oid`) with\n * direct SQL, the one read mechanism (docs/2026-06-26-read-surface-sharpening-design.md).\n * So this only ever rebuilds or drops the projection on push; there is no read method\n * here by design. It is a derived projection of the canonical objects — no duplicate\n * blob bytes, no orphan reaper (the redesign's collapse, §4.5) — droppable and\n * rebuildable at will. The wire repo name resolves to its bigint surrogate (memoized)\n * here, like the other stores.\n */\nexport function createRepoFileProjection(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\treturn {\n\t\t/** Drop a repo's entire projection (all branches) — the clean slate for a full\n\t\t * rebuild. No blob bytes to reap; the index is the only state. */\n\t\tasync clearRepo(repoId: string): Promise<void> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return\n\t\t\tawait db.deleteFrom(\"repo_file\").where(\"repo_id\", \"=\", id).execute()\n\t\t},\n\n\t\t/** Drop `refName`'s snapshot (branch deleted). */\n\t\tasync dropRefSnapshot(repoId: string, refName: string): Promise<void> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"repo_file\")\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"ref_name\", \"=\", refName)\n\t\t\t\t.execute()\n\t\t},\n\n\t\t/** Replace `refName`'s snapshot with `fileList` (one atomic transaction). The\n\t\t * blobs already live in git_object — we store only the path→blob_oid index. */\n\t\tasync rebuildRefSnapshot(\n\t\t\trepoId: string,\n\t\t\trefName: string,\n\t\t\tfileList: FileList,\n\t\t): Promise<void> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tconst rows: CopyValue[][] = fileList.files.map((f) => [\n\t\t\t\t{ t: \"int8\", v: id },\n\t\t\t\t{ t: \"text\", v: refName },\n\t\t\t\t{ t: \"text\", v: f.path },\n\t\t\t\t{ t: \"text\", v: f.mode },\n\t\t\t\t{ t: \"bytea\", v: Buffer.from(f.blobOid, \"hex\") },\n\t\t\t])\n\t\t\t// Replace the branch's snapshot in one transaction. COPY into staging has no\n\t\t\t// bind-parameter ceiling, so a tip with any file count lands in a single\n\t\t\t// statement (an un-chunked multi-row INSERT died at ~13k files, §a06).\n\t\t\tawait pg.begin(async (tx) => {\n\t\t\t\tawait tx`delete from repo_file where repo_id = ${id} and ref_name = ${refName}`\n\t\t\t\tawait copyInsert(\n\t\t\t\t\ttx,\n\t\t\t\t\t\"repo_file\",\n\t\t\t\t\t[\"repo_id\", \"ref_name\", \"path\", \"mode\", \"blob_oid\"],\n\t\t\t\t\trows,\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\t}\n}\n","import { GitFormatError } from \"@/object/format-error\"\nimport {\n\tcommitParents,\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\treferencedOids,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/**\n * Edge kinds stored in `git_edge.kind`. tree→blob (would be `4`) is deliberately\n * NOT a kind: blobs are enumerated from tree content, never stored as edges (§4.3),\n * so `4` is reserved/unused.\n */\nexport const EDGE_KIND = {\n\tCOMMIT_PARENT: 2,\n\tCOMMIT_TREE: 1,\n\tTAG_TARGET: 5,\n\tTREE_SUBTREE: 3,\n} as const\n\nexport type DerivedEdge = { child: string; kind: number }\n\n/** A tree entry pointing at a commit in *another* repo — no blob, no edge here. */\nconst GITLINK_MODE = \"160000\"\n\nconst WELL_FORMED_OID = /^[0-9a-f]{40}$/\n\n/**\n * Validate an OID parsed from a commit/tag header. `commitParents`/`commitTreeOid`/\n * `referencedOids` take whatever follows the header key verbatim — a forged object\n * could carry a non-OID there and yield a bogus edge child — so reject it loudly at\n * the ingest boundary (§5.1). Tree-entry OIDs are exempt: `treeEntries` already\n * guarantees a 20-byte value, and the `bytea CHECK(length(child)=20)` is the\n * database-level backstop for every edge.\n */\nfunction assertOid(oid: string, context: string): string {\n\tif (!WELL_FORMED_OID.test(oid)) {\n\t\tthrow new GitFormatError(\n\t\t\t\"malformed-oid\",\n\t\t\t`${context}: not a well-formed object id: ${JSON.stringify(oid)}`,\n\t\t)\n\t}\n\treturn oid\n}\n\n/** Count the leading `key value` header lines (up to the blank line that ends a\n * commit/tag's header block). */\nfunction countHeader(content: Buffer, key: string): number {\n\tconst prefix = `${key} `\n\tlet n = 0\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tif (line.startsWith(prefix)) n++\n\t}\n\treturn n\n}\n\n/**\n * fsck-grade structural validation at the ingest boundary (§5.1, invariant §10.2):\n * reject the malformed objects that OID-wellformedness and tree parsing do not\n * catch. A commit must not carry more than one `tree` header (git fsck:\n * multipleTrees — `commitTreeOid` would otherwise silently take the first and drop\n * the rest, recording an edge to a tree the object does not actually root). An\n * annotated tag must carry exactly one `object` header (git fsck: missingObject /\n * an extra object line): zero yields no `kind=5` edge and silently breaks peeling\n * and connectivity; more than one yields multiple divergent `kind=5` edges and a\n * nondeterministic `peeled_oid`. The other structural guarantees are already\n * enforced downstream: `assertOid` on every referenced OID (below), a present root\n * `tree` (`commitTreeOid`, which also rejects a zero-tree commit), and a well-formed\n * tree body (`treeEntries` throws). Called by the store once per object before\n * derivation, in the ingest transaction, so a malformed push aborts before any row\n * lands.\n */\nexport function validateObject(type: GitObjectType, content: Buffer): void {\n\tif (type === \"commit\" && countHeader(content, \"tree\") > 1) {\n\t\tthrow new GitFormatError(\n\t\t\t\"multiple-tree-headers\",\n\t\t\t\"commit carries more than one tree header\",\n\t\t)\n\t}\n\tif (type === \"tag\") {\n\t\tconst objects = countHeader(content, \"object\")\n\t\tif (objects < 1) {\n\t\t\tthrow new GitFormatError(\"missing-tag-object\", \"annotated tag has no object header\")\n\t\t}\n\t\tif (objects > 1) {\n\t\t\tthrow new GitFormatError(\n\t\t\t\t\"multiple-tag-objects\",\n\t\t\t\t\"annotated tag carries more than one object header\",\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * The edges an object contributes to `git_edge`, with the object's own OID as the\n * parent — the §4.3 standing rule, mode-aware:\n * - commit → its tree (kind 1) then each parent (kind 2);\n * - tree → its **subtrees only** (mode `40000` → kind 3). Blobs and gitlinks\n * (`160000`, a commit living in another repo) are NOT edges — `isTreeEntryMode`\n * admits only `40000`, so both are dropped;\n * - tag → its target (kind 5);\n * - blob → nothing.\n *\n * This is the single derivation the store inserts alongside the object row, in the\n * same transaction (§10.1), so edges are a validated total function of content.\n */\nexport function deriveEdges(type: GitObjectType, content: Buffer): DerivedEdge[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tchild: assertOid(commitTreeOid(content), \"commit tree\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_TREE,\n\t\t\t\t},\n\t\t\t\t...commitParents(content).map((p) => ({\n\t\t\t\t\tchild: assertOid(p, \"commit parent\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_PARENT,\n\t\t\t\t})),\n\t\t\t]\n\t\tcase \"tag\":\n\t\t\treturn referencedOids(\"tag\", content).map((t) => ({\n\t\t\t\tchild: assertOid(t, \"tag target\"),\n\t\t\t\tkind: EDGE_KIND.TAG_TARGET,\n\t\t\t}))\n\t\tcase \"tree\":\n\t\t\treturn treeEntries(content)\n\t\t\t\t.filter((e) => isTreeEntryMode(e.mode))\n\t\t\t\t.map((e) => ({ child: e.oid, kind: EDGE_KIND.TREE_SUBTREE }))\n\t}\n}\n\n/**\n * The blob OIDs directly in a tree — the §4.3 standing rule's other half: blobs\n * are enumerated from tree content, never stored as edges. A tree entry is a blob\n * unless it is a subtree (`deriveEdges` covers those as kind-3 edges) or a gitlink\n * (`160000`, a submodule commit living in another repo — neither blob nor edge).\n * Connectivity uses this to find the blobs a present tree requires, since no\n * tree→blob edge exists to anchor a missing one.\n */\nexport function treeBlobOids(content: Buffer): string[] {\n\treturn treeEntries(content)\n\t\t.filter((e) => !isTreeEntryMode(e.mode) && e.mode !== GITLINK_MODE)\n\t\t.map((e) => e.oid)\n}\n","/**\n * Pack object header: a variable-length encoding of (type, uncompressed size)\n * that prefixes every object entry in a packfile.\n *\n * First byte: `[c|ttt|ssss]` — continuation bit `c`, 3-bit type `ttt`, low 4 bits\n * of size. Each continuation byte contributes 7 more size bits, least-significant\n * group first. See gitformat-pack.\n *\n * Size arithmetic uses `*`/`Math.floor`, NOT `<<`/`>>` — JS bitwise ops are\n * 32-bit and would corrupt object sizes ≥ 2³¹.\n */\n\nexport const PACK_OBJ_TYPE = {\n\tBLOB: 3,\n\tCOMMIT: 1,\n\tOFS_DELTA: 6,\n\tREF_DELTA: 7,\n\tTAG: 4,\n\tTREE: 2,\n} as const\n\nexport type PackObjType = (typeof PACK_OBJ_TYPE)[keyof typeof PACK_OBJ_TYPE]\n\nexport type DecodedObjectHeader = {\n\ttype: number\n\tsize: number\n\tbytesRead: number\n}\n\nexport function encodeObjectHeader(type: number, size: number): Buffer {\n\tlet rest = Math.floor(size / 16)\n\tlet first = (type << 4) | (size % 16)\n\tif (rest > 0) first |= 0x80\n\tconst bytes = [first]\n\twhile (rest > 0) {\n\t\tlet byte = rest % 128\n\t\trest = Math.floor(rest / 128)\n\t\tif (rest > 0) byte |= 0x80\n\t\tbytes.push(byte)\n\t}\n\treturn Buffer.from(bytes)\n}\n\nexport function decodeObjectHeader(buf: Buffer, offset: number): DecodedObjectHeader {\n\tlet b = buf.readUInt8(offset)\n\tlet bytesRead = 1\n\tconst type = (b >> 4) & 0x07\n\tlet size = b & 0x0f\n\tlet mult = 16\n\twhile (b & 0x80) {\n\t\tb = buf.readUInt8(offset + bytesRead)\n\t\tbytesRead++\n\t\tsize += (b & 0x7f) * mult\n\t\tmult *= 128\n\t}\n\treturn { bytesRead, size, type }\n}\n","import { GitFormatError } from \"@/object/format-error\"\n\n/**\n * Apply a git delta to its base, producing the target object. The delta begins\n * with two LEB128 varints (source size, target size), then a stream of\n * instructions: a COPY (high bit set — copy a run from the base at a given\n * offset/size) or an INSERT (1..127 literal bytes that follow). See\n * gitformat-pack \"Deltified representation\". We only ever READ/apply deltas;\n * the serve path emits none (spec §3.4).\n */\nexport function applyDelta(base: Buffer, delta: Buffer): Buffer {\n\tlet pos = 0\n\n\tconst readVarint = (): number => {\n\t\tlet result = 0\n\t\tlet shift = 0\n\t\tlet byte: number\n\t\tdo {\n\t\t\tbyte = delta.readUInt8(pos)\n\t\t\tpos += 1\n\t\t\tresult += (byte & 0x7f) * 2 ** shift\n\t\t\tshift += 7\n\t\t} while (byte & 0x80)\n\t\treturn result\n\t}\n\n\tconst sourceSize = readVarint()\n\tconst targetSize = readVarint()\n\tif (base.length !== sourceSize) {\n\t\tthrow new GitFormatError(\n\t\t\t\"delta-base-size-mismatch\",\n\t\t\t`delta: base size ${base.length} ≠ declared ${sourceSize}`,\n\t\t)\n\t}\n\n\tconst out = Buffer.alloc(targetSize)\n\tlet outPos = 0\n\twhile (pos < delta.length) {\n\t\tconst op = delta.readUInt8(pos)\n\t\tpos += 1\n\n\t\tif (op & 0x80) {\n\t\t\t// COPY: present bits select which little-endian offset/size bytes follow.\n\t\t\tlet copyOffset = 0\n\t\t\tif (op & 0x01) copyOffset |= delta.readUInt8(pos++)\n\t\t\tif (op & 0x02) copyOffset |= delta.readUInt8(pos++) << 8\n\t\t\tif (op & 0x04) copyOffset |= delta.readUInt8(pos++) << 16\n\t\t\tif (op & 0x08) copyOffset += delta.readUInt8(pos++) * 2 ** 24\n\t\t\tlet copySize = 0\n\t\t\tif (op & 0x10) copySize |= delta.readUInt8(pos++)\n\t\t\tif (op & 0x20) copySize |= delta.readUInt8(pos++) << 8\n\t\t\tif (op & 0x40) copySize |= delta.readUInt8(pos++) << 16\n\t\t\tif (copySize === 0) copySize = 0x10000\n\t\t\tbase.copy(out, outPos, copyOffset, copyOffset + copySize)\n\t\t\toutPos += copySize\n\t\t} else if (op !== 0) {\n\t\t\t// INSERT: `op` literal bytes follow.\n\t\t\tdelta.copy(out, outPos, pos, pos + op)\n\t\t\toutPos += op\n\t\t\tpos += op\n\t\t} else {\n\t\t\tthrow new GitFormatError(\"delta-reserved-opcode\", \"delta: reserved opcode 0x00\")\n\t\t}\n\t}\n\n\tif (outPos !== targetSize) {\n\t\tthrow new GitFormatError(\n\t\t\t\"delta-target-size-mismatch\",\n\t\t\t`delta: produced ${outPos} bytes, declared ${targetSize}`,\n\t\t)\n\t}\n\treturn out\n}\n","import { createHash } from \"node:crypto\"\nimport { createInflate } from \"node:zlib\"\nimport { count } from \"@/instrument\"\nimport { GitFormatError } from \"@/object/format-error\"\nimport { computeOid, type GitObjectType } from \"@/object/object\"\nimport { applyDelta } from \"@/pack/delta\"\nimport { decodeObjectHeader, PACK_OBJ_TYPE } from \"@/pack/object-header\"\n\nexport type ParsedObject = {\n\ttype: GitObjectType\n\tcontent: Buffer\n\toid: string\n}\n\nconst CODE_TO_TYPE: Record<number, GitObjectType> = {\n\t[PACK_OBJ_TYPE.COMMIT]: \"commit\",\n\t[PACK_OBJ_TYPE.TREE]: \"tree\",\n\t[PACK_OBJ_TYPE.BLOB]: \"blob\",\n\t[PACK_OBJ_TYPE.TAG]: \"tag\",\n}\n\ntype RawEntry =\n\t| { kind: \"base\"; type: GitObjectType; content: Buffer }\n\t| { kind: \"ofs\"; baseOffset: number; delta: Buffer }\n\t| { kind: \"ref\"; baseOid: string; delta: Buffer }\n\ntype Resolved = { type: GitObjectType; content: Buffer }\n\n/**\n * Inflate exactly one zlib stream at the front of `buf`, returning the data and\n * how many COMPRESSED bytes it consumed — the seam for walking the back-to-back\n * zlib streams in a packfile. (`bytesWritten` = input consumed up to\n * Z_STREAM_END; trailing bytes untouched — verified empirically against\n * node:zlib.)\n */\nfunction inflateOne(buf: Buffer): Promise<{ data: Buffer; compressedLength: number }> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst inf = createInflate()\n\t\tconst chunks: Buffer[] = []\n\t\tinf.on(\"data\", (chunk: Buffer) => chunks.push(chunk))\n\t\tinf.on(\"end\", () =>\n\t\t\tresolve({ compressedLength: inf.bytesWritten, data: Buffer.concat(chunks) }),\n\t\t)\n\t\tinf.on(\"error\", (e) =>\n\t\t\treject(\n\t\t\t\tnew GitFormatError(\n\t\t\t\t\t\"inflate-failed\",\n\t\t\t\t\t`pack: zlib inflate failed: ${e instanceof Error ? e.message : String(e)}`,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\tinf.end(buf)\n\t})\n}\n\n/**\n * The pack OFS_DELTA \"offset encoding\" — NOT plain LEB128. Each continuation\n * adds 1 before shifting, so encodings are unambiguous. Arithmetic (not `<<`) so\n * offsets ≥ 2³¹ stay correct.\n */\nfunction readOffsetVarint(\n\tbuf: Buffer,\n\toffset: number,\n): { value: number; bytesRead: number } {\n\tlet b = buf.readUInt8(offset)\n\tlet bytesRead = 1\n\tlet value = b & 0x7f\n\twhile (b & 0x80) {\n\t\tb = buf.readUInt8(offset + bytesRead)\n\t\tbytesRead += 1\n\t\tvalue = (value + 1) * 128 + (b & 0x7f)\n\t}\n\treturn { bytesRead, value }\n}\n\n/**\n * Parse a v2 packfile into its objects, resolving OFS_DELTA and REF_DELTA\n * (including delta chains). Bases come from the same pack; a REF_DELTA whose base\n * is NOT in the pack — a thin pack, as `git push` sends by default — is resolved\n * via `resolveExternalBase` (the Postgres store on push ingest). Without a\n * resolver, an external base is a hard error.\n */\nexport async function readPack(\n\tpack: Buffer,\n\tresolveExternalBase?: (oid: string) => Promise<Resolved | null>,\n): Promise<ParsedObject[]> {\n\tcount(\"readPackCalls\")\n\tif (pack.subarray(0, 4).toString(\"latin1\") !== \"PACK\") {\n\t\tthrow new GitFormatError(\"bad-magic\", \"pack: bad magic\")\n\t}\n\tconst version = pack.readUInt32BE(4)\n\tif (version !== 2) {\n\t\tthrow new GitFormatError(\n\t\t\t\"unsupported-version\",\n\t\t\t`pack: unsupported version ${version}`,\n\t\t)\n\t}\n\tconst objectCount = pack.readUInt32BE(8)\n\n\tconst trailerOffset = pack.length - 20\n\tconst actualTrailer = createHash(\"sha1\")\n\t\t.update(pack.subarray(0, trailerOffset))\n\t\t.digest()\n\tif (!pack.subarray(trailerOffset).equals(actualTrailer)) {\n\t\tthrow new GitFormatError(\"trailer-mismatch\", \"pack: trailer SHA-1 mismatch\")\n\t}\n\n\t// Pass 1 — parse every entry's raw form, keyed by its start offset.\n\tconst entries = new Map<number, RawEntry>()\n\tconst order: number[] = []\n\tlet offset = 12\n\tfor (let i = 0; i < objectCount; i++) {\n\t\tconst start = offset\n\t\tconst { type, size, bytesRead } = decodeObjectHeader(pack, offset)\n\t\toffset += bytesRead\n\n\t\tif (type === PACK_OBJ_TYPE.OFS_DELTA) {\n\t\t\tconst { value: negOffset, bytesRead: ob } = readOffsetVarint(pack, offset)\n\t\t\toffset += ob\n\t\t\tconst { data, compressedLength } = await inflateOne(pack.subarray(offset))\n\t\t\tcount(\"bytesInflated\", data.length)\n\t\t\toffset += compressedLength\n\t\t\tentries.set(start, { baseOffset: start - negOffset, delta: data, kind: \"ofs\" })\n\t\t} else if (type === PACK_OBJ_TYPE.REF_DELTA) {\n\t\t\tconst baseOid = pack.subarray(offset, offset + 20).toString(\"hex\")\n\t\t\toffset += 20\n\t\t\tconst { data, compressedLength } = await inflateOne(pack.subarray(offset))\n\t\t\tcount(\"bytesInflated\", data.length)\n\t\t\toffset += compressedLength\n\t\t\tentries.set(start, { baseOid, delta: data, kind: \"ref\" })\n\t\t} else {\n\t\t\tconst typeName = CODE_TO_TYPE[type]\n\t\t\tif (!typeName) {\n\t\t\t\tthrow new GitFormatError(\n\t\t\t\t\t\"unknown-object-type\",\n\t\t\t\t\t`pack: unknown object type ${type}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst { data, compressedLength } = await inflateOne(pack.subarray(offset))\n\t\t\tcount(\"bytesInflated\", data.length)\n\t\t\tif (data.length !== size) {\n\t\t\t\tthrow new GitFormatError(\n\t\t\t\t\t\"size-mismatch\",\n\t\t\t\t\t`pack: size mismatch (header ${size}, inflated ${data.length})`,\n\t\t\t\t)\n\t\t\t}\n\t\t\toffset += compressedLength\n\t\t\tentries.set(start, { content: data, kind: \"base\", type: typeName })\n\t\t}\n\t\torder.push(start)\n\t}\n\tif (offset !== trailerOffset) {\n\t\tthrow new GitFormatError(\n\t\t\t\"trailing-bytes\",\n\t\t\t`pack: consumed ${offset} bytes, expected ${trailerOffset} before trailer`,\n\t\t)\n\t}\n\n\t// Pass 2 — resolve deltas by base availability (git's index-pack approach). Each\n\t// pass resolves every still-pending delta whose base is now known: by offset\n\t// (OFS_DELTA), by OID from an already-resolved pack object (REF_DELTA), or from\n\t// the external resolver (a thin pack's store-resident base, fetched once). A\n\t// REF_DELTA base may itself be another in-pack delta's OUTPUT, so we cannot index\n\t// OIDs up front — we iterate to a fixpoint instead, which also handles arbitrary\n\t// chains and pack orderings. A pass that resolves nothing while entries remain ⇒\n\t// a genuinely missing base or a cycle.\n\tconst resolved = new Map<number, ParsedObject>()\n\tconst byOid = new Map<string, ParsedObject>()\n\tconst externalCache = new Map<string, Resolved | null>()\n\n\tconst fetchExternal = async (oid: string): Promise<Resolved | null> => {\n\t\tconst cached = externalCache.get(oid)\n\t\tif (cached !== undefined) return cached\n\t\tconst fetched = resolveExternalBase ? await resolveExternalBase(oid) : null\n\t\texternalCache.set(oid, fetched)\n\t\treturn fetched\n\t}\n\n\tconst record = (off: number, type: GitObjectType, content: Buffer): void => {\n\t\tconst obj: ParsedObject = { content, oid: computeOid(type, content), type }\n\t\tresolved.set(off, obj)\n\t\tbyOid.set(obj.oid, obj)\n\t}\n\n\tfor (const off of order) {\n\t\tconst entry = entries.get(off)\n\t\tif (entry?.kind === \"base\") record(off, entry.type, entry.content)\n\t}\n\n\tlet pending = order.filter((off) => !resolved.has(off))\n\twhile (pending.length > 0) {\n\t\tconst stillPending: number[] = []\n\t\tfor (const off of pending) {\n\t\t\tconst entry = entries.get(off)\n\t\t\tif (!entry || entry.kind === \"base\") continue\n\t\t\tconst base: Resolved | null =\n\t\t\t\tentry.kind === \"ofs\"\n\t\t\t\t\t? (resolved.get(entry.baseOffset) ?? null)\n\t\t\t\t\t: (byOid.get(entry.baseOid) ?? (await fetchExternal(entry.baseOid)))\n\t\t\tif (!base) {\n\t\t\t\tstillPending.push(off)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecord(off, base.type, applyDelta(base.content, entry.delta))\n\t\t}\n\t\tif (stillPending.length === pending.length) {\n\t\t\tconst off = stillPending[0] as number\n\t\t\tconst entry = entries.get(off)\n\t\t\tconst base =\n\t\t\t\tentry?.kind === \"ref\"\n\t\t\t\t\t? entry.baseOid\n\t\t\t\t\t: `offset ${entry?.kind === \"ofs\" ? entry.baseOffset : \"?\"}`\n\t\t\tthrow new GitFormatError(\n\t\t\t\t\"unresolved-base\",\n\t\t\t\t`pack: ref-delta base ${base} not found in pack or store`,\n\t\t\t)\n\t\t}\n\t\tpending = stillPending\n\t}\n\n\treturn order.map((off) => resolved.get(off) as ParsedObject)\n}\n","import { createHash } from \"node:crypto\"\nimport { deflateSync } from \"node:zlib\"\nimport { count } from \"@/instrument\"\nimport type { GitObjectType } from \"@/object/object\"\nimport { encodeObjectHeader, PACK_OBJ_TYPE, type PackObjType } from \"@/pack/object-header\"\n\nexport type PackInputObject = {\n\ttype: GitObjectType\n\tcontent: Buffer\n}\n\nconst TYPE_CODE: Record<GitObjectType, PackObjType> = {\n\tblob: PACK_OBJ_TYPE.BLOB,\n\tcommit: PACK_OBJ_TYPE.COMMIT,\n\ttag: PACK_OBJ_TYPE.TAG,\n\ttree: PACK_OBJ_TYPE.TREE,\n}\n\n/**\n * The 12-byte pack header: `PACK` magic, version 2, object count. The count is\n * fixed up front, so a streaming encoder must know its object total before the\n * first object (the row-store's closure provides it without reading content).\n */\nexport function packHeader(objectCount: number): Buffer {\n\tconst header = Buffer.alloc(12)\n\theader.write(\"PACK\", 0, \"latin1\")\n\theader.writeUInt32BE(2, 4)\n\theader.writeUInt32BE(objectCount, 8)\n\treturn header\n}\n\n/** One packed object: its varint (type, uncompressed size) header + zlib-deflated\n * content. Undeltified — we never emit deltas (spec §3.4 asymmetric kernel). */\nexport function packObject(type: GitObjectType, content: Buffer): Buffer {\n\tconst deflated = deflateSync(content)\n\tcount(\"deflateInputBytes\", content.length)\n\tcount(\"deflateOutputBytes\", deflated.length)\n\treturn Buffer.concat([encodeObjectHeader(TYPE_CODE[type], content.length), deflated])\n}\n\n/**\n * Serialize objects into a self-contained, **undeltified** packfile (v2): the\n * header, each object's (header + deflated content), then a trailing SHA-1 of all\n * preceding bytes. The serve path streams the same primitives object-by-object\n * (object-store `buildPack`); this all-at-once form builds test packs and the\n * empty pack.\n */\nexport function writePack(objects: PackInputObject[]): Buffer {\n\tcount(\"writePackCalls\")\n\tconst parts: Buffer[] = [packHeader(objects.length)]\n\tfor (const obj of objects) parts.push(packObject(obj.type, obj.content))\n\n\tconst body = Buffer.concat(parts)\n\tconst trailer = createHash(\"sha1\").update(body).digest()\n\treturn Buffer.concat([body, trailer])\n}\n","import { type Kysely, sql } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { EDGE_KIND, treeBlobOids } from \"@/object/edges\"\nimport { PACK_OBJ_TYPE } from \"@/pack/object-header\"\n\n/** Objects looked up per round-trip when chunking tree/blob existence queries. */\nconst LOOKUP_BATCH = 1000\n\n/** Split `items` into consecutive batches of at most `size`. */\nfunction batches<T>(items: T[], size: number): T[][] {\n\tconst out: T[][] = []\n\tfor (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n\treturn out\n}\n\n/**\n * The objects reachable from `roots` over the stored DAG — the ONE reachability\n * engine shared by connectivity, clone, and incremental fetch (so they can never\n * disagree). A recursive CTE walks `git_edge` (all stored kinds 1,2,3,5) for the\n * commit/tree/tag closure; the LEFT JOIN marks which are present. Blobs are not\n * edges (§4.3), so unless `omitBlobs` they are enumerated from each present tree's\n * content (mode-aware) and their presence checked. Returns the reachable set\n * partitioned into present / missing. `::bigint`/`::bytea` casts and the\n * `VALUES (…::bytea)` seed pin types in the raw CTE (the porsager driver can't\n * bind a raw `bytea[]`, OQ-13); array lookups use Kysely's `in`-expansion.\n */\nexport async function reachableClosure(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\troots: string[],\n\tomitBlobs: boolean,\n): Promise<{ present: Set<string>; missing: Set<string> }> {\n\tconst present = new Set<string>()\n\tconst missing = new Set<string>()\n\tif (roots.length === 0) return { missing, present }\n\n\tconst seed = sql.join(roots.map((r) => sql`(${Buffer.from(r, \"hex\")}::bytea)`))\n\tconst closure = await sql<{ oid: Buffer; type: number | null }>`\n\t\twith recursive closure(oid) as (\n\t\t\tselect oid from (values ${seed}) as roots(oid)\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin closure c on e.parent = c.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t)\n\t\tselect c.oid, o.type\n\t\tfrom closure c\n\t\tleft join git_object o on o.repo_id = ${id}::bigint and o.oid = c.oid\n\t`.execute(db)\n\n\tconst treeOids: Buffer[] = []\n\tfor (const r of closure.rows) {\n\t\tconst hex = r.oid.toString(\"hex\")\n\t\tif (r.type === null) {\n\t\t\tmissing.add(hex)\n\t\t} else {\n\t\t\tpresent.add(hex)\n\t\t\tif (r.type === PACK_OBJ_TYPE.TREE) treeOids.push(r.oid)\n\t\t}\n\t}\n\tif (omitBlobs || treeOids.length === 0) return { missing, present }\n\n\tconst blobCandidates = new Set<string>()\n\tfor (const batch of batches(treeOids, LOOKUP_BATCH)) {\n\t\tconst trees = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"content\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\"oid\", \"in\", batch)\n\t\t\t.execute()\n\t\tfor (const t of trees) {\n\t\t\tfor (const blob of treeBlobOids(t.content)) blobCandidates.add(blob)\n\t\t}\n\t}\n\tif (blobCandidates.size === 0) return { missing, present }\n\n\tconst presentBlobs = new Set<string>()\n\tfor (const batch of batches([...blobCandidates], LOOKUP_BATCH)) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"oid\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\n\t\t\t\t\"oid\",\n\t\t\t\t\"in\",\n\t\t\t\tbatch.map((h) => Buffer.from(h, \"hex\")),\n\t\t\t)\n\t\t\t.execute()\n\t\tfor (const r of rows) presentBlobs.add(r.oid.toString(\"hex\"))\n\t}\n\tfor (const b of blobCandidates) (presentBlobs.has(b) ? present : missing).add(b)\n\treturn { missing, present }\n}\n\n/** Does `want`'s commit/tag ancestry (edge kinds 2,5) reach any oid in `common`?\n * The ancestry-only CTE that underpins `readyToGiveUp`. */\nexport async function ancestryReachesCommon(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\twant: string,\n\tcommonBufs: Buffer[],\n): Promise<boolean> {\n\tif (commonBufs.length === 0) return false\n\tconst commons = sql.join(commonBufs.map((b) => sql`(${b}::bytea)`))\n\tconst result = await sql<{ reached: boolean }>`\n\t\twith recursive anc(oid) as (\n\t\t\tselect ${Buffer.from(want, \"hex\")}::bytea\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin anc a on e.parent = a.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t\t\t\tand e.kind in (${EDGE_KIND.COMMIT_PARENT}, ${EDGE_KIND.TAG_TARGET})\n\t\t)\n\t\tselect exists (\n\t\t\tselect 1 from anc join (values ${commons}) as c(oid) on c.oid = anc.oid\n\t\t) as reached\n\t`.execute(db)\n\treturn result.rows[0]?.reached ?? false\n}\n","import { createHash } from \"node:crypto\"\nimport { sql } from \"kysely\"\nimport type { Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport { type CopyValue, copyInsert } from \"@/database/copy-insert\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { count, withPhase } from \"@/instrument\"\nimport { deriveEdges, EDGE_KIND, validateObject } from \"@/object/edges\"\nimport { computeOid, type GitObjectType } from \"@/object/object\"\nimport { PACK_OBJ_TYPE } from \"@/pack/object-header\"\nimport { readPack } from \"@/pack/read-pack\"\nimport {\n\ttype PackInputObject,\n\tpackHeader,\n\tpackObject,\n\twritePack,\n} from \"@/pack/write-pack\"\nimport { WantNotFoundError } from \"@/protocol/errors\"\nimport { ancestryReachesCommon, reachableClosure } from \"@/store/reachability\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\n/** Objects fetched per round-trip when streaming content into a served pack. */\nconst PACK_BATCH = 1000\n\n/**\n * A stored object at/over this size is read in size-bounded chunks, never in one\n * round-trip. The porsager driver decodes a `bytea` RESULT from its text form\n * (`\\x` + hex, DOUBLE the byte length), so a value over ~256MiB would build a JS\n * string past V8's max length and throw on the SERVE path — the read-side mirror of\n * the ingest string-cap that binary COPY fixed (a07/blb01). Kept well under the cap\n * so the doubled hex of a single chunk stays safely below it.\n */\nconst BIG_OBJECT_BYTES = 200_000_000\nconst READ_CHUNK_BYTES = 100_000_000\n\n/** Split `items` into consecutive batches of at most `size`. */\nfunction batches<T>(items: T[], size: number): T[][] {\n\tconst out: T[][] = []\n\tfor (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n\treturn out\n}\n\nexport type StoredObject = {\n\ttype: GitObjectType\n\tcontent: Buffer\n}\n\nexport type ObjectStore = ReturnType<typeof createObjectStore>\n\n// `git_object.type` stores the pack object-type code (1 commit, 2 tree, 3 blob,\n// 4 tag) — so it maps straight to the pack header on serve. Mirrors the codec's\n// own private map (write-pack.ts), referencing the same constants; the codec\n// stays storage-independent and untouched.\nconst TYPE_TO_CODE: Record<GitObjectType, number> = {\n\tblob: PACK_OBJ_TYPE.BLOB,\n\tcommit: PACK_OBJ_TYPE.COMMIT,\n\ttag: PACK_OBJ_TYPE.TAG,\n\ttree: PACK_OBJ_TYPE.TREE,\n}\n\nconst CODE_TO_TYPE = new Map<number, GitObjectType>([\n\t[PACK_OBJ_TYPE.BLOB, \"blob\"],\n\t[PACK_OBJ_TYPE.COMMIT, \"commit\"],\n\t[PACK_OBJ_TYPE.TAG, \"tag\"],\n\t[PACK_OBJ_TYPE.TREE, \"tree\"],\n])\n\nfunction typeFromCode(code: number): GitObjectType {\n\tconst type = CODE_TO_TYPE.get(code)\n\tif (!type) throw new Error(`object-store: unknown git object type code ${code}`)\n\treturn type\n}\n\n/**\n * Postgres-backed git object store. Each immutable object is one row in the\n * per-repo, HASH-partitioned `git_object` (raw 20-byte `bytea` OID, pack type\n * code, raw inflated body lz4-TOASTed Postgres-side) — packs are a transport\n * encoding produced on serve and consumed on ingest, never stored. So a fetch is\n * a primary-key point-read, not a whole-pack re-inflate.\n *\n * The store is the wire→DB boundary: callers speak hex OIDs and the wire repo\n * name; OIDs are coerced hex↔raw here, and the repo name is resolved to its\n * bigint surrogate (memoized) here.\n */\nexport function createObjectStore(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\tconst store = {\n\t\t/**\n\t\t * Build the served pack for a fetch: the want-closure minus the have-closure,\n\t\t * re-adding the explicit wants (promisor lazy-fetch roots — a partial clone may\n\t\t * want a blob reachable from a tree it already has, so it must not be\n\t\t * subtracted). The object count is known from the closure before any content is\n\t\t * read; content then streams in keyset batches into the pack encoder, so only\n\t\t * one batch of inflated content is ever held (never the whole repo).\n\t\t */\n\t\tasync buildPack(\n\t\t\trepoId: string,\n\t\t\twants: string[],\n\t\t\thaves: string[],\n\t\t\tomitBlobs: boolean,\n\t\t\tincludeTag = false,\n\t\t): Promise<Buffer> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null || wants.length === 0) return writePack([])\n\n\t\t\tconst served = await withPhase(\"closure\", async () => {\n\t\t\t\tconst want = await reachableClosure(db, id, wants, omitBlobs)\n\t\t\t\t// A want whose closure is incomplete cannot be served (git rejects it\n\t\t\t\t// too) — fail loud rather than ship a short pack. The have side may be\n\t\t\t\t// incomplete (we just don't subtract what we lack), so only wants matter.\n\t\t\t\tif (want.missing.size > 0) {\n\t\t\t\t\tthrow new WantNotFoundError([...want.missing])\n\t\t\t\t}\n\t\t\t\tconst have =\n\t\t\t\t\thaves.length > 0\n\t\t\t\t\t\t? await reachableClosure(db, id, haves, omitBlobs)\n\t\t\t\t\t\t: { missing: new Set<string>(), present: new Set<string>() }\n\t\t\t\tconst set = new Set<string>()\n\t\t\t\tfor (const o of want.present) if (!have.present.has(o)) set.add(o)\n\t\t\t\t// Under a blobless/partial filter the client may explicitly want an object\n\t\t\t\t// reachable from a `have` whose closure it does NOT fully possess (a promisor\n\t\t\t\t// root the omitBlobs subtraction drops), so re-add those wants. On an unfiltered\n\t\t\t\t// fetch a `have` implies its whole closure — a want already in it is genuinely\n\t\t\t\t// owned, so re-adding would re-send what the client has (a non-minimal pack).\n\t\t\t\tif (omitBlobs) for (const w of wants) if (want.present.has(w)) set.add(w)\n\t\t\t\tif (includeTag) await augmentWithTags(id, set)\n\t\t\t\treturn [...set]\n\t\t\t})\n\n\t\t\treturn withPhase(\"pack-encode\", async () => {\n\t\t\t\tconst hash = createHash(\"sha1\")\n\t\t\t\tconst parts: Buffer[] = []\n\t\t\t\tconst push = (chunk: Buffer) => {\n\t\t\t\t\thash.update(chunk)\n\t\t\t\t\tparts.push(chunk)\n\t\t\t\t}\n\t\t\t\tpush(packHeader(served.length))\n\t\t\t\tfor (const batch of batches(served, PACK_BATCH)) {\n\t\t\t\t\tconst rows = await db\n\t\t\t\t\t\t.selectFrom(\"git_object\")\n\t\t\t\t\t\t.select([\"oid\", \"type\"])\n\t\t\t\t\t\t// Keep a >256MiB blob's content server-side (CASE → NULL on the wire) so the\n\t\t\t\t\t\t// porsager driver never builds its over-cap `\\x`+hex string; oversized rows\n\t\t\t\t\t\t// (content NULL here) are read in size-bounded chunks below.\n\t\t\t\t\t\t.select(sql<number>`octet_length(content)`.as(\"size\"))\n\t\t\t\t\t\t.select(\n\t\t\t\t\t\t\tsql<Buffer | null>`case when octet_length(content) < ${BIG_OBJECT_BYTES} then content end`.as(\n\t\t\t\t\t\t\t\t\"content\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t\t\t.where(\n\t\t\t\t\t\t\t\"oid\",\n\t\t\t\t\t\t\t\"in\",\n\t\t\t\t\t\t\tbatch.map((h) => Buffer.from(h, \"hex\")),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.execute()\n\t\t\t\t\tfor (const r of rows) {\n\t\t\t\t\t\tconst content = r.content ?? (await readContentChunked(id, r.oid, r.size))\n\t\t\t\t\t\tpush(packObject(typeFromCode(r.type), content))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst pack = Buffer.concat([...parts, hash.digest()])\n\t\t\t\tcount(\"objectsServed\", served.length)\n\t\t\t\tcount(\"packBytes\", pack.length)\n\t\t\t\treturn pack\n\t\t\t})\n\t\t},\n\n\t\t/** The subset of `haves` this repo actually has — the negotiation common set,\n\t\t * in one indexed lookup rather than a per-have probe. */\n\t\tasync commonHaves(repoId: string, haves: string[]): Promise<string[]> {\n\t\t\tif (haves.length === 0) return []\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return []\n\t\t\tconst rows = await db\n\t\t\t\t.selectFrom(\"git_object\")\n\t\t\t\t.select(\"oid\")\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\n\t\t\t\t\t\"oid\",\n\t\t\t\t\t\"in\",\n\t\t\t\t\thaves.map((h) => Buffer.from(h, \"hex\")),\n\t\t\t\t)\n\t\t\t\t.execute()\n\t\t\t// Preserve the client's `have` order (the ACK lines echo it) — the `in`\n\t\t\t// query returns rows in arbitrary order.\n\t\t\tconst present = new Set(rows.map((r) => r.oid.toString(\"hex\")))\n\t\t\treturn haves.filter((h) => present.has(h))\n\t\t},\n\t\tasync getObject(repoId: string, oid: string): Promise<StoredObject | null> {\n\t\t\tcount(\"getObjectCalls\")\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return null\n\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"git_object\")\n\t\t\t\t.select([\"type\"])\n\t\t\t\t.select(sql<number>`octet_length(content)`.as(\"size\"))\n\t\t\t\t.select(\n\t\t\t\t\tsql<Buffer | null>`case when octet_length(content) < ${BIG_OBJECT_BYTES} then content end`.as(\n\t\t\t\t\t\t\"content\",\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"oid\", \"=\", Buffer.from(oid, \"hex\"))\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (!row) return null\n\n\t\t\t// A >256MiB object comes back with content NULL (the CASE guard); read it chunked\n\t\t\t// so its bytes never transit the porsager driver as one over-cap hex string.\n\t\t\tconst content =\n\t\t\t\trow.content ?? (await readContentChunked(id, Buffer.from(oid, \"hex\"), row.size))\n\t\t\tcount(\"objectBytesRead\", content.length)\n\t\t\treturn { content, type: typeFromCode(row.type) }\n\t\t},\n\n\t\tasync hasObject(repoId: string, oid: string): Promise<boolean> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return false\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"git_object\")\n\t\t\t\t.select(\"oid\")\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"oid\", \"=\", Buffer.from(oid, \"hex\"))\n\t\t\t\t.executeTakeFirst()\n\t\t\treturn row !== undefined\n\t\t},\n\n\t\t/**\n\t\t * Ingest a received pack: parse it — resolving in-pack deltas, and thin-pack\n\t\t * REF_DELTA bases against objects already in this repo — then insert every\n\t\t * resolved object as a row.\n\t\t */\n\t\tasync ingestPack(repoId: string, packBytes: Buffer): Promise<{ oids: string[] }> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tconst parsed = await readPack(packBytes, (oid) => store.getObject(repoId, oid))\n\t\t\tconst oids = await insertObjects(\n\t\t\t\tid,\n\t\t\t\tparsed.map((p) => ({ content: p.content, type: p.type })),\n\t\t\t)\n\t\t\treturn { oids }\n\t\t},\n\n\t\t/**\n\t\t * Connectivity check (spec §5.2): is every object reachable from `oid` present?\n\t\t * A push whose new tip fails this references an object the pack neither carried\n\t\t * nor delta-resolved, and must be rejected. Delegates to the one reachability\n\t\t * engine (`reachableClosure`) shared with clone/fetch, so connectivity and\n\t\t * serving can never disagree on what is reachable. Full-closure (matching the\n\t\t * old walk's scope); the bounded \"new objects only\" form is a deferred\n\t\t * optimization (OQ-14).\n\t\t */\n\t\tasync isConnected(repoId: string, oid: string): Promise<boolean> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return false\n\t\t\tconst { missing } = await reachableClosure(db, id, [oid], false)\n\t\t\treturn missing.size === 0\n\t\t},\n\n\t\t/** Seed objects directly (the differential harness + perf bench path): insert\n\t\t * each as a row, idempotently. Equivalent to `ingestPack` minus the pack codec. */\n\t\tasync putPack(\n\t\t\trepoId: string,\n\t\t\tobjects: PackInputObject[],\n\t\t): Promise<{ oids: string[] }> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tconst oids = await insertObjects(id, objects)\n\t\t\treturn { oids }\n\t\t},\n\n\t\t/**\n\t\t * git's `ok_to_give_up`: ready once every want reaches a common have by commit/\n\t\t * tag ancestry (the haves form a cut below all wants, so the delta is well-\n\t\t * defined). One ancestry CTE (edge kinds 2,5) per want replaces `reachesCommon`'s\n\t\t * per-object BFS. Generation-number pruning is a deferred §6.4 lever.\n\t\t */\n\t\tasync readyToGiveUp(\n\t\t\trepoId: string,\n\t\t\twants: string[],\n\t\t\tcommon: string[],\n\t\t): Promise<boolean> {\n\t\t\tif (common.length === 0) return false\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return false\n\t\t\tconst commonBufs = common.map((h) => Buffer.from(h, \"hex\"))\n\t\t\tfor (const want of wants) {\n\t\t\t\tif (!(await ancestryReachesCommon(db, id, want, commonBufs))) return false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t}\n\n\t/** Insert objects as rows + their derived edges, idempotent (re-sent objects are\n\t * skipped). Each object row and its complete edge set go in ONE transaction from\n\t * ONE derivation (§10.1) — so no object ever exists without its edges. Edge\n\t * derivation validates at the boundary and throws on malformed content (§5.1),\n\t * aborting the ingest before any row lands. Returns every object's hex OID, in\n\t * input order. */\n\tasync function insertObjects(\n\t\tid: Awaited<ReturnType<typeof repos.ensureRepoId>>,\n\t\tobjects: PackInputObject[],\n\t): Promise<string[]> {\n\t\tconst entries = objects.map((obj) => {\n\t\t\tvalidateObject(obj.type, obj.content)\n\t\t\tconst hex = computeOid(obj.type, obj.content)\n\t\t\tconst oid = Buffer.from(hex, \"hex\")\n\t\t\treturn {\n\t\t\t\tedges: deriveEdges(obj.type, obj.content).map((e) => ({\n\t\t\t\t\tchild: Buffer.from(e.child, \"hex\"),\n\t\t\t\t\tkind: e.kind,\n\t\t\t\t\tparent: oid,\n\t\t\t\t\trepo_id: id,\n\t\t\t\t})),\n\t\t\t\thex,\n\t\t\t\trow: {\n\t\t\t\t\tcontent: obj.content,\n\t\t\t\t\toid,\n\t\t\t\t\trepo_id: id,\n\t\t\t\t\tsize: obj.content.length,\n\t\t\t\t\ttype: TYPE_TO_CODE[obj.type],\n\t\t\t\t},\n\t\t\t}\n\t\t})\n\t\tif (entries.length === 0) return []\n\n\t\tconst objectRows: CopyValue[][] = entries.map((e) => [\n\t\t\t{ t: \"int8\", v: e.row.repo_id },\n\t\t\t{ t: \"bytea\", v: e.row.oid },\n\t\t\t{ t: \"int2\", v: e.row.type },\n\t\t\t{ t: \"int4\", v: e.row.size },\n\t\t\t{ t: \"bytea\", v: e.row.content },\n\t\t])\n\t\tconst edgeRows: CopyValue[][] = entries.flatMap((e) =>\n\t\t\te.edges.map((edge): CopyValue[] => [\n\t\t\t\t{ t: \"int8\", v: edge.repo_id },\n\t\t\t\t{ t: \"bytea\", v: edge.parent },\n\t\t\t\t{ t: \"bytea\", v: edge.child },\n\t\t\t\t{ t: \"int2\", v: edge.kind },\n\t\t\t]),\n\t\t)\n\t\t// One transaction (the object⟺edges invariant, §10.1) via COPY into staging:\n\t\t// no bind-parameter ceiling and content streams as raw bytes (see copyInsert),\n\t\t// so neither object count nor blob size has a hard wall. Empty edgeRows (an\n\t\t// all-blob push) is a no-op, never an empty insert.\n\t\tawait pg.begin(async (tx) => {\n\t\t\tawait copyInsert(\n\t\t\t\ttx,\n\t\t\t\t\"git_object\",\n\t\t\t\t[\"repo_id\", \"oid\", \"type\", \"size\", \"content\"],\n\t\t\t\tobjectRows,\n\t\t\t)\n\t\t\tawait copyInsert(tx, \"git_edge\", [\"repo_id\", \"parent\", \"child\", \"kind\"], edgeRows)\n\t\t})\n\t\t// Stamp the repo's GC-activity watermark AFTER the ingest commits — never inside\n\t\t// the txn, where `clock_timestamp()` would be read BEFORE the commit. These objects\n\t\t// are reclaim candidates (a force-commit orphans the prior snapshot the instant the\n\t\t// ref moves), so the self-scheduling drain must judge this repo eligible\n\t\t// (gc-scheduler.ts §2). Stamping post-commit guarantees `last_pushed_at` is never\n\t\t// earlier than the commit of the orphan it announces, so a drain reading\n\t\t// `t0 = clock_timestamp()` after these objects' commit can't settle\n\t\t// `last_gc_at >= last_pushed_at` and lose that garbage — the same no-lost-garbage\n\t\t// rule refs-store's post-commit `stampPushed` follows. A tiny single-row HOT update\n\t\t// on the churn-tuned `repos` (0004) — reached only on a non-empty ingest (the empty\n\t\t// case returned above).\n\t\tawait pg`update repos set last_pushed_at = clock_timestamp() where id = ${id}::bigint`\n\t\treturn entries.map((e) => e.hex)\n\t}\n\n\t/**\n\t * Read a single object's `content` in size-bounded chunks via `substring`, so a\n\t * blob larger than V8's max string length never reaches the porsager driver as one\n\t * over-cap `\\x`+hex string — the serve-side mirror of the binary COPY ingest. Used\n\t * only for objects at/over BIG_OBJECT_BYTES (smaller content comes back inline).\n\t */\n\tasync function readContentChunked(\n\t\tid: ReposId,\n\t\toid: Buffer,\n\t\tsize: number,\n\t): Promise<Buffer> {\n\t\tconst parts: Buffer[] = []\n\t\tfor (let off = 0; off < size; off += READ_CHUNK_BYTES) {\n\t\t\tconst len = Math.min(READ_CHUNK_BYTES, size - off)\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"git_object\")\n\t\t\t\t.select(sql<Buffer>`substring(content from ${off + 1} for ${len})`.as(\"chunk\"))\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"oid\", \"=\", oid)\n\t\t\t\t.executeTakeFirstOrThrow()\n\t\t\tparts.push(row.chunk)\n\t\t}\n\t\treturn Buffer.concat(parts)\n\t}\n\n\t/**\n\t * include-tag augmentation (§6.5): annotated tags whose peeled target is in the\n\t * served set get their tag OBJECTS added — transitively over `kind=5`, so a\n\t * tag-of-tag chain ships every tag object in it (each must be present for the\n\t * client's fsck). Annotated tags are few, so we fetch them all and filter by\n\t * served membership app-side rather than feeding the whole served set into SQL.\n\t * Mutates `served`. Peeled targets are already in `served` (they qualified the\n\t * tag), so re-adding the chain's terminal commit is a no-op.\n\t */\n\tasync function augmentWithTags(id: ReposId, served: Set<string>): Promise<void> {\n\t\tconst tagRefs = await db\n\t\t\t.selectFrom(\"git_ref\")\n\t\t\t.select([\"oid\", \"peeled_oid\"])\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\"oid\", \"is not\", null)\n\t\t\t.where(\"peeled_oid\", \"is not\", null)\n\t\t\t.execute()\n\t\tconst qualifying = tagRefs\n\t\t\t.filter((r) => r.peeled_oid !== null && served.has(r.peeled_oid.toString(\"hex\")))\n\t\t\t.map((r) => (r.oid as Buffer).toString(\"hex\"))\n\t\tif (qualifying.length === 0) return\n\n\t\tconst seed = sql.join(qualifying.map((r) => sql`(${Buffer.from(r, \"hex\")}::bytea)`))\n\t\tconst chain = await sql<{ oid: Buffer }>`\n\t\t\twith recursive tags(oid) as (\n\t\t\t\tselect oid from (values ${seed}) as roots(oid)\n\t\t\t\tunion\n\t\t\t\tselect e.child from git_edge e\n\t\t\t\t\tjoin tags t on e.parent = t.oid\n\t\t\t\t\twhere e.repo_id = ${id}::bigint and e.kind = ${EDGE_KIND.TAG_TARGET}\n\t\t\t)\n\t\t\tselect oid from tags\n\t\t`.execute(db)\n\t\tfor (const r of chain.rows) served.add(r.oid.toString(\"hex\"))\n\t}\n\n\treturn store\n}\n","import { type Kysely, sql } from \"kysely\"\nimport type { Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport type { GitRefName } from \"@/database/models/public/GitRef\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { EDGE_KIND } from \"@/object/edges\"\nimport { PACK_OBJ_TYPE } from \"@/pack/object-header\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\nexport type RefRow = { name: string; oid: string; peeled?: string }\n\n/** A ref change: create (oldOid=zero), update (old→new), or delete (newOid=zero). */\nexport type RefUpdate = { oldOid: string; newOid: string; ref: string }\n\nexport type RefStore = ReturnType<typeof createRefStore>\n\nconst isZero = (oid: string): boolean => /^0{40}$/.test(oid)\n\nconst toOid = (hex: string): Buffer => Buffer.from(hex, \"hex\")\n\n/**\n * A ref CAS discriminated on the wire hex strings — BEFORE any `bytea` coercion.\n * The all-zeros sentinel marks create/delete and is classified here, so it can\n * never be coerced to a real all-zero `bytea` and reach a CAS `WHERE` (which would\n * corrupt a ref instead of deleting it). Only the genuine OIDs become `bytea`.\n */\ntype RefOp =\n\t| { kind: \"create\"; newOid: Buffer }\n\t// `oldOid: null` ⇒ the client asserted no expected value (zero old-oid): an\n\t// unconditional delete — drop the ref if present, a no-op success otherwise.\n\t| { kind: \"delete\"; oldOid: Buffer | null }\n\t| { kind: \"update\"; oldOid: Buffer; newOid: Buffer }\n\nfunction classifyRefUpdate(cmd: RefUpdate): RefOp {\n\tconst create = isZero(cmd.oldOid)\n\tconst del = isZero(cmd.newOid)\n\t// A zero new-oid is a delete regardless of the old-oid. git sends `<zero>\n\t// <zero> ref` to delete a ref it knows no value for — including one that does\n\t// not exist, which canonical receive-pack reports as a no-op success — so a\n\t// zero old-oid here means \"delete unconditionally\" (oldOid null), never a\n\t// malformed command. The all-zeros sentinel is classified away here and never\n\t// coerced into a real all-zero bytea.\n\tif (del) return { kind: \"delete\", oldOid: create ? null : toOid(cmd.oldOid) }\n\tif (create) return { kind: \"create\", newOid: toOid(cmd.newOid) }\n\treturn { kind: \"update\", newOid: toOid(cmd.newOid), oldOid: toOid(cmd.oldOid) }\n}\n\n/**\n * The peeled target of a ref oid: if it is an annotated tag, follow the `kind=5`\n * (tag→target) chain — while the current node is a tag — to its terminal non-tag\n * object. A branch or a lightweight tag (the oid is not a tag object) peels to\n * `null` → `ls-refs` emits no `peeled` line. git imposes NO depth bound on ref\n * peeling, so neither do we: the `is_tag` predicate terminates the recursion at the\n * first non-tag, and a content-addressed tag chain is acyclic (an oid cannot embed\n * its own hash) hence finite. Computed at ref-write, so the tag's edges + target\n * are already present (connectivity proved the chain on push). Replaces the\n * per-`ls-refs` app-side tag walk.\n */\nasync function peelRef(\n\texec: Kysely<Database>,\n\trepoId: ReposId,\n\toid: Buffer,\n): Promise<Buffer | null> {\n\tconst result = await sql<{ peeled: Buffer }>`\n\t\twith recursive chain(oid, is_tag, depth) as (\n\t\t\tselect o.oid, o.type = ${PACK_OBJ_TYPE.TAG}, 0\n\t\t\t\tfrom git_object o\n\t\t\t\twhere o.repo_id = ${repoId}::bigint and o.oid = ${oid}::bytea\n\t\t\tunion all\n\t\t\tselect e.child, co.type = ${PACK_OBJ_TYPE.TAG}, c.depth + 1\n\t\t\t\tfrom chain c\n\t\t\t\tjoin git_edge e\n\t\t\t\t\ton e.repo_id = ${repoId}::bigint\n\t\t\t\t\tand e.parent = c.oid\n\t\t\t\t\tand e.kind = ${EDGE_KIND.TAG_TARGET}\n\t\t\t\tleft join git_object co\n\t\t\t\t\ton co.repo_id = ${repoId}::bigint and co.oid = e.child\n\t\t\t\twhere c.is_tag\n\t\t)\n\t\tselect oid as peeled from chain where not is_tag and depth > 0\n\t\t\torder by depth desc limit 1\n\t`.execute(exec)\n\treturn result.rows[0]?.peeled ?? null\n}\n\n/** Sentinel thrown inside a transaction to roll an atomic batch all the way back. */\nclass AtomicAbort extends Error {}\n\n/**\n * A repo is born with `HEAD → refs/heads/main`, mirroring `git init --bare`\n * (init.defaultBranch). Established lazily on the first ref write — a repo's birth\n * is its first push — and never overwritten (do-nothing on conflict). So once the\n * default branch exists `ls-refs` advertises HEAD and a clone checks it out;\n * before then HEAD dangles unadvertised, exactly like a bare repo whose HEAD\n * points at an unborn `main`.\n */\nconst DEFAULT_HEAD_TARGET = \"refs/heads/main\"\n\nasync function ensureHeadDefault(exec: Kysely<Database>, repoId: ReposId): Promise<void> {\n\tawait exec\n\t\t.insertInto(\"git_ref\")\n\t\t.values({\n\t\t\tname: \"HEAD\" as GitRefName,\n\t\t\trepo_id: repoId,\n\t\t\tsymref_target: DEFAULT_HEAD_TARGET,\n\t\t})\n\t\t.onConflict((oc) => oc.columns([\"repo_id\", \"name\"]).doNothing())\n\t\t.execute()\n}\n\n/**\n * Stamp the repo's GC-activity watermark (`repos.last_pushed_at`) — a ref change\n * makes the prior tip a reclaim candidate, so the self-scheduling drain must judge\n * the repo eligible (gc-scheduler.ts §2). Called only when a ref ROW actually\n * changed (not on a no-op success like deleting an absent ref), so non-mutating\n * traffic never re-triggers GC. A tiny single-row HOT update on the churn-tuned\n * `repos` (0004); `clock_timestamp()` is the server-side wall clock.\n */\nasync function stampPushed(exec: Kysely<Database>, repoId: ReposId): Promise<void> {\n\tawait exec\n\t\t.updateTable(\"repos\")\n\t\t.set({ last_pushed_at: sql`clock_timestamp()` })\n\t\t.where(\"id\", \"=\", repoId)\n\t\t.execute()\n}\n\n/** The outcome of one CAS: `ok` is the report-status success the client sees;\n * `mutated` is whether a ref row actually changed. They differ only for an\n * unconditional delete of an absent ref — a no-op SUCCESS that mutated nothing,\n * which must NOT stamp activity. */\ntype CasResult = { ok: boolean; mutated: boolean }\n\n/**\n * Apply one ref change by compare-and-swap against the client's advertised old\n * oid, on the given executor (the db, or a transaction for an atomic batch).\n * Returns the report-status `ok` and whether a row actually changed (`mutated`).\n * Non-ff is accepted by default — CAS guards concurrency, not ancestry (spec §3.6).\n */\nasync function casRefUpdate(\n\texec: Kysely<Database>,\n\trepoId: ReposId,\n\tcmd: RefUpdate,\n): Promise<CasResult> {\n\tconst name = cmd.ref as GitRefName\n\tconst op = classifyRefUpdate(cmd)\n\tswitch (op.kind) {\n\t\tcase \"create\": {\n\t\t\tconst peeled = await peelRef(exec, repoId, op.newOid)\n\t\t\tconst rows = await exec\n\t\t\t\t.insertInto(\"git_ref\")\n\t\t\t\t.values({ name, oid: op.newOid, peeled_oid: peeled, repo_id: repoId })\n\t\t\t\t.onConflict((oc) => oc.doNothing())\n\t\t\t\t.returningAll()\n\t\t\t\t.execute()\n\t\t\tconst mutated = rows.length === 1\n\t\t\treturn { mutated, ok: mutated }\n\t\t}\n\t\tcase \"delete\": {\n\t\t\t// CAS the delete on the asserted old-oid; an unconditional delete (zero\n\t\t\t// old-oid ⇒ null) drops the ref by name and succeeds even when it was\n\t\t\t// already absent — git treats deleting a non-existent ref as a no-op. That\n\t\t\t// no-op is `ok` but NOT a mutation (no row removed), so it does not stamp.\n\t\t\tlet q = exec\n\t\t\t\t.deleteFrom(\"git_ref\")\n\t\t\t\t.where(\"repo_id\", \"=\", repoId)\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\tif (op.oldOid !== null) q = q.where(\"oid\", \"=\", op.oldOid)\n\t\t\tconst rows = await q.returningAll().execute()\n\t\t\tconst mutated = rows.length === 1\n\t\t\treturn { mutated, ok: op.oldOid === null || mutated }\n\t\t}\n\t\tcase \"update\": {\n\t\t\tconst peeled = await peelRef(exec, repoId, op.newOid)\n\t\t\tconst rows = await exec\n\t\t\t\t.updateTable(\"git_ref\")\n\t\t\t\t.set({ oid: op.newOid, peeled_oid: peeled, symref_target: null })\n\t\t\t\t.where(\"repo_id\", \"=\", repoId)\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.where(\"oid\", \"=\", op.oldOid)\n\t\t\t\t.returningAll()\n\t\t\t\t.execute()\n\t\t\tconst mutated = rows.length === 1\n\t\t\treturn { mutated, ok: mutated }\n\t\t}\n\t}\n}\n\n/**\n * Postgres-backed git refs: direct refs (name → oid) and symbolic refs\n * (HEAD → refs/heads/...). Push applies ref changes through `applyRefUpdates`;\n * `setRef`/`setSymref` are the seeding helpers.\n *\n * Like the object store, this is the wire→DB boundary: the repo name resolves to\n * its bigint surrogate (memoized) here, ref names cast to their branded column\n * type, and oids coerce hex↔raw `bytea`.\n */\nexport function createRefStore(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\treturn {\n\t\t/**\n\t\t * Apply a batch of ref CAS updates. Non-atomic (the default push mode): each\n\t\t * ref is independent and the returned flags are per-command. Atomic\n\t\t * (`--atomic`): all-or-nothing in one transaction — if any CAS fails, every\n\t\t * command is rolled back and the result is all-false (spec §3.6).\n\t\t */\n\t\tasync applyRefUpdates(\n\t\t\trepoId: string,\n\t\t\tcommands: RefUpdate[],\n\t\t\tatomic: boolean,\n\t\t): Promise<boolean[]> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tawait ensureHeadDefault(db, id)\n\t\t\tif (!atomic) {\n\t\t\t\tconst results: boolean[] = []\n\t\t\t\tlet mutated = false\n\t\t\t\tfor (const cmd of commands) {\n\t\t\t\t\tconst r = await casRefUpdate(db, id, cmd)\n\t\t\t\t\tresults.push(r.ok)\n\t\t\t\t\tif (r.mutated) mutated = true\n\t\t\t\t}\n\t\t\t\t// One activity stamp per push, only when a ref actually changed — a batch\n\t\t\t\t// of pure no-ops leaves the watermark untouched (so GC is not re-triggered).\n\t\t\t\tif (mutated) await stampPushed(db, id)\n\t\t\t\treturn results\n\t\t\t}\n\t\t\t// Atomic batch: take the per-ref row locks in a deterministic by-name order\n\t\t\t// so two concurrent batches touching the same refs can never form a lock\n\t\t\t// cycle (Postgres 40P01 deadlock). An atomic result is uniform — every CAS\n\t\t\t// succeeds or the first failure aborts the whole batch — so the by-name CAS\n\t\t\t// order never affects the per-command flags, which stay in input order.\n\t\t\tconst ordered = [...commands].sort((a, b) =>\n\t\t\t\ta.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0,\n\t\t\t)\n\t\t\tlet anyMutated = false\n\t\t\ttry {\n\t\t\t\tawait db.transaction().execute(async (trx) => {\n\t\t\t\t\tfor (const cmd of ordered) {\n\t\t\t\t\t\tconst r = await casRefUpdate(trx, id, cmd)\n\t\t\t\t\t\tif (!r.ok) throw new AtomicAbort()\n\t\t\t\t\t\tif (r.mutated) anyMutated = true\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof AtomicAbort) return commands.map(() => false)\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\t// Stamp AFTER the batch commits — NOT inside the txn. `clock_timestamp()` must\n\t\t\t// be read at/after the ref-move's COMMIT so the activity watermark is never\n\t\t\t// stamped earlier than the orphan it announces; an in-txn stamp evaluates\n\t\t\t// before commit, letting a concurrent GC pass write `last_gc_at` past it and\n\t\t\t// lose that garbage forever (the GC primitive's snapshot still protects\n\t\t\t// liveness, so this is leak-not-corruption — but a leak the durable signal is\n\t\t\t// meant to prevent). Mirrors the non-atomic path, which already stamps after\n\t\t\t// its CAS commits.\n\t\t\tif (anyMutated) await stampPushed(db, id)\n\t\t\treturn commands.map(() => true)\n\t\t},\n\n\t\tasync getSymref(repoId: string, name: string): Promise<string | null> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return null\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"git_ref\")\n\t\t\t\t.select(\"symref_target\")\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"name\", \"=\", name as GitRefName)\n\t\t\t\t.executeTakeFirst()\n\t\t\treturn row?.symref_target ?? null\n\t\t},\n\n\t\t/** Direct refs (name → oid + peeled tag target), sorted by name. Excludes\n\t\t * symbolic refs. */\n\t\tasync listRefs(repoId: string): Promise<RefRow[]> {\n\t\t\tconst id = await repos.resolveRepoId(repoId)\n\t\t\tif (id === null) return []\n\t\t\tconst rows = await db\n\t\t\t\t.selectFrom(\"git_ref\")\n\t\t\t\t.select([\"name\", \"oid\", \"peeled_oid\"])\n\t\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t\t.where(\"oid\", \"is not\", null)\n\t\t\t\t.orderBy(\"name\")\n\t\t\t\t.execute()\n\t\t\treturn rows.map((r) => ({\n\t\t\t\tname: r.name,\n\t\t\t\toid: (r.oid as Buffer).toString(\"hex\"),\n\t\t\t\tpeeled: r.peeled_oid ? r.peeled_oid.toString(\"hex\") : undefined,\n\t\t\t}))\n\t\t},\n\n\t\tasync setRef(repoId: string, name: string, oid: string): Promise<void> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tconst value = toOid(oid)\n\t\t\tconst peeled = await peelRef(db, id, value)\n\t\t\tawait db\n\t\t\t\t.insertInto(\"git_ref\")\n\t\t\t\t.values({ name: name as GitRefName, oid: value, peeled_oid: peeled, repo_id: id })\n\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\toc\n\t\t\t\t\t\t.columns([\"repo_id\", \"name\"])\n\t\t\t\t\t\t.doUpdateSet({ oid: value, peeled_oid: peeled, symref_target: null }),\n\t\t\t\t)\n\t\t\t\t.execute()\n\t\t},\n\n\t\tasync setSymref(repoId: string, name: string, target: string): Promise<void> {\n\t\t\tconst id = await repos.ensureRepoId(repoId)\n\t\t\tawait db\n\t\t\t\t.insertInto(\"git_ref\")\n\t\t\t\t.values({ name: name as GitRefName, repo_id: id, symref_target: target })\n\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\toc\n\t\t\t\t\t\t.columns([\"repo_id\", \"name\"])\n\t\t\t\t\t\t// Clear peeled_oid too: a symref has no oid, so it can carry no peeled\n\t\t\t\t\t\t// target (else a stale value survives a tag→symref overwrite).\n\t\t\t\t\t\t.doUpdateSet({ oid: null, peeled_oid: null, symref_target: target }),\n\t\t\t\t)\n\t\t\t\t.execute()\n\t\t},\n\t}\n}\n","import type { Kysely } from \"kysely\"\nimport type { ReservedSql, Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport { copyInsert } from \"@/database/copy-insert\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { reachableClosure } from \"@/store/reachability\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\n/**\n * Per-repo reachability GC — the one piece of the Postgres-native redesign (§7)\n * that the rest of the spine deferred. See `docs/2026-06-24-force-commit-gc-design.md`\n * for the design; the observable contract is §4 of that doc, and the authoritative\n * algorithm is §7 of `internal/archived/2026-06-22-pggit-postgres-native-storage-\n * redesign.md`.\n *\n * The mechanism (data-structures-first): materialize the LIVE set — the reachable\n * closure from every ref tip — into an UNLOGGED table, then sweep `git_object` in\n * batched short transactions with a server-side anti-join (`NOT EXISTS`) against\n * that table plus a `created_at` grace cutoff. Reachability itself is NOT re-derived\n * here: it is exactly `reachableClosure(omitBlobs=false)`, the one engine clone /\n * fetch / connectivity already share, so GC can never disagree with them about what\n * is reachable.\n */\n\n/** Tunables for one GC pass. `graceSeconds` is REQUIRED — no silent default: an\n * object is reclaimed iff it is unreachable from every ref AND its `created_at`\n * is older than `graceSeconds` (0 ⇒ reclaim all unreachable; a huge value ⇒\n * retain). `batchLimit` caps the per-batch DELETE size (sweep tuning only — it\n * never changes the final observable state). `maintain` (default true) runs the\n * post-sweep VACUUM/REINDEX; the self-scheduling drain passes `false` so a\n * frequent per-repo pass never triggers a full-table VACUUM on the hot cadence\n * (autovacuum reclaims the GC churn instead). Maintenance is observable-neutral —\n * it changes dead-tuple bloat, never the row/clone state. */\nexport type GcOptions = { graceSeconds: number; batchLimit?: number; maintain?: boolean }\n\n/**\n * Internal-only test seam (NOT part of the public `GcOptions` contract): hooks the\n * GC pass at the one point §5 in-flight safety depends on. `afterLiveSet` is awaited\n * AFTER the live set is materialized and BEFORE the object sweep begins, so a test\n * can deterministically interpose a concurrent push there and assert the just-pushed\n * tip is never partially reclaimed. Test-only; do not document or use in production.\n */\ntype GcHooks = { afterLiveSet?: () => Promise<void> }\ntype InternalGcOptions = GcOptions & { _hooks?: GcHooks }\n\n/** What one GC pass reclaimed: the deleted `git_object` / `git_edge` row counts. */\nexport type GcResult = { deletedObjects: number; deletedEdges: number }\n\nexport type Gc = ReturnType<typeof createGc>\n\n/** Default per-batch DELETE cap when the caller omits `batchLimit`. Large enough to\n * sweep a typical force-commit orphan set in one or two batches, small enough to\n * bound the dead-tuple burst and lock duration per transaction (§7). */\nconst DEFAULT_BATCH_LIMIT = 10_000\n\n/** OIDs loaded per COPY round-trip into the live table (the live set can be the whole\n * reachable tree, so it streams in bounded batches, never one giant payload). */\nconst LIVE_LOAD_BATCH = 10_000\n\n/**\n * Build the GC over a porsager client (the same wire→DB boundary the object and ref\n * stores take). `gc(repo, opts)` reclaims a single repo's unreachable-and-old-enough\n * objects offline; reachable objects are always retained.\n */\nexport function createGc(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\treturn {\n\t\tasync gc(repo: string, opts: InternalGcOptions): Promise<GcResult> {\n\t\t\t// 1. Resolve the repo. A name never written has nothing to reclaim.\n\t\t\tconst id = await repos.resolveRepoId(repo)\n\t\t\tif (id === null) return { deletedEdges: 0, deletedObjects: 0 }\n\n\t\t\tconst batchLimit = opts.batchLimit ?? DEFAULT_BATCH_LIMIT\n\n\t\t\t// 2 + 3. Materialize the live set under a consistent snapshot.\n\t\t\t//\n\t\t\t// CONCURRENCY: the write/ingest path (`object-store.insertObjects`) does NOT\n\t\t\t// yet take a per-repo `pg_advisory_xact_lock` — that lock was deferred to this\n\t\t\t// GC chunk (redesign §5.4 / §12) and has no other consumer. So GC takes its\n\t\t\t// safety from two defenses (§5): (a) a REPEATABLE READ transaction, so the\n\t\t\t// ref-tip read and the closure walk see ONE consistent MVCC snapshot that\n\t\t\t// hides any push not yet committed when the snapshot opened; and (b) the\n\t\t\t// `created_at` grace below, which protects the present-but-unreachable window\n\t\t\t// (just-ingested objects a ref does not yet reach). When the write path adopts\n\t\t\t// the same per-repo advisory lock, GC should take that SAME key around this\n\t\t\t// read for full §5 mutual exclusion — DO NOT add a key here that the write path\n\t\t\t// does not also hold, or the lock would guard nothing.\n\t\t\t//\n\t\t\t// The live OIDs land in a per-repo UNLOGGED table (named by repo id, so two\n\t\t\t// DIFFERENT repos' GC passes never collide) — server-side so the sweep's\n\t\t\t// anti-join scales to a ~30k-orphan repo without pulling the orphan set through\n\t\t\t// JS. SINGLE-INSTANCE ONLY: two pggit processes GC'ing the SAME repo would share\n\t\t\t// this id-named table — B's `truncate`/`drop` could wipe A's live set mid-sweep,\n\t\t\t// and A's anti-join would then match (and delete) the whole reachable set. So the\n\t\t\t// deferred multi-instance advisory lock (redesign §5.4; scheduler design §8) MUST\n\t\t\t// wrap the ENTIRE pass (create→load→sweep→drop) AND the staging table be\n\t\t\t// instance-scoped, before a second instance is ever run.\n\t\t\tconst live = `gc_live_${id}`\n\t\t\tawait pg.unsafe(\n\t\t\t\t`create unlogged table if not exists ${live} (oid bytea primary key)`,\n\t\t\t)\n\t\t\ttry {\n\t\t\t\tawait pg.unsafe(`truncate ${live}`)\n\n\t\t\t\tconst roots = await liveSet(id)\n\t\t\t\tawait loadLive(live, roots)\n\n\t\t\t\t// TEST SEAM (§5 in-flight safety): interpose a concurrent push here, between\n\t\t\t\t// the live-set materialization and the object sweep.\n\t\t\t\tawait opts._hooks?.afterLiveSet?.()\n\n\t\t\t\t// 4. SWEEP objects: batched DELETE, each batch its own short transaction,\n\t\t\t\t// anti-join `NOT EXISTS` against the live set, `created_at` past the grace\n\t\t\t\t// cutoff. `clock_timestamp()` (not `now()`) so the cutoff advances per batch.\n\t\t\t\tconst deletedObjects = await sweepObjects(id, live, opts.graceSeconds, batchLimit)\n\n\t\t\t\t// 5. SWEEP edges: drop every edge whose PARENT object no longer survives —\n\t\t\t\t// run AFTER the object sweep, so a grace-retained object keeps its edges and a\n\t\t\t\t// surviving (reachable) parent's edges (which point only at reachable children)\n\t\t\t\t// never dangle.\n\t\t\t\tconst deletedEdges = await sweepEdges(id, batchLimit)\n\n\t\t\t\t// 6. MAINTENANCE (best-effort, not part of the counted deletion): reclaim the\n\t\t\t\t// dead tuples + reindex the walk index. VACUUM cannot run in a txn block, so\n\t\t\t\t// these are standalone statements run outside any transaction. Skipped when\n\t\t\t\t// the pass reclaimed nothing (no dead tuples to chase) or the caller opted out\n\t\t\t\t// (`maintain: false`, the drain's choice) — so a frequent per-repo drain never\n\t\t\t\t// triggers a full-table VACUUM/REINDEX on its hot cadence; the leaf partitions'\n\t\t\t\t// autovacuum (0001_init.ts) reclaims the GC churn. Observable-neutral either way.\n\t\t\t\tif (opts.maintain !== false && deletedObjects + deletedEdges > 0) {\n\t\t\t\t\tawait maintain()\n\t\t\t\t}\n\n\t\t\t\treturn { deletedEdges, deletedObjects }\n\t\t\t} finally {\n\t\t\t\tawait pg.unsafe(`drop table if exists ${live}`)\n\t\t\t}\n\t\t},\n\t}\n\n\t/**\n\t * The live set: the reachable closure from every ref tip, read under ONE\n\t * REPEATABLE READ snapshot so the ref-tip read and the multi-statement closure\n\t * walk cannot interleave with a concurrent push's ref update (§5 defense (a)).\n\t *\n\t * `reachableClosure` is the shared engine and takes a `Kysely`, but the\n\t * kysely-postgres-js dialect drives queries by calling `.reserve()` on its\n\t * `postgres` client for EACH query — so a plain pooled Kysely would scatter the\n\t * closure's statements across connections (no shared snapshot), and a\n\t * transaction-scoped `Sql` has no `.reserve()` at all. So we pin ONE porsager\n\t * connection, open a REPEATABLE READ transaction on it, and back a Kysely with a\n\t * shim whose `reserve()` always returns that pinned connection with a no-op\n\t * `release()` — every closure statement then runs on the one snapshotted\n\t * connection. The transaction is read-only; it commits (releasing the snapshot)\n\t * before the sweep's own short write transactions begin.\n\t */\n\tasync function liveSet(id: ReposId): Promise<Set<string>> {\n\t\tconst conn = await pg.reserve()\n\t\ttry {\n\t\t\tawait conn`begin isolation level repeatable read`\n\t\t\tconst pinned = pinnedKysely(conn)\n\t\t\tconst rows = await conn<{ oid: Buffer | null; peeled_oid: Buffer | null }[]>`\n\t\t\t\tselect oid, peeled_oid from git_ref where repo_id = ${id} and oid is not null\n\t\t\t`\n\t\t\t// Roots: every direct ref tip plus each annotated tag's peeled target. The\n\t\t\t// closure over kinds (1,2,3,5) already descends tag→target, so the peeled\n\t\t\t// target is redundant for the walk, but it is included to match §7 and stay\n\t\t\t// correct even if a tag ref's edge were ever absent.\n\t\t\tconst tips = new Set<string>()\n\t\t\tfor (const r of rows) {\n\t\t\t\tif (r.oid) tips.add(r.oid.toString(\"hex\"))\n\t\t\t\tif (r.peeled_oid) tips.add(r.peeled_oid.toString(\"hex\"))\n\t\t\t}\n\t\t\tconst { present } = await reachableClosure(pinned, id, [...tips], false)\n\t\t\tawait conn`commit`\n\t\t\treturn present\n\t\t} finally {\n\t\t\tconn.release()\n\t\t}\n\t}\n\n\t/** A Kysely pinned to a single porsager connection: its dialect `reserve()`s the\n\t * same connection for every statement (so a multi-statement read shares one MVCC\n\t * snapshot) and `release()` is a no-op (the caller owns the connection's lifetime).\n\t * The shim is a callable with a `reserve` property, the shape the dialect probes\n\t * for (`isPostgresJSSql`). */\n\tfunction pinnedKysely(conn: ReservedSql): Kysely<Database> {\n\t\t// The dialect only ever calls `.unsafe(sql, params)` then `.release()` on the\n\t\t// reserved connection — so hand it the real `conn` for `.unsafe` but swallow\n\t\t// `.release()` (a no-op), keeping the connection pinned across every closure\n\t\t// statement. The caller releases `conn` exactly once when the snapshot is done.\n\t\tconst nonReleasing = new Proxy(conn, {\n\t\t\tget: (target, prop) =>\n\t\t\t\tprop === \"release\" ? () => {} : Reflect.get(target, prop, target),\n\t\t})\n\t\tconst handle = Object.assign(\n\t\t\t() => {\n\t\t\t\tthrow new Error(\"pggit gc: pinned client used as a tagged template\")\n\t\t\t},\n\t\t\t{ reserve: async () => nonReleasing },\n\t\t)\n\t\treturn initKysely<Database>(handle as unknown as Sql)\n\t}\n\n\t/** Bulk-load the live OID set into the UNLOGGED `live` table via binary COPY (the\n\t * one bytea-safe bulk path, copy-insert.ts), batched so the payload stays bounded.\n\t * Each COPY runs in its own transaction so the staging temp table drops on commit. */\n\tasync function loadLive(live: string, oids: Set<string>): Promise<void> {\n\t\tif (oids.size === 0) return\n\t\tconst all = [...oids]\n\t\tfor (let i = 0; i < all.length; i += LIVE_LOAD_BATCH) {\n\t\t\tconst chunk = all.slice(i, i + LIVE_LOAD_BATCH)\n\t\t\tawait pg.begin(async (tx) => {\n\t\t\t\tawait copyInsert(\n\t\t\t\t\ttx,\n\t\t\t\t\tlive,\n\t\t\t\t\t[\"oid\"],\n\t\t\t\t\tchunk.map((hex) => [{ t: \"bytea\", v: Buffer.from(hex, \"hex\") }]),\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\t}\n\n\t/** Batched object sweep. Postgres `DELETE` has no `LIMIT`, so each batch picks a\n\t * `LIMIT`-bounded set of victim OIDs then deletes them by PRIMARY KEY `(repo_id,\n\t * oid)`. The match is on the PK — NOT `ctid`: `ctid` is per-partition-relative, so\n\t * matching `ctid` across the HASH-partitioned table would delete same-ctid rows in\n\t * OTHER partitions (other tenants). The loop ends when a batch deletes nothing.\n\t * Each batch is its own (implicit) transaction, so `clock_timestamp()` re-evaluates\n\t * per batch and the grace cutoff advances. Returns total rows deleted. */\n\tasync function sweepObjects(\n\t\tid: ReposId,\n\t\tlive: string,\n\t\tgraceSeconds: number,\n\t\tbatchLimit: number,\n\t): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect o.oid from git_object o\n\t\t\t\t\twhere o.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (select 1 from ${live} l where l.oid = o.oid)\n\t\t\t\t\t\tand o.created_at < clock_timestamp() - make_interval(secs => $2::float8)\n\t\t\t\t\tlimit $3::int\n\t\t\t\t)\n\t\t\t\tdelete from git_object o using victims v\n\t\t\t\twhere o.repo_id = $1::bigint and o.oid = v.oid returning 1 as n`,\n\t\t\t\t[String(id), String(graceSeconds), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Batched edge sweep: delete every `git_edge` row whose PARENT object no longer\n\t * exists in `git_object` (a deleted object's outgoing edges). No FK cascade exists\n\t * (0003_git_edge.ts), so dangling edges must be swept explicitly. Anti-join on the\n\t * parent only: a surviving parent is reachable, so all its children are reachable\n\t * and present — its edges never dangle. Like the object sweep, each batch picks a\n\t * `LIMIT`-bounded victim set then deletes by PRIMARY KEY `(repo_id, parent, child)`\n\t * — never `ctid`, which is per-partition and would reach into other tenants. */\n\tasync function sweepEdges(id: ReposId, batchLimit: number): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect e.parent, e.child from git_edge e\n\t\t\t\t\twhere e.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (\n\t\t\t\t\t\t\tselect 1 from git_object o where o.repo_id = e.repo_id and o.oid = e.parent\n\t\t\t\t\t\t)\n\t\t\t\t\tlimit $2::int\n\t\t\t\t)\n\t\t\t\tdelete from git_edge e using victims v\n\t\t\t\twhere e.repo_id = $1::bigint and e.parent = v.parent and e.child = v.child\n\t\t\t\treturning 1 as n`,\n\t\t\t\t[String(id), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Post-sweep maintenance (best-effort): reclaim the dead tuples GC produced in\n\t * the heap + TOAST and refresh planner stats, then reindex the walk index.\n\t * `VACUUM` cannot run inside a transaction block, so these are standalone\n\t * statements run outside any txn. */\n\tasync function maintain(): Promise<void> {\n\t\tawait pg.unsafe(`vacuum (analyze) git_object`)\n\t\tawait pg.unsafe(`vacuum (analyze) git_edge`)\n\t\tawait pg.unsafe(`reindex index git_edge_walk`)\n\t}\n}\n","import type { Sql } from \"postgres\"\nimport { createGc } from \"@/store/gc\"\n\n/**\n * Self-scheduling GC — the background drain that decides WHEN the per-repo\n * reachability GC (`store/gc.ts`) runs, off the push/fetch hot path. See\n * `docs/2026-06-24-gc-scheduler-design.md`; the observable contract is §6 of that\n * doc (SCH-1 … SCH-11 / PBT-S1).\n *\n * Mechanism (data-structures-first): every storage-mutating push stamps\n * `repos.last_pushed_at` in its own transaction (the store), so the scheduler is a\n * pure poll loop over Postgres with NO coupling to the request path. One pass\n * (`drainOnce`) selects the eligible repos — `last_pushed_at > last_gc_at`\n * (or `last_gc_at is null`) — and runs `gc()` on each (per-repo serialized,\n * bounded concurrency), then advances `last_gc_at` to the pass's start time so a\n * push landing mid-pass re-qualifies the repo next loop (no lost garbage). `start`\n * is just `drainOnce` on a `setInterval`; all correctness lives in `drainOnce`,\n * which the tests drive directly (the timer is never in a test's critical path).\n */\n\n/** One repo's outcome in a drain pass: the repo and what its GC reclaimed.\n * Emitted for EVERY repo the pass judged eligible (including zero-reclaim), so the\n * eligible set itself is observable (SCH-3). */\nexport type DrainEntry = { repo: string; deletedObjects: number; deletedEdges: number }\n\n/** What one `drainOnce()` reclaimed, one entry per eligible repo. */\nexport type DrainSummary = DrainEntry[]\n\n/** Scheduler tunables (resolved from `env` / `startServer` opts). `graceSeconds`\n * is passed straight to `gc()`; `intervalMs` is the drain cadence (the debounce\n * window); `concurrency` caps repos GC'd at once per pass so one large-orphan repo\n * cannot head-of-line-block the rest. */\nexport type GcSchedulerOptions = {\n\tgraceSeconds: number\n\tintervalMs: number\n\tconcurrency: number\n}\n\nexport type GcScheduler = ReturnType<typeof createGcScheduler>\n\n/** A candidate repo for one drain pass: its id + wire name. The pass-start\n * watermark is captured per-repo (in `drainRepo`, before that repo's GC snapshot)\n * and written back as `last_gc_at`. */\ntype Candidate = { id: string; name: string }\n\n/**\n * Build the GC scheduler over a porsager client (the same wire→DB boundary the\n * stores take). `drainOnce()` runs one poll+sweep pass; `start()`/`stop()` drive\n * it on `intervalMs`. Reachable objects are never touched — it only invokes the\n * per-repo GC primitive, which is reachability-safe.\n */\nexport function createGcScheduler(pg: Sql, opts: GcSchedulerOptions) {\n\tconst gc = createGc(pg)\n\tlet timer: ReturnType<typeof setInterval> | undefined\n\t// The in-flight pass (if any). Doubles as the overlap guard (a tick skips while a\n\t// pass runs, so two passes never touch the same repo at once) and the shutdown\n\t// barrier (`stop()` awaits it).\n\tlet inFlight: Promise<unknown> | undefined\n\n\t/** The eligible repos for this pass — the §2 predicate. */\n\tasync function selectCandidates(): Promise<Candidate[]> {\n\t\treturn pg<Candidate[]>`\n\t\t\tselect r.id::text as id, r.name\n\t\t\tfrom repos r\n\t\t\twhere r.last_pushed_at is not null\n\t\t\t\tand (r.last_gc_at is null or r.last_pushed_at > r.last_gc_at)\n\t\t`\n\t}\n\n\t/**\n\t * GC one candidate. `t0 = clock_timestamp()` is captured BEFORE `gc()` opens its\n\t * snapshot, then written as `last_gc_at` after the sweep: any push committing\n\t * after t0 re-stamps `last_pushed_at > t0` (the store stamps after commit) and\n\t * re-qualifies the repo next pass (no lost garbage). A per-repo failure is\n\t * ISOLATED — logged and skipped (the repo keeps its old `last_gc_at`, so it\n\t * re-qualifies and is retried next pass) — so one poison repo never aborts the\n\t * rest of the pass. `maintain: false`: the drain leans on autovacuum, never a\n\t * per-pass full-table VACUUM (gc.ts).\n\t */\n\tasync function drainRepo(c: Candidate): Promise<DrainEntry | null> {\n\t\ttry {\n\t\t\tconst [t] = await pg<{ t0: string }[]>`select clock_timestamp()::text as t0`\n\t\t\tif (!t) throw new Error(\"pggit gc-scheduler: clock_timestamp() returned no row\")\n\t\t\tconst { deletedObjects, deletedEdges } = await gc.gc(c.name, {\n\t\t\t\tgraceSeconds: opts.graceSeconds,\n\t\t\t\tmaintain: false,\n\t\t\t})\n\t\t\tawait pg`update repos set last_gc_at = ${t.t0}::timestamptz where id = ${c.id}::bigint`\n\t\t\treturn { deletedEdges, deletedObjects, repo: c.name }\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit gc-scheduler: GC of repo ${JSON.stringify(c.name)} failed (retried next pass):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/** One drain pass: GC every eligible repo (bounded concurrency, distinct repos so\n\t * a pass never double-GCs one). Returns an entry per repo GC'd this pass — a repo\n\t * whose GC threw is skipped (not in the summary) and retried next pass. */\n\tasync function drainOnce(): Promise<DrainSummary> {\n\t\tconst candidates = await selectCandidates()\n\t\tconst results = await mapPool(candidates, Math.max(1, opts.concurrency), drainRepo)\n\t\treturn results.filter((e): e is DrainEntry => e !== null)\n\t}\n\n\t/** Run the drain on `intervalMs`. The `inFlight` guard ensures passes never\n\t * overlap — so two passes can never touch the same repo at once — and a slow pass\n\t * simply skips the next tick. A pass failure is logged, never thrown into the\n\t * timer. The timer is `unref`'d so it alone does not keep the process alive (the\n\t * server's socket does). */\n\tfunction start(): void {\n\t\tif (timer) return\n\t\ttimer = setInterval(() => {\n\t\t\tif (inFlight) return\n\t\t\tinFlight = drainOnce()\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(\"pggit gc-scheduler: drain pass failed:\", err)\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tinFlight = undefined\n\t\t\t\t})\n\t\t}, opts.intervalMs)\n\t\ttimer.unref?.()\n\t}\n\n\t/** Halt the background drain and AWAIT any pass already in flight, so a caller may\n\t * safely tear the connection pool down afterwards (no query runs into a closed\n\t * pool). Idempotent. */\n\tasync function stop(): Promise<void> {\n\t\tif (timer) {\n\t\t\tclearInterval(timer)\n\t\t\ttimer = undefined\n\t\t}\n\t\tawait inFlight\n\t}\n\n\treturn { drainOnce, start, stop }\n}\n\n/** Run `fn` over `items` with at most `limit` concurrent, preserving result order.\n * A bounded worker pool — `limit` workers pull from a shared cursor — so one\n * large-orphan repo cannot head-of-line-block the rest of a pass. */\nasync function mapPool<T, R>(\n\titems: T[],\n\tlimit: number,\n\tfn: (item: T) => Promise<R>,\n): Promise<R[]> {\n\tconst results = new Array<R>(items.length)\n\tlet cursor = 0\n\tasync function worker(): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst i = cursor++\n\t\t\tif (i >= items.length) return\n\t\t\tresults[i] = await fn(items[i] as T)\n\t\t}\n\t}\n\tconst workers = Array.from({ length: Math.min(limit, items.length) }, worker)\n\tawait Promise.all(workers)\n\treturn results\n}\n","import { gunzipSync } from \"node:zlib\"\nimport { type Context, Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport type { Sql } from \"postgres\"\nimport { count, runRequest } from \"@/instrument\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport {\n\tencodeReceivePackAdvertisement,\n\thandleReceivePack,\n\ttype ReceiveBackend,\n} from \"@/protocol/receive-pack\"\nimport { handleUploadPack, type RepoBackend } from \"@/protocol/upload-pack\"\nimport { encodeAdvertisement } from \"@/protocol/v2\"\nimport { syncRefSnapshot } from \"@/repo-view/rebuild\"\nimport {\n\tcreateRepoFileProjection,\n\ttype RepoFileProjection,\n} from \"@/repo-view/repo-file-projection\"\nimport { createObjectStore, type ObjectStore } from \"@/store/object-store\"\nimport { createRefStore, type RefStore } from \"@/store/refs-store\"\n\nexport type GitAppDeps = {\n\tobjects: ObjectStore\n\trefs: RefStore\n\t/** Optional queryable-view layer. When provided, push maintains a `repo_file`\n\t * path→blob index per branch; when omitted, this is a plain git remote. */\n\tsnapshots?: RepoFileProjection\n}\n\n// Smart-HTTP info/refs body: the `# service` preamble + flush, then the v2\n// capability advertisement.\nconst UPLOAD_PACK_ADVERTISEMENT = Buffer.concat([\n\tencodePktLine(Buffer.from(\"# service=git-upload-pack\\n\")),\n\tencodePkt({ type: \"flush\" }),\n\tencodeAdvertisement(),\n])\n\n// Hono's body types want an ArrayBuffer, not a Node Buffer (a Uint8Array view).\nfunction toArrayBuffer(buf: Buffer): ArrayBuffer {\n\treturn buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\nconst ADVERTISEMENT_BODY = toArrayBuffer(UPLOAD_PACK_ADVERTISEMENT)\n\n/**\n * Read a smart-HTTP POST body, honoring `Content-Encoding`. Git compresses the\n * upload-pack/receive-pack request body with gzip once it is large enough\n * (`remote-curl.c`), exactly as `git http-backend` decompresses on the server\n * side — so we must too. Any other declared encoding is a hard error, never fed\n * raw to the pkt-line parser.\n */\nasync function readRequestBody(c: Context): Promise<Buffer> {\n\tconst raw = Buffer.from(await c.req.arrayBuffer())\n\tconst encoding = c.req.header(\"content-encoding\")?.toLowerCase()\n\tif (encoding === undefined || encoding === \"identity\") return raw\n\tif (encoding === \"gzip\" || encoding === \"x-gzip\") {\n\t\t// A body that declares gzip but fails to inflate is a CLIENT wire fault, not a\n\t\t// server error — surface it on the same clean 400 path as the unsupported\n\t\t// encodings below, never let the ZlibError escape to a 500.\n\t\ttry {\n\t\t\treturn gunzipSync(raw)\n\t\t} catch (err) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`request body declared Content-Encoding ${JSON.stringify(encoding)} but failed to gunzip: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t)\n\t\t}\n\t}\n\tthrow new GitProtocolError(\n\t\t`unsupported request Content-Encoding: ${JSON.stringify(encoding)}`,\n\t)\n}\n\n/**\n * Fetch is served over git protocol v2 ONLY (the charter). git requests v2 with a\n * `Git-Protocol: version=2` header (a `:`-joined key list; git ≥ 2.26 sends it by\n * default). A v0/v1 client sends no such header and cannot parse the v2\n * advertisement — it would read the `version 2` line + flush as an empty repo and\n * silently clone nothing. So reject the unnegotiated case loudly at the boundary\n * (400) instead of handing back an advertisement it will misread.\n */\nfunction assertProtocolV2(header: string | undefined): void {\n\tconst requested = (header ?? \"\").split(\":\").map((s) => s.trim())\n\tif (!requested.includes(\"version=2\")) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"pggit serves fetch over git protocol v2 only; set protocol.version=2 (git ≥ 2.26 negotiates it by default)\",\n\t\t)\n\t}\n}\n\n/**\n * Split a smart-HTTP request path into the targeted git service and the repoId\n * prefix that precedes it. The repoId is everything before the trailing service\n * segment(s) — opaque and possibly slash-delimited (the persistence grammar\n * `<kind>/<owner>/<ws>[/<user>]`), matching how the store keys repos. `c.req.path`\n * must be mount-relative (a host embeds this app with `app.mount`, not\n * `app.route`; see the route comment).\n */\ntype GitService = \"info/refs\" | \"git-upload-pack\" | \"git-receive-pack\"\nconst GIT_PATH_SUFFIXES = [\n\t[\"/info/refs\", \"info/refs\"],\n\t[\"/git-upload-pack\", \"git-upload-pack\"],\n\t[\"/git-receive-pack\", \"git-receive-pack\"],\n] as const satisfies readonly (readonly [string, GitService])[]\nfunction parseGitPath(path: string): { repoId: string; service: GitService } | null {\n\tfor (const [suffix, service] of GIT_PATH_SUFFIXES) {\n\t\tif (path.endsWith(suffix)) {\n\t\t\tconst repoId = path.slice(1, -suffix.length)\n\t\t\treturn repoId.length > 0 ? { repoId, service } : null\n\t\t}\n\t}\n\treturn null\n}\n\nfunction backendFor(deps: GitAppDeps, repoId: string): RepoBackend {\n\treturn {\n\t\tbuildPack: (wants, haves, omitBlobs, includeTag) =>\n\t\t\tdeps.objects.buildPack(repoId, wants, haves, omitBlobs, includeTag),\n\t\tcommonHaves: (haves) => deps.objects.commonHaves(repoId, haves),\n\t\tgetSymref: (name) => deps.refs.getSymref(repoId, name),\n\t\tlistRefs: () => deps.refs.listRefs(repoId),\n\t\treadyToGiveUp: (wants, common) => deps.objects.readyToGiveUp(repoId, wants, common),\n\t}\n}\n\nfunction receiveBackendFor(deps: GitAppDeps, repoId: string): ReceiveBackend {\n\tconst backend: ReceiveBackend = {\n\t\tapplyRefUpdates: (commands, atomic) =>\n\t\t\tdeps.refs.applyRefUpdates(repoId, commands, atomic),\n\t\tingest: async (pack) => {\n\t\t\tawait deps.objects.ingestPack(repoId, pack)\n\t\t},\n\t\tisConnected: (oid) => deps.objects.isConnected(repoId, oid),\n\t}\n\tif (deps.snapshots) {\n\t\tconst sdeps = { objects: deps.objects, snapshots: deps.snapshots }\n\t\tbackend.syncRefSnapshot = (ref, newOid) => syncRefSnapshot(sdeps, repoId, ref, newOid)\n\t}\n\treturn backend\n}\n\n/** v0 receive-pack ref advertisement body: the `# service` preamble + ref list. */\nasync function receivePackAdvertBody(deps: GitAppDeps, repoId: string): Promise<Buffer> {\n\tconst refs = await deps.refs.listRefs(repoId)\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"# service=git-receive-pack\\n\")),\n\t\tencodePkt({ type: \"flush\" }),\n\t\tencodeReceivePackAdvertisement(refs),\n\t])\n}\n\n/**\n * Build the git-remote Hono app (smart-HTTP, protocol v2 fetch). Mountable into\n * a host app via `host.mount(\"/git\", createGitApp(deps).fetch)` (mount, NOT route —\n * mount strips the prefix so the catch-all parses a mount-relative path); the host owns the\n * Postgres lifecycle behind `deps`.\n */\nexport function createGitApp(\n\tdeps: GitAppDeps,\n\topts: { instrument?: boolean } = {},\n): Hono {\n\tconst app = new Hono()\n\tif (opts.instrument) {\n\t\tapp.use((c, next) =>\n\t\t\trunRequest({ method: c.req.method, path: c.req.path }, () => next()),\n\t\t)\n\t}\n\tapp.use(cors())\n\n\t// A client-caused boundary error (malformed request, unsupported capability) is\n\t// a clean 400 with its message; anything else is an internal 500, logged loud.\n\tapp.onError((err, c) => {\n\t\tif (err instanceof GitProtocolError) return c.text(err.message, 400)\n\t\tconsole.error(err)\n\t\treturn c.text(\"internal server error\", 500)\n\t})\n\n\tapp.get(\"/health\", (c) => c.text(\"ok\"))\n\n\t// Smart-HTTP routes — a catch-all per method, with the trailing git service\n\t// split off the path by `parseGitPath`, rather than a `:repo` param. Two\n\t// reasons: (1) repoIds are slash-delimited (`<kind>/<owner>/<ws>[/<user>]`) and\n\t// Hono's RegExpRouter can't span a `:param` across slashes once routes coexist\n\t// (verified); (2) the repoId is exactly the opaque, possibly slash-containing\n\t// key the store already uses. A mounted host MUST embed this app with\n\t// `app.mount` (which strips the mount prefix from `c.req.path`) — `app.route`\n\t// would leave the prefix in the path and corrupt the parsed repoId.\n\tapp.get(\"/*\", async (c) => {\n\t\tconst parsed = parseGitPath(c.req.path)\n\t\tif (parsed?.service !== \"info/refs\") return c.notFound()\n\t\tconst service = c.req.query(\"service\")\n\t\tif (service === \"git-upload-pack\") {\n\t\t\tassertProtocolV2(c.req.header(\"git-protocol\"))\n\t\t\treturn c.body(ADVERTISEMENT_BODY, 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\tif (service === \"git-receive-pack\") {\n\t\t\tconst body = await receivePackAdvertBody(deps, parsed.repoId)\n\t\t\treturn c.body(toArrayBuffer(body), 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-receive-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\treturn c.text(`unsupported service ${JSON.stringify(service)}`, 403)\n\t})\n\n\tapp.post(\"/*\", async (c) => {\n\t\tconst parsed = parseGitPath(c.req.path)\n\t\tif (!parsed) return c.notFound()\n\t\tif (parsed.service === \"git-upload-pack\") {\n\t\t\tconst body = await readRequestBody(c)\n\t\t\tconst out = await handleUploadPack(body, backendFor(deps, parsed.repoId))\n\t\t\t// Layer-1 wire size, measured at the boundary where bytes actually leave —\n\t\t\t// a no-op unless the perf harness activated a collector, so production pays\n\t\t\t// nothing and the metric survives any restructure of the core.\n\t\t\tcount(\"wireBytes\", out.length)\n\t\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t\t})\n\t\t}\n\t\tif (parsed.service === \"git-receive-pack\") {\n\t\t\tconst body = await readRequestBody(c)\n\t\t\tconst out = await handleReceivePack(body, receiveBackendFor(deps, parsed.repoId))\n\t\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-receive-pack-result\",\n\t\t\t})\n\t\t}\n\t\treturn c.notFound()\n\t})\n\n\treturn app\n}\n\n/**\n * Build the git-app deps from a Postgres connection — the embed-into-a-host\n * factory. The host owns the `pg` lifecycle and mounts pggit with\n * `host.mount(\"/git\", createGitApp(createGitDeps(pg)).fetch)` — `app.mount` strips\n * the mount prefix so the catch-all parse sees a mount-relative path (`app.route`\n * would not). `startServer` (server.ts) is the standalone equivalent of this same\n * composition. `snapshots` is always\n * included so a mounted host gets the queryable `repo_file` projection maintained\n * on push (the read surface).\n */\nexport function createGitDeps(pg: Sql): GitAppDeps {\n\treturn {\n\t\tobjects: createObjectStore(pg),\n\t\trefs: createRefStore(pg),\n\t\tsnapshots: createRepoFileProjection(pg),\n\t}\n}\n\nexport type {\n\tDrainEntry,\n\tDrainSummary,\n\tGcScheduler,\n\tGcSchedulerOptions,\n} from \"@/gc-scheduler\"\n// Public GC surface: the per-repo reachability GC primitive (a host may drive it\n// directly on its own schedule) and the self-scheduling background drain that\n// decides when to run it (docs/2026-06-24-gc-scheduler-design.md §4).\nexport { createGcScheduler } from \"@/gc-scheduler\"\nexport type { Gc, GcOptions, GcResult } from \"@/store/gc\"\nexport { createGc } from \"@/store/gc\"\n"],"mappings":"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"}
|