@toon-protocol/rig 2.4.1 → 2.6.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/standalone-mode.ts","../src/standalone/topology-cache.ts"],"sourcesContent":["/**\n * The embedded-client (standalone) publisher backing every paid `rig`\n * command: build a nonce-guarded {@link StandalonePublisher} from the\n * caller's own identity and config.\n *\n * Identity comes from the #248 precedence chain (`./identity.ts`:\n * RIG_MNEMONIC env → TOON_CLIENT_MNEMONIC env alias → project `.env` →\n * `~/.toon-client` keystore/config). The remaining config resolution\n * DUPLICATES the toon-clientd conventions\n * (`packages/client-mcp/src/daemon/config.ts`) the same way\n * `../standalone/nonce-guard.ts` does — this package must not import\n * `@toon-protocol/client-mcp` (circular; see that module's doc). Keep in sync:\n *\n * - state dir: `TOON_CLIENT_HOME`, else `~/.toon-client`; config `config.json`\n * - env overrides: `TOON_CLIENT_PROXY_URL`, `TOON_CLIENT_BTP_URL`,\n * `TOON_CLIENT_RELAY_URL`, `TOON_CLIENT_DESTINATION`,\n * `TOON_CLIENT_PUBLISH_DESTINATION`, `TOON_CLIENT_STORE_DESTINATION`,\n * `TOON_CLIENT_CHAIN`\n *\n * NETWORK BOOTSTRAP (#264): what explicit config does not pin is resolved\n * from the network itself — the payment peer's live kind:10032 announce on\n * the relay-origin, falling back to `@toon-protocol/core`'s committed\n * genesis peer seed. Uplink, channel anchor, publish/store routes, the\n * settlement chain and its TokenNetwork/token/RPC parameters all follow the\n * `explicit config > live announce > genesis seed` order documented in\n * `../standalone/network-bootstrap.ts`. The pure resolution lives in\n * {@link resolveNetworkTopology} (unit-testable without any network).\n *\n * TOPOLOGY CACHE (#279): the resolved topology is persisted under\n * `TOON_CLIENT_HOME` (`../standalone/topology-cache.ts`) keyed by\n * relay-origin + identity + an explicit-config fingerprint, with a 15-min\n * TTL (`RIG_TOPOLOGY_TTL_MS` overrides; `0` disables). A cache hit skips\n * announce discovery and the funded-chain probes entirely; a cached\n * topology that then fails to BOOTSTRAP is invalidated and re-resolved\n * live in-process ({@link TopologyRecoveringPublisher}), so staleness costs\n * one failed attempt, never a broken run. Only paid-path resolutions\n * (`requireUplink !== false`) are written — a free-read topology may lack\n * an uplink and must not shadow the paid path's MissingUplinkError.\n *\n * CORE COEXISTENCE NOTE: rig performs discovery with ITS OWN\n * `@toon-protocol/core` (^2.0.x — live genesis seed), while the embedded\n * `@toon-protocol/client` keeps its internal core (^1.6.x) for its own\n * bootstrap/negotiation. The two never exchange class instances — rig feeds\n * the client plain config (`knownPeers`, settlement maps), so the version\n * split is safe by construction.\n *\n * This module statically imports `@toon-protocol/client` (heavy: viem,\n * noble, nostr-tools), so it must only ever be reached through the dynamic\n * import in `push.ts` (see `./standalone-context.ts`).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport { EvmSigner, deriveNostrKeyFromMnemonic } from '@toon-protocol/client';\nimport {\n decodeEventFromToon,\n encodeEventToToon,\n} from '@toon-protocol/core';\nimport type {\n FeeRates,\n GitObjectUpload,\n Publisher,\n PublishReceipt,\n UploadReceipt,\n} from '../publisher.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n ChannelMapStore,\n RIG_CHANNEL_MAP_FILENAME,\n type ChannelMapRecord,\n} from '../standalone/channel-map.js';\nimport {\n TOPOLOGY_CACHE_FILENAME,\n TOPOLOGY_TTL_ENV,\n TopologyCache,\n explicitConfigFingerprint,\n topologyCacheKey,\n topologyCacheTtlMs,\n} from '../standalone/topology-cache.js';\nimport {\n DISCOVERY_TIMEOUT_MS,\n TokenNetworkUnderivableError,\n discoverAnnouncedPeers,\n evmTokenBalance,\n genesisSeedPubkeys,\n loadGenesisSeed,\n pickPaymentPeer,\n resolveChainSettlement,\n selectSettlementChain,\n type AnnouncedPeer,\n type ChainSelection,\n type ChannelRecordLike,\n type EvmBalanceProbe,\n type ExplicitChainConfig,\n} from '../standalone/network-bootstrap.js';\nimport { StandalonePublisher } from '../standalone/standalone-publisher.js';\nimport { fetchRemoteState } from '../remote-state.js';\nimport { resolveIdentity } from './identity.js';\nimport type {\n StandaloneContext,\n StandaloneLoadOptions,\n} from './standalone-context.js';\n\n/** The subset of the shared client config file standalone mode consumes. */\nexport interface ClientConfigFile {\n network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';\n mnemonicAccountIndex?: number;\n btpUrl?: string;\n proxyUrl?: string;\n relayUrl?: string;\n destination?: string;\n publishDestination?: string;\n storeDestination?: string;\n feePerEvent?: string;\n channelStorePath?: string;\n /** Settlement chain: family (`evm`) or full id (`evm:31337`). */\n chain?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n}\n\n/** An identity was resolved, but there is no way to send paid writes. */\nexport class MissingUplinkError extends Error {\n constructor(configPath: string, relayUrl: string | undefined) {\n const discovered = relayUrl\n ? `no announce with a btp/http endpoint was found on ${relayUrl} and ` +\n 'the genesis seed has none; '\n : '';\n super(\n `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL ` +\n '(connector payment proxy) or TOON_CLIENT_BTP_URL, or add ' +\n `proxyUrl/btpUrl to ${configPath}`\n );\n this.name = 'MissingUplinkError';\n }\n}\n\nfunction configDir(env: NodeJS.ProcessEnv): string {\n return env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\nfunction readClientConfig(path: string): ClientConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as ClientConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n/** `https://host/ilp` → `https://host` (the client re-derives `/ilp`). */\nfunction proxyBaseOf(httpEndpoint: string): string {\n return httpEndpoint.replace(/\\/+$/, '').replace(/\\/ilp$/i, '');\n}\n\n// ---------------------------------------------------------------------------\n// Pure topology resolution (#264) — exported for tests\n// ---------------------------------------------------------------------------\n\n/** A genesis-seed peer as this module consumes it (rig's core 2.x shape). */\nexport interface GenesisSeedLike {\n pubkey: string;\n relayUrl: string;\n ilpAddress: string;\n btpEndpoint: string;\n}\n\n/** Inputs to {@link resolveNetworkTopology} (side-effect free). */\nexport interface NetworkTopologyInputs {\n env: NodeJS.ProcessEnv;\n file: ClientConfigFile;\n /** For error messages (\"add X to <configPath>\"). */\n configPath: string;\n /** Resolved relay-origin (already precedence-resolved). */\n relayUrl: string;\n /** The discovered payment-peer announce, if any. */\n announce: AnnouncedPeer | undefined;\n /** The committed genesis seed entry, if any. */\n genesisSeed: GenesisSeedLike | undefined;\n identity: { mnemonic: string; accountIndex: number; pubkey: string };\n /**\n * #262 channel-map records for this identity (chain selection input).\n * A thunk so the (possibly corrupt-throwing) map read only happens when\n * chain selection actually needs it.\n */\n channelRecords: () => ChannelRecordLike[];\n /** Balance probe override (tests); default: raw `eth_call`. */\n probeBalance?: EvmBalanceProbe;\n /**\n * When false (#263 free reads, e.g. `rig balance`), a missing uplink is\n * tolerated instead of throwing {@link MissingUplinkError}.\n */\n requireUplink?: boolean;\n warn: (line: string) => void;\n}\n\n/** The resolved payment topology feeding the embedded client config. */\nexport interface NetworkTopology {\n proxyUrl?: string;\n btpUrl?: string;\n /** Channel anchor / default ILP destination. */\n destination: string;\n publishDestination?: string;\n storeDestination?: string;\n /** The peer the embedded client bootstraps + negotiates with. */\n knownPeers: { pubkey: string; relayUrl: string; btpEndpoint: string }[];\n /** The selected settlement chain + rationale (absent: nothing known). */\n selection?: ChainSelection;\n supportedChains?: string[];\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n}\n\n/**\n * Resolve the payment topology per the #264 precedence order —\n * `explicit config > live announce > genesis seed` for every field, plus the\n * documented settlement-chain selection rule (see\n * `../standalone/network-bootstrap.ts`).\n *\n * @throws {MissingUplinkError} when no source yields an uplink.\n * @throws {TokenNetworkUnderivableError} when the selected EVM chain's\n * TokenNetwork cannot be derived from config, announce, or chain preset.\n */\nexport async function resolveNetworkTopology(\n inputs: NetworkTopologyInputs\n): Promise<NetworkTopology> {\n const { env, file, configPath, relayUrl, announce, genesisSeed, warn } =\n inputs;\n\n // ── Explicit config (always wins, per field) ─────────────────────────────\n const explicitProxyUrl = env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl;\n const explicitBtpUrl = env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl;\n const explicitDestination =\n env['TOON_CLIENT_DESTINATION'] ?? file.destination;\n const explicitPublish =\n env['TOON_CLIENT_PUBLISH_DESTINATION'] ?? file.publishDestination;\n const explicitStore =\n env['TOON_CLIENT_STORE_DESTINATION'] ?? file.storeDestination;\n const explicitChain = env['TOON_CLIENT_CHAIN'] ?? file.chain;\n const explicitMaps: ExplicitChainConfig = {\n ...(file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {}),\n ...(file.preferredTokens ? { preferredTokens: file.preferredTokens } : {}),\n ...(file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}),\n };\n\n // ── Uplink: explicit > announce (http > btp) > genesis seed ──────────────\n let proxyUrl = explicitProxyUrl;\n let btpUrl = explicitBtpUrl;\n if (!proxyUrl && !btpUrl) {\n if (announce?.info.httpEndpoint) {\n proxyUrl = proxyBaseOf(announce.info.httpEndpoint);\n } else if (announce?.info.btpEndpoint) {\n btpUrl = announce.info.btpEndpoint;\n } else if (genesisSeed?.btpEndpoint) {\n btpUrl = genesisSeed.btpEndpoint;\n } else if (inputs.requireUplink !== false) {\n // Free reads (`rig balance`, #263) tolerate a missing uplink; paid\n // commands fail here, after every source has been tried.\n throw new MissingUplinkError(configPath, relayUrl);\n }\n }\n\n // ── Destination anchor + publish/store routes ────────────────────────────\n // The channel anchors at the peer's announced ilpAddress; publish/store\n // routes come from the announce's `routes` map. Explicit values always\n // win; with neither, the publisher's `<base>.relay.store` anchor-derivation\n // convention remains as the last-resort fallback (explicit anchors only).\n const destination =\n explicitDestination ??\n announce?.info.ilpAddress ??\n genesisSeed?.ilpAddress ??\n 'g.proxy';\n const publishDestination = explicitPublish ?? announce?.routes?.publish;\n const storeDestination = explicitStore ?? announce?.routes?.store;\n\n // ── Known peer for the embedded client's own bootstrap ───────────────────\n // The client re-queries the peer's announce itself (its internal core) and\n // negotiates the settlement chain; rig just tells it WHO to bootstrap with.\n const knownPeers = announce\n ? [\n {\n pubkey: announce.pubkey,\n relayUrl,\n btpEndpoint: announce.info.btpEndpoint ?? '',\n },\n ]\n : genesisSeed\n ? [\n {\n pubkey: genesisSeed.pubkey,\n relayUrl: genesisSeed.relayUrl,\n btpEndpoint: genesisSeed.btpEndpoint,\n },\n ]\n : [];\n\n // ── Settlement chain + per-chain parameters ──────────────────────────────\n // NOTE: the `network` preset field is deliberately NOT forwarded to the\n // embedded client. `applyNetworkPresets` puts preset chains FIRST in\n // `supportedChains`, which is what steered devnet negotiation to the\n // unfunded public Solana preset (#260 root cause 4). The announce + the\n // rule below define the chain; presets only serve as per-chain parameter\n // fallbacks inside `resolveChainSettlement`.\n const announcedChains = announce?.info.supportedChains ?? [];\n const resolveSettlement = (chain: string) =>\n resolveChainSettlement(chain, explicitMaps, announce);\n\n let selection: ChainSelection | undefined;\n let supportedChains: string[] | undefined;\n let preferredTokens: Record<string, string> | undefined;\n let tokenNetworks: Record<string, string> | undefined;\n let chainRpcUrls: Record<string, string> | undefined;\n\n if (file.supportedChains?.length) {\n // Explicit chain list: pass through as-is (its order IS the negotiation\n // preference), filling per-chain parameter gaps from announce/presets.\n supportedChains = file.supportedChains;\n preferredTokens = { ...file.preferredTokens };\n tokenNetworks = { ...file.tokenNetworks };\n chainRpcUrls = { ...file.chainRpcUrls };\n for (const chain of supportedChains) {\n const s = resolveSettlement(chain);\n if (s.tokenAddress && !preferredTokens[chain]) {\n preferredTokens[chain] = s.tokenAddress;\n }\n if (s.tokenNetwork && !tokenNetworks[chain]) {\n tokenNetworks[chain] = s.tokenNetwork;\n }\n if (s.rpcUrl && !chainRpcUrls[chain]) {\n chainRpcUrls[chain] = s.rpcUrl;\n }\n // Same fail-fast guarantee as the announce/selection path below: an\n // explicitly configured EVM chain whose TokenNetwork/RPC cannot be\n // derived must fail HERE with an actionable error, not later as the\n // embedded client's generic \"tokenNetwork address is required\".\n if (s.family === 'evm') {\n if (!tokenNetworks[chain]) {\n throw new TokenNetworkUnderivableError(chain, announce, relayUrl);\n }\n if (!chainRpcUrls[chain]) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${chain}\"` +\n ` — add chainRpcUrls[\"${chain}\"] to ${configPath}`\n );\n }\n }\n }\n selection = {\n chain: supportedChains[0] as string,\n reason: 'explicit',\n detail: 'supportedChains set by config',\n };\n } else if (explicitChain || announcedChains.length > 0) {\n // Selection rule: explicit > persisted channel > funded > first EVM.\n const { secretKey } = deriveNostrKeyFromMnemonic(\n inputs.identity.mnemonic,\n inputs.identity.accountIndex\n );\n const evmAddress = new EvmSigner(secretKey).address;\n selection = await selectSettlementChain({\n ...(explicitChain ? { explicitChain } : {}),\n announcedChains,\n records: inputs.channelRecords(),\n evmAddress,\n resolveSettlement,\n probeBalance: inputs.probeBalance ?? evmTokenBalance,\n });\n const settlement = resolveSettlement(selection.chain);\n if (settlement.family === 'evm') {\n if (!settlement.tokenNetwork) {\n throw new TokenNetworkUnderivableError(\n selection.chain,\n announce,\n relayUrl\n );\n }\n if (!settlement.rpcUrl) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${selection.chain}\"` +\n ` — add chainRpcUrls[\"${selection.chain}\"] to ${configPath}`\n );\n }\n }\n supportedChains = [selection.chain];\n preferredTokens = settlement.tokenAddress\n ? { [selection.chain]: settlement.tokenAddress }\n : undefined;\n tokenNetworks = settlement.tokenNetwork\n ? { [selection.chain]: settlement.tokenNetwork }\n : undefined;\n chainRpcUrls = settlement.rpcUrl\n ? { [selection.chain]: settlement.rpcUrl }\n : undefined;\n if (selection.reason !== 'explicit') {\n warn(\n `rig: settlement chain ${selection.chain} selected — ` +\n `${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains ` +\n 'in the client config) to override'\n );\n }\n } else {\n warn(\n 'rig: no settlement chains are configured or announced — paid writes ' +\n 'will fail until a chain is configured (supportedChains) or the ' +\n `payment peer announces its chains on ${relayUrl}`\n );\n }\n\n const network = env['TOON_CLIENT_NETWORK'] ?? file.network;\n if (network && network !== 'custom' && !file.supportedChains?.length) {\n warn(\n `rig: ignoring the \"${network}\" network preset for settlement — the ` +\n \"settlement chain comes from the payment peer's announce and your \" +\n 'config, because preset chains can point at networks your wallet ' +\n 'has no funds on; set supportedChains explicitly to use preset chains'\n );\n }\n\n return {\n ...(proxyUrl ? { proxyUrl } : {}),\n ...(btpUrl ? { btpUrl } : {}),\n destination,\n ...(publishDestination ? { publishDestination } : {}),\n ...(storeDestination ? { storeDestination } : {}),\n knownPeers,\n ...(selection ? { selection } : {}),\n ...(supportedChains ? { supportedChains } : {}),\n ...(preferredTokens && Object.keys(preferredTokens).length > 0\n ? { preferredTokens }\n : {}),\n ...(tokenNetworks && Object.keys(tokenNetworks).length > 0\n ? { tokenNetworks }\n : {}),\n ...(chainRpcUrls && Object.keys(chainRpcUrls).length > 0\n ? { chainRpcUrls }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cached-topology recovery (#279)\n// ---------------------------------------------------------------------------\n\n/** Structural check for a cached {@link NetworkTopology} document. */\nfunction isNetworkTopology(value: unknown): value is NetworkTopology {\n if (typeof value !== 'object' || value === null) return false;\n const t = value as Record<string, unknown>;\n return typeof t['destination'] === 'string' && Array.isArray(t['knownPeers']);\n}\n\n/**\n * Start failures that must NEVER trigger a cache-invalidation retry: they\n * are concurrency/state guards, not topology staleness — retrying would\n * bypass exactly what they protect.\n */\nconst NON_RECOVERABLE_START_ERRORS: ReadonlySet<string> = new Set([\n 'DaemonIdentityConflictError',\n 'StandaloneLockError',\n 'ChannelMapCorruptError',\n]);\n\n/**\n * Publisher wrapper implementing the #279 cache-invalidation contract: when\n * the inner publisher was built from a CACHED topology and its bootstrap\n * (`start`/`startClientOnly`) fails, the cache entry is invalidated, the\n * topology is re-resolved LIVE, a fresh publisher replaces the inner one,\n * and the operation proceeds — one retry, never more. Bootstrap failures\n * are pre-payment by construction (start() completes before any claim is\n * signed), so the retry can never double-pay. Publishers built from a live\n * resolution get no recovery hook and fail through unchanged.\n */\nclass TopologyRecoveringPublisher implements Publisher {\n private inner: StandalonePublisher;\n private rebuild: (() => Promise<StandalonePublisher>) | undefined;\n private readonly warn: (line: string) => void;\n\n constructor(\n inner: StandalonePublisher,\n rebuild: (() => Promise<StandalonePublisher>) | undefined,\n warn: (line: string) => void\n ) {\n this.inner = inner;\n this.rebuild = rebuild;\n this.warn = warn;\n }\n\n getPublicKey(): string {\n return this.inner.getPublicKey();\n }\n\n getFeeRates(): Promise<FeeRates> {\n return this.inner.getFeeRates();\n }\n\n /**\n * Run the bootstrap step, recovering ONCE from a stale cached topology.\n * A rebuild failure surfaces the LIVE resolution's error — that is the\n * network's real state, strictly more actionable than the cached failure.\n */\n private async ensure(\n start: (p: StandalonePublisher) => Promise<void>\n ): Promise<StandalonePublisher> {\n try {\n await start(this.inner);\n return this.inner;\n } catch (err) {\n const rebuild = this.rebuild;\n this.rebuild = undefined;\n const name = err instanceof Error ? err.name : '';\n if (!rebuild || NON_RECOVERABLE_START_ERRORS.has(name)) throw err;\n this.warn(\n 'rig: bootstrap with the cached network topology failed ' +\n `(${err instanceof Error ? err.message : String(err)}) — ` +\n 'invalidating the cache and re-resolving live'\n );\n const fresh = await rebuild();\n try {\n // Failed starts release their own lock/state; stop() is idempotent.\n await this.inner.stop();\n } catch {\n // best-effort teardown of the abandoned publisher\n }\n this.inner = fresh;\n await start(this.inner);\n return this.inner;\n }\n }\n\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.publishEvent(event, relayUrls);\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.uploadGitObject(upload);\n }\n\n // ── money lifecycle passthroughs (#263) — same recovery contract ─────────\n\n async openChannelExplicit(\n opts?: Parameters<StandalonePublisher['openChannelExplicit']>[0]\n ): ReturnType<StandalonePublisher['openChannelExplicit']> {\n const p = await this.ensure((x) => x.start());\n return p.openChannelExplicit(opts);\n }\n\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['closeRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.closeRecordedChannel(record);\n }\n\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['settleRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.settleRecordedChannel(record);\n }\n\n /** Free read on the unstarted client — no bootstrap, no recovery needed. */\n readWalletChainBalances(): ReturnType<\n StandalonePublisher['readWalletChainBalances']\n > {\n return this.inner.readWalletChainBalances();\n }\n\n async stop(): Promise<void> {\n await this.inner.stop();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context factory\n// ---------------------------------------------------------------------------\n\n/**\n * #262 channel-map records for this identity, reduced to the slice chain\n * selection consumes (`closed` folds in the claim-watermark timers).\n */\nfunction chainRecordsFor(\n map: ChannelMapStore,\n identity: string\n): ChannelRecordLike[] {\n return map\n .list()\n .filter((r) => r.identity === identity)\n .map((r) => {\n const watermark = map.readWatermark(r.channelId);\n return {\n chain: r.chain,\n lastUsedAt: r.lastUsedAt,\n closed:\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined,\n };\n });\n}\n\n/**\n * Assemble an embedded-client standalone context: resolved identity + config\n * → network bootstrap (announce discovery / genesis seed) → ToonClientConfig\n * → nonce-guarded StandalonePublisher (guard + client start + channel open\n * happen lazily on the first paid call, or eagerly via the publisher's own\n * `start`).\n */\nexport async function createStandaloneContext(\n options: StandaloneLoadOptions\n): Promise<StandaloneContext> {\n const { env } = options;\n const warn = (line: string) => options.warn(line);\n const dir = configDir(env);\n const configPath = join(dir, 'config.json');\n const file = readClientConfig(configPath);\n const identity = await resolveIdentity(options);\n\n const genesisSeed = loadGenesisSeed();\n\n // ── Relay-origin ──────────────────────────────────────────────────────────\n // The relay the paid command resolved via `rig remote` (passed by the\n // command) is the user's clearest network statement; env/file follow, and\n // the genesis seed's relay is the out-of-the-box fallback.\n const relayUrl =\n options.relayUrl ??\n env['TOON_CLIENT_RELAY_URL'] ??\n file.relayUrl ??\n genesisSeed?.relayUrl ??\n 'ws://localhost:7100';\n\n // ── Peer→channel persistence (#262) ──────────────────────────────────────\n const channelStorePath = file.channelStorePath ?? join(dir, 'channels.json');\n const channelMap = new ChannelMapStore({\n mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),\n watermarkPath: channelStorePath,\n });\n\n // ── Topology cache (#279): keyed by relay-origin + identity + explicit\n // config; a hit skips discovery AND the pure-but-probing resolution below.\n const cache = new TopologyCache<NetworkTopology>({\n path: join(dir, TOPOLOGY_CACHE_FILENAME),\n ttlMs: topologyCacheTtlMs(env),\n validate: isNetworkTopology,\n });\n const cacheKey = topologyCacheKey({\n relayUrl,\n identity: identity.pubkey,\n fingerprint: explicitConfigFingerprint(\n env,\n file as Record<string, unknown>\n ),\n });\n\n const resolveLiveTopology = async (): Promise<NetworkTopology> => {\n // ── Live announce discovery ────────────────────────────────────────────\n // Skipped when explicit config already pins the whole payment topology\n // (fully-configured setups keep their zero-roundtrip start and their\n // exact pre-#264 behavior). Discovery failure is non-fatal: warn +\n // genesis seed.\n const fullyExplicit =\n Boolean(\n (env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl) ||\n (env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl)\n ) &&\n Boolean(env['TOON_CLIENT_DESTINATION'] ?? file.destination) &&\n Boolean(file.supportedChains?.length);\n let announce: AnnouncedPeer | undefined;\n if (!fullyExplicit) {\n try {\n const peers = await discoverAnnouncedPeers(relayUrl, {\n timeoutMs: DISCOVERY_TIMEOUT_MS,\n });\n announce = pickPaymentPeer(peers, genesisSeedPubkeys());\n if (!announce) {\n warn(\n `rig: no payment-peer announce (kind:10032) found on ${relayUrl} — ` +\n 'falling back to the genesis peer seed'\n );\n }\n } catch (err) {\n warn(\n `rig: announce discovery on ${relayUrl} failed ` +\n `(${err instanceof Error ? err.message : String(err)}) — falling ` +\n 'back to the genesis peer seed'\n );\n }\n }\n\n // ── Topology resolution (pure; explicit > announce > genesis) ──────────\n const resolved = await resolveNetworkTopology({\n env,\n file,\n configPath,\n relayUrl,\n announce,\n genesisSeed,\n identity: {\n mnemonic: identity.mnemonic,\n accountIndex: identity.accountIndex,\n pubkey: identity.pubkey,\n },\n channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),\n ...(options.requireUplink !== undefined\n ? { requireUplink: options.requireUplink }\n : {}),\n warn,\n });\n // Cache only paid-path resolutions: a `requireUplink: false` free read\n // may resolve WITHOUT an uplink, and caching that would let a later paid\n // command skip past MissingUplinkError with a broken topology.\n if (options.requireUplink !== false) cache.write(cacheKey, resolved);\n return resolved;\n };\n\n const cached = cache.read(cacheKey);\n if (cached) {\n warn(\n `rig: network topology from cache (${Math.round(cached.ageMs / 1000)}s ` +\n `old; ${TOPOLOGY_TTL_ENV}=0 disables) — skipping announce discovery`\n );\n }\n const topology = cached?.topology ?? (await resolveLiveTopology());\n\n const eventFee = BigInt(file.feePerEvent ?? '1');\n\n const buildPublisher = (topo: NetworkTopology): StandalonePublisher => {\n const clientConfig: ToonClientConfig = {\n // validateConfig requires connectorUrl OR proxyUrl; with BTP-only\n // config a dummy connectorUrl satisfies it (unused at runtime — same\n // convention as the daemon).\n ...(topo.proxyUrl\n ? { proxyUrl: topo.proxyUrl }\n : { connectorUrl: 'http://127.0.0.1:1' }),\n mnemonic: identity.mnemonic,\n mnemonicAccountIndex: identity.accountIndex,\n ilpInfo: {\n pubkey: '00'.repeat(32),\n ilpAddress: 'g.toon.client',\n btpEndpoint: topo.btpUrl ?? '',\n assetCode: 'USD',\n assetScale: 6,\n },\n toonEncoder: encodeEventToToon,\n toonDecoder: decodeEventFromToon,\n ...(topo.btpUrl ? { btpUrl: topo.btpUrl, btpAuthToken: '' } : {}),\n destinationAddress: topo.destination,\n // The embedded client bootstraps against the known peer above; its\n // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.\n relayUrl: '',\n knownPeers: topo.knownPeers,\n channelStorePath,\n ...(topo.supportedChains\n ? { supportedChains: topo.supportedChains }\n : {}),\n ...(file.settlementAddresses\n ? { settlementAddresses: file.settlementAddresses }\n : {}),\n ...(topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {}),\n ...(topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {}),\n ...(topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {}),\n ...(file.solanaChannel ? { solanaChannel: file.solanaChannel } : {}),\n ...(file.minaChannel ? { minaChannel: file.minaChannel } : {}),\n };\n\n return new StandalonePublisher({\n clientConfig,\n eventFee,\n channelMap,\n warn,\n ...(topo.publishDestination\n ? { publishDestination: topo.publishDestination }\n : {}),\n ...(topo.storeDestination\n ? { storeDestination: topo.storeDestination }\n : {}),\n // `rig channel open --peer` (#263): anchor the channel (and its map\n // key) to an explicit peer destination instead of the configured\n // default.\n ...(options.channelDestination\n ? { channelDestination: options.channelDestination }\n : {}),\n // The peer's announce does not carry TokenNetwork/token parameters, so\n // the client's negotiation leaves them empty (#260 root cause 3) — the\n // publisher back-fills them from the derived per-chain maps before the\n // channel opens.\n ...(topo.tokenNetworks || topo.preferredTokens\n ? {\n negotiationFallbacks: {\n ...(topo.tokenNetworks\n ? { tokenNetworks: topo.tokenNetworks }\n : {}),\n ...(topo.preferredTokens\n ? { preferredTokens: topo.preferredTokens }\n : {}),\n },\n }\n : {}),\n });\n };\n\n // Cache-sourced publishers get the #279 recovery hook: a failed bootstrap\n // invalidates the entry, re-resolves live (which re-writes the cache), and\n // retries once. Live-resolved publishers fail through unchanged.\n const publisher = new TopologyRecoveringPublisher(\n buildPublisher(topology),\n cached\n ? async () => {\n cache.invalidate(cacheKey);\n return buildPublisher(await resolveLiveTopology());\n }\n : undefined,\n warn\n );\n\n return {\n ownerPubkey: publisher.getPublicKey(),\n identitySource: identity.source,\n identitySourceLabel: identity.sourceLabel,\n publisher,\n defaultRelayUrls: [relayUrl],\n fetchRemote: (args) => fetchRemoteState(args),\n // Money lifecycle (#263): same guard/start/channel-map machinery as the\n // paid-write path, surfaced for fund/balance/channel open|close|settle.\n money: {\n openChannel: (opts) => publisher.openChannelExplicit(opts),\n closeChannel: (record) => publisher.closeRecordedChannel(record),\n settleChannel: (record) => publisher.settleRecordedChannel(record),\n walletChainBalances: () => publisher.readWalletChainBalances(),\n },\n stop: () => publisher.stop(),\n };\n}\n","/**\n * Standalone network-topology cache (#279): persist the #264 bootstrap\n * result — the announce-derived uplink, ILP routes, settlement chain and its\n * TokenNetwork/token/RPC parameters (or the genesis-seed fallback) — so\n * back-to-back CLI invocations skip the kind:10032 relay query, the\n * payment-peer pick, and the funded-chain `eth_call` probes.\n *\n * SAFETY MODEL — the cache holds only DISCOVERY results, never money state:\n *\n * - The claim watermark (nonce/cumulative) lives in the client's\n * `channels.json` and the #262 channel map, untouched by this cache;\n * every paid write still resumes from the persisted cumulative.\n * - Entries are keyed by relay-origin + identity pubkey + a fingerprint of\n * every explicit env/config field that feeds topology resolution, so a\n * changed relay, identity, or override can never hit a stale entry.\n * - Entries expire after a TTL (default 15 min;\n * `RIG_TOPOLOGY_TTL_MS` overrides, `0` disables the cache entirely).\n * - A cached topology that fails to bootstrap is EXPLICITLY invalidated\n * and re-resolved live (see `cli/standalone-mode.ts`), so a rotated peer\n * endpoint costs one failed attempt, not a broken 15 minutes.\n * - A corrupt/unreadable cache file is a MISS (discovery re-runs), never\n * an error: unlike the #262 channel map, nothing here guards money.\n *\n * Dependency-light on purpose (node:crypto + node:fs): the cached value is a\n * generic JSON document; `cli/standalone-mode.ts` instantiates the class\n * with its `NetworkTopology` shape and a structural validator.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/** Cache filename under the shared client state dir (`TOON_CLIENT_HOME`). */\nexport const TOPOLOGY_CACHE_FILENAME = 'rig-topology-cache.json';\n\n/** Default entry TTL: 15 minutes. */\nexport const DEFAULT_TOPOLOGY_TTL_MS = 15 * 60 * 1000;\n\n/** TTL env override (milliseconds; `0` disables caching entirely). */\nexport const TOPOLOGY_TTL_ENV = 'RIG_TOPOLOGY_TTL_MS';\n\n/** Resolve the entry TTL: `RIG_TOPOLOGY_TTL_MS` env (>= 0), else 15 min. */\nexport function topologyCacheTtlMs(env: NodeJS.ProcessEnv): number {\n const raw = env[TOPOLOGY_TTL_ENV];\n if (raw === undefined || raw === '') return DEFAULT_TOPOLOGY_TTL_MS;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0\n ? parsed\n : DEFAULT_TOPOLOGY_TTL_MS;\n}\n\n/**\n * Stable cache key: relay-origin + identity pubkey + the explicit-config\n * fingerprint. Hashed so the key is filename/JSON-safe and the mnemonic-side\n * inputs never appear in the cache file verbatim.\n */\nexport function topologyCacheKey(args: {\n relayUrl: string;\n identity: string;\n fingerprint: string;\n}): string {\n return createHash('sha256')\n .update(`${args.relayUrl}\\n${args.identity}\\n${args.fingerprint}`)\n .digest('hex');\n}\n\n/**\n * Canonical fingerprint of the explicit inputs that steer topology\n * resolution (see `resolveNetworkTopology`'s precedence order): the\n * TOON_CLIENT_* env overrides plus the shared-config fields. Any change\n * produces a different cache key — explicit config can never be shadowed by\n * a cached resolution made under different settings.\n */\nexport function explicitConfigFingerprint(\n env: NodeJS.ProcessEnv,\n file: Record<string, unknown>\n): string {\n const envKeys = [\n 'TOON_CLIENT_PROXY_URL',\n 'TOON_CLIENT_BTP_URL',\n 'TOON_CLIENT_DESTINATION',\n 'TOON_CLIENT_PUBLISH_DESTINATION',\n 'TOON_CLIENT_STORE_DESTINATION',\n 'TOON_CLIENT_CHAIN',\n 'TOON_CLIENT_NETWORK',\n ] as const;\n const fileKeys = [\n 'network',\n 'btpUrl',\n 'proxyUrl',\n 'destination',\n 'publishDestination',\n 'storeDestination',\n 'chain',\n 'supportedChains',\n 'settlementAddresses',\n 'preferredTokens',\n 'tokenNetworks',\n 'chainRpcUrls',\n ] as const;\n const picked: Record<string, unknown> = {};\n for (const key of envKeys) {\n if (env[key] !== undefined) picked[`env:${key}`] = env[key];\n }\n for (const key of fileKeys) {\n if (file[key] !== undefined) picked[`file:${key}`] = file[key];\n }\n // Keys are inserted in fixed order above → deterministic serialization.\n return JSON.stringify(picked);\n}\n\ninterface CacheEntry {\n /** ISO timestamp the entry was written. */\n cachedAt: string;\n /** The cached (JSON-safe) topology document. */\n topology: unknown;\n}\n\ninterface CacheFile {\n version: 1;\n entries: Record<string, CacheEntry>;\n}\n\nexport interface TopologyCacheOptions<T> {\n /** Cache file path (under `TOON_CLIENT_HOME`). */\n path: string;\n /** Entry TTL in ms; `0` (or negative) disables reads AND writes. */\n ttlMs: number;\n /** Structural validator for cached values (a failed check is a miss). */\n validate: (value: unknown) => value is T;\n /** Clock override (tests). */\n now?: () => number;\n}\n\n/**\n * File-backed, TTL'd topology cache. All operations are best-effort and\n * synchronous; failures degrade to cache misses / no-ops (discovery is the\n * always-correct fallback).\n */\nexport class TopologyCache<T> {\n readonly path: string;\n readonly ttlMs: number;\n private readonly validate: (value: unknown) => value is T;\n private readonly now: () => number;\n\n constructor(options: TopologyCacheOptions<T>) {\n this.path = options.path;\n this.ttlMs = options.ttlMs;\n this.validate = options.validate;\n this.now = options.now ?? Date.now;\n }\n\n /** True when the cache is disabled (`RIG_TOPOLOGY_TTL_MS=0`). */\n get disabled(): boolean {\n return this.ttlMs <= 0;\n }\n\n /**\n * A fresh, structurally-valid entry for `key` — or undefined (expired,\n * missing, invalid, corrupt file, or disabled). Also reports the entry age\n * for the \"topology from cache\" stderr line.\n */\n read(key: string): { topology: T; ageMs: number } | undefined {\n if (this.disabled) return undefined;\n const entry = this.readFile().entries[key];\n if (!entry) return undefined;\n const cachedAt = Date.parse(entry.cachedAt);\n if (!Number.isFinite(cachedAt)) return undefined;\n const ageMs = this.now() - cachedAt;\n if (ageMs < 0 || ageMs > this.ttlMs) return undefined;\n if (!this.validate(entry.topology)) return undefined;\n return { topology: entry.topology, ageMs };\n }\n\n /** Persist `topology` under `key` (prunes expired entries; best-effort). */\n write(key: string, topology: T): void {\n if (this.disabled) return;\n try {\n const file = this.readFile();\n const nowMs = this.now();\n const fresh = Object.fromEntries(\n Object.entries(file.entries).filter(([, entry]) => {\n const at = Date.parse(entry.cachedAt);\n return Number.isFinite(at) && nowMs - at <= this.ttlMs;\n })\n );\n fresh[key] = {\n cachedAt: new Date(nowMs).toISOString(),\n topology,\n };\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(\n this.path,\n JSON.stringify(\n { version: 1, entries: fresh } satisfies CacheFile,\n null,\n 2\n ),\n { mode: 0o600 }\n );\n } catch {\n // Best-effort — a failed write just means the next run discovers live.\n }\n }\n\n /** Drop `key` (a cached topology failed to bootstrap). Best-effort. */\n invalidate(key: string): void {\n try {\n const file = this.readFile();\n if (!(key in file.entries)) return;\n const remaining = Object.fromEntries(\n Object.entries(file.entries).filter(([k]) => k !== key)\n );\n if (Object.keys(remaining).length === 0) {\n unlinkSync(this.path);\n return;\n }\n writeFileSync(\n this.path,\n JSON.stringify(\n { version: 1, entries: remaining } satisfies CacheFile,\n null,\n 2\n ),\n { mode: 0o600 }\n );\n } catch {\n // Best-effort — worst case the entry expires by TTL.\n }\n }\n\n private readFile(): CacheFile {\n const empty: CacheFile = { version: 1, entries: {} };\n let raw: string;\n try {\n raw = readFileSync(this.path, 'utf8');\n } catch {\n return empty;\n }\n try {\n const parsed = JSON.parse(raw) as CacheFile;\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n parsed.version !== 1 ||\n typeof parsed.entries !== 'object' ||\n parsed.entries === null\n ) {\n return empty;\n }\n return parsed;\n } catch {\n return empty; // corrupt cache = miss, never an error (see module doc)\n }\n }\n}\n"],"mappings":"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","names":["readFileSync","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/rig",
3
- "version": "2.4.1",
3
+ "version": "2.6.0",
4
4
  "description": "Git-to-TOON write path core — build git objects and NIP-34 events for the Rig control plane",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Green",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@toon-format/toon": "^1.0.0",
46
46
  "@toon-protocol/core": "^2.0.1",
47
- "@toon-protocol/client": "^0.15.0"
47
+ "@toon-protocol/client": "^0.16.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^20.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/standalone/standalone-publisher.ts"],"sourcesContent":["/**\n * StandalonePublisher — impl 2 of the {@link Publisher} seam (#228): an\n * EMBEDDED ToonClient constructed from the caller's config (mnemonic +\n * account index, the exact `packages/client/src/config.ts` shape) instead of\n * routing through a running toon-clientd. Made for CI jobs, servers, and\n * one-shot CLI runs where no daemon exists.\n *\n * Paid-write mechanics mirror the production daemon path\n * (`client-mcp/src/daemon/client-runner.ts`) and the proven seed pipeline\n * (`rig/tests/e2e/seed/lib/{publish,git-builder}.ts`):\n *\n * - one signed balance-proof CLAIM per write (`signBalanceProof(channelId,\n * fee)` — the ChannelManager accumulates the cumulative watermark),\n * - publishes route to the relay write destination with a flat per-event\n * fee (the daemon's `feePerEvent` convention),\n * - git objects upload as kind:5094 store writes tagged\n * Git-SHA/Git-Type/Repo, priced at bytes × per-byte rate (the seed\n * pipeline's bid), routed to the store destination via `proxyPath:\n * '/store'`, with the Arweave txId decoded from the FULFILL data.\n *\n * Because the embedded client signs claims on the SAME channels a daemon on\n * the same identity would, every paid operation is preceded by the nonce\n * guard (./nonce-guard.ts): refuse if a toon-clientd holds this identity,\n * and hold an exclusive per-pubkey lockfile against other standalone\n * processes for the lifetime of this publisher.\n *\n * Channel REUSE (#262): with a `channelMap` configured, start() resumes the\n * channel recorded for (identity, channel anchor) — `trackChannel` rehydrates\n * the cumulative-claim watermark from the client's channels.json — and\n * records any fresh lazy open, so sequential CLI invocations share ONE\n * on-chain channel instead of stranding a deposit per run (./channel-map.ts).\n */\n\nimport type {\n PublishEventResult,\n SignedBalanceProof,\n ToonClientConfig,\n} from '@toon-protocol/client';\nimport { ToonClient, parseFulfillHttp } from '@toon-protocol/client';\nimport { MAX_OBJECT_SIZE } from '../objects.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport type {\n FeeRates,\n GitObjectUpload,\n PublishReceipt,\n Publisher,\n UploadReceipt,\n} from '../publisher.js';\nimport {\n recordKey,\n type ChannelMapRecord,\n type ChannelMapStore,\n type PersistedChannelContext,\n} from './channel-map.js';\nimport type {\n ChannelCloseOutcome,\n ChannelOpenOutcome,\n ChannelSettleOutcome,\n WalletBalanceInfo,\n} from './money.js';\nimport { checkDaemonIdentity, NonceLock } from './nonce-guard.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A fully-signed Nostr event (structural subset of nostr-tools' NostrEvent). */\nexport interface SignedNostrEvent extends UnsignedEvent {\n id: string;\n pubkey: string;\n sig: string;\n}\n\n/**\n * The slice of `ToonClient` the publisher drives. Kept structural so tests\n * inject a mock and alternative client builds stay compatible.\n */\nexport interface ToonClientLike {\n start(): Promise<unknown>;\n stop(): Promise<void>;\n isStarted?(): boolean;\n getPublicKey(): string;\n signEvent(template: UnsignedEvent): SignedNostrEvent;\n openChannel(destination?: string): Promise<string>;\n signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof>;\n publishEvent(\n event: SignedNostrEvent,\n options?: {\n destination?: string;\n claim?: SignedBalanceProof;\n ilpAmount?: bigint;\n proxyPath?: string;\n }\n ): Promise<PublishEventResult>;\n /** On-chain deposit total for a tracked channel (channel-map bookkeeping). */\n getChannelDepositTotal?(channelId: string): bigint;\n /** Re-read a RESUMED channel's on-chain deposit (persisted state omits it). */\n rehydrateChannelDeposit?(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined>;\n // ── money lifecycle (#263) — optional, matches ToonClient's surface ──────\n /** Deposit extra collateral (base-unit delta) into an open channel. */\n depositToChannel?(\n channelId: string,\n amount: string | bigint\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }>;\n /** Close a channel — starts the settlement challenge window (on-chain). */\n closeChannel?(channelId: string): Promise<{\n channelId: string;\n txHash?: string;\n /** Unix SECONDS, string-encoded. */\n closedAt: string;\n settleableAt: string;\n }>;\n /** Settle a closed channel after its window — releases funds (on-chain). */\n settleChannel?(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string }>;\n /** Free on-chain wallet-balance read (works on an UNSTARTED client). */\n getBalances?(): Promise<WalletBalanceInfo[]>;\n}\n\nexport interface StandalonePublisherOptions {\n /**\n * ToonClient config (mnemonic + mnemonicAccountIndex + proxy/BTP uplink +\n * settlement fields — see `packages/client/src/config.ts`). Exactly one of\n * `clientConfig` | `client` is required.\n */\n clientConfig?: ToonClientConfig;\n /** Pre-built client (tests / advanced callers). */\n client?: ToonClientLike;\n /**\n * ILP route for event publishes (relay `/write`). Default: derived from the\n * config's `destinationAddress` anchor (`<base>.relay.store` → `<base>.relay`,\n * matching the daemon's route derivation), else the anchor itself.\n */\n publishDestination?: string;\n /**\n * ILP route for git-object uploads (store `/store` → Arweave). Default:\n * derived like `publishDestination` (`<base>.relay.store` → `<base>.store`).\n */\n storeDestination?: string;\n /**\n * ILP destination the payment channel anchors to. Default: the client\n * config's `destinationAddress`.\n */\n channelDestination?: string;\n /** Flat fee per published event (daemon `feePerEvent` convention). Default 1n. */\n eventFee?: bigint;\n /** Upload fee per object body byte (seed pipeline's bid rate). Default 10n. */\n uploadFeePerByte?: bigint;\n /** Daemon control API port probed by the nonce guard. */\n daemonPort?: number;\n /** Directory for the per-identity advisory lockfile. */\n lockDir?: string;\n /** Fetch impl for the daemon probe (tests). */\n fetchImpl?: typeof fetch;\n /**\n * Peer→channel map store (#262): start() resumes the channel recorded for\n * (identity, channel anchor) instead of opening a fresh one, and records\n * any fresh lazy open for the next invocation. Absent (embedded callers\n * managing their own channel lifecycle): the historical behaviour — open\n * lazily every run, record nothing.\n */\n channelMap?: ChannelMapStore;\n /**\n * Per-chain settlement parameters to BACK-FILL into the client's peer\n * negotiations after start (#264/#260): peers whose kind:10032 announce\n * carries no `tokenNetworks`/`preferredTokens` negotiate with those fields\n * empty, and the on-chain channel open then fails (\"tokenNetwork address\n * is required\"). Values here never override what a peer DID announce —\n * they only fill gaps, keyed by the negotiated chain id.\n */\n negotiationFallbacks?: {\n tokenNetworks?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n };\n /** Sink for non-fatal channel-persistence warnings (default: stderr). */\n warn?: (line: string) => void;\n}\n\n/** A relay/store rejected a paid write (fee NOT spent iff the claim failed too). */\nexport class StandalonePublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StandalonePublishError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route derivation (duplicated from client-mcp daemon/config.ts — that\n// package depends on this one for #227, so importing it back would be\n// circular; keep in sync)\n// ---------------------------------------------------------------------------\n\n/**\n * Derive publish/store routes from the channel anchor. Behind the devnet\n * proxy the anchor is `<base>.relay.store` (e.g. `g.proxy.relay.store`):\n * publishes terminate at `<base>.relay`, uploads at `<base>.store`. Anchors\n * not matching the convention pass through unchanged.\n */\nexport function deriveRouteDestinations(anchor: string): {\n publish: string;\n store: string;\n} {\n const segs = anchor.split('.');\n if (segs.at(-1) === 'store' && segs.at(-2) === 'relay') {\n const base = segs.slice(0, -2).join('.');\n return { publish: `${base}.relay`, store: `${base}.store` };\n }\n return { publish: anchor, store: anchor };\n}\n\n// ---------------------------------------------------------------------------\n// FULFILL → Arweave txId (mirrors @toon-protocol/client blob-storage.ts,\n// whose extractor is not exported; uses the exported parseFulfillHttp)\n// ---------------------------------------------------------------------------\n\n/** Arweave tx IDs are base64url-encoded 32-byte values (43 chars). */\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\n/**\n * Decode the Arweave txId from a store-write FULFILL. The deployed payment\n * proxy returns the store's verbatim HTTP/1.1 response\n * (`{\"accept\":true,\"txId\":…}` body); legacy non-proxy providers return bare\n * `base64(utf8(txId))`.\n *\n * @throws {StandalonePublishError} when no valid txId can be extracted.\n */\nexport function extractArweaveTxId(base64Data: string): string {\n const http = parseFulfillHttp(base64Data);\n\n if (!http.isHttp) {\n const legacy = Buffer.from(base64Data, 'base64').toString('utf8');\n if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {\n throw new StandalonePublishError(\n `FULFILL data is not a valid Arweave tx ID: \"${legacy}\"`\n );\n }\n return legacy;\n }\n\n if (http.status < 200 || http.status >= 300) {\n throw new StandalonePublishError(\n `git-object upload failed: store returned HTTP ${http.status}` +\n (http.body ? ` - ${http.body}` : '')\n );\n }\n\n let parsed: { accept?: boolean; txId?: unknown; data?: unknown; error?: unknown };\n try {\n parsed = JSON.parse(http.body) as typeof parsed;\n } catch {\n throw new StandalonePublishError(\n `git-object upload response body was not valid JSON: \"${http.body}\"`\n );\n }\n\n if (parsed.accept === false) {\n const reason = typeof parsed.error === 'string' ? `: ${parsed.error}` : '';\n throw new StandalonePublishError(\n `git-object upload rejected by store (accept:false)${reason}`\n );\n }\n\n if (typeof parsed.txId === 'string' && ARWEAVE_TX_ID_REGEX.test(parsed.txId)) {\n return parsed.txId;\n }\n if (typeof parsed.data === 'string' && parsed.data.length > 0) {\n const decoded = Buffer.from(parsed.data, 'base64').toString('utf8');\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;\n }\n\n throw new StandalonePublishError(\n `git-object upload response did not contain a valid Arweave tx ID: \"${http.body}\"`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Channel-resume introspection (#262)\n//\n// The peer negotiation table and the ChannelManager's peer→channel map are\n// PRIVATE on ToonClient, so resuming a channel reaches into them through a\n// structural runtime cast — the exact pattern the daemon uses\n// (client-mcp/src/daemon/client-runner.ts, openOrResumeApexChannel /\n// routeChildPeersThroughApexChannel). Keep the shapes in sync with\n// @toon-protocol/client's ToonClient/ChannelManager.\n// ---------------------------------------------------------------------------\n\n/** The slice of a `PeerNegotiation` the channel map records. */\ninterface NegotiationLike {\n chain: string;\n chainType: string;\n chainId: number | string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n}\n\ninterface ChannelInternals {\n peerNegotiations?: Map<string, NegotiationLike>;\n channelManager?: {\n trackChannel?: (\n channelId: string,\n context: PersistedChannelContext\n ) => void;\n peerChannels?: Map<string, string>;\n };\n /**\n * The client's on-chain channel client caches per-channel chain context at\n * OPEN time only; close/settle/deposit on a channel resumed from the map\n * would throw \"No on-chain context\" without re-seeding it (#263). Same\n * structural-cast contract as the rest of this block — keep in sync with\n * `@toon-protocol/client`'s OnChainChannelClient.\n */\n onChainChannelClient?: {\n channelContext?: Map<\n string,\n { chain: string; tokenNetworkAddress: string; tokenAddress?: string }\n >;\n };\n}\n\n/** Best-effort access to the client's private negotiation/channel state. */\nfunction channelInternals(client: ToonClientLike): ChannelInternals {\n const c = client as unknown as ChannelInternals;\n const negotiations =\n c.peerNegotiations instanceof Map ? c.peerNegotiations : undefined;\n const cm =\n c.channelManager &&\n typeof c.channelManager.trackChannel === 'function' &&\n c.channelManager.peerChannels instanceof Map\n ? c.channelManager\n : undefined;\n const onChain =\n c.onChainChannelClient &&\n c.onChainChannelClient.channelContext instanceof Map\n ? c.onChainChannelClient\n : undefined;\n return {\n ...(negotiations ? { peerNegotiations: negotiations } : {}),\n ...(cm ? { channelManager: cm } : {}),\n ...(onChain ? { onChainChannelClient: onChain } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// StandalonePublisher\n// ---------------------------------------------------------------------------\n\nexport class StandalonePublisher implements Publisher {\n private readonly client: ToonClientLike;\n private readonly ownsClient: boolean;\n private readonly publishDestination: string | undefined;\n private readonly storeDestination: string | undefined;\n private readonly channelDestination: string | undefined;\n private readonly eventFee: bigint;\n private readonly uploadFeePerByte: bigint;\n private readonly daemonPort: number | undefined;\n private readonly lockDir: string | undefined;\n private readonly fetchImpl: typeof fetch | undefined;\n private readonly channelMap: ChannelMapStore | undefined;\n /** ILP anchor the channel is keyed by in the map (peer/apex destination). */\n private readonly channelAnchor: string | undefined;\n private readonly negotiationFallbacks:\n | StandalonePublisherOptions['negotiationFallbacks']\n | undefined;\n private readonly warn: (line: string) => void;\n\n private lock: NonceLock | undefined;\n private channelId: string | undefined;\n private readyPromise: Promise<void> | undefined;\n /** Guard + client start only (no channel) — see {@link startClientOnly}. */\n private clientReadyPromise: Promise<void> | undefined;\n /** True when the last start() RESUMED the recorded channel (#263). */\n private lastOpenResumed = false;\n\n constructor(options: StandalonePublisherOptions) {\n if (options.client && options.clientConfig) {\n throw new Error(\n 'StandalonePublisher: provide either `clientConfig` or `client`, not both'\n );\n }\n if (options.client) {\n this.client = options.client;\n this.ownsClient = false;\n } else if (options.clientConfig) {\n this.client = new ToonClient(options.clientConfig);\n this.ownsClient = true;\n } else {\n throw new Error(\n 'StandalonePublisher: one of `clientConfig` (mnemonic-based ToonClient config) or `client` is required'\n );\n }\n\n // Routes: explicit option → derived from the channel anchor (same\n // `<base>.relay.store` convention the daemon resolves).\n const anchor =\n options.channelDestination ?? options.clientConfig?.destinationAddress;\n const routes = anchor ? deriveRouteDestinations(anchor) : undefined;\n this.publishDestination = options.publishDestination ?? routes?.publish;\n this.storeDestination = options.storeDestination ?? routes?.store;\n this.channelDestination = options.channelDestination;\n\n this.eventFee = options.eventFee ?? 1n;\n this.uploadFeePerByte = options.uploadFeePerByte ?? 10n;\n this.daemonPort = options.daemonPort;\n this.lockDir = options.lockDir;\n this.fetchImpl = options.fetchImpl;\n this.channelMap = options.channelMap;\n this.channelAnchor = anchor;\n this.negotiationFallbacks = options.negotiationFallbacks;\n this.warn =\n options.warn ?? ((line) => process.stderr.write(`${line}\\n`));\n }\n\n /** Hex Nostr pubkey of the embedded identity (available before start). */\n getPublicKey(): string {\n return this.client.getPublicKey();\n }\n\n /**\n * Run the nonce guard, start the embedded client, and open (or resume) the\n * payment channel. Called lazily by the first paid operation; safe to call\n * eagerly to fail fast. Idempotent.\n */\n start(): Promise<void> {\n this.readyPromise ??= (async () => {\n await this.startClientOnly();\n try {\n this.channelId = await this.openOrResumeChannel();\n } catch (err) {\n // Release the identity lock on a failed channel open (pre-#263\n // behaviour): nothing holds claims yet, so another process may go.\n this.lock?.release();\n this.lock = undefined;\n this.clientReadyPromise = undefined;\n throw err;\n }\n })().catch((err: unknown) => {\n // Let a later call retry (e.g. after the conflicting daemon stops).\n this.readyPromise = undefined;\n throw err;\n });\n return this.readyPromise;\n }\n\n /**\n * Run the nonce guard and start the embedded client WITHOUT touching the\n * payment channel (#263): `channel close`/`settle` operate on a RECORDED\n * channel and must never open a fresh one as a side effect of starting.\n * Idempotent; `start()` layers the channel open/resume on top of this.\n */\n startClientOnly(): Promise<void> {\n this.clientReadyPromise ??= this.doStartClient().catch((err: unknown) => {\n this.clientReadyPromise = undefined;\n throw err;\n });\n return this.clientReadyPromise;\n }\n\n private async doStartClient(): Promise<void> {\n const pubkey = this.client.getPublicKey();\n\n // Guard 1: refuse while a toon-clientd holds this identity.\n await checkDaemonIdentity(pubkey, {\n ...(this.daemonPort !== undefined ? { port: this.daemonPort } : {}),\n ...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),\n });\n\n // Guard 2: exclusive advisory lock against other standalone processes.\n this.lock = await NonceLock.acquire(pubkey, {\n ...(this.lockDir !== undefined ? { dir: this.lockDir } : {}),\n });\n\n try {\n if (this.client.isStarted?.() !== true) {\n await this.client.start();\n }\n // #264: back-fill negotiation gaps as soon as the client's bootstrap\n // negotiation exists — before any channel open (start()) or recorded-\n // channel operation reads them.\n this.applyNegotiationFallbacks();\n } catch (err) {\n this.lock.release();\n this.lock = undefined;\n throw err;\n }\n }\n\n /**\n * Back-fill negotiated peer metadata the announce did not carry\n * (#264/#260 root cause 3): after the client's bootstrap negotiation, any\n * peer negotiation missing `tokenNetwork`/`tokenAddress` gets the derived\n * per-chain fallback for its negotiated chain, BEFORE the lazy channel\n * open reads them. Announced values are never overridden.\n */\n private applyNegotiationFallbacks(): void {\n const fallbacks = this.negotiationFallbacks;\n if (!fallbacks) return;\n const negotiations = channelInternals(this.client).peerNegotiations;\n if (!negotiations) {\n this.warn(\n 'rig: settlement fallbacks configured but the client does not ' +\n 'expose negotiation internals — on-chain channel opening may fail ' +\n 'if the peer announced no TokenNetwork'\n );\n return;\n }\n for (const negotiation of negotiations.values()) {\n if (!negotiation.tokenNetwork) {\n const tokenNetwork = fallbacks.tokenNetworks?.[negotiation.chain];\n if (tokenNetwork) negotiation.tokenNetwork = tokenNetwork;\n }\n if (!negotiation.tokenAddress) {\n const tokenAddress = fallbacks.preferredTokens?.[negotiation.chain];\n if (tokenAddress) negotiation.tokenAddress = tokenAddress;\n }\n }\n }\n\n /**\n * Open the payment channel — or, when the channel map has a record for\n * (identity, anchor), RESUME the recorded on-chain channel (#262).\n *\n * Resume seeds the client's ChannelManager (`trackChannel` rehydrates the\n * cumulative-claim watermark from channels.json; `peerChannels` makes the\n * subsequent `openChannel` return the same id instead of opening on-chain).\n * A fresh open is RECORDED so the next invocation resumes it. A corrupt map\n * file throws BEFORE anything is opened — never a silent duplicate open.\n */\n private async openOrResumeChannel(): Promise<string> {\n const map = this.channelMap;\n if (!map) {\n // No persistence configured: historical lazy open, nothing recorded.\n return this.client.openChannel(this.channelDestination);\n }\n if (!this.channelAnchor) {\n this.warn(\n 'rig: no channel anchor destination configured — the peer→channel ' +\n 'mapping cannot be persisted, so this run may open a fresh channel'\n );\n return this.client.openChannel(this.channelDestination);\n }\n\n const anchor = this.channelAnchor;\n const identity = this.client.getPublicKey();\n // Corruption check happens HERE, before any on-chain open (throws).\n const candidates = map.listFor(identity, anchor);\n const internals = channelInternals(this.client);\n const resumed = await this.resumeRecordedChannel(map, candidates, internals);\n\n // Idempotent — returns the (resumed or existing) channel for the peer if\n // one is tracked, else opens lazily on-chain.\n const channelId = await this.client.openChannel(this.channelDestination);\n\n if (resumed && channelId === resumed.channelId) {\n this.lastOpenResumed = true;\n map.touch(recordKey(resumed));\n } else {\n this.lastOpenResumed = false;\n this.recordOpenedChannel(map, internals, identity, anchor, channelId);\n }\n return channelId;\n }\n\n /**\n * Try to resume one recorded channel: the first candidate whose peer is\n * still negotiated on the SAME chain + tokenNetwork and whose watermark\n * does not show it closed/settled. Returns the resumed record, if any.\n */\n private async resumeRecordedChannel(\n map: ChannelMapStore,\n candidates: ChannelMapRecord[],\n internals: ChannelInternals\n ): Promise<ChannelMapRecord | undefined> {\n if (candidates.length === 0) return undefined;\n const cm = internals.channelManager;\n if (!cm?.trackChannel || !cm.peerChannels) {\n this.warn(\n 'rig: a recorded channel exists but the client does not expose ' +\n 'channel internals to resume it — a fresh channel may be opened'\n );\n return undefined;\n }\n\n for (const record of candidates) {\n // The peer must still be negotiated on the recorded chain/tokenNetwork;\n // a rotated peer identity or re-negotiated settlement gets a fresh\n // channel (recorded under its own key) instead of stale claims.\n const negotiation = internals.peerNegotiations?.get(record.peerId);\n if (\n !negotiation ||\n negotiation.chain !== record.chain ||\n (negotiation.tokenNetwork ?? '') !== record.tokenNetwork\n ) {\n continue;\n }\n\n // Never resume a channel the withdraw flow already closed/settled.\n const watermark = map.readWatermark(record.channelId);\n if (\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined\n ) {\n continue;\n }\n if (!watermark) {\n // Fresh channels are seeded at record time, so a missing entry means\n // the watermark store was lost. Resuming from nonce 0 fails SAFE: a\n // regressed cumulative claim is rejected by the peer (no double\n // spend), but warn so the failure is diagnosable.\n this.warn(\n `rig: resuming channel ${record.channelId} with no local claim ` +\n `watermark (${map.watermarkPath}) — if this channel was claimed ` +\n 'against before, the peer will reject the stale claims; remove ' +\n `its entry from ${map.mapPath} to open a fresh channel instead`\n );\n }\n\n // trackChannel rehydrates nonce/cumulative from the watermark store;\n // seeding peerChannels makes ensureChannel/openChannel reuse the id.\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n\n // Persisted channel state omits the on-chain deposit — re-read it so\n // fee/balance accounting is right (EVM only; mirrors the daemon).\n // #279 latency trim: skipped when the record already carries a\n // depositTotal — deposits only change through rig's own flows (open\n // --deposit / a later resume), which update the record; this is\n // display/accounting state, not the claim watermark, so a stale value\n // can never double-spend (the peer enforces the cumulative claim).\n if (\n record.context.chainType === 'evm' &&\n record.depositTotal === undefined &&\n this.client.rehydrateChannelDeposit\n ) {\n try {\n const deposit = await this.client.rehydrateChannelDeposit(\n record.channelId,\n {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n }\n );\n if (deposit !== undefined) {\n map.touch(recordKey(record), {\n depositTotal: deposit.toString(),\n });\n }\n } catch (err) {\n this.warn(\n `rig: deposit re-read for resumed channel ${record.channelId} ` +\n `failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n return record;\n }\n return undefined;\n }\n\n /** Record a freshly opened channel (+ seed its claim watermark at 0/0). */\n private recordOpenedChannel(\n map: ChannelMapStore,\n internals: ChannelInternals,\n identity: string,\n destination: string,\n channelId: string\n ): void {\n let peerId: string | undefined;\n for (const [peer, channel] of internals.channelManager?.peerChannels ??\n []) {\n if (channel === channelId) {\n peerId = peer;\n break;\n }\n }\n const negotiation =\n peerId !== undefined\n ? internals.peerNegotiations?.get(peerId)\n : undefined;\n if (peerId === undefined || !negotiation) {\n this.warn(\n `rig: opened channel ${channelId} but could not record the ` +\n 'peer→channel mapping (client does not expose negotiation ' +\n 'internals) — the NEXT invocation may open another channel'\n );\n return;\n }\n\n let depositTotal: bigint | undefined;\n try {\n depositTotal = this.client.getChannelDepositTotal?.(channelId);\n } catch {\n // deposit unknown — recorded without it; a later resume re-reads it\n }\n\n map.record({\n channelId,\n peerId,\n identity,\n destination,\n chain: negotiation.chain,\n tokenNetwork: negotiation.tokenNetwork ?? '',\n // Context mirrors the daemon's persistApexChannel shape — exactly what\n // trackChannel needs on resume.\n context: {\n chainType: negotiation.chainType,\n chainId:\n typeof negotiation.chainId === 'number' ? negotiation.chainId : 0,\n tokenNetworkAddress: negotiation.tokenNetwork ?? '',\n ...(negotiation.tokenAddress\n ? { tokenAddress: negotiation.tokenAddress }\n : {}),\n recipient: negotiation.settlementAddress,\n },\n ...(depositTotal !== undefined && depositTotal > 0n\n ? { depositTotal: depositTotal.toString() }\n : {}),\n });\n // Seed nonce 0 / cumulative 0 so a later resume can tell \"never claimed\n // against\" apart from \"watermark lost\" (which only fails claim-rejected).\n map.seedWatermark(channelId);\n }\n\n /** Release the identity lock and stop the embedded client (if we own it). */\n async stop(): Promise<void> {\n this.lock?.release();\n this.lock = undefined;\n this.readyPromise = undefined;\n this.clientReadyPromise = undefined;\n this.channelId = undefined;\n // Never stop a client that was never started (`ToonClient.stop()` throws\n // INVALID_STATE) — e.g. after a free `rig balance` read (#263).\n if (this.ownsClient && this.client.isStarted?.() !== false) {\n await this.client.stop();\n }\n }\n\n // ── Money lifecycle (#263) ──────────────────────────────────────────────────\n\n /**\n * Explicit `rig channel open`: the SAME resume-or-open path the lazy paid\n * writes use (guard → start → resume the recorded channel or open + record\n * a fresh one), surfaced with a receipt — plus an optional extra collateral\n * deposit on top of the open/resume.\n */\n async openChannelExplicit(opts?: {\n /** Extra collateral to deposit AFTER the open/resume (base units). */\n deposit?: bigint;\n }): Promise<ChannelOpenOutcome> {\n await this.start();\n const channelId = this.requireChannel();\n const identity = this.client.getPublicKey();\n const destination = this.channelAnchor ?? '';\n const record =\n this.channelMap && this.channelAnchor\n ? this.channelMap\n .listFor(identity, this.channelAnchor)\n .find((r) => r.channelId === channelId)\n : undefined;\n\n const outcome: ChannelOpenOutcome = {\n channelId,\n resumed: this.lastOpenResumed,\n destination,\n ...(record ? { chain: record.chain, peerId: record.peerId } : {}),\n ...(record?.depositTotal !== undefined\n ? { depositTotal: record.depositTotal }\n : {}),\n };\n\n if (opts?.deposit !== undefined && opts.deposit > 0n) {\n if (!this.client.depositToChannel) {\n throw new StandalonePublishError(\n 'this client build does not support channel deposits ' +\n '(depositToChannel is unavailable)'\n );\n }\n const deposited = await this.client.depositToChannel(\n channelId,\n opts.deposit\n );\n outcome.depositAdded = opts.deposit.toString();\n outcome.depositTotal = deposited.depositTotal;\n if (deposited.txHash) outcome.depositTxHash = deposited.txHash;\n if (record) {\n this.channelMap?.touch(recordKey(record), {\n depositTotal: deposited.depositTotal,\n });\n }\n }\n return outcome;\n }\n\n /**\n * Adopt a RECORDED channel into the running client so on-chain close/\n * settle/deposit can act on it: `trackChannel` rehydrates the claim\n * watermark + withdraw timers from channels.json, `peerChannels` binds the\n * peer, and the on-chain client's context cache is re-seeded (it only\n * learns context at open time — a resumed channel would otherwise throw\n * \"No on-chain context\").\n */\n private adoptRecordedChannel(record: ChannelMapRecord): void {\n const internals = channelInternals(this.client);\n const cm = internals.channelManager;\n if (cm?.trackChannel && cm.peerChannels) {\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n }\n const contextCache = internals.onChainChannelClient?.channelContext;\n if (contextCache && !contextCache.has(record.channelId)) {\n contextCache.set(record.channelId, {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n ...(record.context.tokenAddress\n ? { tokenAddress: record.context.tokenAddress }\n : {}),\n });\n }\n }\n\n /**\n * Close a recorded channel: starts the on-chain settlement challenge\n * window. The client persists `closedAt`/`settleableAt` into the claim\n * watermark store, which is exactly where `rig channel list`/`balance`\n * derive the closing/settleable/settled status from. Guard + client start,\n * but NEVER a channel open ({@link startClientOnly}).\n */\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelCloseOutcome> {\n await this.startClientOnly();\n if (!this.client.closeChannel) {\n throw new StandalonePublishError(\n 'this client build does not support closing channels ' +\n '(closeChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.closeChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * Settle a recorded channel after its challenge window — releases the\n * remaining collateral. The client enforces the `now >= settleableAt` time\n * guard BEFORE spending gas (a too-early call throws its retryable\n * SettleTooEarlyError) and persists `settledAt` into the watermark store.\n */\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelSettleOutcome> {\n await this.startClientOnly();\n if (!this.client.settleChannel) {\n throw new StandalonePublishError(\n 'this client build does not support settling channels ' +\n '(settleChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.settleChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * On-chain wallet balances for the embedded identity — a FREE read on the\n * UNSTARTED client (no nonce guard, no uplink, no channel): the client\n * reads the settlement chain its channels actually use (its EVM key is\n * derived at construction; Solana/Mina keys only exist after a start, so\n * those chains appear once a start-requiring command ran — same\n * best-effort contract as the client's own getBalances).\n */\n async readWalletBalances(): Promise<WalletBalanceInfo[]> {\n if (!this.client.getBalances) return [];\n return await this.client.getBalances();\n }\n\n // ── Publisher ─────────────────────────────────────────────────────────────\n\n /**\n * Fee rates for `planPush` estimation: the flat per-event fee and the\n * per-byte upload rate this publisher pays (daemon `feePerEvent` and seed\n * bid-rate conventions; override via options).\n */\n getFeeRates(): Promise<FeeRates> {\n return Promise.resolve({\n uploadFeePerByte: this.uploadFeePerByte,\n eventFee: this.eventFee,\n });\n }\n\n /**\n * Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo\n * tagged — the proven seed-pipeline shape), signing one balance-proof claim\n * for `body.length × uploadFeePerByte`.\n */\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n if (upload.body.length > MAX_OBJECT_SIZE) {\n throw new StandalonePublishError(\n `git object ${upload.sha} exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const fee = BigInt(upload.body.length) * this.uploadFeePerByte;\n const event = this.client.signEvent({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ['i', upload.body.toString('base64'), 'blob'],\n ['bid', fee.toString(), 'usdc'],\n ['output', 'application/octet-stream'],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n });\n\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(event, {\n ...(this.storeDestination ? { destination: this.storeDestination } : {}),\n claim,\n ilpAmount: fee,\n // The store backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `git-object upload rejected (${upload.sha}): ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.data) {\n throw new StandalonePublishError(\n `git-object upload FULFILL carried no data (${upload.sha}); expected the Arweave tx ID`\n );\n }\n return { txId: extractArweaveTxId(result.data), feePaid: fee };\n }\n\n /**\n * Sign the event with the embedded identity and pay-to-publish it through\n * the relay write route, one claim for the flat per-event fee.\n *\n * `relayUrls` is the interface's plural forward-compat surface (parked\n * #84): the standalone impl routes over ILP to its single configured\n * publish destination, so more than one relay is refused rather than\n * silently half-published.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new StandalonePublishError(\n `multi-relay publish is not supported yet (got ${relayUrls.length} relays) — the standalone publisher routes to a single relay destination (#84 parked)`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const signed = this.client.signEvent(event);\n const fee = this.eventFee;\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(signed, {\n ...(this.publishDestination\n ? { destination: this.publishDestination }\n : {}),\n claim,\n ilpAmount: fee,\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `publish rejected (kind ${event.kind}): ${result.error ?? 'relay rejected the event'}`\n );\n }\n return { eventId: result.eventId ?? signed.id, feePaid: fee };\n }\n\n private requireChannel(): string {\n if (!this.channelId) {\n throw new StandalonePublishError(\n 'no payment channel open — start() did not complete'\n );\n }\n return this.channelId;\n }\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n"],"mappings":";;;;;;;;;;AAsCA,SAAS,YAAY,wBAAwB;AAoJtC,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAcO,SAAS,wBAAwB,QAGtC;AACA,QAAM,OAAO,OAAO,MAAM,GAAG;AAC7B,MAAI,KAAK,GAAG,EAAE,MAAM,WAAW,KAAK,GAAG,EAAE,MAAM,SAAS;AACtD,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AACvC,WAAO,EAAE,SAAS,GAAG,IAAI,UAAU,OAAO,GAAG,IAAI,SAAS;AAAA,EAC5D;AACA,SAAO,EAAE,SAAS,QAAQ,OAAO,OAAO;AAC1C;AAQA,IAAM,sBAAsB;AAUrB,SAAS,mBAAmB,YAA4B;AAC7D,QAAM,OAAO,iBAAiB,UAAU;AAExC,MAAI,CAAC,KAAK,QAAQ;AAChB,UAAM,SAAS,OAAO,KAAK,YAAY,QAAQ,EAAE,SAAS,MAAM;AAChE,QAAI,CAAC,oBAAoB,KAAK,MAAM,GAAG;AACrC,YAAM,IAAI;AAAA,QACR,+CAA+C,MAAM;AAAA,MACvD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,SAAS,OAAO,KAAK,UAAU,KAAK;AAC3C,UAAM,IAAI;AAAA,MACR,iDAAiD,KAAK,MAAM,MACzD,KAAK,OAAO,MAAM,KAAK,IAAI,KAAK;AAAA,IACrC;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK,IAAI;AAAA,EAC/B,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,wDAAwD,KAAK,IAAI;AAAA,IACnE;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,OAAO;AAC3B,UAAM,SAAS,OAAO,OAAO,UAAU,WAAW,KAAK,OAAO,KAAK,KAAK;AACxE,UAAM,IAAI;AAAA,MACR,qDAAqD,MAAM;AAAA,IAC7D;AAAA,EACF;AAEA,MAAI,OAAO,OAAO,SAAS,YAAY,oBAAoB,KAAK,OAAO,IAAI,GAAG;AAC5E,WAAO,OAAO;AAAA,EAChB;AACA,MAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,GAAG;AAC7D,UAAM,UAAU,OAAO,KAAK,OAAO,MAAM,QAAQ,EAAE,SAAS,MAAM;AAClE,QAAI,oBAAoB,KAAK,OAAO,EAAG,QAAO;AAAA,EAChD;AAEA,QAAM,IAAI;AAAA,IACR,sEAAsE,KAAK,IAAI;AAAA,EACjF;AACF;AAgDA,SAAS,iBAAiB,QAA0C;AAClE,QAAM,IAAI;AACV,QAAM,eACJ,EAAE,4BAA4B,MAAM,EAAE,mBAAmB;AAC3D,QAAM,KACJ,EAAE,kBACF,OAAO,EAAE,eAAe,iBAAiB,cACzC,EAAE,eAAe,wBAAwB,MACrC,EAAE,iBACF;AACN,QAAM,UACJ,EAAE,wBACF,EAAE,qBAAqB,0BAA0B,MAC7C,EAAE,uBACF;AACN,SAAO;AAAA,IACL,GAAI,eAAe,EAAE,kBAAkB,aAAa,IAAI,CAAC;AAAA,IACzD,GAAI,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;AAAA,IACnC,GAAI,UAAU,EAAE,sBAAsB,QAAQ,IAAI,CAAC;AAAA,EACrD;AACF;AAMO,IAAM,sBAAN,MAA+C;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EAGA;AAAA,EAET;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA,kBAAkB;AAAA,EAE1B,YAAY,SAAqC;AAC/C,QAAI,QAAQ,UAAU,QAAQ,cAAc;AAC1C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,QAAQ,QAAQ;AAClB,WAAK,SAAS,QAAQ;AACtB,WAAK,aAAa;AAAA,IACpB,WAAW,QAAQ,cAAc;AAC/B,WAAK,SAAS,IAAI,WAAW,QAAQ,YAAY;AACjD,WAAK,aAAa;AAAA,IACpB,OAAO;AACL,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAIA,UAAM,SACJ,QAAQ,sBAAsB,QAAQ,cAAc;AACtD,UAAM,SAAS,SAAS,wBAAwB,MAAM,IAAI;AAC1D,SAAK,qBAAqB,QAAQ,sBAAsB,QAAQ;AAChE,SAAK,mBAAmB,QAAQ,oBAAoB,QAAQ;AAC5D,SAAK,qBAAqB,QAAQ;AAElC,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ;AACvB,SAAK,YAAY,QAAQ;AACzB,SAAK,aAAa,QAAQ;AAC1B,SAAK,gBAAgB;AACrB,SAAK,uBAAuB,QAAQ;AACpC,SAAK,OACH,QAAQ,SAAS,CAAC,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,EAC/D;AAAA;AAAA,EAGA,eAAuB;AACrB,WAAO,KAAK,OAAO,aAAa;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAuB;AACrB,SAAK,kBAAkB,YAAY;AACjC,YAAM,KAAK,gBAAgB;AAC3B,UAAI;AACF,aAAK,YAAY,MAAM,KAAK,oBAAoB;AAAA,MAClD,SAAS,KAAK;AAGZ,aAAK,MAAM,QAAQ;AACnB,aAAK,OAAO;AACZ,aAAK,qBAAqB;AAC1B,cAAM;AAAA,MACR;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,QAAiB;AAE3B,WAAK,eAAe;AACpB,YAAM;AAAA,IACR,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAiC;AAC/B,SAAK,uBAAuB,KAAK,cAAc,EAAE,MAAM,CAAC,QAAiB;AACvE,WAAK,qBAAqB;AAC1B,YAAM;AAAA,IACR,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,gBAA+B;AAC3C,UAAM,SAAS,KAAK,OAAO,aAAa;AAGxC,UAAM,oBAAoB,QAAQ;AAAA,MAChC,GAAI,KAAK,eAAe,SAAY,EAAE,MAAM,KAAK,WAAW,IAAI,CAAC;AAAA,MACjE,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IACxD,CAAC;AAGD,SAAK,OAAO,MAAM,UAAU,QAAQ,QAAQ;AAAA,MAC1C,GAAI,KAAK,YAAY,SAAY,EAAE,KAAK,KAAK,QAAQ,IAAI,CAAC;AAAA,IAC5D,CAAC;AAED,QAAI;AACF,UAAI,KAAK,OAAO,YAAY,MAAM,MAAM;AACtC,cAAM,KAAK,OAAO,MAAM;AAAA,MAC1B;AAIA,WAAK,0BAA0B;AAAA,IACjC,SAAS,KAAK;AACZ,WAAK,KAAK,QAAQ;AAClB,WAAK,OAAO;AACZ,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,4BAAkC;AACxC,UAAM,YAAY,KAAK;AACvB,QAAI,CAAC,UAAW;AAChB,UAAM,eAAe,iBAAiB,KAAK,MAAM,EAAE;AACnD,QAAI,CAAC,cAAc;AACjB,WAAK;AAAA,QACH;AAAA,MAGF;AACA;AAAA,IACF;AACA,eAAW,eAAe,aAAa,OAAO,GAAG;AAC/C,UAAI,CAAC,YAAY,cAAc;AAC7B,cAAM,eAAe,UAAU,gBAAgB,YAAY,KAAK;AAChE,YAAI,aAAc,aAAY,eAAe;AAAA,MAC/C;AACA,UAAI,CAAC,YAAY,cAAc;AAC7B,cAAM,eAAe,UAAU,kBAAkB,YAAY,KAAK;AAClE,YAAI,aAAc,aAAY,eAAe;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,sBAAuC;AACnD,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,KAAK;AAER,aAAO,KAAK,OAAO,YAAY,KAAK,kBAAkB;AAAA,IACxD;AACA,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK;AAAA,QACH;AAAA,MAEF;AACA,aAAO,KAAK,OAAO,YAAY,KAAK,kBAAkB;AAAA,IACxD;AAEA,UAAM,SAAS,KAAK;AACpB,UAAM,WAAW,KAAK,OAAO,aAAa;AAE1C,UAAM,aAAa,IAAI,QAAQ,UAAU,MAAM;AAC/C,UAAM,YAAY,iBAAiB,KAAK,MAAM;AAC9C,UAAM,UAAU,MAAM,KAAK,sBAAsB,KAAK,YAAY,SAAS;AAI3E,UAAM,YAAY,MAAM,KAAK,OAAO,YAAY,KAAK,kBAAkB;AAEvE,QAAI,WAAW,cAAc,QAAQ,WAAW;AAC9C,WAAK,kBAAkB;AACvB,UAAI,MAAM,UAAU,OAAO,CAAC;AAAA,IAC9B,OAAO;AACL,WAAK,kBAAkB;AACvB,WAAK,oBAAoB,KAAK,WAAW,UAAU,QAAQ,SAAS;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,sBACZ,KACA,YACA,WACuC;AACvC,QAAI,WAAW,WAAW,EAAG,QAAO;AACpC,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,IAAI,gBAAgB,CAAC,GAAG,cAAc;AACzC,WAAK;AAAA,QACH;AAAA,MAEF;AACA,aAAO;AAAA,IACT;AAEA,eAAW,UAAU,YAAY;AAI/B,YAAM,cAAc,UAAU,kBAAkB,IAAI,OAAO,MAAM;AACjE,UACE,CAAC,eACD,YAAY,UAAU,OAAO,UAC5B,YAAY,gBAAgB,QAAQ,OAAO,cAC5C;AACA;AAAA,MACF;AAGA,YAAM,YAAY,IAAI,cAAc,OAAO,SAAS;AACpD,UACE,WAAW,aAAa,UACxB,WAAW,cAAc,QACzB;AACA;AAAA,MACF;AACA,UAAI,CAAC,WAAW;AAKd,aAAK;AAAA,UACH,yBAAyB,OAAO,SAAS,mCACzB,IAAI,aAAa,qHAEb,IAAI,OAAO;AAAA,QACjC;AAAA,MACF;AAIA,SAAG,aAAa,OAAO,WAAW,OAAO,OAAO;AAChD,SAAG,aAAa,IAAI,OAAO,QAAQ,OAAO,SAAS;AASnD,UACE,OAAO,QAAQ,cAAc,SAC7B,OAAO,iBAAiB,UACxB,KAAK,OAAO,yBACZ;AACA,YAAI;AACF,gBAAM,UAAU,MAAM,KAAK,OAAO;AAAA,YAChC,OAAO;AAAA,YACP;AAAA,cACE,OAAO,OAAO;AAAA,cACd,qBAAqB,OAAO,QAAQ;AAAA,YACtC;AAAA,UACF;AACA,cAAI,YAAY,QAAW;AACzB,gBAAI,MAAM,UAAU,MAAM,GAAG;AAAA,cAC3B,cAAc,QAAQ,SAAS;AAAA,YACjC,CAAC;AAAA,UACH;AAAA,QACF,SAAS,KAAK;AACZ,eAAK;AAAA,YACH,4CAA4C,OAAO,SAAS,YAC/C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,oBACN,KACA,WACA,UACA,aACA,WACM;AACN,QAAI;AACJ,eAAW,CAAC,MAAM,OAAO,KAAK,UAAU,gBAAgB,gBACtD,CAAC,GAAG;AACJ,UAAI,YAAY,WAAW;AACzB,iBAAS;AACT;AAAA,MACF;AAAA,IACF;AACA,UAAM,cACJ,WAAW,SACP,UAAU,kBAAkB,IAAI,MAAM,IACtC;AACN,QAAI,WAAW,UAAa,CAAC,aAAa;AACxC,WAAK;AAAA,QACH,uBAAuB,SAAS;AAAA,MAGlC;AACA;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,qBAAe,KAAK,OAAO,yBAAyB,SAAS;AAAA,IAC/D,QAAQ;AAAA,IAER;AAEA,QAAI,OAAO;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,YAAY;AAAA,MACnB,cAAc,YAAY,gBAAgB;AAAA;AAAA;AAAA,MAG1C,SAAS;AAAA,QACP,WAAW,YAAY;AAAA,QACvB,SACE,OAAO,YAAY,YAAY,WAAW,YAAY,UAAU;AAAA,QAClE,qBAAqB,YAAY,gBAAgB;AAAA,QACjD,GAAI,YAAY,eACZ,EAAE,cAAc,YAAY,aAAa,IACzC,CAAC;AAAA,QACL,WAAW,YAAY;AAAA,MACzB;AAAA,MACA,GAAI,iBAAiB,UAAa,eAAe,KAC7C,EAAE,cAAc,aAAa,SAAS,EAAE,IACxC,CAAC;AAAA,IACP,CAAC;AAGD,QAAI,cAAc,SAAS;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,SAAK,MAAM,QAAQ;AACnB,SAAK,OAAO;AACZ,SAAK,eAAe;AACpB,SAAK,qBAAqB;AAC1B,SAAK,YAAY;AAGjB,QAAI,KAAK,cAAc,KAAK,OAAO,YAAY,MAAM,OAAO;AAC1D,YAAM,KAAK,OAAO,KAAK;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,oBAAoB,MAGM;AAC9B,UAAM,KAAK,MAAM;AACjB,UAAM,YAAY,KAAK,eAAe;AACtC,UAAM,WAAW,KAAK,OAAO,aAAa;AAC1C,UAAM,cAAc,KAAK,iBAAiB;AAC1C,UAAM,SACJ,KAAK,cAAc,KAAK,gBACpB,KAAK,WACF,QAAQ,UAAU,KAAK,aAAa,EACpC,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS,IACxC;AAEN,UAAM,UAA8B;AAAA,MAClC;AAAA,MACA,SAAS,KAAK;AAAA,MACd;AAAA,MACA,GAAI,SAAS,EAAE,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MAC/D,GAAI,QAAQ,iBAAiB,SACzB,EAAE,cAAc,OAAO,aAAa,IACpC,CAAC;AAAA,IACP;AAEA,QAAI,MAAM,YAAY,UAAa,KAAK,UAAU,IAAI;AACpD,UAAI,CAAC,KAAK,OAAO,kBAAkB;AACjC,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AACA,YAAM,YAAY,MAAM,KAAK,OAAO;AAAA,QAClC;AAAA,QACA,KAAK;AAAA,MACP;AACA,cAAQ,eAAe,KAAK,QAAQ,SAAS;AAC7C,cAAQ,eAAe,UAAU;AACjC,UAAI,UAAU,OAAQ,SAAQ,gBAAgB,UAAU;AACxD,UAAI,QAAQ;AACV,aAAK,YAAY,MAAM,UAAU,MAAM,GAAG;AAAA,UACxC,cAAc,UAAU;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,qBAAqB,QAAgC;AAC3D,UAAM,YAAY,iBAAiB,KAAK,MAAM;AAC9C,UAAM,KAAK,UAAU;AACrB,QAAI,IAAI,gBAAgB,GAAG,cAAc;AACvC,SAAG,aAAa,OAAO,WAAW,OAAO,OAAO;AAChD,SAAG,aAAa,IAAI,OAAO,QAAQ,OAAO,SAAS;AAAA,IACrD;AACA,UAAM,eAAe,UAAU,sBAAsB;AACrD,QAAI,gBAAgB,CAAC,aAAa,IAAI,OAAO,SAAS,GAAG;AACvD,mBAAa,IAAI,OAAO,WAAW;AAAA,QACjC,OAAO,OAAO;AAAA,QACd,qBAAqB,OAAO,QAAQ;AAAA,QACpC,GAAI,OAAO,QAAQ,eACf,EAAE,cAAc,OAAO,QAAQ,aAAa,IAC5C,CAAC;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,qBACJ,QAC8B;AAC9B,UAAM,KAAK,gBAAgB;AAC3B,QAAI,CAAC,KAAK,OAAO,cAAc;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,SAAK,qBAAqB,MAAM;AAChC,UAAM,SAAS,MAAM,KAAK,OAAO,aAAa,OAAO,SAAS;AAC9D,SAAK,YAAY,MAAM,UAAU,MAAM,CAAC;AACxC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,sBACJ,QAC+B;AAC/B,UAAM,KAAK,gBAAgB;AAC3B,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,SAAK,qBAAqB,MAAM;AAChC,UAAM,SAAS,MAAM,KAAK,OAAO,cAAc,OAAO,SAAS;AAC/D,SAAK,YAAY,MAAM,UAAU,MAAM,CAAC;AACxC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,qBAAmD;AACvD,QAAI,CAAC,KAAK,OAAO,YAAa,QAAO,CAAC;AACtC,WAAO,MAAM,KAAK,OAAO,YAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAiC;AAC/B,WAAO,QAAQ,QAAQ;AAAA,MACrB,kBAAkB,KAAK;AAAA,MACvB,UAAU,KAAK;AAAA,IACjB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAgB,QAAiD;AACrE,QAAI,OAAO,KAAK,SAAS,iBAAiB;AACxC,YAAM,IAAI;AAAA,QACR,cAAc,OAAO,GAAG,gBAAgB,eAAe,gBAAgB,OAAO,KAAK,MAAM;AAAA,MAC3F;AAAA,IACF;AACA,UAAM,KAAK,MAAM;AACjB,UAAM,YAAY,KAAK,eAAe;AAEtC,UAAM,MAAM,OAAO,OAAO,KAAK,MAAM,IAAI,KAAK;AAC9C,UAAM,QAAQ,KAAK,OAAO,UAAU;AAAA,MAClC,MAAM;AAAA,MACN,SAAS;AAAA,MACT,YAAY,WAAW;AAAA,MACvB,MAAM;AAAA,QACJ,CAAC,KAAK,OAAO,KAAK,SAAS,QAAQ,GAAG,MAAM;AAAA,QAC5C,CAAC,OAAO,IAAI,SAAS,GAAG,MAAM;AAAA,QAC9B,CAAC,UAAU,0BAA0B;AAAA,QACrC,CAAC,WAAW,OAAO,GAAG;AAAA,QACtB,CAAC,YAAY,OAAO,IAAI;AAAA,QACxB,CAAC,QAAQ,OAAO,MAAM;AAAA,MACxB;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,MAAM,KAAK,OAAO,iBAAiB,WAAW,GAAG;AAC/D,UAAM,SAAS,MAAM,KAAK,OAAO,aAAa,OAAO;AAAA,MACnD,GAAI,KAAK,mBAAmB,EAAE,aAAa,KAAK,iBAAiB,IAAI,CAAC;AAAA,MACtE;AAAA,MACA,WAAW;AAAA;AAAA,MAEX,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI;AAAA,QACR,+BAA+B,OAAO,GAAG,MAAM,OAAO,SAAS,0BAA0B;AAAA,MAC3F;AAAA,IACF;AACA,QAAI,CAAC,OAAO,MAAM;AAChB,YAAM,IAAI;AAAA,QACR,8CAA8C,OAAO,GAAG;AAAA,MAC1D;AAAA,IACF;AACA,WAAO,EAAE,MAAM,mBAAmB,OAAO,IAAI,GAAG,SAAS,IAAI;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aACJ,OACA,WACyB;AACzB,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,IAAI;AAAA,QACR,iDAAiD,UAAU,MAAM;AAAA,MACnE;AAAA,IACF;AACA,UAAM,KAAK,MAAM;AACjB,UAAM,YAAY,KAAK,eAAe;AAEtC,UAAM,SAAS,KAAK,OAAO,UAAU,KAAK;AAC1C,UAAM,MAAM,KAAK;AACjB,UAAM,QAAQ,MAAM,KAAK,OAAO,iBAAiB,WAAW,GAAG;AAC/D,UAAM,SAAS,MAAM,KAAK,OAAO,aAAa,QAAQ;AAAA,MACpD,GAAI,KAAK,qBACL,EAAE,aAAa,KAAK,mBAAmB,IACvC,CAAC;AAAA,MACL;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI;AAAA,QACR,0BAA0B,MAAM,IAAI,MAAM,OAAO,SAAS,0BAA0B;AAAA,MACtF;AAAA,IACF;AACA,WAAO,EAAE,SAAS,OAAO,WAAW,OAAO,IAAI,SAAS,IAAI;AAAA,EAC9D;AAAA,EAEQ,iBAAyB;AAC/B,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AACF;AAEA,SAAS,aAAqB;AAC5B,SAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACrC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../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 * 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 /** 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';\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 // Final accounting.\n const finalClosure = walkClosure(tips, objects, present);\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 /** Σ size × uploadFeePerByte. */\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\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 };\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 { MAX_OBJECT_SIZE, type GitObjectType } from './objects.js';\nimport type {\n FeeRates,\n PublishReceipt,\n 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/** One object exceeding {@link MAX_OBJECT_SIZE}. */\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 * Thrown by {@link planPush} when any object in the delta exceeds the 95KB\n * upload limit (hard error in v1 — the paid blob path is a follow-up spike).\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\n .map((o) => `${o.path ?? o.sha} (${o.size} bytes)`)\n .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 /** Σ size × uploadFeePerByte (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\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 * 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({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'new' });\n continue;\n }\n if (remoteSha === ref.sha) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'up-to-date' });\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({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'fast-forward' });\n } else if (force) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'forced' });\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 const oversize: OversizeObject[] = [];\n for (const stat of stats) {\n if (stat.size > MAX_OBJECT_SIZE) {\n const path = pathBySha.get(stat.sha);\n oversize.push({ ...stat, ...(path ? { path } : {}) });\n }\n }\n if (oversize.length > 0) throw new OversizeObjectsError(oversize);\n\n // 4. Upload order: ref tips last. -----------------------------------------\n const tipShas = new Set(updates.map((u) => u.localSha));\n const planned: PlannedObject[] = stats.map((stat) => {\n const path = pathBySha.get(stat.sha);\n return { ...stat, ...(path ? { path } : {}), isRefTip: tipShas.has(stat.sha) };\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 const announceNeeded = !remoteState.announced;\n const totalObjectBytes = objects.reduce((sum, o) => sum + o.size, 0);\n const uploadFee = BigInt(totalObjectBytes) * feeRates.uploadFeePerByte;\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 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 },\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 });\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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/objects.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 * Maximum uploadable git object body size: 95KB safety margin under the\n * 100KB free tier (R10-005). Larger objects are a hard error in v1.\n */\nexport const MAX_OBJECT_SIZE = 95 * 1024;\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"],"mappings":";AAaA,SAAS,kBAAkB;AAsBpB,IAAM,kBAAkB,KAAK;AAU7B,SAAS,cAAc,MAAqB,MAAyB;AAC1E,QAAM,SAAS,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,MAAM,IAAI;AACrD,QAAM,aAAa,OAAO,OAAO,CAAC,QAAQ,IAAI,CAAC;AAC/C,QAAM,MAAM,WAAW,MAAM,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAC9D,SAAO,EAAE,KAAK,QAAQ,YAAY,KAAK;AACzC;AAYO,SAAS,cAAc,SAA4B;AACxD,SAAO,cAAc,QAAQ,OAAO,KAAK,SAAS,OAAO,CAAC;AAC5D;AASO,SAAS,cACd,SACW;AAEX,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE;AAAA,IAAK,CAAC,GAAG,MACnC,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI;AAAA,EAC/C;AAEA,QAAM,eAAyB,CAAC;AAChC,aAAW,SAAS,QAAQ;AAC1B,UAAM,cAAc,OAAO,KAAK,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI;AAE/D,UAAM,SAAS,OAAO,KAAK,MAAM,KAAK,KAAK;AAC3C,iBAAa,KAAK,OAAO,OAAO,CAAC,aAAa,MAAM,CAAC,CAAC;AAAA,EACxD;AAEA,SAAO,cAAc,QAAQ,OAAO,OAAO,YAAY,CAAC;AAC1D;AAQO,SAAS,gBAAgB,MAOlB;AACZ,QAAM,QAAQ;AAAA,IACZ,QAAQ,KAAK,OAAO;AAAA,IACpB,GAAI,KAAK,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,IACrD,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE,aAAa,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IAC3E;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACvE;AASO,SAAS,aAAa,MAWf;AACZ,QAAM,QAAQ;AAAA,IACZ,UAAU,KAAK,SAAS;AAAA,IACxB,QAAQ,KAAK,UAAU;AAAA,IACvB,OAAO,KAAK,OAAO;AAAA,IACnB,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACpE;","names":[]}