@toon-protocol/rig 4.1.0 → 4.2.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/README.md CHANGED
@@ -488,8 +488,10 @@ non-fast-forward updates; `--repo-id <id>` overrides the configured repo id.
488
488
 
489
489
  Repo addressing (`30617:<owner>:<repoId>`) comes from the `toon.repoid` / `toon.owner`
490
490
  git config keys `rig init` writes — an unconfigured repo is a clear "run `rig init`"
491
- error, and pushing never mutates git config. Objects over 95KB are a hard error in
492
- v1 (large-object support: toon-client#235).
491
+ error, and pushing never mutates git config. Objects over the storage upstream's
492
+ free per-item ceiling (107,520 bytes) are **not** an error — they upload as paid
493
+ writes, priced by the store route's per-KiB schedule, and the confirm table shows
494
+ the cost before anything is spent.
493
495
 
494
496
  ## Relays are origins
495
497
 
@@ -550,7 +552,8 @@ it — that stays behind the `Publisher` seam:
550
552
 
551
553
  - `objects.ts` — git object construction with SHA-1 envelope hashing: `createGitBlob`,
552
554
  `createGitTree`, `createGitCommit`, `createGitTag`, `hashGitObject`, and the
553
- `MAX_OBJECT_SIZE` (95KB) upload guard.
555
+ `FREE_TIER_MAX_ITEM_BYTES` (107,520) free-tier threshold (`MAX_OBJECT_SIZE` is
556
+ a deprecated alias; it is no longer a cap).
554
557
  - `nip34-events.ts` — NIP-34 event builders returning `UnsignedEvent`:
555
558
  `buildRepoAnnouncement` (30617), `buildRepoRefs` (30618), `buildIssue` (1621),
556
559
  `buildComment` (1622), `buildPatch` (1617), `buildStatus` (1630–1633).
@@ -0,0 +1,16 @@
1
+ // src/publisher.ts
2
+ var UPLOAD_ENVELOPE_OVERHEAD_BYTES = 704;
3
+ function estimateSealedUploadBytes(bodyBytes) {
4
+ return Math.ceil(bodyBytes / 3) * 4 + UPLOAD_ENVELOPE_OVERHEAD_BYTES;
5
+ }
6
+ function uploadChargeFor(rates, bodyBytes) {
7
+ const perKib = rates.uploadPerKib ?? 0n;
8
+ if (perKib === 0n) return rates.uploadFee;
9
+ const sealed = estimateSealedUploadBytes(bodyBytes);
10
+ return rates.uploadFee + perKib * BigInt(Math.floor(sealed / 1024) + 1);
11
+ }
12
+
13
+ export {
14
+ uploadChargeFor
15
+ };
16
+ //# sourceMappingURL=chunk-CXREFXB6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/publisher.ts"],"sourcesContent":["/**\n * Publisher — the paid-write seam between push planning (this package) and\n * the two transport implementations that follow it in epic #222:\n *\n * 1. daemon (#227): client-mcp ControlClient → toon-clientd loopback\n * /git/* routes, backed by `ClientRunner.publish`/`uploadBlob` (the\n * production paid-publish path — apex channel, signBalanceProof, flat\n * per-event fee).\n * 2. standalone (#228): an embedded ToonClient constructed from a\n * mnemonic, uploading git objects as kind:5094 store writes with\n * Git-SHA/Git-Type/Repo tags (the proven seed-pipeline shape) and\n * publishing NIP-34 events through the relay.\n *\n * The interface is deliberately minimal so both fit:\n *\n * - `uploadGitObject` takes the raw object body (content only — no git\n * envelope header; the store re-derives the SHA envelope from the\n * Git-Type/Git-SHA tags) and returns the Arweave txId plus the fee paid.\n * - `publishEvent` takes an UNSIGNED event template — signing stays with\n * the implementation (the daemon-held key never leaves the daemon; the\n * standalone impl signs with its own keypair). Relay URLs are plural\n * from day one (forward-compat for parked #84/#92; size 1 today).\n * - `getFeeRates` exposes what `planPush` needs for the pre-push estimate:\n * a per-byte upload rate (seed pipeline bids bytes × rate) and a flat\n * per-event publish fee (the daemon charges `apex.feePerEvent` regardless\n * of event size).\n *\n * All fees are bigint in the smallest asset unit (matches `@toon-protocol/client`\n * channel math); HTTP transports serialize them as strings and convert at\n * the boundary.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { GitObjectType } from './objects.js';\n\n/** A git object queued for upload to Arweave via the paid store path. */\nexport interface GitObjectUpload {\n /** Full 40-hex SHA-1 (over the git envelope — see `hashGitObject`). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no `<type> <size>\\0` header). */\n body: Buffer;\n /** Repository identifier — becomes the `Repo` tag on the store write. */\n repoId: string;\n /**\n * Path the blob was reached by in the tree (#368), if known. Its extension\n * derives the `Content-Type` sent in the store write's `output` tag so a\n * gateway serves the blob as its real media type instead of\n * `application/octet-stream`. Absent (or a non-blob object) → octet-stream.\n */\n path?: string;\n}\n\n/**\n * A NON-git blob queued for upload to Arweave with an explicit `Content-Type`\n * (#368): the ar.io path manifest that turns a pushed repo into a permaweb\n * site. Unlike {@link GitObjectUpload} it carries no `Git-SHA`/`Git-Type`\n * tags, so the store stores the raw bytes verbatim (no git-envelope\n * re-derivation) — exactly what a manifest needs.\n */\nexport interface BlobUpload {\n /** Raw bytes to store. */\n body: Buffer;\n /** MIME type sent in the store write's `output` tag. */\n contentType: string;\n /** Optional repository identifier for provenance (`Repo` tag). */\n repoId?: string;\n}\n\n/** Receipt for one uploaded git object. */\nexport interface UploadReceipt {\n /** Arweave transaction ID the object is retrievable under. */\n txId: string;\n /** Fee paid for this upload, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Receipt for one published Nostr event. */\nexport interface PublishReceipt {\n /** Event ID as accepted by the relay(s). */\n eventId: string;\n /** Fee paid for this publish, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/**\n * One NIP-90 job for a store node's DVM, carried on the PAID path (#101).\n *\n * The store sits behind the connector's payment termination, so there is no\n * public HTTP endpoint to POST these to — a reachable one would be an\n * anonymous free gateway to a paid handler, which is the failure ADR 0020\n * exists to close. The job therefore travels exactly as a kind:5094 store\n * write does: inside a paid ILP packet, with `/store` as the envelope target\n * BENEATH the route's handler path rather than as a URL path.\n */\nexport interface StoreJobRequest {\n /** NIP-90 job kind — 5095 brokered ArNS buy, 5096 gas station. */\n kind: number;\n /** Job arguments, sent as `[\"param\", key, value]` tags. */\n params: [string, string][];\n}\n\n/**\n * A store DVM's answer, opened from the sealed FULFILL.\n *\n * ⚠️ A refusal is a RESULT, not an error. Under ADR 0020 an HTTP status is\n * envelope content and never a packet outcome, so `accept: false` arrives on a\n * FULFILL and the payer was charged for it either way. Implementations must\n * return it rather than throw: the zero-ARIO rehearsal (submit a buy with no\n * `processId` and watch the handler refuse by name before it quotes or touches\n * the registry) is the cheapest proof the whole paid path works, and it only\n * works if the refusal comes back as data.\n */\nexport interface StoreJobResponse {\n /** HTTP status the app answered with, inside the envelope. */\n status: number;\n /** The DVM's own accept flag. `false` is a refusal, not a transport fault. */\n accept: boolean;\n /** Machine-readable refusal reason, when the DVM sent one. */\n code?: string;\n /** Human-readable detail, when the DVM sent one. */\n message?: string;\n /** The job's output, shape depending on `kind`. */\n result?: Record<string, unknown>;\n /** Fee paid to carry this job, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/**\n * Fee rates used by `planPush` for the pre-push estimate.\n *\n * Both figures are FLAT per packet. ADR 0020 (toon-client#452) removed\n * byte-proportional pricing from the protocol: one handler, one price, and an\n * app that wants to charge differently exposes more handlers. A 100-byte and\n * a 100 KB upload to the same store route now cost the same, and the\n * connector charges accordingly regardless of what a client computes — so\n * there is no `uploadFeePerByte` to multiply and no floor to apply, because\n * the route's price is the whole fee rather than a lower bound on one.\n */\nexport interface FeeRates {\n /**\n * Flat cost per git-object/blob upload (smallest asset unit): the store\n * destination's route price, as reported by the terminating connector's\n * `GET /ilp/routes/price`. The connector gates every paid packet at that\n * price, so an estimate built from it is exactly what a push pays.\n */\n uploadFee: bigint;\n /**\n * The store route's per-kibibyte rate, when it meters by size (ADR 0065: a\n * price is a schedule `{base, per_kib}`; a flat route has no slope). Absent\n * or zero means every upload costs exactly `uploadFee`.\n */\n uploadPerKib?: bigint;\n /**\n * Flat cost per published event (smallest asset unit). Implementations\n * already fold any per-packet route-price floor into this flat value, so\n * estimates using it match the claims actually signed.\n */\n eventFee: bigint;\n}\n\n/**\n * Paid transport for `executePush` — implemented by the daemon route\n * handlers (#227) and the standalone embedded client (#228).\n *\n * Implementations must be safe to call sequentially (uploads are issued one\n * at a time in dependency-safe order) and should throw on any failure —\n * `executePush` does not retry; a crashed push is resumed by re-planning\n * (content-addressed uploads make the retry idempotent).\n */\nexport interface Publisher {\n /** Current fee rates for estimation (may be queried before every plan). */\n getFeeRates(): Promise<FeeRates>;\n /** Upload one git object body to Arweave; paid. */\n uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;\n /**\n * Upload one raw blob (no git envelope) with an explicit `Content-Type`;\n * paid. Used by `rig site` (#368) for the ar.io path manifest. Optional so\n * transports that only move git objects (and pre-#368 test fakes) need not\n * implement it — `rig site` requires it and errors clearly when absent.\n */\n uploadBlob?(upload: BlobUpload): Promise<UploadReceipt>;\n /**\n * Submit one NIP-90 job to the store node's DVM over the paid path and\n * return its answer; paid. Used by `rig name buy/set --via` (#101) for the\n * brokered kind:5095 buy and the kind:5096 gas station.\n *\n * Optional for the same reason as {@link uploadBlob}: transports that only\n * move git objects (and pre-#101 test fakes) need not implement it, and\n * `rig name --via` errors clearly when it is absent.\n */\n submitStoreJob?(request: StoreJobRequest): Promise<StoreJobResponse>;\n /**\n * Sign (implementation-held key) and pay-to-publish one event to the\n * given relay(s).\n */\n publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt>;\n}\n\n// ---------------------------------------------------------------------------\n// Per-upload charge on a metered route\n// ---------------------------------------------------------------------------\n\n/**\n * Bytes the connector meters that are not the caller's object: the kind:5094\n * event around it (id, pubkey, sig, kind, created_at, tag names), the `{event}`\n * body wrapper, the request envelope and the gift wrap. Measured against the\n * live edge and rounded up; a KiB boundary crossed by this margin costs one\n * `uploadPerKib` more than the store will actually charge, never less.\n */\nexport const UPLOAD_ENVELOPE_OVERHEAD_BYTES = 704;\n\n/**\n * The bytes a connector meters for an upload of `bodyBytes`: the object is\n * base64 in an `i` tag (4/3 expansion), plus {@link UPLOAD_ENVELOPE_OVERHEAD_BYTES}.\n */\nexport function estimateSealedUploadBytes(bodyBytes: number): number {\n return Math.ceil(bodyBytes / 3) * 4 + UPLOAD_ENVELOPE_OVERHEAD_BYTES;\n}\n\n/**\n * What one upload of `bodyBytes` costs under `rates`: the flat `uploadFee`,\n * plus — on a metered route — `uploadPerKib` per started kibibyte of the\n * sealed payload, counted from one (the connector's own rule:\n * `price + pricePerKib × (⌊bytes / 1024⌋ + 1)`).\n */\nexport function uploadChargeFor(rates: FeeRates, bodyBytes: number): bigint {\n const perKib = rates.uploadPerKib ?? 0n;\n if (perKib === 0n) return rates.uploadFee;\n const sealed = estimateSealedUploadBytes(bodyBytes);\n return rates.uploadFee + perKib * BigInt(Math.floor(sealed / 1024) + 1);\n}\n"],"mappings":";AAqNO,IAAM,iCAAiC;AAMvC,SAAS,0BAA0B,WAA2B;AACnE,SAAO,KAAK,KAAK,YAAY,CAAC,IAAI,IAAI;AACxC;AAQO,SAAS,gBAAgB,OAAiB,WAA2B;AAC1E,QAAM,SAAS,MAAM,gBAAgB;AACrC,MAAI,WAAW,GAAI,QAAO,MAAM;AAChC,QAAM,SAAS,0BAA0B,SAAS;AAClD,SAAO,MAAM,YAAY,SAAS,OAAO,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;AACxE;","names":[]}
@@ -3,9 +3,8 @@ import {
3
3
  contentTypeForPath
4
4
  } from "./chunk-YZRR2UO6.js";
5
5
  import {
6
- MAX_OBJECT_SIZE,
7
6
  uploadChargeFor
8
- } from "./chunk-GVXBUL3C.js";
7
+ } from "./chunk-CXREFXB6.js";
9
8
 
10
9
  // src/standalone/connector-publisher.ts
11
10
  import { finalizeEvent } from "nostr-tools/pure";
@@ -164,11 +163,6 @@ var ConnectorPublisher = class {
164
163
  return { txId, feePaid: result.claim?.amount ?? estimate };
165
164
  }
166
165
  async uploadGitObject(upload) {
167
- if (upload.body.length > MAX_OBJECT_SIZE) {
168
- throw new ConnectorPublishError(
169
- `git object ${upload.sha} exceeds the ${String(MAX_OBJECT_SIZE)}-byte limit: ${String(upload.body.length)} bytes`
170
- );
171
- }
172
166
  const contentType = upload.type === "blob" ? contentTypeForPath(upload.path) : DEFAULT_CONTENT_TYPE;
173
167
  return this.storeWrite(
174
168
  [
@@ -183,11 +177,6 @@ var ConnectorPublisher = class {
183
177
  );
184
178
  }
185
179
  async uploadBlob(upload) {
186
- if (upload.body.length > MAX_OBJECT_SIZE) {
187
- throw new ConnectorPublishError(
188
- `blob exceeds the ${String(MAX_OBJECT_SIZE)}-byte limit: ${String(upload.body.length)} bytes`
189
- );
190
- }
191
180
  return this.storeWrite(
192
181
  [
193
182
  ["i", upload.body.toString("base64"), "blob"],
@@ -287,4 +276,4 @@ export {
287
276
  extractArweaveTxId,
288
277
  ConnectorPublisher
289
278
  };
290
- //# sourceMappingURL=chunk-CDSFVZDV.js.map
279
+ //# sourceMappingURL=chunk-FT2ZEKPX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/connector-publisher.ts"],"sourcesContent":["/**\n * The paid write path on `@toon-protocol/client` 2.x — a {@link Publisher}\n * that pays a TOON connector per request.\n *\n * Everything a paid command needs comes from one connector URL: the client\n * reads the node's own `GET /ilp` (addresses, prices, settlement chains,\n * sealing key — connector ADR 0050), opens or adopts a payment channel, and\n * pays each request with a signed claim. There is no relay to discover peers\n * on (kind:10032 was removed by ADR 0046) and no topology to negotiate: a\n * destination is a route the node prices, and `send()` pays what the route\n * costs — including the per-kibibyte part of an ADR 0065 schedule, which the\n * client works out from the sealed payload so this publisher never has to.\n *\n * Three request shapes, all ordinary paid POSTs:\n *\n * 1. `publishEvent` — a NIP-34 event signed with the OWNER's Nostr key,\n * sent as `{ event }` to the publish route; the relay answers\n * `{ eventId, storedAt, payment? }`.\n * 2. `uploadGitObject` / `uploadBlob` — a kind:5094 store job carrying the\n * bytes base64 in an `i` tag; the store answers the Arweave tx id.\n * 3. `submitStoreJob` — a kind:5095 / 5096 job with `param` tags; the\n * DVM's answer comes back as data, refusal included (ADR 0020: an answer\n * is an answer, and the payer was charged for it).\n *\n * The store route may terminate on a different node than the one addressed\n * (the edge forwards it). Then the payload must be sealed to THAT node's\n * identity — `sealTo` — and, when the store node holds its own channel, paid\n * through its own client. Both are options here; the common single-node case\n * needs neither.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport { contentTypeForPath, DEFAULT_CONTENT_TYPE } from '../mime.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n uploadChargeFor,\n type BlobUpload,\n type FeeRates,\n type GitObjectUpload,\n type PublishReceipt,\n type Publisher,\n type StoreJobRequest,\n type StoreJobResponse,\n type UploadReceipt,\n} from '../publisher.js';\n\n// ---------------------------------------------------------------------------\n// The slice of ToonClient this publisher drives (structural, so tests mock it)\n// ---------------------------------------------------------------------------\n\n/** A paid request, as `ToonClient.send` takes it. */\nexport interface PaidRequest {\n method?: string;\n target?: string;\n headers?: Record<string, string> | [string, string][];\n body?: string | Uint8Array | object;\n}\n\nexport interface PaidSendOptions {\n /** The terminating connector's `GET /ilp` URL or raw key, on a forwarded route. */\n sealTo?: string | Uint8Array;\n timeoutMs?: number;\n}\n\nexport interface PaidFulfilled {\n fulfilled: true;\n status: number;\n body: Uint8Array;\n /** What this request paid; absent on a route priced at zero. */\n claim?: { amount: bigint };\n}\n\nexport interface PaidRefused {\n fulfilled: false;\n code: string;\n message: string;\n refusedBy?: string;\n}\n\nexport type PaidSendResult = PaidFulfilled | PaidRefused;\n\n/** The route terms a connector publishes: a base price and an optional slope. */\nexport interface RouteTerms {\n price: bigint;\n pricePerKib?: bigint;\n}\n\n/** The slice of `ToonClient` (2.x) this publisher needs. */\nexport interface PaidClientLike {\n send(\n destination: string,\n request: PaidRequest,\n options?: PaidSendOptions\n ): Promise<PaidSendResult>;\n /** The node's terms for a destination, or `null` when it prices no such route. */\n routePrice(destination: string): Promise<RouteTerms | null>;\n}\n\n/** One leg of the write path: who pays, where to, and whom to seal to. */\nexport interface PaidLeg {\n client: PaidClientLike;\n destination: string;\n sealTo?: string | Uint8Array;\n}\n\nexport interface ConnectorPublisherOptions {\n /** Pays the publish route and signs nothing — the Nostr key below signs. */\n publish: PaidLeg;\n /** Pays the store route (often the same client as `publish`). */\n store: PaidLeg;\n /** The owner's Nostr secret key: signs every event this publisher sends. */\n nostrSecretKey: Uint8Array;\n /**\n * A configured per-event fee. Informational floor only: the connector's\n * published price for the publish route wins when it is higher, and the\n * client pays that price whatever this says.\n */\n eventFee?: bigint;\n /** Per-packet timeout for uploads (the store's Arweave round trip). */\n uploadTimeoutMs?: number;\n warn?(line: string): void;\n}\n\nexport class ConnectorPublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConnectorPublishError';\n }\n}\n\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\nfunction decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nfunction describeRefusal(result: PaidRefused): string {\n const by = result.refusedBy ? ` by the ${result.refusedBy}` : '';\n return `${result.code}${by}: ${result.message}`;\n}\n\n/**\n * Read the Arweave tx id out of a store's kind:5094 answer.\n *\n * The store answers one of two ways — `{ txId }` as JSON, or the bare id\n * base64 in `data` — and refuses with `{ accept: false, error }`. A non-2xx\n * status is a refusal too; both are thrown, because an upload without an id\n * is an upload that did not happen (even though the packet was paid for).\n */\nexport function extractArweaveTxId(status: number, body: Uint8Array): string {\n const text = decodeUtf8(body);\n if (status < 200 || status >= 300) {\n throw new ConnectorPublishError(\n `the store answered HTTP ${String(status)}${text ? ` - ${text}` : ''}`\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n if (ARWEAVE_TX_ID_REGEX.test(text.trim())) return text.trim();\n throw new ConnectorPublishError(\n `the store's answer was not valid JSON: ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n const answer = (parsed ?? {}) as {\n accept?: unknown;\n error?: unknown;\n txId?: unknown;\n data?: unknown;\n result?: unknown;\n };\n if (answer.accept === false) {\n const reason = typeof answer.error === 'string' ? `: ${answer.error}` : '';\n throw new ConnectorPublishError(\n `upload refused by the store (accept:false)${reason}`\n );\n }\n if (\n typeof answer.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(answer.txId)\n ) {\n return answer.txId;\n }\n const nested = answer.result as { txId?: unknown } | undefined;\n if (\n nested &&\n typeof nested.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(nested.txId)\n ) {\n return nested.txId;\n }\n if (typeof answer.data === 'string' && answer.data.length > 0) {\n const decoded = Buffer.from(answer.data, 'base64').toString('utf8');\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;\n }\n throw new ConnectorPublishError(\n `the store's answer carried no Arweave tx id: ${JSON.stringify(text.slice(0, 200))}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// The publisher\n// ---------------------------------------------------------------------------\n\nexport class ConnectorPublisher implements Publisher {\n private readonly publish: PaidLeg;\n private readonly store: PaidLeg;\n private readonly nostrSecretKey: Uint8Array;\n private readonly configuredEventFee: bigint;\n private readonly uploadTimeoutMs: number | undefined;\n private readonly warn: (line: string) => void;\n private rates: Promise<FeeRates> | undefined;\n\n constructor(options: ConnectorPublisherOptions) {\n this.publish = options.publish;\n this.store = options.store;\n this.nostrSecretKey = options.nostrSecretKey;\n this.configuredEventFee = options.eventFee ?? 0n;\n this.uploadTimeoutMs = options.uploadTimeoutMs;\n this.warn = options.warn ?? (() => undefined);\n }\n\n /** Where events go, and where objects go. */\n get destinations(): { publish: string; store: string } {\n return { publish: this.publish.destination, store: this.store.destination };\n }\n\n /**\n * The two routes' published terms, read once per publisher. A route the\n * node does not price is a configuration error and says which knob to fix.\n */\n getFeeRates(): Promise<FeeRates> {\n this.rates ??= this.readFeeRates();\n return this.rates;\n }\n\n private async readFeeRates(): Promise<FeeRates> {\n const [publishTerms, storeTerms] = await Promise.all([\n this.publish.client.routePrice(this.publish.destination),\n this.store.client.routePrice(this.store.destination),\n ]);\n if (!publishTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the publish destination ${this.publish.destination} ` +\n '(set publishDestination / TOON_CLIENT_PUBLISH_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (!storeTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the store destination ${this.store.destination} ` +\n '(set storeDestination / TOON_CLIENT_STORE_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (\n publishTerms.pricePerKib !== undefined &&\n publishTerms.pricePerKib > 0n\n ) {\n this.warn(\n `rig: the publish route ${this.publish.destination} meters by size ` +\n `(${String(publishTerms.pricePerKib)}/KiB); the per-event estimate shows its base price only`\n );\n }\n const eventFee =\n this.configuredEventFee > publishTerms.price\n ? this.configuredEventFee\n : publishTerms.price;\n return {\n uploadFee: storeTerms.price,\n ...(storeTerms.pricePerKib !== undefined && storeTerms.pricePerKib > 0n\n ? { uploadPerKib: storeTerms.pricePerKib }\n : {}),\n eventFee,\n };\n }\n\n private sign(template: UnsignedEvent) {\n return finalizeEvent(\n {\n kind: template.kind,\n content: template.content,\n tags: template.tags,\n created_at: template.created_at,\n },\n this.nostrSecretKey\n );\n }\n\n /** One paid kind:5094 write; returns the tx id and what the claim spent. */\n private async storeWrite(\n tags: string[][],\n bodyBytes: number,\n what: string\n ): Promise<UploadReceipt> {\n const rates = await this.getFeeRates();\n const estimate = uploadChargeFor(rates, bodyBytes);\n const event = this.sign({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [...tags, ['bid', estimate.toString(), 'usdc']],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n {\n ...(this.store.sealTo !== undefined\n ? { sealTo: this.store.sealTo }\n : {}),\n ...(this.uploadTimeoutMs !== undefined\n ? { timeoutMs: this.uploadTimeoutMs }\n : {}),\n }\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `${what} rejected — ${describeRefusal(result)}`\n );\n }\n let txId: string;\n try {\n txId = extractArweaveTxId(result.status, result.body);\n } catch (err) {\n throw new ConnectorPublishError(\n `${what}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return { txId, feePaid: result.claim?.amount ?? estimate };\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n // No size gate (#102): the store route prices per KiB, so a body above\n // FREE_TIER_MAX_ITEM_BYTES is a paid upload rather than a refusal.\n const contentType =\n upload.type === 'blob'\n ? contentTypeForPath(upload.path)\n : DEFAULT_CONTENT_TYPE;\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', contentType],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n upload.body.length,\n `git-object upload (${upload.sha})`\n );\n }\n\n async uploadBlob(upload: BlobUpload): Promise<UploadReceipt> {\n // No size gate (#102), matching uploadGitObject: the store route prices\n // per KiB, so a blob above FREE_TIER_MAX_ITEM_BYTES costs more rather than\n // being refused.\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', upload.contentType],\n ...(upload.repoId ? [['Repo', upload.repoId]] : []),\n ],\n upload.body.length,\n 'blob upload'\n );\n }\n\n /**\n * One NIP-90 job to the store node's DVM, over the paid path. A refusal is\n * returned, not thrown: `accept: false` and a non-2xx status are the\n * answer's content, and the payer was charged for the answer (ADR 0020).\n */\n async submitStoreJob(request: StoreJobRequest): Promise<StoreJobResponse> {\n const rates = await this.getFeeRates();\n const fee = rates.uploadFee;\n const event = this.sign({\n kind: request.kind,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ...request.params.map(([key, value]) => ['param', key, value]),\n ['bid', fee.toString(), 'usdc'],\n ],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n this.store.sealTo !== undefined ? { sealTo: this.store.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job rejected — ${describeRefusal(result)}`\n );\n }\n const text = decodeUtf8(result.body);\n let body: {\n accept?: unknown;\n code?: unknown;\n message?: unknown;\n result?: unknown;\n };\n try {\n body = JSON.parse(text) as typeof body;\n } catch {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job answer was not valid JSON ` +\n `(HTTP ${String(result.status)}): ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n return {\n status: result.status,\n accept: body.accept === true,\n ...(typeof body.code === 'string' ? { code: body.code } : {}),\n ...(typeof body.message === 'string' ? { message: body.message } : {}),\n ...(body.result !== null && typeof body.result === 'object'\n ? { result: body.result as Record<string, unknown> }\n : {}),\n feePaid: result.claim?.amount ?? fee,\n };\n }\n\n /**\n * Sign the event with the owner's key and pay to publish it. `relayUrls`\n * is the seam's plural surface: this publisher routes to one configured\n * destination, so more than one relay is refused rather than half-done.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new ConnectorPublishError(\n `multi-relay publish is not supported (got ${String(relayUrls.length)} relays) — ` +\n 'the connector publisher routes to a single publish destination'\n );\n }\n const rates = await this.getFeeRates();\n const signed = this.sign(event);\n const result = await this.publish.client.send(\n this.publish.destination,\n { body: { event: signed } },\n this.publish.sealTo !== undefined ? { sealTo: this.publish.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `publish rejected (kind ${String(event.kind)}) — ${describeRefusal(result)}`\n );\n }\n if (result.status < 200 || result.status >= 300) {\n throw new ConnectorPublishError(\n `publish (kind ${String(event.kind)}): the relay answered HTTP ${String(result.status)} - ${decodeUtf8(result.body)}`\n );\n }\n let eventId = signed.id;\n try {\n const answer = JSON.parse(decodeUtf8(result.body)) as {\n eventId?: unknown;\n };\n if (typeof answer.eventId === 'string' && answer.eventId)\n eventId = answer.eventId;\n } catch {\n // A 2xx without a JSON body still stored the event we signed.\n }\n return { eventId, feePaid: result.claim?.amount ?? rates.eventFee };\n }\n}\n"],"mappings":"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","names":[]}
@@ -6,11 +6,57 @@ import {
6
6
  isValidArweaveTxId
7
7
  } from "./chunk-NCEMQUPA.js";
8
8
  import {
9
- EMPTY_BLOB_SHA,
10
- MAX_OBJECT_SIZE,
11
- hashGitObject,
12
9
  uploadChargeFor
13
- } from "./chunk-GVXBUL3C.js";
10
+ } from "./chunk-CXREFXB6.js";
11
+
12
+ // src/objects.ts
13
+ import { createHash } from "crypto";
14
+ var FREE_TIER_MAX_ITEM_BYTES = 107520;
15
+ var MAX_OBJECT_SIZE = FREE_TIER_MAX_ITEM_BYTES;
16
+ var EMPTY_BLOB_SHA = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391";
17
+ function hashGitObject(type, body) {
18
+ const header = Buffer.from(`${type} ${body.length}\0`);
19
+ const fullObject = Buffer.concat([header, body]);
20
+ const sha = createHash("sha1").update(fullObject).digest("hex");
21
+ return { sha, buffer: fullObject, body };
22
+ }
23
+ function createGitBlob(content) {
24
+ return hashGitObject("blob", Buffer.from(content, "utf-8"));
25
+ }
26
+ function createGitTree(entries) {
27
+ const sorted = [...entries].sort(
28
+ (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
29
+ );
30
+ const entryBuffers = [];
31
+ for (const entry of sorted) {
32
+ const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\0`);
33
+ const rawSha = Buffer.from(entry.sha, "hex");
34
+ entryBuffers.push(Buffer.concat([modeAndName, rawSha]));
35
+ }
36
+ return hashGitObject("tree", Buffer.concat(entryBuffers));
37
+ }
38
+ function createGitCommit(opts) {
39
+ const lines = [
40
+ `tree ${opts.treeSha}`,
41
+ ...opts.parentSha ? [`parent ${opts.parentSha}`] : [],
42
+ `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,
43
+ `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,
44
+ "",
45
+ opts.message
46
+ ];
47
+ return hashGitObject("commit", Buffer.from(lines.join("\n"), "utf-8"));
48
+ }
49
+ function createGitTag(opts) {
50
+ const lines = [
51
+ `object ${opts.objectSha}`,
52
+ `type ${opts.objectType}`,
53
+ `tag ${opts.tagName}`,
54
+ `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,
55
+ "",
56
+ opts.message
57
+ ];
58
+ return hashGitObject("tag", Buffer.from(lines.join("\n"), "utf-8"));
59
+ }
14
60
 
15
61
  // src/repo-reader.ts
16
62
  import { execFile, spawn } from "child_process";
@@ -1060,14 +1106,6 @@ async function planPush(options) {
1060
1106
  `objects vanished from the local repository during planning: ${missing.join(", ")}`
1061
1107
  );
1062
1108
  }
1063
- const oversize = [];
1064
- for (const stat of stats) {
1065
- if (stat.size > MAX_OBJECT_SIZE) {
1066
- const path = pathBySha.get(stat.sha);
1067
- oversize.push({ ...stat, ...path ? { path } : {} });
1068
- }
1069
- }
1070
- if (oversize.length > 0) throw new OversizeObjectsError(oversize);
1071
1109
  const tipShas = new Set(updates.map((u) => u.localSha));
1072
1110
  const planned = [];
1073
1111
  const skippedEmptyObjects = [];
@@ -1217,6 +1255,12 @@ async function executePush(options) {
1217
1255
  }
1218
1256
 
1219
1257
  export {
1258
+ MAX_OBJECT_SIZE,
1259
+ hashGitObject,
1260
+ createGitBlob,
1261
+ createGitTree,
1262
+ createGitCommit,
1263
+ createGitTag,
1220
1264
  GitError,
1221
1265
  GitRepoReader,
1222
1266
  DEFAULT_CONCURRENCY,
@@ -1247,4 +1291,4 @@ export {
1247
1291
  planPush,
1248
1292
  executePush
1249
1293
  };
1250
- //# sourceMappingURL=chunk-7VCVR4GK.js.map
1294
+ //# sourceMappingURL=chunk-TJPIE5X4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/objects.ts","../src/repo-reader.ts","../src/object-fetch.ts","../src/read-pipeline.ts","../src/materialize.ts","../src/npub.ts","../src/routes.ts","../src/push.ts"],"sourcesContent":["/**\n * Pure git object construction and SHA-1 envelope hashing.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/git-builder.ts` (#223) —\n * the proven seed pipeline builders, now the core of the Git-to-TOON write\n * path. Everything here is pure: no network, no signing, no payments.\n * Upload/publish lives with the Publisher (#226).\n *\n * Git object format: `<type> <size>\\0<content>`. The SHA-1 is computed over\n * the full envelope (header + NUL + content); the `body` (content only) is\n * what gets uploaded to Arweave.\n */\n\nimport { createHash } from 'node:crypto';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** All git object types TOON can carry. */\nexport type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';\n\nexport interface GitObject {\n /** SHA-1 hex digest computed over full git envelope */\n sha: string;\n /** Full git object (header + null + content) */\n buffer: Buffer;\n /** Body only (content after the null byte) — this is what gets uploaded */\n body: Buffer;\n}\n\n/**\n * The storage upstream's FREE per-item ceiling, in bytes.\n *\n * Read live from `https://upload.ardrive.io/`, which publishes it twice and\n * agreeing (2026-09-06):\n *\n * { \"freeUploadLimitBytes\": 107520,\n * \"freeTier\": { \"maxItemBytes\": 107520, ... } }\n *\n * The old value was `95 * 1024` (97,280) described as a \"95KB safety margin\n * under the 100KB free tier\". The service publishes no 100KB limit — the\n * figure was never upstream's — so the margin refused ~10 KiB of objects the\n * upstream would have taken for free (#102).\n *\n * This is a PRICING boundary, not a cap. An object at or under it rides the\n * free tier; a larger one is uploaded and PAID FOR through the store route's\n * per-KiB schedule (ADR 0065) — `g.toon.relay.store` advertises\n * `{price: \"1001\", pricePerKib: \"10\"}`, and that per-KiB term exists precisely\n * because size costs the serving node money. Nothing rejects on size.\n */\nexport const FREE_TIER_MAX_ITEM_BYTES = 107_520;\n\n/**\n * @deprecated Renamed to {@link FREE_TIER_MAX_ITEM_BYTES}, and no longer a\n * maximum: objects above it upload as paid writes rather than erroring. Kept\n * as an alias so the 4.x export keeps resolving.\n */\nexport const MAX_OBJECT_SIZE = FREE_TIER_MAX_ITEM_BYTES;\n\n/**\n * Git's universal empty-blob SHA-1: the object for a zero-byte file.\n *\n * It is the ONLY zero-byte object git can produce (trees, commits, and tags\n * are never empty) and it always has this exact constant value. The store\n * rejects a zero-byte kind:5094 blob upload as malformed (F00), so `rig` never\n * uploads this object — it is skipped on push and synthesized locally on\n * clone/fetch (both keyed off an ACTUAL zero-length body, never a heuristic).\n */\nexport const EMPTY_BLOB_SHA = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';\n\n// ---------------------------------------------------------------------------\n// Envelope hashing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a raw object body in the git envelope (`<type> <size>\\0`) and compute\n * its SHA-1. This is exactly what `git hash-object -t <type>` does.\n */\nexport function hashGitObject(type: GitObjectType, body: Buffer): GitObject {\n const header = Buffer.from(`${type} ${body.length}\\0`);\n const fullObject = Buffer.concat([header, body]);\n const sha = createHash('sha1').update(fullObject).digest('hex');\n return { sha, buffer: fullObject, body };\n}\n\n// ---------------------------------------------------------------------------\n// Git object construction\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a git blob object and compute its SHA-1.\n *\n * Format: blob <size>\\0<content>\n * SHA is over the full envelope; body is content only (for upload).\n */\nexport function createGitBlob(content: string): GitObject {\n return hashGitObject('blob', Buffer.from(content, 'utf-8'));\n}\n\n/**\n * Construct a git tree object from sorted entries.\n *\n * Format: tree <size>\\0<entries>\n * Each entry: <mode> <name>\\0<20-byte-raw-sha1>\n * Entries MUST be sorted by name (byte-wise).\n */\nexport function createGitTree(\n entries: { mode: string; name: string; sha: string }[]\n): GitObject {\n // Git sorts tree entries by raw byte order (NOT locale-aware)\n const sorted = [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0\n );\n\n const entryBuffers: Buffer[] = [];\n for (const entry of sorted) {\n const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\\0`);\n // Raw 20-byte SHA-1 (NOT hex)\n const rawSha = Buffer.from(entry.sha, 'hex');\n entryBuffers.push(Buffer.concat([modeAndName, rawSha]));\n }\n\n return hashGitObject('tree', Buffer.concat(entryBuffers));\n}\n\n/**\n * Construct a git commit object.\n *\n * Format: commit <size>\\0tree <tree-sha>\\n[parent ...]\\nauthor ...\\ncommitter ...\\n\\n<message>\n * Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.\n */\nexport function createGitCommit(opts: {\n treeSha: string;\n parentSha?: string;\n authorName: string;\n authorPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `tree ${opts.treeSha}`,\n ...(opts.parentSha ? [`parent ${opts.parentSha}`] : []),\n `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('commit', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n\n/**\n * Construct an annotated git tag object.\n *\n * Format: tag <size>\\0object <sha>\\ntype <type>\\ntag <name>\\ntagger ...\\n\\n<message>\n * The tagged object is usually a commit, but git allows tagging any object\n * type (including another tag).\n */\nexport function createGitTag(opts: {\n /** SHA-1 of the object being tagged (hex, 40 chars) */\n objectSha: string;\n /** Type of the tagged object (usually 'commit') */\n objectType: GitObjectType;\n /** Tag name, e.g. 'v1.0.0' */\n tagName: string;\n taggerName: string;\n taggerPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `object ${opts.objectSha}`,\n `type ${opts.objectType}`,\n `tag ${opts.tagName}`,\n `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('tag', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n","/**\n * GitRepoReader — read a local repository via `execFile` git plumbing.\n *\n * Real git gives perfect fidelity (packfiles, delta chains, exotic history)\n * with zero new deps — no isomorphic-git. Everything here is read-only and\n * injection-safe:\n *\n * - child processes are spawned with `execFile`/`spawn` and argument\n * ARRAYS — never a shell, never string interpolation;\n * - every caller-supplied revision/range is validated against strict\n * regexes that (among other things) reject a leading `-`, so a value\n * like `--upload-pack=…` can never be parsed as an option;\n * - `--` terminators are appended where git supports them so nothing\n * user-supplied can be re-interpreted as a pathspec.\n *\n * Push planning/publishing live in follow-up tickets of epic\n * toon-client#222 — this module only reads.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { GitObjectType } from './objects.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Generous cap for plumbing stdout (rev-list on big repos, format-patch). */\nconst MAX_BUFFER = 256 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single ref from `git for-each-ref` (branches + tags). */\nexport interface GitRef {\n /** Full refname, e.g. `refs/heads/main` or `refs/tags/v1.0.0`. */\n refname: string;\n /**\n * SHA the ref points at. For annotated tags this is the TAG object's SHA\n * (the peeled commit is in {@link peeledSha}); for branches and\n * lightweight tags it is the commit SHA.\n */\n sha: string;\n /** Type of the referenced object: `commit`, or `tag` for annotated tags. */\n type: GitObjectType;\n /** For annotated tags: the peeled (target) object SHA. */\n peeledSha?: string;\n}\n\n/** Result of {@link GitRepoReader.listRefs}. */\nexport interface RepoRefs {\n /**\n * Full refname HEAD points at (e.g. `refs/heads/main`), or `undefined`\n * when HEAD is detached.\n */\n head?: string;\n refs: GitRef[];\n}\n\n/** One object streamed out of `git cat-file --batch`. */\nexport interface ReadGitObject {\n /** Full 40-hex SHA-1. */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** Result of {@link GitRepoReader.readObjects}. */\nexport interface ReadObjectsResult {\n /** Objects found, in input order (minus missing ones). */\n objects: ReadGitObject[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** One object from `rev-list --objects`: SHA plus the path it was reached by. */\nexport interface ObjectWithPath {\n /** Full 40-hex SHA-1. */\n sha: string;\n /**\n * Path the object was first reached by (blobs and non-root trees);\n * `undefined` for commits, root trees, and tag objects.\n */\n path?: string;\n}\n\n/** One object's metadata from `cat-file --batch-check`. */\nexport interface ObjectStat {\n sha: string;\n type: GitObjectType;\n /** Object body size in bytes (content only, no envelope header). */\n size: number;\n}\n\n/** Result of {@link GitRepoReader.statObjects}. */\nexport interface StatObjectsResult {\n /** Stats found, in input order (minus missing ones). */\n objects: ObjectStat[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** Error from a git child process, carrying exit code and stderr. */\nexport class GitError extends Error {\n constructor(\n message: string,\n /** Process exit code (undefined when the process failed to spawn). */\n public readonly exitCode: number | undefined,\n /** Captured stderr, trimmed. */\n public readonly stderr: string\n ) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Argument validation (injection defense)\n// ---------------------------------------------------------------------------\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * One revision token: a SHA prefix (4–40 hex) or a refname-ish word with an\n * optional `^`/`~<n>` ancestry suffix. Must start with an alphanumeric, so a\n * leading `-` (option injection) is impossible; `@{…}`, whitespace, and other\n * revspec exotica are deliberately rejected.\n */\nconst REV_TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*(?:[~^][0-9]*)*$/;\n\nfunction isValidRevision(rev: string): boolean {\n if (rev.length === 0 || rev.length > 1024) return false;\n if (!REV_TOKEN_RE.test(rev)) return false;\n // Refname rules git enforces that our charset alone doesn't:\n if (rev.includes('..')) return false; // range separator / invalid in refnames\n if (rev.endsWith('.lock') || rev.endsWith('/') || rev.endsWith('.')) return false;\n return true;\n}\n\nfunction assertRevision(rev: string, what: string): void {\n if (!isValidRevision(rev)) {\n throw new Error(\n `${what} is not a valid git revision (got ${JSON.stringify(rev)}); ` +\n 'expected a SHA or simple refname — options/ranges are rejected'\n );\n }\n}\n\nfunction assertFullSha(sha: string, what: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(\n `${what} is not a full 40-hex SHA-1 (got ${JSON.stringify(sha)})`\n );\n }\n}\n\n/**\n * A revision range for format-patch: `<rev>`, `<rev>..<rev>`, or\n * `<rev>...<rev>` where each side passes {@link isValidRevision}.\n */\nfunction assertRange(range: string, what: string): void {\n const parts = range.split(/\\.{2,3}/);\n const separators = range.match(/\\.{2,3}/g) ?? [];\n const ok =\n parts.length <= 2 &&\n separators.length === parts.length - 1 &&\n parts.every((p) => isValidRevision(p));\n if (!ok) {\n throw new Error(\n `${what} is not a valid revision range (got ${JSON.stringify(range)}); ` +\n 'expected <rev>, <rev>..<rev>, or <rev>...<rev>'\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// cat-file --batch incremental parser\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: ReadonlySet<string> = new Set(['blob', 'tree', 'commit', 'tag']);\n\n/**\n * Incremental parser for `git cat-file --batch` output:\n * `<sha> <type> <size>\\n<body>\\n` per found object, `<name> missing\\n` for\n * absent ones. Bodies are raw bytes (possibly binary) and may be split\n * across arbitrary chunk boundaries, so parsing is strictly size-driven.\n */\nclass BatchParser {\n private buf: Buffer = Buffer.alloc(0);\n private pending: { sha: string; type: GitObjectType; size: number } | null = null;\n\n readonly objects: ReadGitObject[] = [];\n readonly missing: string[] = [];\n\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /** True when no partially-parsed record remains. */\n isComplete(): boolean {\n return this.pending === null && this.buf.length === 0;\n }\n\n private drain(): void {\n for (;;) {\n if (this.pending) {\n // Need body + trailing LF before the record is complete.\n const needed = this.pending.size + 1;\n if (this.buf.length < needed) return;\n const body = Buffer.from(this.buf.subarray(0, this.pending.size));\n this.objects.push({ sha: this.pending.sha, type: this.pending.type, body });\n this.buf = this.buf.subarray(needed);\n this.pending = null;\n continue;\n }\n\n const nl = this.buf.indexOf(0x0a);\n if (nl === -1) return;\n const header = this.buf.subarray(0, nl).toString('utf-8');\n this.buf = this.buf.subarray(nl + 1);\n\n const [name, second, third] = header.split(' ');\n if (name && second === 'missing' && third === undefined) {\n this.missing.push(name);\n continue;\n }\n if (name && second && third !== undefined && OBJECT_TYPES.has(second)) {\n const size = Number.parseInt(third, 10);\n if (Number.isSafeInteger(size) && size >= 0) {\n this.pending = { sha: name, type: second as GitObjectType, size };\n continue;\n }\n }\n throw new GitError(\n `unexpected cat-file --batch header: ${JSON.stringify(header)}`,\n undefined,\n ''\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// GitRepoReader\n// ---------------------------------------------------------------------------\n\n/**\n * Read-only view of a local git repository via git plumbing commands.\n *\n * All methods throw {@link GitError} when the underlying git process fails\n * unexpectedly, and plain `Error` when a caller-supplied argument fails\n * validation (before any process is spawned).\n */\nexport class GitRepoReader {\n constructor(\n /** Absolute or relative path to the repository worktree (or .git dir). */\n public readonly repoPath: string\n ) {}\n\n /** Run git with argument-array safety; resolves stdout as UTF-8. */\n private async git(\n args: string[],\n opts: { allowExitCodes?: number[] } = {}\n ): Promise<{ stdout: string; exitCode: number }> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: this.repoPath,\n maxBuffer: MAX_BUFFER,\n encoding: 'utf-8',\n });\n return { stdout, exitCode: 0 };\n } catch (err) {\n const e = err as NodeJS.ErrnoException & {\n code?: number | string;\n stdout?: string;\n stderr?: string;\n };\n const exitCode = typeof e.code === 'number' ? e.code : undefined;\n if (exitCode !== undefined && opts.allowExitCodes?.includes(exitCode)) {\n return { stdout: e.stdout ?? '', exitCode };\n }\n throw new GitError(\n `git ${args[0]} failed${exitCode !== undefined ? ` (exit ${exitCode})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`,\n exitCode,\n (e.stderr ?? '').trim()\n );\n }\n }\n\n /**\n * List all branches and tags plus the symbolic HEAD.\n *\n * Annotated tags report the tag object's SHA/type with the peeled target\n * in `peeledSha`. A detached HEAD is tolerated (`head` is `undefined`).\n */\n async listRefs(): Promise<RepoRefs> {\n const format = '%(refname)%00%(objectname)%00%(objecttype)%00%(*objectname)';\n const [refsRes, headRes] = await Promise.all([\n this.git(['for-each-ref', `--format=${format}`, 'refs/heads', 'refs/tags']),\n // Exit 1 = detached HEAD (or unborn branch pointer oddities) — tolerated.\n this.git(['symbolic-ref', '--quiet', 'HEAD'], { allowExitCodes: [1] }),\n ]);\n\n const refs: GitRef[] = [];\n for (const line of refsRes.stdout.split('\\n')) {\n if (!line) continue;\n const [refname, sha, objecttype, peeled] = line.split('\\0');\n if (!refname || !sha || !objecttype || !OBJECT_TYPES.has(objecttype)) {\n throw new GitError(`unexpected for-each-ref line: ${JSON.stringify(line)}`, undefined, '');\n }\n refs.push({\n refname,\n sha,\n type: objecttype as GitObjectType,\n ...(peeled ? { peeledSha: peeled } : {}),\n });\n }\n\n const head = headRes.exitCode === 0 ? headRes.stdout.trim() || undefined : undefined;\n return { head, refs };\n }\n\n /**\n * SHAs of every object reachable from `want` but not from `have`\n * (`git rev-list --objects <want…> --not <have…>`), i.e. the push delta.\n *\n * Haves that don't exist locally (e.g. remote tips we never fetched) are\n * filtered out first via one `cat-file --batch-check` pass — rev-list\n * would otherwise die on them.\n */\n async objectsBetween(want: string[], have: string[]): Promise<string[]> {\n const objects = await this.objectsBetweenWithPaths(want, have);\n return objects.map((o) => o.sha);\n }\n\n /**\n * Like {@link objectsBetween} but keeps the path each object was reached\n * by (`rev-list --objects` emits `<sha> <path>` for blobs and non-root\n * trees) — used by push planning to report actionable oversize errors.\n */\n async objectsBetweenWithPaths(\n want: string[],\n have: string[]\n ): Promise<ObjectWithPath[]> {\n for (const w of want) assertRevision(w, 'want');\n for (const h of have) assertRevision(h, 'have');\n if (want.length === 0) return [];\n\n const knownHaves = await this.filterExisting(have);\n\n const args = ['rev-list', '--objects', ...want];\n if (knownHaves.length > 0) args.push('--not', ...knownHaves);\n args.push('--'); // nothing user-supplied can become a pathspec\n const { stdout } = await this.git(args);\n\n const objects: ObjectWithPath[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n // `--objects` lines are `<sha>` or `<sha> <path>`.\n const spaceIdx = line.indexOf(' ');\n if (spaceIdx === -1) {\n objects.push({ sha: line });\n } else {\n const path = line.slice(spaceIdx + 1);\n objects.push({\n sha: line.slice(0, spaceIdx),\n ...(path ? { path } : {}),\n });\n }\n }\n return objects;\n }\n\n /**\n * List every blob (file) reachable from a ref's root tree, recursively,\n * with the path it is served at (#368: the ar.io site manifest join key).\n * Uses `git ls-tree -r -z` — NUL-terminated records so binary/spaced paths\n * survive verbatim, and no path quoting to undo. Submodule (`commit`)\n * gitlink entries and directories are excluded; only real file blobs remain.\n */\n async listBlobs(rev: string): Promise<{ path: string; sha: string }[]> {\n assertRevision(rev, 'ref');\n const { stdout } = await this.git(['ls-tree', '-r', '-z', rev, '--']);\n const blobs: { path: string; sha: string }[] = [];\n for (const record of stdout.split('\\0')) {\n if (!record) continue;\n // `<mode> SP <type> SP <sha> TAB <path>`\n const tab = record.indexOf('\\t');\n if (tab === -1) {\n throw new GitError(\n `unexpected ls-tree record: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n const meta = record.slice(0, tab);\n const path = record.slice(tab + 1);\n const [, type, sha] = meta.split(' ');\n if (type !== 'blob') continue; // trees are flattened by -r; skip gitlinks\n if (!sha || !FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `unexpected ls-tree object id: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n blobs.push({ path, sha });\n }\n return blobs;\n }\n\n /** Run git feeding `input` on stdin; resolves collected stdout bytes. */\n private runWithStdin(args: string[], input: string): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const child = spawn('git', args, {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const out: Buffer[] = [];\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => out.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git ${args[0]}: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new GitError(`git ${args[0]} failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n resolve(Buffer.concat(out));\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(input);\n child.stdin.end();\n });\n }\n\n /** Of the given revisions, keep only those resolvable locally. */\n private async filterExisting(revs: string[]): Promise<string[]> {\n if (revs.length === 0) return [];\n const { missing } = await this.batchCheck(revs);\n const missingSet = new Set(missing);\n return revs.filter((r) => !missingSet.has(r));\n }\n\n /** One `cat-file --batch-check` pass; returns names reported missing. */\n private async batchCheck(names: string[]): Promise<{ missing: string[] }> {\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n names.join('\\n') + '\\n'\n );\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (line.endsWith(' missing')) missing.push(line.slice(0, -' missing'.length));\n }\n return { missing };\n }\n\n /**\n * Object metadata (type + body size) for a batch of SHAs via one\n * `cat-file --batch-check` pass — no bodies are read. Missing objects are\n * reported, not thrown.\n */\n async statObjects(shas: string[]): Promise<StatObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n shas.join('\\n') + '\\n'\n );\n\n const objects: ObjectStat[] = [];\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (!line) continue;\n if (line.endsWith(' missing')) {\n missing.push(line.slice(0, -' missing'.length));\n continue;\n }\n const [sha, type, sizeStr] = line.split(' ');\n const size = Number.parseInt(sizeStr ?? '', 10);\n if (\n !sha ||\n !type ||\n !OBJECT_TYPES.has(type) ||\n !Number.isSafeInteger(size) ||\n size < 0\n ) {\n throw new GitError(\n `unexpected cat-file --batch-check line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n objects.push({ sha, type: type as GitObjectType, size });\n }\n return { objects, missing };\n }\n\n /**\n * Read raw object bodies via a single streaming `git cat-file --batch`\n * child process. Bodies may be binary and are parsed size-driven across\n * chunk boundaries. Missing objects are reported, not thrown.\n */\n async readObjects(shas: string[]): Promise<ReadObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const parser = new BatchParser();\n await new Promise<void>((resolve, reject) => {\n const child = spawn('git', ['cat-file', '--batch'], {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n let parseError: Error | null = null;\n\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.stdout.on('data', (chunk: Buffer) => {\n if (parseError) return;\n try {\n parser.push(chunk);\n } catch (err) {\n parseError = err as Error;\n child.kill();\n }\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git cat-file: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (parseError) return reject(parseError);\n if (code !== 0) {\n return reject(\n new GitError(`git cat-file --batch failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n if (!parser.isComplete()) {\n return reject(\n new GitError('git cat-file --batch output ended mid-record', code ?? undefined, stderr.trim())\n );\n }\n resolve();\n });\n\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' will surface the error.\n });\n child.stdin.write(shas.join('\\n') + '\\n');\n child.stdin.end();\n });\n\n return { objects: parser.objects, missing: parser.missing };\n }\n\n /**\n * `git merge-base --is-ancestor <a> <b>` — true when `a` is an ancestor\n * of `b` (fast-forward check / force detection). Exit codes other than\n * 0/1 (e.g. unknown revisions) throw.\n */\n async isAncestor(a: string, b: string): Promise<boolean> {\n assertRevision(a, 'ancestor candidate');\n assertRevision(b, 'descendant candidate');\n const { exitCode } = await this.git(\n ['merge-base', '--is-ancestor', a, b],\n { allowExitCodes: [1] }\n );\n return exitCode === 0;\n }\n\n /**\n * `git format-patch --stdout <range>` — the full mbox-formatted patch\n * series text (empty string when the range selects no commits).\n */\n async formatPatch(range: string): Promise<string> {\n assertRange(range, 'range');\n const { stdout } = await this.git(['format-patch', '--stdout', range, '--']);\n return stdout;\n }\n\n /**\n * Parent SHAs for a batch of commit SHAs via one\n * `git rev-list --no-walk=unsorted --parents` pass. Root commits map to an\n * empty array. Used to derive the kind:1617 `commit`/`parent-commit` tag\n * pairs for exactly the commits a format-patch series carries.\n */\n async commitParents(shas: string[]): Promise<Map<string, string[]>> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return new Map();\n const { stdout } = await this.git([\n 'rev-list',\n '--no-walk=unsorted',\n '--parents',\n ...shas,\n '--',\n ]);\n const parents = new Map<string, string[]>();\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const [sha, ...rest] = line.split(' ');\n if (!sha || !FULL_SHA_RE.test(sha) || rest.some((p) => !FULL_SHA_RE.test(p))) {\n throw new GitError(\n `unexpected rev-list --parents line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n parents.set(sha, rest);\n }\n return parents;\n }\n\n /**\n * Resolve a ref/revision to a full SHA via `git rev-parse --verify`.\n * Throws {@link GitError} when the name doesn't resolve.\n */\n async resolveRef(name: string): Promise<string> {\n assertRevision(name, 'ref name');\n const { stdout } = await this.git(['rev-parse', '--verify', '--quiet', name]);\n const sha = stdout.trim();\n if (!FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `rev-parse --verify returned unexpected output for ${JSON.stringify(name)}: ${JSON.stringify(sha)}`,\n undefined,\n ''\n );\n }\n return sha;\n }\n}\n","/**\n * The Git-from-TOON READ pipeline core (#278): download git object bodies\n * from Arweave gateways, verify them against their SHA-1, and walk the\n * object graph to prove a ref closure is complete.\n *\n * This is the CLI counterpart of rig-web's proven browser read path\n * (`web/arweave-client.ts` + `web/git-objects.ts` + the commit walker) — the\n * logic is mirrored, not imported, because the SPA package is not a library.\n *\n * Everything here is FREE (reads only: Arweave gateway GETs + the GraphQL\n * Git-SHA resolver) and pure of git — materializing objects into a real\n * repository lives in ./materialize.ts.\n *\n * INTEGRITY IS NON-NEGOTIABLE: an Arweave upload stores the object BODY\n * (content after the envelope NUL). Re-wrapping the body as each of the four\n * git object types and comparing the envelope SHA-1 against the expected SHA\n * both AUTHENTICATES the bytes and DISCOVERS the object's type in one step —\n * a body that matches under no type is rejected as corrupt/tampered, never\n * written.\n */\n\nimport {\n ARWEAVE_FETCH_TIMEOUT_MS,\n ARWEAVE_GATEWAYS,\n isValidArweaveTxId,\n} from '@toon-protocol/arweave';\nimport { hashGitObject, type GitObjectType } from './objects.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A downloaded git object: SHA-verified body + the type that verified it. */\nexport interface FetchedObject {\n /** Full 40-hex SHA-1 (verified against the body). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** WHATWG-fetch seam (injectable for tests). */\nexport type FetchLike = (\n url: string,\n init?: { signal?: AbortSignal }\n) => Promise<{\n ok: boolean;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\nexport interface GatewayFetchOptions {\n /** Ordered gateway base URLs (default: the shared preference list). */\n gateways?: readonly string[];\n /** fetch implementation (default: global fetch). */\n fetchFn?: FetchLike;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n}\n\nexport interface DownloadOptions extends GatewayFetchOptions {\n /** Maximum concurrent gateway downloads (default {@link DEFAULT_CONCURRENCY}). */\n concurrency?: number;\n /** Progress callback, called once per finished object. */\n onObject?: (done: number, total: number) => void;\n}\n\n/** Result of {@link downloadGitObjects}. */\nexport interface DownloadResult {\n /** SHA → verified object, for every SHA that could be downloaded. */\n objects: Map<string, FetchedObject>;\n /** SHAs whose txId 404'd/errored on EVERY gateway (propagation lag). */\n unavailable: { sha: string; txId: string }[];\n}\n\n/** Default parallel-download cap. */\nexport const DEFAULT_CONCURRENCY = 8;\n\n/**\n * A downloaded body did not hash to its expected SHA under ANY git object\n * type — corrupt or tampered content. The clone/fetch pipelines treat this\n * as a hard failure: nothing is written.\n */\nexport class ObjectIntegrityError extends Error {\n constructor(\n /** The objects that failed verification. */\n public readonly objects: { sha: string; txId: string }[]\n ) {\n super(\n `${objects.length} downloaded object(s) failed SHA-1 verification — ` +\n 'the gateway content does not match the announced git SHA(s): ' +\n objects.map((o) => `${o.sha} (tx ${o.txId})`).join(', ') +\n '. Refusing to write corrupt/tampered objects.'\n );\n this.name = 'ObjectIntegrityError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Verification (SHA check == type discovery)\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: readonly GitObjectType[] = [\n 'blob',\n 'tree',\n 'commit',\n 'tag',\n];\n\n/**\n * Verify a downloaded body against its expected SHA-1 by trying the four git\n * envelope types. Returns the verified object, or null when no type matches\n * (corrupt/tampered bytes).\n */\nexport function verifyObjectBody(\n expectedSha: string,\n bytes: Uint8Array\n): FetchedObject | null {\n const body = Buffer.from(bytes);\n for (const type of OBJECT_TYPES) {\n if (hashGitObject(type, body).sha === expectedSha) {\n return { sha: expectedSha, type, body };\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway download (fallback chain, mirrors rig-web's fetchArweaveObject)\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch raw bytes for an Arweave tx id, trying each gateway in preference\n * order. Returns null when every gateway fails (404 / error / timeout).\n */\nexport async function fetchTxBytes(\n txId: string,\n options: GatewayFetchOptions = {}\n): Promise<Uint8Array | null> {\n if (!isValidArweaveTxId(txId)) return null;\n const gateways = options.gateways ?? ARWEAVE_GATEWAYS;\n const fetchFn = options.fetchFn ?? (fetch as FetchLike);\n const timeoutMs = options.timeoutMs ?? ARWEAVE_FETCH_TIMEOUT_MS;\n\n for (const gateway of gateways) {\n try {\n const response = await fetchFn(`${gateway}/${txId}`, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) continue;\n return new Uint8Array(await response.arrayBuffer());\n } catch {\n // Network error, timeout, or other failure — try the next gateway.\n }\n }\n return null;\n}\n\n/**\n * Download + verify a batch of git objects (sha → Arweave txId) with a\n * concurrency cap and per-gateway fallback.\n *\n * Objects that 404 on every gateway are reported in `unavailable` (Arweave\n * propagation lag — the caller decides whether that is fatal). Objects whose\n * bytes fail SHA-1 verification throw {@link ObjectIntegrityError}: corrupt\n * content is NEVER returned.\n */\nexport async function downloadGitObjects(\n entries: Iterable<[sha: string, txId: string]>,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const queue = [...entries];\n const total = queue.length;\n const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY);\n\n const objects = new Map<string, FetchedObject>();\n const unavailable: { sha: string; txId: string }[] = [];\n const corrupt: { sha: string; txId: string }[] = [];\n let done = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const next = queue.shift();\n if (!next) return;\n const [sha, txId] = next;\n const bytes = await fetchTxBytes(txId, options);\n if (bytes === null) {\n unavailable.push({ sha, txId });\n } else {\n const verified = verifyObjectBody(sha, bytes);\n if (verified === null) {\n corrupt.push({ sha, txId });\n } else {\n objects.set(sha, verified);\n }\n }\n done += 1;\n options.onObject?.(done, total);\n }\n };\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, total) }, () => worker())\n );\n\n if (corrupt.length > 0) throw new ObjectIntegrityError(corrupt);\n return { objects, unavailable };\n}\n\n// ---------------------------------------------------------------------------\n// Object-graph references (mirrors rig-web's git-objects parsing)\n// ---------------------------------------------------------------------------\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/** Submodule (gitlink) tree-entry mode: references a commit in ANOTHER repo. */\nconst GITLINK_MODE = '160000';\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0');\n return hex;\n}\n\n/**\n * SHAs an object references inside the SAME repository:\n * - commit → its tree + parents\n * - tree → entry SHAs, EXCEPT gitlinks (mode 160000: submodule commits\n * live in another repository and are never present — git fsck\n * skips them too)\n * - tag → the tagged object\n * - blob → nothing\n */\nexport function referencedShas(object: FetchedObject): string[] {\n switch (object.type) {\n case 'blob':\n return [];\n case 'tree': {\n const refs: string[] = [];\n const data = object.body;\n let offset = 0;\n while (offset < data.length) {\n const spaceIdx = data.indexOf(0x20, offset);\n if (spaceIdx === -1) break;\n const mode = data.subarray(offset, spaceIdx).toString('utf-8');\n const nulIdx = data.indexOf(0x00, spaceIdx + 1);\n if (nulIdx === -1 || nulIdx + 21 > data.length) break;\n const sha = bytesToHex(data.subarray(nulIdx + 1, nulIdx + 21));\n if (mode !== GITLINK_MODE) refs.push(sha);\n offset = nulIdx + 21;\n }\n return refs;\n }\n case 'commit': {\n const refs: string[] = [];\n const text = object.body.toString('utf-8');\n const headerEnd = text.indexOf('\\n\\n');\n const header = headerEnd === -1 ? text : text.slice(0, headerEnd);\n for (const line of header.split('\\n')) {\n if (line.startsWith('tree ')) refs.push(line.slice(5).trim());\n else if (line.startsWith('parent ')) refs.push(line.slice(7).trim());\n }\n return refs.filter((sha) => FULL_SHA_RE.test(sha));\n }\n case 'tag': {\n const text = object.body.toString('utf-8');\n const match = /^object ([0-9a-f]{40})$/m.exec(text);\n return match?.[1] ? [match[1]] : [];\n }\n }\n}\n\n/** Result of {@link walkClosure}. */\nexport interface ClosureResult {\n /** Every SHA reachable from the tips that lives in `objects`. */\n reachable: Set<string>;\n /** Reachable SHAs found NEITHER in `objects` nor in `presentLocally`. */\n missing: string[];\n}\n\n/**\n * Walk the object graph from the ref tips over the downloaded object set and\n * report which reachable SHAs are missing. `presentLocally` marks SHAs that\n * already exist in the destination repository — the walk does not descend\n * into them (a consistent local repo carries its own closure; the same\n * assumption `git fetch` makes).\n */\nexport function walkClosure(\n tips: Iterable<string>,\n objects: ReadonlyMap<string, FetchedObject>,\n presentLocally: ReadonlySet<string> = new Set()\n): ClosureResult {\n const reachable = new Set<string>();\n const missing = new Set<string>();\n const stack = [...new Set(tips)];\n\n while (stack.length > 0) {\n const sha = stack.pop() as string;\n if (reachable.has(sha) || missing.has(sha)) continue;\n if (presentLocally.has(sha)) continue; // local closure assumed complete\n const object = objects.get(sha);\n if (!object) {\n missing.add(sha);\n continue;\n }\n reachable.add(sha);\n for (const ref of referencedShas(object)) {\n if (!reachable.has(ref) && !missing.has(ref)) stack.push(ref);\n }\n }\n\n return { reachable, missing: [...missing] };\n}\n","/**\n * The shared clone/fetch object-collection engine (#278).\n *\n * Given the remote's ref tips and its kind:30618 sha→Arweave-txId map, gather\n * every object the refs need:\n *\n * 1. download the mapped objects the destination repo doesn't already have\n * (parallel, gateway fallback chain, SHA-verified — ./object-fetch.ts);\n * 2. walk the object graph from the tips (./object-fetch.ts walkClosure) —\n * the local repository's objects count as present (git fetch's own\n * assumption: a consistent repo carries its own closure);\n * 3. SHAs the map doesn't cover are resolved through the Arweave GraphQL\n * Git-SHA resolver (RemoteState.resolveMissing) and downloaded, looping\n * until the closure is complete or no progress can be made.\n *\n * The result separates FATAL gaps (reachable objects that could not be\n * obtained — usually Arweave gateway propagation lag, 10–20 min for fresh\n * pushes) from harmless ones (mapped-but-unreachable objects, e.g. history\n * that was force-pushed away). Corrupt objects throw ObjectIntegrityError\n * from the download layer and never surface here.\n */\n\nimport {\n downloadGitObjects,\n walkClosure,\n type DownloadOptions,\n type FetchedObject,\n} from './object-fetch.js';\nimport { EMPTY_BLOB_SHA } from './objects.js';\n\n/** A reachable object that could not be obtained. */\nexport interface MissingObject {\n sha: string;\n /** The txId that failed on every gateway, or null when no txId resolved. */\n txId: string | null;\n}\n\nexport interface CollectRepoObjectsOptions extends DownloadOptions {\n /** Ref tip SHAs (commits or annotated tags) the closure must reach. */\n tips: string[];\n /** kind:30618 `arweave` tag map: git SHA → Arweave txId. */\n shaToTxId: ReadonlyMap<string, string>;\n /** GraphQL fallback for SHAs the map doesn't cover (RemoteState.resolveMissing). */\n resolveMissing: (shas: string[]) => Promise<Map<string, string>>;\n /** SHAs already present in the destination repository (fetch delta). */\n presentLocally?: ReadonlySet<string>;\n}\n\nexport interface CollectRepoObjectsResult {\n /** Verified objects to write, keyed by SHA. */\n objects: Map<string, FetchedObject>;\n /** Reachable SHAs that could not be obtained — FATAL for clone/fetch. */\n missing: MissingObject[];\n /** Mapped-but-unreachable SHAs that failed to download — warn only. */\n skippedUnavailable: { sha: string; txId: string }[];\n}\n\n/** Iteration cap: closure depth of NEW unmapped SHAs per pass; generous. */\nconst MAX_PASSES = 64;\n\n/** Collect (download + verify + close over) the objects the ref tips need. */\nexport async function collectRepoObjects(\n options: CollectRepoObjectsOptions\n): Promise<CollectRepoObjectsResult> {\n const { tips, shaToTxId, resolveMissing } = options;\n const present = options.presentLocally ?? new Set<string>();\n\n /** All txId knowledge: the 30618 map + GraphQL-resolved additions. */\n const txIds = new Map<string, string>(shaToTxId);\n /** SHAs we already asked the GraphQL resolver about (avoid re-queries). */\n const resolverAsked = new Set<string>();\n /** SHAs whose download failed on every gateway. */\n const undownloadable = new Map<string, string>();\n const objects = new Map<string, FetchedObject>();\n\n // Pass 0: bulk-download everything the map covers that isn't local yet.\n const initial: [string, string][] = [];\n for (const [sha, txId] of txIds) {\n if (!present.has(sha)) initial.push([sha, txId]);\n }\n const bulk = await downloadGitObjects(initial, options);\n for (const [sha, object] of bulk.objects) objects.set(sha, object);\n for (const { sha, txId } of bulk.unavailable) undownloadable.set(sha, txId);\n\n // Iterate: close over the tips; resolve + download whatever is still open.\n for (let pass = 0; pass < MAX_PASSES; pass++) {\n const closure = walkClosure(tips, objects, present);\n const open = closure.missing.filter((sha) => !undownloadable.has(sha));\n if (open.length === 0) break;\n\n // Resolve txIds for open SHAs we haven't asked the resolver about.\n const toResolve = open.filter(\n (sha) => !txIds.has(sha) && !resolverAsked.has(sha)\n );\n for (const sha of toResolve) resolverAsked.add(sha);\n if (toResolve.length > 0) {\n const resolved = await resolveMissing(toResolve);\n for (const [sha, txId] of resolved) txIds.set(sha, txId);\n }\n\n // Download open SHAs that now have a txId and no failed attempt yet.\n const batch: [string, string][] = [];\n for (const sha of open) {\n const txId = txIds.get(sha);\n if (txId !== undefined && !objects.has(sha)) batch.push([sha, txId]);\n }\n if (batch.length === 0) break; // no progress possible\n const result = await downloadGitObjects(batch, options);\n for (const [sha, object] of result.objects) objects.set(sha, object);\n for (const { sha, txId } of result.unavailable)\n undownloadable.set(sha, txId);\n if (result.objects.size === 0) break; // every attempt failed — stop\n }\n\n // The git empty blob is never uploaded (the store rejects zero-byte\n // content; `rig push` skips it), so a tree that references it reports it\n // \"missing\" here even though it is a git constant. Synthesize it locally —\n // a zero-byte blob body always hashes to EMPTY_BLOB_SHA — instead of\n // erroring, so an empty file reconstructs bit-identically (git fsck clean).\n // Keyed off the EXACT constant SHA: the honest lag-error still fires for any\n // genuinely-missing non-empty object.\n let finalClosure = walkClosure(tips, objects, present);\n if (\n finalClosure.missing.includes(EMPTY_BLOB_SHA) &&\n !present.has(EMPTY_BLOB_SHA)\n ) {\n objects.set(EMPTY_BLOB_SHA, {\n sha: EMPTY_BLOB_SHA,\n type: 'blob',\n body: Buffer.alloc(0),\n });\n finalClosure = walkClosure(tips, objects, present);\n }\n\n // Final accounting.\n const missing: MissingObject[] = finalClosure.missing.map((sha) => ({\n sha,\n txId: txIds.get(sha) ?? null,\n }));\n const reachable = finalClosure.reachable;\n const skippedUnavailable = [...undownloadable]\n .filter(\n ([sha]) => !reachable.has(sha) && !finalClosure.missing.includes(sha)\n )\n .map(([sha, txId]) => ({ sha, txId }));\n\n return { objects, missing, skippedUnavailable };\n}\n\n/**\n * The honest propagation-lag error text: which SHAs are unobtainable and why\n * retrying later is the expected remedy.\n */\nexport function missingObjectsMessage(\n missing: MissingObject[],\n context: string\n): string {\n const listed = missing\n .slice(0, 20)\n .map(\n (m) =>\n ` ${m.sha}${m.txId ? ` (tx ${m.txId})` : ' (no Arweave tx found)'}`\n )\n .join('\\n');\n const more =\n missing.length > 20 ? `\\n … and ${missing.length - 20} more` : '';\n return (\n `${context}: ${missing.length} required object(s) could not be downloaded:\\n` +\n `${listed}${more}\\n` +\n 'Recently pushed objects can take 10-20 minutes to become fetchable from ' +\n 'Arweave gateways — if this repo was just pushed, retry in a few minutes. ' +\n 'Nothing was written.'\n );\n}\n","/**\n * Materialize downloaded git objects into a REAL repository (#278).\n *\n * Objects are written through git's own plumbing — `git hash-object -w\n * --stdin -t <type>` with the raw body on stdin — so git computes, validates\n * (syntax checks for trees/commits/tags), stores (loose object + zlib), and\n * RETURNS the SHA. The returned SHA is compared against the expected one:\n * a second, independent integrity gate after ./object-fetch.ts's envelope\n * verification. Refs land via `git update-ref`, HEAD via `git symbolic-ref`.\n *\n * Same injection posture as GitRepoReader: child processes use\n * `execFile`/`spawn` with argument ARRAYS (never a shell), and refnames are\n * validated with `git check-ref-format` semantics before use.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FetchedObject } from './object-fetch.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** A written object's SHA disagreed with what `git hash-object` computed. */\nexport class ObjectWriteMismatchError extends Error {\n constructor(\n public readonly expectedSha: string,\n public readonly writtenSha: string\n ) {\n super(\n `git hash-object wrote ${writtenSha} where ${expectedSha} was expected — ` +\n 'object content does not round-trip; aborting'\n );\n this.name = 'ObjectWriteMismatchError';\n }\n}\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * Conservative refname validation (superset-safe subset of\n * `git check-ref-format`): must start `refs/`, no component may start with\n * `-` or `.`, no `..`, no control/space/git-special characters, no trailing\n * `/`, `.`, or `.lock`. Rejecting odd-but-legal names is fine — these\n * refnames come from relay events, and a hostile relay must not be able to\n * smuggle options or path traversal into git invocations.\n */\nexport function isSafeRefname(refname: string): boolean {\n if (!refname.startsWith('refs/') || refname.length > 1024) return false;\n // eslint-disable-next-line no-control-regex -- explicit control-char reject\n if (/[\\u0000-\\u0020~^:?*[\\\\\\u007f]/.test(refname)) return false;\n if (refname.includes('..') || refname.includes('@{')) return false;\n if (refname.endsWith('/') || refname.endsWith('.')) return false;\n for (const part of refname.split('/')) {\n if (part === '' || part.startsWith('.') || part.startsWith('-'))\n return false;\n if (part.endsWith('.lock')) return false;\n }\n return true;\n}\n\nfunction assertSafeRefname(refname: string): void {\n if (!isSafeRefname(refname)) {\n throw new Error(\n `unsafe ref name from remote state: ${JSON.stringify(refname)} — refusing`\n );\n }\n}\n\nfunction assertFullSha(sha: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(`not a full 40-hex SHA-1: ${JSON.stringify(sha)}`);\n }\n}\n\n/** Run git with argument-array safety in `repoPath`. */\nexport async function runGit(\n repoPath: string,\n args: string[]\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: repoPath,\n encoding: 'utf-8',\n maxBuffer: 64 * 1024 * 1024,\n });\n return stdout;\n } catch (err) {\n const e = err as {\n code?: number | string;\n stderr?: string;\n message?: string;\n };\n throw new Error(\n `git ${args[0]} failed${typeof e.code === 'number' ? ` (exit ${e.code})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`\n );\n }\n}\n\n/**\n * Write one object via `git hash-object -w --stdin -t <type>` (binary-safe\n * stdin) and verify the SHA git computed matches the expected one.\n */\nexport async function writeGitObject(\n repoPath: string,\n object: FetchedObject\n): Promise<void> {\n assertFullSha(object.sha);\n const written = await new Promise<string>((resolve, reject) => {\n const child = spawn(\n 'git',\n ['hash-object', '-w', '--stdin', '-t', object.type],\n { cwd: repoPath, stdio: ['pipe', 'pipe', 'pipe'] }\n );\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf-8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new Error(`failed to spawn git hash-object: ${err.message}`));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new Error(`git hash-object failed (exit ${code}): ${stderr.trim()}`)\n );\n }\n resolve(stdout.trim());\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(object.body);\n child.stdin.end();\n });\n if (written !== object.sha) {\n throw new ObjectWriteMismatchError(object.sha, written);\n }\n}\n\n/** Write a batch of verified objects into the repository (sequential). */\nexport async function writeGitObjects(\n repoPath: string,\n objects: Iterable<FetchedObject>\n): Promise<number> {\n let count = 0;\n for (const object of objects) {\n await writeGitObject(repoPath, object);\n count += 1;\n }\n return count;\n}\n\n/** `git update-ref <refname> <sha>` with refname/SHA validation. */\nexport async function updateRef(\n repoPath: string,\n refname: string,\n sha: string\n): Promise<void> {\n assertSafeRefname(refname);\n assertFullSha(sha);\n await runGit(repoPath, ['update-ref', refname, sha]);\n}\n\n/** Point HEAD at a branch via `git symbolic-ref HEAD <refname>`. */\nexport async function setHeadSymref(\n repoPath: string,\n refname: string\n): Promise<void> {\n assertSafeRefname(refname);\n await runGit(repoPath, ['symbolic-ref', 'HEAD', refname]);\n}\n","/**\n * Minimal bech32 npub encoding/decoding for Nostr pubkeys (BIP-173 / NIP-19),\n * dependency-free. Mirrors rig-web's proven `web/npub.ts` — the CLI must not\n * import from the SPA package, so the ~100 lines live here too (#278: `rig\n * clone` accepts `<owner-npub-or-hex>/<repo-id>` addresses).\n */\n\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\nfunction bech32Polymod(values: number[]): number {\n const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\n let chk = 1;\n for (const v of values) {\n const b = chk >> 25;\n chk = ((chk & 0x1ffffff) << 5) ^ v;\n for (let i = 0; i < 5; i++) {\n chk ^= (b >> i) & 1 ? (GEN[i] as number) : 0;\n }\n }\n return chk;\n}\n\nfunction bech32HrpExpand(hrp: string): number[] {\n const ret: number[] = [];\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) >> 5);\n }\n ret.push(0);\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) & 31);\n }\n return ret;\n}\n\nfunction bech32CreateChecksum(hrp: string, data: number[]): number[] {\n const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);\n const polymod = bech32Polymod(values) ^ 1;\n const ret: number[] = [];\n for (let i = 0; i < 6; i++) {\n ret.push((polymod >> (5 * (5 - i))) & 31);\n }\n return ret;\n}\n\nfunction convertBits(\n data: number[],\n fromBits: number,\n toBits: number,\n pad: boolean\n): number[] {\n let acc = 0;\n let bits = 0;\n const ret: number[] = [];\n const maxv = (1 << toBits) - 1;\n for (const value of data) {\n acc = (acc << fromBits) | value;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n ret.push((acc >> bits) & maxv);\n }\n }\n if (pad && bits > 0) {\n ret.push((acc << (toBits - bits)) & maxv);\n }\n return ret;\n}\n\nfunction hexToBytes(hex: string): number[] {\n const bytes: number[] = [];\n for (let i = 0; i < hex.length; i += 2) {\n bytes.push(parseInt(hex.slice(i, i + 2), 16));\n }\n return bytes;\n}\n\n/** Encode a 64-char hex pubkey as an `npub1…` bech32 string. */\nexport function hexToNpub(hexPubkey: string): string {\n const hrp = 'npub';\n const bytes = hexToBytes(hexPubkey);\n const words = convertBits(bytes, 8, 5, true);\n const checksum = bech32CreateChecksum(hrp, words);\n const combined = words.concat(checksum);\n return hrp + '1' + combined.map((d) => BECH32_CHARSET[d]).join('');\n}\n\n/**\n * Decode an `npub1…` bech32 string back to a 64-char hex pubkey.\n * Throws on malformed input (prefix, length, charset, checksum, padding).\n */\nexport function npubToHex(npub: string): string {\n const lower = npub.toLowerCase();\n if (lower !== npub && npub.toUpperCase() !== npub) {\n throw new Error('npub: mixed case');\n }\n if (!lower.startsWith('npub1')) {\n throw new Error('npub: invalid prefix');\n }\n if (lower.length !== 63) {\n throw new Error('npub: invalid length');\n }\n\n const data: number[] = [];\n for (let i = 5; i < lower.length; i++) {\n const idx = BECH32_CHARSET.indexOf(lower[i] as string);\n if (idx === -1) throw new Error('npub: invalid character');\n data.push(idx);\n }\n\n // Verify checksum\n const hrpExpanded = bech32HrpExpand('npub');\n if (bech32Polymod(hrpExpanded.concat(data)) !== 1) {\n throw new Error('npub: invalid checksum');\n }\n\n // Strip 6-word checksum, convert 5-bit words back to 8-bit bytes\n const words = data.slice(0, -6);\n const bytes = convertBits(words, 5, 8, false);\n\n if (bytes.length !== 32) {\n throw new Error('npub: invalid data length');\n }\n\n // Validate trailing bits are zero\n const totalBits = words.length * 5;\n const trailingBits = totalBits - bytes.length * 8;\n if (trailingBits > 0) {\n const lastWord = words[words.length - 1] as number;\n const mask = (1 << trailingBits) - 1;\n if ((lastWord & mask) !== 0) {\n throw new Error('npub: non-zero padding bits');\n }\n }\n\n return bytes.map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nconst HEX64_RE = /^[0-9a-f]{64}$/;\n\n/**\n * Normalize a repo-owner reference to a 64-char hex pubkey: accepts lowercase\n * hex verbatim or an `npub1…` string. Throws a caller-facing error otherwise.\n */\nexport function ownerToHex(owner: string): string {\n if (HEX64_RE.test(owner)) return owner;\n if (owner.startsWith('npub1')) {\n try {\n return npubToHex(owner);\n } catch (err) {\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: expected a 64-char lowercase hex pubkey or an npub1… string`\n );\n}\n","/**\n * Wire shapes of the toon-clientd `/git/*` control routes (epic #222).\n *\n * These are the JSON request/response types the daemon serves (bigints as\n * decimal strings, Maps as plain records) — defined HERE, in the dependency\n * root, so both sides of the route can share them: the `rig` CLI (#229)\n * consumes them as a plain-fetch client, and `@toon-protocol/client-mcp`\n * (which depends on this package for the planner — the reverse import would\n * be circular) can adopt them for its `control-api.ts` declarations. TYPES\n * ONLY plus two pure serializers; no transport code lives here.\n *\n * Keep in byte-for-byte sync with\n * `packages/client-mcp/src/control-api.ts` (`Git*` shapes) and\n * `packages/client-mcp/src/daemon/routes.ts` (error envelopes: 409\n * `non_fast_forward` carries `refs`, 413 `oversize_objects` carries\n * `objects`, 503 `bootstrapping` / 402 `insufficient_gas` are retryable).\n */\n\nimport type { PublishReceipt } from './publisher.js';\nimport type { PlannedObject, PushPlan, PushResult, RefUpdate } from './push.js';\n\n/** One planned ref update (JSON-safe as-is). */\nexport type GitRefUpdate = RefUpdate;\n\n/** One object scheduled for upload (JSON-safe as-is). */\nexport type GitPlannedObject = PlannedObject;\n\n/**\n * `POST /git/estimate` — plan a push (local git plumbing + remote-state read)\n * and price it WITHOUT paying anything. The same body (plus `confirm`) drives\n * `POST /git/push`.\n */\nexport interface GitEstimateRequest {\n /** Path to the local git repository (worktree or .git dir). Must exist. */\n repoPath: string;\n /** Repository identifier (NIP-34 `d` tag). The daemon identity is the owner. */\n repoId: string;\n /**\n * Full refnames to push (e.g. `[\"refs/heads/main\"]`). Default: every local\n * branch and tag.\n */\n refspecs?: string[];\n /** Allow non-fast-forward updates (default false → 409 `non_fast_forward`). */\n force?: boolean;\n /**\n * Relay URLs to read remote state from and publish to. Plural from day one\n * (forward-compat); defaults to the daemon's config-seeded relay.\n */\n relayUrls?: string[];\n /** Repo name/description for the first-push kind:30617 announcement. */\n announcement?: { name?: string; description?: string };\n}\n\n/** Pre-push fee table (all fees in base/micro units, decimal strings). */\nexport interface GitFeeEstimate {\n objectCount: number;\n totalObjectBytes: number;\n /** objectCount × the store route's flat price. */\n uploadFee: string;\n /** Events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × per-event fee. */\n eventFees: string;\n /** uploadFee + eventFees. */\n totalFee: string;\n /**\n * Zero-byte objects (the git empty blob) excluded from the upload — the\n * store rejects zero-byte content as malformed, so they are skipped on push\n * and reconstructed on clone/fetch. Optional for wire compatibility with\n * daemons predating the empty-blob handling. Default 0.\n */\n skippedEmptyCount?: number;\n}\n\n/** Serialized `PushPlan` — everything a confirm UI needs. */\nexport interface GitEstimateResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Full new ref state to publish (HEAD target first). */\n newRefs: Record<string, string>;\n headSymref: string | null;\n objects: GitPlannedObject[];\n /** sha→txId hints known WITHOUT uploading (remote tags + resolver finds). */\n knownShaToTxId: Record<string, string>;\n /** True when no kind:30617 exists yet — the push announces first. */\n announceNeeded: boolean;\n announcement: { name: string; description: string };\n estimate: GitFeeEstimate;\n}\n\n/**\n * `POST /git/push` — plan + execute: upload the delta to Arweave and publish\n * the cumulative kind:30618 (+ kind:30617 on first push). PERMANENT + PAID.\n */\nexport interface GitPushRequest extends GitEstimateRequest {\n /** Must be literally `true` — a push spends channel funds irreversibly. */\n confirm: boolean;\n}\n\n/** One object-upload step result. */\nexport interface GitUploadStep {\n sha: string;\n txId: string;\n /** '0' when skipped (already on Arweave — content-addressed resume). */\n feePaid: string;\n skipped: boolean;\n}\n\n/** Receipt for one published event. */\nexport interface GitPublishReceipt {\n eventId: string;\n feePaid: string;\n}\n\n/** Serialized `PushResult` — per-step receipts + total fees actually paid. */\nexport interface GitPushResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Per-object results, in plan order. */\n uploads: GitUploadStep[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: GitPublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: GitPublishReceipt;\n /** Full sha→txId map published in the refs event. */\n arweaveMap: Record<string, string>;\n /** Total fees actually paid (uploads + events), base units, decimal. */\n totalFeePaid: string;\n /** The pre-push estimate the push ran under (compare against totalFeePaid). */\n estimate: GitFeeEstimate;\n}\n\n// ---------------------------------------------------------------------------\n// Single-event git publishes (`/git/issue|comment|patch|status`, #231)\n// ---------------------------------------------------------------------------\n\n/** NIP-34 repository address: the owner+id pair behind `a` tags. */\nexport interface GitRepoAddr {\n /** Repository owner's Nostr pubkey (64-char hex) — author of kind:30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n}\n\n/** `POST /git/issue` — publish a kind:1621 issue against a repo. PAID. */\nexport interface GitIssueRequest {\n repoAddr: GitRepoAddr;\n /** Issue title (`subject` tag). */\n title: string;\n /** Issue body (Markdown content). */\n body: string;\n /** Labels (`t` tags). */\n labels?: string[];\n}\n\n/** `POST /git/comment` — publish a kind:1622 comment on an issue/patch. PAID. */\nexport interface GitCommentRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue or patch being commented on. */\n rootEventId: string;\n /** Comment body (Markdown content). */\n body: string;\n /**\n * Pubkey of the TARGET event's author (NIP-34 `p` threading tag — not the\n * comment author). Defaults to the repo owner.\n */\n parentAuthorPubkey?: string;\n /** `e`-tag marker (default 'root': commenting directly on the issue/patch). */\n marker?: 'root' | 'reply';\n}\n\n/**\n * `POST /git/patch` — publish a kind:1617 patch. Supply EXACTLY ONE of\n * `patchText` (literal `git format-patch` output) or `repoPath`+`range`\n * (the daemon runs `git format-patch --stdout <range>` locally). PAID.\n */\nexport interface GitPatchRequest {\n repoAddr: GitRepoAddr;\n /** Patch/PR title (`subject` tag). */\n title: string;\n /**\n * PR body/cover text (`description` tag). Kept OUT of the event content so\n * `git am` still consumes the patch text verbatim (#280).\n */\n description?: string;\n /** Literal patch text. Mutually exclusive with `repoPath`+`range`. */\n patchText?: string;\n /** Local repository to run format-patch in. Requires `range`. */\n repoPath?: string;\n /** Revision range for format-patch (`<rev>`, `<rev>..<rev>`, `<rev>...<rev>`). */\n range?: string;\n /** Commit/parent pairs for `commit`/`parent-commit` tags. */\n commits?: { sha: string; parentSha: string }[];\n /** Branch name for the `t` tag. */\n branch?: string;\n}\n\nexport type GitStatusValue = 'open' | 'applied' | 'closed' | 'draft';\n\n/** `POST /git/status` — publish a kind:1630-1633 status event. PAID. */\nexport interface GitStatusRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue/patch whose status is being set. */\n targetEventId: string;\n /** open → 1630, applied → 1631, closed → 1632, draft → 1633. */\n status: GitStatusValue;\n /** Pubkey of the target event's author (`p` tag), when known. */\n targetPubkey?: string;\n}\n\n/**\n * Response of the single-event git publishes (issue/comment/patch/status):\n * a publish receipt plus the NIP-34 kind that was published. Daemon\n * responses extend the full `POST /publish` receipt, so the channel fields\n * (`channelId`/`nonce`/…) are present there; they are optional here because\n * the CLI's standalone path publishes through the embedded client and has\n * no channel wire shape to report.\n */\nexport interface GitEventResponse {\n /** Event ID as accepted by the relay. */\n eventId: string;\n /** Fee actually paid for this publish, base units, decimal string. */\n feePaid: string;\n /** The NIP-34 kind that was published. */\n kind: number;\n /** Channel the claim was signed against (daemon responses). */\n channelId?: string;\n /** Channel nonce after this publish (daemon responses). */\n nonce?: number;\n /** FULFILL response data (base64), when the backend returned any. */\n data?: string;\n /** Spendable channel balance after this write, when known. */\n channelBalanceAfter?: string;\n}\n\n/**\n * Uniform error envelope of non-2xx control-route responses. Structured\n * errors put extra fields at the top level: `non_fast_forward` (409) adds\n * `refs`, `oversize_objects` (413) adds `objects`.\n */\nexport interface GitErrorEnvelope {\n error: string;\n detail?: string;\n /** True when the caller should retry (e.g. daemon still bootstrapping). */\n retryable?: boolean;\n [extra: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Serializers (pure) — the exact mapping the daemon routes apply; used by the\n// CLI's standalone mode so both surfaces emit identical wire JSON.\n// ---------------------------------------------------------------------------\n\n/** Serialize a plan's fee estimate onto the wire (bigints → strings). */\nexport function serializeFeeEstimate(plan: PushPlan): GitFeeEstimate {\n return {\n objectCount: plan.estimate.objectCount,\n totalObjectBytes: plan.estimate.totalObjectBytes,\n uploadFee: plan.estimate.uploadFee.toString(),\n eventCount: plan.estimate.eventCount,\n eventFees: plan.estimate.eventFees.toString(),\n totalFee: plan.estimate.totalFee.toString(),\n ...(plan.estimate.skippedEmptyCount > 0\n ? { skippedEmptyCount: plan.estimate.skippedEmptyCount }\n : {}),\n };\n}\n\n/** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */\nexport function serializePushPlan(plan: PushPlan): GitEstimateResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n newRefs: plan.newRefs,\n headSymref: plan.headSymref,\n objects: plan.objects,\n knownShaToTxId: Object.fromEntries(plan.knownShaToTxId),\n announceNeeded: plan.announceNeeded,\n announcement: plan.announcement,\n estimate: serializeFeeEstimate(plan),\n };\n}\n\n/**\n * Serialize a standalone {@link PublishReceipt} into the wire shape the\n * daemon's single-event `/git/*` routes answer with, so `--json` consumers\n * see one `GitEventResponse` shape regardless of publisher mode.\n */\nexport function serializeEventReceipt(\n kind: number,\n receipt: PublishReceipt\n): GitEventResponse {\n return {\n eventId: receipt.eventId,\n feePaid: receipt.feePaid.toString(),\n kind,\n };\n}\n\n/** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */\nexport function serializePushResult(\n plan: PushPlan,\n result: PushResult\n): GitPushResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n uploads: result.uploads.map((u) => ({\n sha: u.sha,\n txId: u.txId,\n feePaid: u.feePaid.toString(),\n skipped: u.skipped,\n })),\n announceReceipt: result.announceReceipt\n ? {\n eventId: result.announceReceipt.eventId,\n feePaid: result.announceReceipt.feePaid.toString(),\n }\n : null,\n refsReceipt: {\n eventId: result.refsReceipt.eventId,\n feePaid: result.refsReceipt.feePaid.toString(),\n },\n arweaveMap: Object.fromEntries(result.arweaveMap),\n totalFeePaid: result.totalFeePaid.toString(),\n estimate: serializeFeeEstimate(plan),\n };\n}\n","/**\n * Push planner/executor — the core of `rig push` (epic #222, ticket #226).\n *\n * `planPush` is network-free (relay/Arweave-wise — it only runs local git\n * plumbing through GitRepoReader plus one injectable async resolver step):\n * it classifies every ref update, computes the object delta against what the\n * remote already stores, hard-errors on oversize objects, and prices the\n * push. The returned {@link PushPlan} carries everything a confirm UI needs.\n *\n * `executePush` spends money: it uploads the planned objects through a\n * {@link Publisher} (ref-tip objects last, so a crashed push never leads to\n * a state where a discoverable tip's history is missing), then publishes ONE\n * cumulative kind:30618 whose `arweave` tags are the MERGE of the remote's\n * existing sha→txId map and the new uploads — kind:30618 is NIP-33\n * replaceable, so dropping prior tags would orphan earlier hints — and whose\n * `r` tags are the full new ref state. On a first push it publishes the\n * kind:30617 announcement before the refs event.\n *\n * Resume safety: uploads are content-addressed (Git-SHA-tagged), so re-running\n * `executePush` after a crash is safe — it consults the merged\n * remote + planned sha→txId map before paying for any upload, and a re-plan\n * with fresh remote state (whose `resolveMissing` finds the already-uploaded\n * objects via GraphQL) skips them entirely.\n */\n\nimport { buildRepoAnnouncement, buildRepoRefs } from './nip34-events.js';\nimport {\n EMPTY_BLOB_SHA,\n MAX_OBJECT_SIZE,\n type GitObjectType,\n} from './objects.js';\nimport {\n uploadChargeFor,\n type FeeRates,\n type PublishReceipt,\n type Publisher,\n} from './publisher.js';\nimport { GitError, type GitRef, type GitRepoReader } from './repo-reader.js';\nimport type { RemoteState } from './remote-state.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** A ref update rejected because it is not a fast-forward. */\nexport interface RejectedRefUpdate {\n refname: string;\n localSha: string;\n remoteSha: string;\n}\n\n/** Thrown by {@link planPush} when a non-fast-forward update lacks `force`. */\nexport class NonFastForwardError extends Error {\n constructor(\n /** The refs that would need `--force` to update. */\n public readonly refs: RejectedRefUpdate[]\n ) {\n super(\n `non-fast-forward update rejected for ${refs\n .map((r) => r.refname)\n .join(', ')} — re-run with force to overwrite the remote ref(s)`\n );\n this.name = 'NonFastForwardError';\n }\n}\n\n/**\n * One object exceeding the old hard upload cap.\n *\n * @deprecated Nothing produces this any more (#102). Size is priced, not\n * refused, so there is no \"oversize\" class of object to report. Retained so\n * the 4.x type export keeps resolving.\n */\nexport interface OversizeObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes. */\n size: number;\n /** Path the object was reached by (blobs / non-root trees), if known. */\n path?: string;\n}\n\n/**\n * @deprecated NEVER THROWN as of #102. `planPush` used to reject any object\n * over a hard 95 KiB cap; the store route prices per KiB, so a large object is\n * now a more expensive upload rather than a refusal, and there is nothing left\n * to throw. Retained so the 4.x export keeps resolving — a `catch` for it is\n * dead code and can be removed.\n */\nexport class OversizeObjectsError extends Error {\n constructor(\n /** The offending objects with paths and sizes. */\n public readonly objects: OversizeObject[]\n ) {\n super(\n `${objects.length} object(s) exceed the ${MAX_OBJECT_SIZE} byte upload limit: ` +\n objects.map((o) => `${o.path ?? o.sha} (${o.size} bytes)`).join(', ')\n );\n this.name = 'OversizeObjectsError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plan types\n// ---------------------------------------------------------------------------\n\n/** How a ref moves relative to the remote. */\nexport type RefUpdateKind =\n /** Ref does not exist on the remote yet. */\n | 'new'\n /** Remote tip is an ancestor of the local tip. */\n | 'fast-forward'\n /** Non-fast-forward, allowed because `force` was set. */\n | 'forced'\n /** Local and remote tips already match — nothing to push. */\n | 'up-to-date';\n\n/** One planned ref update (deletions are out of scope in v1). */\nexport interface RefUpdate {\n /** Full refname, e.g. `refs/heads/main`. */\n refname: string;\n /** Local tip SHA (tag object SHA for annotated tags). */\n localSha: string;\n /** Remote tip SHA, or null when the ref is new. */\n remoteSha: string | null;\n kind: RefUpdateKind;\n}\n\n/** One object scheduled for upload. */\nexport interface PlannedObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes (what the upload fee is charged on). */\n size: number;\n /** Path the object was reached by, if any (blobs / non-root trees). */\n path?: string;\n /** True when this SHA is the tip of a planned ref update (uploaded last). */\n isRefTip: boolean;\n}\n\n/** Pre-push fee estimate — render this in the confirm table. */\nexport interface PushFeeEstimate {\n /** Number of objects to upload. */\n objectCount: number;\n /** Total bytes across all planned object bodies. */\n totalObjectBytes: number;\n /** objectCount × the store route's flat price — smallest asset unit. */\n uploadFee: bigint;\n /** Number of events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × eventFee (smallest asset unit). */\n eventFees: bigint;\n /** uploadFee + eventFees. */\n totalFee: bigint;\n /**\n * Objects excluded from the upload because their body is zero bytes — the\n * git empty blob, which the store rejects as malformed (F00). Reconstructed\n * locally on clone/fetch, so nothing is lost; surfaced here so the fee table\n * can report the skip honestly.\n */\n skippedEmptyCount: number;\n}\n\n/** Everything `executePush` (and a confirm UI) needs. */\nexport interface PushPlan {\n repoId: string;\n /** Every considered ref with its classification (incl. up-to-date). */\n refUpdates: RefUpdate[];\n /**\n * Full new ref state to publish as `r` tags: the remote's refs overlaid\n * with the planned updates (refs not being pushed are preserved — v1\n * never deletes). Ordered with the HEAD target first, which is what\n * `buildRepoRefs` derives the HEAD symref tag from.\n */\n newRefs: Record<string, string>;\n /** HEAD symref target for the new state (first key of {@link newRefs}). */\n headSymref: string | null;\n /**\n * Objects to upload, dependency-safe order: ref-tip objects last so a\n * crashed push never uploads a tip whose history is missing.\n */\n objects: PlannedObject[];\n /**\n * Zero-byte objects (the git empty blob) excluded from {@link objects}: the\n * store rejects a zero-byte kind:5094 upload as malformed (F00), so `rig`\n * never uploads it — the commit/tree still references it and clone/fetch\n * synthesizes it locally. Kept for honest receipts, never uploaded.\n */\n skippedEmptyObjects: PlannedObject[];\n /**\n * sha→txId hints known WITHOUT uploading: the remote's `arweave` tags\n * plus anything `resolveMissing` found. Merged into the published\n * kind:30618 so prior hints are never dropped.\n */\n knownShaToTxId: Map<string, string>;\n /** True when no kind:30617 exists yet — executePush announces first. */\n announceNeeded: boolean;\n /** Announcement metadata used when {@link announceNeeded}. */\n announcement: { name: string; description: string };\n estimate: PushFeeEstimate;\n}\n\nexport interface PlanPushOptions {\n repoReader: GitRepoReader;\n remoteState: RemoteState;\n /** Fee rates from `Publisher.getFeeRates()`. */\n feeRates: FeeRates;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /**\n * Full refnames to push (e.g. `['refs/heads/main']`). Defaults to every\n * local branch and tag. Refs that don't exist locally are an error\n * (deletions are out of scope in v1).\n */\n refs?: string[];\n /** Allow non-fast-forward updates (default false → hard error). */\n force?: boolean;\n /** Repo name/description for the first-push announcement. */\n announcement?: { name?: string; description?: string };\n /**\n * Async resolver for SHAs the remote's `arweave` tags don't cover —\n * consulted before deciding to re-upload. Defaults to\n * `remoteState.resolveMissing` (GraphQL fallback); injectable so the\n * planner core stays testable without network.\n */\n resolveMissing?: (shas: string[]) => Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// planPush\n// ---------------------------------------------------------------------------\n\n/**\n * Classify ref updates, compute the object delta, enforce the size limit,\n * and price the push. Throws {@link NonFastForwardError} /\n * {@link OversizeObjectsError} (both carry structured data for UIs).\n */\nexport async function planPush(options: PlanPushOptions): Promise<PushPlan> {\n const { repoReader, remoteState, feeRates, repoId, force = false } = options;\n const resolveMissing =\n options.resolveMissing ?? remoteState.resolveMissing.bind(remoteState);\n\n // 1. Select and classify refs. -------------------------------------------\n const { head, refs: localRefs } = await repoReader.listRefs();\n const localByName = new Map(localRefs.map((r) => [r.refname, r]));\n\n let selected: GitRef[];\n if (options.refs !== undefined) {\n selected = options.refs.map((name) => {\n const ref = localByName.get(name);\n if (!ref) {\n throw new Error(\n `ref ${JSON.stringify(name)} does not exist locally ` +\n '(ref deletion is out of scope in v1)'\n );\n }\n return ref;\n });\n } else {\n selected = localRefs;\n }\n\n const refUpdates: RefUpdate[] = [];\n const rejected: RejectedRefUpdate[] = [];\n for (const ref of selected) {\n const remoteSha = remoteState.refs.get(ref.refname) ?? null;\n if (remoteSha === null) {\n refUpdates.push({\n refname: ref.refname,\n localSha: ref.sha,\n remoteSha,\n kind: 'new',\n });\n continue;\n }\n if (remoteSha === ref.sha) {\n refUpdates.push({\n refname: ref.refname,\n localSha: ref.sha,\n remoteSha,\n kind: 'up-to-date',\n });\n continue;\n }\n let fastForward = false;\n try {\n fastForward = await repoReader.isAncestor(remoteSha, ref.sha);\n } catch (err) {\n // Remote tip unknown locally (never fetched) or not a commit-ish —\n // we can't prove ancestry, so treat it as non-fast-forward.\n if (!(err instanceof GitError)) throw err;\n fastForward = false;\n }\n if (fastForward) {\n refUpdates.push({\n refname: ref.refname,\n localSha: ref.sha,\n remoteSha,\n kind: 'fast-forward',\n });\n } else if (force) {\n refUpdates.push({\n refname: ref.refname,\n localSha: ref.sha,\n remoteSha,\n kind: 'forced',\n });\n } else {\n rejected.push({ refname: ref.refname, localSha: ref.sha, remoteSha });\n }\n }\n if (rejected.length > 0) throw new NonFastForwardError(rejected);\n\n const updates = refUpdates.filter((u) => u.kind !== 'up-to-date');\n\n // 2. Object delta: reachable from the new tips, minus what the remote has.\n const wantTips = [...new Set(updates.map((u) => u.localSha))];\n const haveTips = [...new Set(remoteState.refs.values())];\n const delta =\n wantTips.length > 0\n ? await repoReader.objectsBetweenWithPaths(wantTips, haveTips)\n : [];\n\n const knownShaToTxId = new Map(remoteState.shaToTxId);\n let candidates = delta.filter((o) => !knownShaToTxId.has(o.sha));\n if (candidates.length > 0) {\n // The remote's `arweave` tags may lag reality (e.g. a crashed push\n // uploaded objects but never published the refs event) — resolve the\n // gaps before paying to re-upload content-addressed data.\n const resolved = await resolveMissing(candidates.map((o) => o.sha));\n for (const [sha, txId] of resolved) knownShaToTxId.set(sha, txId);\n candidates = candidates.filter((o) => !knownShaToTxId.has(o.sha));\n }\n\n // 3. Sizes + oversize hard error. -----------------------------------------\n const pathBySha = new Map(candidates.map((c) => [c.sha, c.path]));\n const { objects: stats, missing } = await repoReader.statObjects(\n candidates.map((c) => c.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during planning: ${missing.join(', ')}`\n );\n }\n\n // No size gate. An object larger than FREE_TIER_MAX_ITEM_BYTES is not an\n // error — it is simply a paid upload: the store route prices per KiB\n // (ADR 0065), so size is charged for rather than refused (#102). The fee\n // table the caller confirms already reflects the per-KiB cost, so an\n // expensive object is visible before anything is spent.\n\n // 4. Split off the empty blob, then order the rest ref-tips-last. ----------\n // The git empty blob uploads as an empty kind:5094 `i` value, which the\n // store rejects as malformed (F00). Skip it: it is reconstructed locally on\n // clone/fetch. Keyed off the EXACT empty-blob constant SHA — NOT a\n // `size === 0` heuristic, which would also match the (distinct, valid) empty\n // TREE object `4b825dc6…` and silently drop it (it is not synthesized on\n // read). An object whose SHA is EMPTY_BLOB_SHA is provably the empty blob.\n const tipShas = new Set(updates.map((u) => u.localSha));\n const planned: PlannedObject[] = [];\n const skippedEmptyObjects: PlannedObject[] = [];\n for (const stat of stats) {\n const path = pathBySha.get(stat.sha);\n const object: PlannedObject = {\n ...stat,\n ...(path ? { path } : {}),\n isRefTip: tipShas.has(stat.sha),\n };\n if (stat.sha === EMPTY_BLOB_SHA) skippedEmptyObjects.push(object);\n else planned.push(object);\n }\n const objects = [\n ...planned.filter((o) => !o.isRefTip),\n ...planned.filter((o) => o.isRefTip),\n ];\n\n // 5. Full new ref state (remote refs overlaid with updates), HEAD first. --\n const newRefsMap = new Map(remoteState.refs);\n for (const update of updates) newRefsMap.set(update.refname, update.localSha);\n\n const headSymref =\n head && newRefsMap.has(head)\n ? head\n : remoteState.headSymref && newRefsMap.has(remoteState.headSymref)\n ? remoteState.headSymref\n : ([...newRefsMap.keys()][0] ?? null);\n\n const newRefs: Record<string, string> = {};\n const headSha = headSymref ? newRefsMap.get(headSymref) : undefined;\n if (headSymref && headSha) newRefs[headSymref] = headSha;\n for (const [refname, sha] of newRefsMap) {\n if (refname !== headSymref) newRefs[refname] = sha;\n }\n\n // 6. Fee estimate. ---------------------------------------------------------\n // A price is a schedule per route (ADR 0065): a flat base per packet, plus\n // an optional per-KiB slope a metered store route publishes. `uploadChargeFor`\n // is the one rule for both, so the confirm table equals what is paid.\n const announceNeeded = !remoteState.announced;\n const totalObjectBytes = objects.reduce((sum, o) => sum + o.size, 0);\n // Per object: the store route's base price plus, on a metered route (ADR\n // 0065 schedule), its per-KiB rate over the sealed payload — the same rule\n // the client applies to the claim, so the table still equals what is paid.\n const uploadFee = objects.reduce(\n (sum, o) => sum + uploadChargeFor(feeRates, o.size),\n 0n\n );\n const eventCount = 1 + (announceNeeded ? 1 : 0);\n const eventFees = BigInt(eventCount) * feeRates.eventFee;\n\n return {\n repoId,\n refUpdates,\n newRefs,\n headSymref,\n objects,\n skippedEmptyObjects,\n knownShaToTxId,\n announceNeeded,\n announcement: {\n name: options.announcement?.name ?? repoId,\n description: options.announcement?.description ?? '',\n },\n estimate: {\n objectCount: objects.length,\n totalObjectBytes,\n uploadFee,\n eventCount,\n eventFees,\n totalFee: uploadFee + eventFees,\n skippedEmptyCount: skippedEmptyObjects.length,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// executePush\n// ---------------------------------------------------------------------------\n\n/** Result of one object-upload step. */\nexport interface UploadStepResult {\n sha: string;\n txId: string;\n /** 0n when skipped (already uploaded — content-addressed resume). */\n feePaid: bigint;\n /** True when the object was already on Arweave and nothing was paid. */\n skipped: boolean;\n}\n\n/** Result of the whole push. */\nexport interface PushResult {\n /** Per-object results, in plan order. */\n uploads: UploadStepResult[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: PublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: PublishReceipt;\n /**\n * The full sha→txId map published in the refs event: remote hints +\n * resolver finds + this push's uploads.\n */\n arweaveMap: Map<string, string>;\n /** Total fees actually paid (uploads + events), smallest asset unit. */\n totalFeePaid: bigint;\n}\n\nexport interface ExecutePushOptions {\n plan: PushPlan;\n publisher: Publisher;\n /**\n * Remote state — pass a FRESH fetch when resuming after a crash so its\n * `shaToTxId` (and `announced`) reflect what the previous attempt already\n * paid for.\n */\n remoteState: RemoteState;\n repoReader: GitRepoReader;\n /** Relay URLs to publish events to (plural from day one; size 1 today). */\n relayUrls: string[];\n}\n\n/** How many object bodies to hold in memory at once between read and upload. */\nconst READ_BATCH_SIZE = 100;\n\n/**\n * Execute a {@link PushPlan}: upload objects (ref tips last), then publish\n * the kind:30617 announcement (first push only) and ONE cumulative\n * kind:30618 whose `arweave` tags merge every known sha→txId hint with the\n * new uploads and whose `r` tags carry the full new ref state.\n *\n * Safe to re-run after a crash: SHAs already present in the merged\n * remote + plan map are skipped without paying.\n */\nexport async function executePush(\n options: ExecutePushOptions\n): Promise<PushResult> {\n const { plan, publisher, remoteState, repoReader, relayUrls } = options;\n\n // Merged sha→txId map: remote hints (fresh on resume) + plan-time finds.\n // Consulted before every upload — this is the resume-safety check.\n const merged = new Map([...remoteState.shaToTxId, ...plan.knownShaToTxId]);\n\n const resultBySha = new Map<string, UploadStepResult>();\n let totalFeePaid = 0n;\n\n const pending: PlannedObject[] = [];\n for (const object of plan.objects) {\n const knownTxId = merged.get(object.sha);\n if (knownTxId !== undefined) {\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: knownTxId,\n feePaid: 0n,\n skipped: true,\n });\n } else {\n pending.push(object);\n }\n }\n\n // Upload in plan order (ref tips are already last), reading bodies in\n // batches so memory stays bounded on large pushes.\n for (let i = 0; i < pending.length; i += READ_BATCH_SIZE) {\n const batch = pending.slice(i, i + READ_BATCH_SIZE);\n const { objects: read, missing } = await repoReader.readObjects(\n batch.map((o) => o.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during push: ${missing.join(', ')}`\n );\n }\n const bodyBySha = new Map(read.map((r) => [r.sha, r.body]));\n for (const object of batch) {\n const body = bodyBySha.get(object.sha);\n if (!body) {\n throw new Error(\n `internal: cat-file returned no body for ${object.sha}`\n );\n }\n const receipt = await publisher.uploadGitObject({\n sha: object.sha,\n type: object.type,\n body,\n repoId: plan.repoId,\n // #368: the path the blob was reached by drives its Content-Type; a\n // non-blob object (no path) uploads as octet-stream.\n ...(object.path ? { path: object.path } : {}),\n });\n merged.set(object.sha, receipt.txId);\n totalFeePaid += receipt.feePaid;\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: receipt.txId,\n feePaid: receipt.feePaid,\n skipped: false,\n });\n }\n }\n\n // Announcement (first push only) goes before the refs event so a repo is\n // never referenced by an `a` tag before its kind:30617 exists. Re-check\n // the (fresh-on-resume) remote state so a crashed push doesn't announce\n // twice.\n let announceReceipt: PublishReceipt | null = null;\n if (plan.announceNeeded && !remoteState.announced) {\n const announceEvent = buildRepoAnnouncement(\n plan.repoId,\n plan.announcement.name,\n plan.announcement.description\n );\n announceReceipt = await publisher.publishEvent(announceEvent, relayUrls);\n totalFeePaid += announceReceipt.feePaid;\n }\n\n // ONE cumulative kind:30618: full ref state + MERGED arweave map (NIP-33\n // replaceable — dropping prior tags would orphan earlier sha→txId hints).\n const refsEvent = buildRepoRefs(\n plan.repoId,\n plan.newRefs,\n Object.fromEntries(merged)\n );\n const refsReceipt = await publisher.publishEvent(refsEvent, relayUrls);\n totalFeePaid += refsReceipt.feePaid;\n\n const uploads = plan.objects.map((o) => {\n const step = resultBySha.get(o.sha);\n if (!step) {\n throw new Error(`internal: no upload result recorded for ${o.sha}`);\n }\n return step;\n });\n\n return {\n uploads,\n announceReceipt,\n refsReceipt,\n arweaveMap: merged,\n totalFeePaid,\n };\n}\n"],"mappings":"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","names":["OBJECT_TYPES","FULL_SHA_RE","execFile","spawn","promisify","execFileAsync","FULL_SHA_RE","assertFullSha"]}