zk-credits 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ receive $25 for a 30-minute session regardless of setup success. That payment
9
9
  is not product revenue or willingness-to-pay evidence.
10
10
 
11
11
  ```bash
12
- npm install --global zk-credits@0.2.7
12
+ npm install --global zk-credits@0.2.8
13
13
  ```
14
14
 
15
15
  ## Pinned pilot versions
@@ -19,17 +19,18 @@ not mix versions between the sidecar, the adapter package, and the gateway:
19
19
 
20
20
  | Package | Version |
21
21
  | --- | --- |
22
- | `zk-credits` (this sidecar) | `0.2.7` |
22
+ | `zk-credits` (this sidecar) | `0.2.8` |
23
23
  | `@zk-credits/x402-zk-prepaid` (adapter) | `0.1.0` |
24
24
  | `@zk-credits/shared` | `0.1.0` |
25
25
 
26
- The pinned Base Sepolia trial version is `0.2.7`. It uses the non-streaming
26
+ The slot A Base Sepolia trial version is `0.2.8`. It uses the non-streaming
27
27
  `POST /v1/chat/completions` path below, includes the narrow loopback Responses
28
- bridge for supported Codex text and function-tool requests, and acquires the
29
- pinned private proving bundle through the local GitHub CLI session. It also
28
+ bridge for supported Codex text and function-tool requests, and includes the
29
+ pinned proving bundle in the package. It also
30
30
  synchronizes V2 Base events and checks the gateway known root before local
31
- setup completes. The proving artifacts are available only from the private
32
- immutable release pinned by `circuits/manifest.json`.
31
+ setup completes. The packaged archive is checked against the immutable release
32
+ digest pinned by `circuits/manifest.json`. Version 0.2.8 must be published by
33
+ the owner before inviting a slot A participant.
33
34
 
34
35
  Verify the shipped artifacts before your first prove. The package's
35
36
  `circuits/manifest.json` fixes the SHA-256 of the frozen
@@ -60,16 +61,13 @@ still receives the class. A rejected request consumes no credit.
60
61
  ## First run
61
62
 
62
63
  1. Redeem the founder-prepared invite and download the encrypted V2 credential
63
- export. The founder grants access to the private bundle repository
64
- `mangekyou-labs/zk-credits-base-sepolia-v2-bundle` before the session.
64
+ export. Slot A needs no access to the proving-bundle repository.
65
65
  2. Give Codex the single setup instruction in the
66
66
  [first-use guide](../../docs/onboarding/base-zk-credits-codex-first-use.md).
67
- Codex checks local GitHub CLI authentication and repository access, installs
68
- this pinned release, downloads and verifies the bundle, and runs setup.
67
+ Codex installs this pinned release, verifies its packaged bundle, and runs setup.
69
68
  The human signs in if needed, enters the recovery password at the hidden
70
69
  prompt, gives consent, and chooses their own task. No product payment is
71
- involved. The sidecar uses the local GitHub CLI session and never receives
72
- or stores the GitHub token.
70
+ involved. The sidecar needs no GitHub token to install proving material.
73
71
  3. The setup command sets the credential, gateway, and Base Sepolia RPC
74
72
  endpoint:
75
73
 
@@ -80,10 +78,10 @@ still receives the class. A rejected request consumes no credit.
80
78
  ```
81
79
 
82
80
  `zk-credits setup codex` searches the standard local folders first. If it
83
- finds no valid bundle, it downloads the pinned release, verifies the
84
- release id, tag, immutability, archive digest, and all three file digests,
81
+ finds no valid bundle, it reads the packaged archive, verifies its pinned
82
+ archive digest and all three file digests,
85
83
  then atomically installs the bundle under `~/.zk-credits/artifacts` with
86
- owner-only permissions. Missing GitHub access or any mismatch stops setup.
84
+ owner-only permissions. Any mismatch stops setup.
87
85
  It syncs public Base events from the pinned V2 bond and deployment block,
88
86
  validates that the resulting witness resolves the activated credential,
89
87
  and asks the gateway's public `POST /v1/root-known` endpoint whether the
Binary file
@@ -5,6 +5,7 @@ export interface AcquirePinnedBundleOptions {
5
5
  manifest: CircuitManifest;
6
6
  targetDirectory: string;
7
7
  runGithubCommand?: GithubCommand;
8
+ bundledArchivePath?: string;
8
9
  }
9
- /** Fetches a pinned private GitHub release and atomically installs its safe USTAR bundle. */
10
+ /** Installs the pinned package archive, or fetches the release for legacy/test callers. */
10
11
  export declare function acquirePinnedArtifactBundle(options: AcquirePinnedBundleOptions): Promise<string>;
@@ -5,7 +5,8 @@ import { gunzipSync } from 'node:zlib';
5
5
  import { promisify } from 'node:util';
6
6
  import { chmod, lstat, mkdir, open, readFile, rename, rm, } from 'node:fs/promises';
7
7
  import { dirname, join, resolve } from 'node:path';
8
- import { resolvePinnedArtifactBundle } from './artifact-bundle.js';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { ArtifactBundleError, resolvePinnedArtifactBundle } from './artifact-bundle.js';
9
10
  const execFile = promisify(nodeExecFile);
10
11
  const MAX_COMPRESSED_BYTES = 20 * 1024 * 1024;
11
12
  const MAX_EXPANDED_BYTES = 32 * 1024 * 1024;
@@ -126,14 +127,15 @@ function pinnedAsset(release, raw) {
126
127
  }
127
128
  const record = value;
128
129
  const assets = Array.isArray(record.assets) ? record.assets : [];
129
- const asset = assets.find((entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
130
+ const matchingAssets = assets.filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
130
131
  && entry.name === release.assetName);
132
+ const asset = matchingAssets[0];
131
133
  if (record.id !== release.releaseId
132
134
  || record.tag_name !== release.tag
133
135
  || record.draft !== false
134
136
  || record.immutable !== true
135
- || !asset
136
- || (typeof asset.digest === 'string' && asset.digest !== 'sha256:' + release.sha256)) {
137
+ || matchingAssets.length !== 1
138
+ || asset.digest !== 'sha256:' + release.sha256) {
137
139
  throw new Error('Pinned proving release identity or immutability check failed');
138
140
  }
139
141
  }
@@ -185,13 +187,15 @@ async function replaceDirectoryAtomically(stage, target) {
185
187
  await rm(stage, { recursive: true, force: true });
186
188
  }
187
189
  }
188
- /** Fetches a pinned private GitHub release and atomically installs its safe USTAR bundle. */
190
+ /** Installs the pinned package archive, or fetches the release for legacy/test callers. */
189
191
  export async function acquirePinnedArtifactBundle(options) {
190
192
  const release = options.manifest.release;
191
193
  if (!release)
192
194
  throw new Error('This sidecar manifest has no pinned artifact release');
193
195
  const names = new Set(options.manifest.artifacts.map((artifact) => artifact.file));
194
196
  const command = options.runGithubCommand ?? defaultGithubCommand;
197
+ const bundledArchivePath = options.bundledArchivePath
198
+ ?? (!options.runGithubCommand ? fileURLToPath(new URL('../circuits/base-sepolia-v2.tar.gz', import.meta.url)) : undefined);
195
199
  const parent = dirname(resolve(options.targetDirectory));
196
200
  const tempDirectory = join(parent, '.base-sepolia-download-' + randomUUID());
197
201
  const stage = join(parent, '.base-sepolia-stage-' + randomUUID());
@@ -199,16 +203,49 @@ export async function acquirePinnedArtifactBundle(options) {
199
203
  await mkdir(parent, { recursive: true, mode: 0o700 });
200
204
  await mkdir(tempDirectory, { recursive: false, mode: 0o700 });
201
205
  await mkdir(stage, { recursive: false, mode: 0o700 });
202
- const releaseJson = await command(['api', `repos/${release.repository}/releases/${release.releaseId}`]);
203
- pinnedAsset(release, releaseJson);
204
- await command([
205
- 'release', 'download', release.tag,
206
- '--repo', release.repository,
207
- '--dir', tempDirectory,
208
- '--pattern', release.assetName,
209
- ]);
210
- const archivePath = join(tempDirectory, release.assetName);
211
- const archiveInfo = await lstat(archivePath);
206
+ if (!bundledArchivePath) {
207
+ try {
208
+ await command(['auth', 'status']);
209
+ }
210
+ catch {
211
+ throw new Error('GitHub CLI authentication is required; run gh auth login locally before downloading the pinned proving bundle.');
212
+ }
213
+ try {
214
+ const repositoryJson = await command(['api', `repos/${release.repository}`]);
215
+ const repository = JSON.parse(repositoryJson);
216
+ if (repository.full_name !== release.repository)
217
+ throw new Error('Repository identity mismatch');
218
+ }
219
+ catch {
220
+ throw new Error(`Pinned proving bundle requires read access to ${release.repository}; ask the founder to grant access to your signed-in GitHub account.`);
221
+ }
222
+ let releaseJson;
223
+ try {
224
+ releaseJson = await command(['api', `repos/${release.repository}/releases/${release.releaseId}`]);
225
+ }
226
+ catch {
227
+ throw new Error('Pinned proving release metadata could not be read; ask the founder to verify the published release with an authorized account. A 404 does not distinguish a missing release from insufficient access.');
228
+ }
229
+ pinnedAsset(release, releaseJson);
230
+ try {
231
+ await command([
232
+ 'release', 'download', release.tag,
233
+ '--repo', release.repository,
234
+ '--dir', tempDirectory,
235
+ '--pattern', release.assetName,
236
+ ]);
237
+ }
238
+ catch {
239
+ throw new Error('Pinned proving bundle download failed; verify gh access and the published release asset.');
240
+ }
241
+ }
242
+ const archivePath = bundledArchivePath ?? join(tempDirectory, release.assetName);
243
+ const archiveInfo = await lstat(archivePath).catch((error) => {
244
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
245
+ throw new Error('Pinned proving bundle archive is missing or too large');
246
+ }
247
+ throw error;
248
+ });
212
249
  if (archiveInfo.isSymbolicLink() || !archiveInfo.isFile() || archiveInfo.size > MAX_COMPRESSED_BYTES) {
213
250
  throw new Error('Pinned proving bundle archive is missing or too large');
214
251
  }
@@ -226,15 +263,23 @@ export async function acquirePinnedArtifactBundle(options) {
226
263
  for (const [name, bytes] of files)
227
264
  await writeBundleFile(stage, name, bytes);
228
265
  await chmod(stage, 0o700);
229
- await resolvePinnedArtifactBundle({ artifactDirectory: stage, manifest: options.manifest });
266
+ try {
267
+ await resolvePinnedArtifactBundle({ artifactDirectory: stage, manifest: options.manifest });
268
+ }
269
+ catch (error) {
270
+ if (error instanceof ArtifactBundleError && error.category === 'artifact_hash_mismatch') {
271
+ throw new Error(`Pinned proving bundle file SHA-256 check failed: ${error.message}`, { cause: error });
272
+ }
273
+ throw error;
274
+ }
230
275
  await replaceDirectoryAtomically(stage, resolve(options.targetDirectory));
231
276
  await resolvePinnedArtifactBundle({ artifactDirectory: resolve(options.targetDirectory), manifest: options.manifest });
232
277
  return resolve(options.targetDirectory);
233
278
  }
234
279
  catch (error) {
235
- if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle)/u.test(error.message))
280
+ if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle|GitHub CLI authentication)/u.test(error.message))
236
281
  throw error;
237
- throw new Error('Pinned proving bundle acquisition failed; verify gh access and the published release pin.');
282
+ throw new Error('Pinned proving bundle acquisition failed; verify the packaged archive and manifest pins.');
238
283
  }
239
284
  finally {
240
285
  await rm(tempDirectory, { recursive: true, force: true });
@@ -24,7 +24,7 @@ export interface ResolveSetupInputsOptions {
24
24
  validateWitness(source: SetupWitnessSource): Promise<void>;
25
25
  /** Called only when more than one valid local candidate exists. */
26
26
  chooseCandidate?(kind: 'proving bundle' | 'witness file', candidates: readonly string[]): Promise<string>;
27
- /** Test seam for clean-install acquisition; production uses gh-authenticated release download. */
27
+ /** Test seam for clean-install acquisition; production reads the packaged pinned archive. */
28
28
  acquireArtifactBundle?(targetDirectory: string, manifest: CircuitManifest): Promise<void>;
29
29
  workingDirectory?: string;
30
30
  }
@@ -82027,6 +82027,7 @@ import { gunzipSync } from "node:zlib";
82027
82027
  import { promisify as promisify2 } from "node:util";
82028
82028
  import { chmod as chmod3, lstat, mkdir as mkdir5, open, readFile as readFile4, rename as rename3, rm } from "node:fs/promises";
82029
82029
  import { dirname as dirname4, join as join4, resolve as resolve2 } from "node:path";
82030
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
82030
82031
  var execFile = promisify2(nodeExecFile);
82031
82032
  var MAX_COMPRESSED_BYTES = 20 * 1024 * 1024;
82032
82033
  var MAX_EXPANDED_BYTES = 32 * 1024 * 1024;
@@ -82145,8 +82146,9 @@ function pinnedAsset(release, raw) {
82145
82146
  }
82146
82147
  const record = value;
82147
82148
  const assets = Array.isArray(record.assets) ? record.assets : [];
82148
- const asset = assets.find((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === release.assetName);
82149
- if (record.id !== release.releaseId || record.tag_name !== release.tag || record.draft !== false || record.immutable !== true || !asset || typeof asset.digest === "string" && asset.digest !== "sha256:" + release.sha256) {
82149
+ const matchingAssets = assets.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === release.assetName);
82150
+ const asset = matchingAssets[0];
82151
+ if (record.id !== release.releaseId || record.tag_name !== release.tag || record.draft !== false || record.immutable !== true || matchingAssets.length !== 1 || asset.digest !== "sha256:" + release.sha256) {
82150
82152
  throw new Error("Pinned proving release identity or immutability check failed");
82151
82153
  }
82152
82154
  }
@@ -82198,6 +82200,7 @@ async function acquirePinnedArtifactBundle(options) {
82198
82200
  throw new Error("This sidecar manifest has no pinned artifact release");
82199
82201
  const names = new Set(options.manifest.artifacts.map((artifact) => artifact.file));
82200
82202
  const command = options.runGithubCommand ?? defaultGithubCommand;
82203
+ const bundledArchivePath = options.bundledArchivePath ?? (!options.runGithubCommand ? fileURLToPath2(new URL("../circuits/base-sepolia-v2.tar.gz", import.meta.url)) : void 0);
82201
82204
  const parent = dirname4(resolve2(options.targetDirectory));
82202
82205
  const tempDirectory = join4(parent, ".base-sepolia-download-" + randomUUID());
82203
82206
  const stage = join4(parent, ".base-sepolia-stage-" + randomUUID());
@@ -82205,21 +82208,50 @@ async function acquirePinnedArtifactBundle(options) {
82205
82208
  await mkdir5(parent, { recursive: true, mode: 448 });
82206
82209
  await mkdir5(tempDirectory, { recursive: false, mode: 448 });
82207
82210
  await mkdir5(stage, { recursive: false, mode: 448 });
82208
- const releaseJson = await command(["api", `repos/${release.repository}/releases/${release.releaseId}`]);
82209
- pinnedAsset(release, releaseJson);
82210
- await command([
82211
- "release",
82212
- "download",
82213
- release.tag,
82214
- "--repo",
82215
- release.repository,
82216
- "--dir",
82217
- tempDirectory,
82218
- "--pattern",
82219
- release.assetName
82220
- ]);
82221
- const archivePath = join4(tempDirectory, release.assetName);
82222
- const archiveInfo = await lstat(archivePath);
82211
+ if (!bundledArchivePath) {
82212
+ try {
82213
+ await command(["auth", "status"]);
82214
+ } catch {
82215
+ throw new Error("GitHub CLI authentication is required; run gh auth login locally before downloading the pinned proving bundle.");
82216
+ }
82217
+ try {
82218
+ const repositoryJson = await command(["api", `repos/${release.repository}`]);
82219
+ const repository = JSON.parse(repositoryJson);
82220
+ if (repository.full_name !== release.repository)
82221
+ throw new Error("Repository identity mismatch");
82222
+ } catch {
82223
+ throw new Error(`Pinned proving bundle requires read access to ${release.repository}; ask the founder to grant access to your signed-in GitHub account.`);
82224
+ }
82225
+ let releaseJson;
82226
+ try {
82227
+ releaseJson = await command(["api", `repos/${release.repository}/releases/${release.releaseId}`]);
82228
+ } catch {
82229
+ throw new Error("Pinned proving release metadata could not be read; ask the founder to verify the published release with an authorized account. A 404 does not distinguish a missing release from insufficient access.");
82230
+ }
82231
+ pinnedAsset(release, releaseJson);
82232
+ try {
82233
+ await command([
82234
+ "release",
82235
+ "download",
82236
+ release.tag,
82237
+ "--repo",
82238
+ release.repository,
82239
+ "--dir",
82240
+ tempDirectory,
82241
+ "--pattern",
82242
+ release.assetName
82243
+ ]);
82244
+ } catch {
82245
+ throw new Error("Pinned proving bundle download failed; verify gh access and the published release asset.");
82246
+ }
82247
+ }
82248
+ const archivePath = bundledArchivePath ?? join4(tempDirectory, release.assetName);
82249
+ const archiveInfo = await lstat(archivePath).catch((error) => {
82250
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
82251
+ throw new Error("Pinned proving bundle archive is missing or too large");
82252
+ }
82253
+ throw error;
82254
+ });
82223
82255
  if (archiveInfo.isSymbolicLink() || !archiveInfo.isFile() || archiveInfo.size > MAX_COMPRESSED_BYTES) {
82224
82256
  throw new Error("Pinned proving bundle archive is missing or too large");
82225
82257
  }
@@ -82236,14 +82268,21 @@ async function acquirePinnedArtifactBundle(options) {
82236
82268
  for (const [name, bytes] of files)
82237
82269
  await writeBundleFile(stage, name, bytes);
82238
82270
  await chmod3(stage, 448);
82239
- await resolvePinnedArtifactBundle({ artifactDirectory: stage, manifest: options.manifest });
82271
+ try {
82272
+ await resolvePinnedArtifactBundle({ artifactDirectory: stage, manifest: options.manifest });
82273
+ } catch (error) {
82274
+ if (error instanceof ArtifactBundleError && error.category === "artifact_hash_mismatch") {
82275
+ throw new Error(`Pinned proving bundle file SHA-256 check failed: ${error.message}`, { cause: error });
82276
+ }
82277
+ throw error;
82278
+ }
82240
82279
  await replaceDirectoryAtomically(stage, resolve2(options.targetDirectory));
82241
82280
  await resolvePinnedArtifactBundle({ artifactDirectory: resolve2(options.targetDirectory), manifest: options.manifest });
82242
82281
  return resolve2(options.targetDirectory);
82243
82282
  } catch (error) {
82244
- if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle)/u.test(error.message))
82283
+ if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle|GitHub CLI authentication)/u.test(error.message))
82245
82284
  throw error;
82246
- throw new Error("Pinned proving bundle acquisition failed; verify gh access and the published release pin.");
82285
+ throw new Error("Pinned proving bundle acquisition failed; verify the packaged archive and manifest pins.");
82247
82286
  } finally {
82248
82287
  await rm(tempDirectory, { recursive: true, force: true });
82249
82288
  await rm(stage, { recursive: true, force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zk-credits",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Loopback x402 zk-prepaid transport for private Base API credits.",
5
5
  "homepage": "https://feature-zk-api-credits-gadillacers-projects.vercel.app",
6
6
  "repository": {