@jinn-network/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-json.d.ts +1 -0
- package/dist/canonical-json.js +67 -0
- package/dist/captured-task.d.ts +140 -0
- package/dist/captured-task.js +122 -0
- package/dist/contribution-store.d.ts +97 -0
- package/dist/contribution-store.js +770 -0
- package/dist/corpus-read/acquire.d.ts +39 -0
- package/dist/corpus-read/acquire.js +212 -0
- package/dist/corpus-read/cache.d.ts +14 -0
- package/dist/corpus-read/cache.js +17 -0
- package/dist/corpus-read/capture-meta.d.ts +16 -0
- package/dist/corpus-read/capture-meta.js +18 -0
- package/dist/corpus-read/create-corpus.d.ts +2 -0
- package/dist/corpus-read/create-corpus.js +97 -0
- package/dist/corpus-read/fetch-artifact.d.ts +21 -0
- package/dist/corpus-read/fetch-artifact.js +32 -0
- package/dist/corpus-read/fetch.d.ts +12 -0
- package/dist/corpus-read/fetch.js +24 -0
- package/dist/corpus-read/http-discovery.d.ts +9 -0
- package/dist/corpus-read/http-discovery.js +128 -0
- package/dist/corpus-read/index.d.ts +10 -0
- package/dist/corpus-read/index.js +10 -0
- package/dist/corpus-read/ipfs.d.ts +5 -0
- package/dist/corpus-read/ipfs.js +72 -0
- package/dist/corpus-read/route-resolver.d.ts +16 -0
- package/dist/corpus-read/route-resolver.js +19 -0
- package/dist/corpus-read/types.d.ts +177 -0
- package/dist/corpus-read/types.js +42 -0
- package/dist/envelope.d.ts +134 -0
- package/dist/envelope.js +162 -0
- package/dist/evidence-adapter.d.ts +26 -0
- package/dist/evidence-adapter.js +321 -0
- package/dist/evidence-filesystem.d.ts +40 -0
- package/dist/evidence-filesystem.js +267 -0
- package/dist/evidence-index.d.ts +117 -0
- package/dist/evidence-index.js +1083 -0
- package/dist/evidence-store-lock.d.ts +2 -0
- package/dist/evidence-store-lock.js +208 -0
- package/dist/execution-envelope.d.ts +3142 -0
- package/dist/execution-envelope.js +203 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/manifest.d.ts +75 -0
- package/dist/manifest.js +184 -0
- package/dist/paired.d.ts +68 -0
- package/dist/paired.js +92 -0
- package/dist/scrub/build.d.ts +52 -0
- package/dist/scrub/build.js +84 -0
- package/dist/scrub/emit-scrub.d.ts +41 -0
- package/dist/scrub/emit-scrub.js +87 -0
- package/dist/scrub/index.d.ts +12 -0
- package/dist/scrub/index.js +12 -0
- package/dist/scrub/key-policy.d.ts +20 -0
- package/dist/scrub/key-policy.js +42 -0
- package/dist/scrub/layer2.d.ts +23 -0
- package/dist/scrub/layer2.js +32 -0
- package/dist/scrub/ml-pii-stage.d.ts +24 -0
- package/dist/scrub/ml-pii-stage.js +42 -0
- package/dist/scrub/openredaction-stage.d.ts +196 -0
- package/dist/scrub/openredaction-stage.js +270 -0
- package/dist/scrub/pii-build.d.ts +38 -0
- package/dist/scrub/pii-build.js +67 -0
- package/dist/scrub/pipeline.d.ts +38 -0
- package/dist/scrub/pipeline.js +83 -0
- package/dist/scrub/plain-patterns-stage.d.ts +31 -0
- package/dist/scrub/plain-patterns-stage.js +84 -0
- package/dist/scrub/secretlint-stage.d.ts +36 -0
- package/dist/scrub/secretlint-stage.js +249 -0
- package/dist/scrub/transformers-detector.d.ts +35 -0
- package/dist/scrub/transformers-detector.js +67 -0
- package/dist/scrub/types.d.ts +25 -0
- package/dist/scrub/types.js +1 -0
- package/dist/session-provenance.d.ts +75 -0
- package/dist/session-provenance.js +20 -0
- package/dist/skill-artifact.d.ts +442 -0
- package/dist/skill-artifact.js +136 -0
- package/dist/trajectory/hash-chain.d.ts +18 -0
- package/dist/trajectory/hash-chain.js +47 -0
- package/dist/trajectory/index.d.ts +4 -0
- package/dist/trajectory/index.js +4 -0
- package/dist/trajectory/schema.d.ts +606 -0
- package/dist/trajectory/schema.js +110 -0
- package/dist/trajectory/transcript-parsers/aider-history.d.ts +42 -0
- package/dist/trajectory/transcript-parsers/aider-history.js +181 -0
- package/dist/trajectory/transcript-parsers/claude-code-jsonl.d.ts +37 -0
- package/dist/trajectory/transcript-parsers/claude-code-jsonl.js +142 -0
- package/dist/trajectory/transcript-parsers/codex-session.d.ts +42 -0
- package/dist/trajectory/transcript-parsers/codex-session.js +235 -0
- package/dist/trajectory/transcript-parsers/continue-devdata.d.ts +41 -0
- package/dist/trajectory/transcript-parsers/continue-devdata.js +190 -0
- package/dist/trajectory/transcript-parsers/cursor-sqlite.d.ts +38 -0
- package/dist/trajectory/transcript-parsers/cursor-sqlite.js +118 -0
- package/dist/trajectory/transcript-parsers/gemini-session.d.ts +35 -0
- package/dist/trajectory/transcript-parsers/gemini-session.js +137 -0
- package/dist/trajectory/transcript-parsers/index.d.ts +7 -0
- package/dist/trajectory/transcript-parsers/index.js +7 -0
- package/dist/trajectory/transcript-parsers/types.d.ts +101 -0
- package/dist/trajectory/transcript-parsers/types.js +32 -0
- package/dist/trajectory/transcript-to-spans/attrs.d.ts +4 -0
- package/dist/trajectory/transcript-to-spans/attrs.js +30 -0
- package/dist/trajectory/transcript-to-spans/claude-code-stream-json.d.ts +8 -0
- package/dist/trajectory/transcript-to-spans/claude-code-stream-json.js +162 -0
- package/dist/trajectory/transcript-to-spans/codex-exec-json.d.ts +8 -0
- package/dist/trajectory/transcript-to-spans/codex-exec-json.js +253 -0
- package/dist/trajectory/transcript-to-spans/index.d.ts +4 -0
- package/dist/trajectory/transcript-to-spans/index.js +4 -0
- package/dist/trajectory/transcript-to-spans/types.d.ts +18 -0
- package/dist/trajectory/transcript-to-spans/types.js +1 -0
- package/dist/window.d.ts +12 -0
- package/dist/window.js +5 -0
- package/package.json +67 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-artifact resolution chain: cache → self-store → routeResolver → origin.
|
|
3
|
+
*
|
|
4
|
+
* Always hash-verifies before persisting to cache.
|
|
5
|
+
*
|
|
6
|
+
* Spec §2.3 step 4-6.
|
|
7
|
+
*/
|
|
8
|
+
import { type AcquireResult } from './fetch-artifact.js';
|
|
9
|
+
import type { ArtifactContent, ArtifactSource, CorpusStorePort, RouteResolver } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Origin acquire function. Historically returned `Buffer | null`; the
|
|
12
|
+
* discriminated union lets callers distinguish not_found vs network_error.
|
|
13
|
+
* We accept either shape so test fakes that still return `Buffer | null`
|
|
14
|
+
* keep working. `privateKey` is accepted for signature compatibility with
|
|
15
|
+
* older fakes but is unused — acquisition is a free fetch now.
|
|
16
|
+
*/
|
|
17
|
+
type AcquireFn = (endpoint: string, sha256: string, privateKey?: string) => Promise<Buffer | null | AcquireResult>;
|
|
18
|
+
type FetchFromIpfsFn = (gatewayUrl: string, cid: string) => Promise<unknown>;
|
|
19
|
+
export interface AcquireArtifactArgs {
|
|
20
|
+
sha256: string;
|
|
21
|
+
artifactType: string;
|
|
22
|
+
access: {
|
|
23
|
+
endpoint: string;
|
|
24
|
+
priceUsdc: string;
|
|
25
|
+
};
|
|
26
|
+
store: CorpusStorePort;
|
|
27
|
+
selfSafeAddress: string;
|
|
28
|
+
privateKey: string;
|
|
29
|
+
routeResolver?: RouteResolver;
|
|
30
|
+
envelopeCid?: string;
|
|
31
|
+
sources?: ArtifactSource[];
|
|
32
|
+
ipfsGatewayUrl?: string;
|
|
33
|
+
/** Safe address that produced this artifact, when known (from envelope.participant). */
|
|
34
|
+
ownerSafe?: string;
|
|
35
|
+
acquireFn?: AcquireFn;
|
|
36
|
+
fetchFromIpfs?: FetchFromIpfsFn;
|
|
37
|
+
}
|
|
38
|
+
export declare function acquireArtifactContent(args: AcquireArtifactArgs): Promise<ArtifactContent>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-artifact resolution chain: cache → self-store → routeResolver → origin.
|
|
3
|
+
*
|
|
4
|
+
* Always hash-verifies before persisting to cache.
|
|
5
|
+
*
|
|
6
|
+
* Spec §2.3 step 4-6.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { fetchArtifactContent } from './fetch-artifact.js';
|
|
10
|
+
import { AcquireError, HashMismatchError } from './types.js';
|
|
11
|
+
import { fetchFromIpfs as defaultFetchFromIpfs } from './ipfs.js';
|
|
12
|
+
const DONATION_ARTIFACT_ENCODING = 'jinn.artifact.donation.v1';
|
|
13
|
+
function sha256Hex(buf) {
|
|
14
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
function decodeDonationArtifact(raw, expectedSha256) {
|
|
17
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
18
|
+
throw new Error('donation artifact payload is not an object');
|
|
19
|
+
}
|
|
20
|
+
const record = raw;
|
|
21
|
+
if (record['schemaVersion'] !== DONATION_ARTIFACT_ENCODING || record['encoding'] !== DONATION_ARTIFACT_ENCODING) {
|
|
22
|
+
throw new Error('donation artifact payload has unexpected encoding');
|
|
23
|
+
}
|
|
24
|
+
if (record['sha256'] !== expectedSha256) {
|
|
25
|
+
throw new Error('donation artifact sha256 does not match requested artifact');
|
|
26
|
+
}
|
|
27
|
+
if (typeof record['data'] !== 'string') {
|
|
28
|
+
throw new Error('donation artifact payload is missing base64 data');
|
|
29
|
+
}
|
|
30
|
+
return Buffer.from(record['data'], 'base64');
|
|
31
|
+
}
|
|
32
|
+
export async function acquireArtifactContent(args) {
|
|
33
|
+
const { sha256, artifactType, access, store, selfSafeAddress, privateKey, routeResolver, envelopeCid, sources = [], ipfsGatewayUrl, ownerSafe, acquireFn = (endpoint, sha256) => fetchArtifactContent(endpoint, sha256), fetchFromIpfs = defaultFetchFromIpfs, } = args;
|
|
34
|
+
const now = () => new Date().toISOString();
|
|
35
|
+
// 1. Cache hit
|
|
36
|
+
const cached = store.getNetworkArtifact(sha256);
|
|
37
|
+
if (cached) {
|
|
38
|
+
store.touchNetworkArtifactUsage(sha256, now());
|
|
39
|
+
return {
|
|
40
|
+
sha256,
|
|
41
|
+
bytes: cached.content,
|
|
42
|
+
artifactType: cached.artifactType,
|
|
43
|
+
source: 'cache',
|
|
44
|
+
paidAmountUsdc: '0',
|
|
45
|
+
fetchedAt: cached.fetchedAt,
|
|
46
|
+
sourceOperator: cached.sourceOperator ?? undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// 2. Public IPFS donation source.
|
|
50
|
+
const ipfsSource = sources.find((source) => source.kind === 'ipfs');
|
|
51
|
+
if (ipfsSource && ipfsGatewayUrl) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = await fetchFromIpfs(ipfsGatewayUrl, ipfsSource.cid);
|
|
54
|
+
const bytes = decodeDonationArtifact(raw, sha256);
|
|
55
|
+
const actualSha = sha256Hex(bytes);
|
|
56
|
+
if (actualSha !== sha256) {
|
|
57
|
+
throw new HashMismatchError(sha256, actualSha, 'ipfs', ownerSafe);
|
|
58
|
+
}
|
|
59
|
+
const ts = now();
|
|
60
|
+
store.saveNetworkArtifact({
|
|
61
|
+
sha256,
|
|
62
|
+
artifactType,
|
|
63
|
+
envelopeCid: envelopeCid ?? null,
|
|
64
|
+
content: bytes,
|
|
65
|
+
source: 'origin',
|
|
66
|
+
sourceOperator: ownerSafe ?? null,
|
|
67
|
+
sourceEndpoint: `ipfs://${ipfsSource.cid}`,
|
|
68
|
+
paidAmountUsdc: '0',
|
|
69
|
+
fetchedAt: ts,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
sha256,
|
|
73
|
+
bytes,
|
|
74
|
+
artifactType,
|
|
75
|
+
source: 'ipfs',
|
|
76
|
+
paidAmountUsdc: '0',
|
|
77
|
+
fetchedAt: ts,
|
|
78
|
+
sourceOperator: ownerSafe,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err instanceof HashMismatchError)
|
|
83
|
+
throw err;
|
|
84
|
+
// Donated IPFS is an opportunistic fast path. Gateway failures and
|
|
85
|
+
// malformed donation payloads should not block the acquisition chain;
|
|
86
|
+
// only verified byte hash mismatches remain fatal.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 3. Self-store fast path
|
|
90
|
+
if (ownerSafe && ownerSafe.toLowerCase() === selfSafeAddress.toLowerCase()) {
|
|
91
|
+
const own = store.getServedArtifact(sha256);
|
|
92
|
+
if (own) {
|
|
93
|
+
// Re-verify before mirroring. If served_artifacts has been corrupted
|
|
94
|
+
// (disk error, manual edit, future migration bug, etc.) we must NOT
|
|
95
|
+
// propagate the bad bytes into network_artifacts where peers can
|
|
96
|
+
// fetch them. Throwing closes the cache-poisoning gap; the self-store
|
|
97
|
+
// path is now hash-equivalent to origin / route-resolver.
|
|
98
|
+
const actualSha = sha256Hex(own.content);
|
|
99
|
+
if (actualSha !== sha256) {
|
|
100
|
+
throw new HashMismatchError(sha256, actualSha, 'self-store', selfSafeAddress);
|
|
101
|
+
}
|
|
102
|
+
// Mirror into cache so peer asks for the same content can hit cache (provenance: self-store-mirror).
|
|
103
|
+
const ts = now();
|
|
104
|
+
store.saveNetworkArtifact({
|
|
105
|
+
sha256,
|
|
106
|
+
artifactType: own.artifactType,
|
|
107
|
+
envelopeCid: own.envelopeCid,
|
|
108
|
+
content: own.content,
|
|
109
|
+
source: 'self-store-mirror',
|
|
110
|
+
paidAmountUsdc: '0',
|
|
111
|
+
fetchedAt: ts,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
sha256,
|
|
115
|
+
bytes: own.content,
|
|
116
|
+
artifactType: own.artifactType,
|
|
117
|
+
source: 'self-store',
|
|
118
|
+
paidAmountUsdc: '0',
|
|
119
|
+
fetchedAt: ts,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// 4. Route resolver
|
|
124
|
+
if (routeResolver) {
|
|
125
|
+
try {
|
|
126
|
+
const out = await routeResolver.resolve({ sha256, access, requesterSafe: selfSafeAddress });
|
|
127
|
+
if (out) {
|
|
128
|
+
const actualSha = sha256Hex(out.bytes);
|
|
129
|
+
if (actualSha !== sha256) {
|
|
130
|
+
throw new HashMismatchError(sha256, actualSha, 'route-resolver', out.sourceOperator);
|
|
131
|
+
}
|
|
132
|
+
const ts = now();
|
|
133
|
+
store.saveNetworkArtifact({
|
|
134
|
+
sha256,
|
|
135
|
+
artifactType,
|
|
136
|
+
envelopeCid: envelopeCid ?? null,
|
|
137
|
+
content: out.bytes,
|
|
138
|
+
source: 'route-resolver',
|
|
139
|
+
sourceOperator: out.sourceOperator ?? null,
|
|
140
|
+
paidAmountUsdc: out.pricePaidUsdc,
|
|
141
|
+
fetchedAt: ts,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
sha256,
|
|
145
|
+
bytes: out.bytes,
|
|
146
|
+
artifactType,
|
|
147
|
+
source: 'route-resolver',
|
|
148
|
+
paidAmountUsdc: out.pricePaidUsdc,
|
|
149
|
+
fetchedAt: ts,
|
|
150
|
+
sourceOperator: out.sourceOperator,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
if (err instanceof HashMismatchError)
|
|
156
|
+
throw err;
|
|
157
|
+
throw new AcquireError(sha256, 'routeResolver failed', err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// 5. Origin fetch
|
|
161
|
+
let bytes;
|
|
162
|
+
try {
|
|
163
|
+
const raw = await acquireFn(access.endpoint, sha256, privateKey);
|
|
164
|
+
// Accept both the legacy `Buffer | null` shape and the discriminated
|
|
165
|
+
// union from fetchArtifactContent. Buffer.isBuffer guards before we
|
|
166
|
+
// duck-type into the union.
|
|
167
|
+
if (raw === null || Buffer.isBuffer(raw)) {
|
|
168
|
+
bytes = raw;
|
|
169
|
+
}
|
|
170
|
+
else if (raw.ok === true) {
|
|
171
|
+
bytes = raw.content;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// ok: false — preserve the reason so the caller / daemon route can
|
|
175
|
+
// surface it via AcquireError.cause.
|
|
176
|
+
throw new AcquireError(sha256, `origin fetch failed: ${raw.reason}${raw.message ? ` (${raw.message})` : ''}`, raw);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (err instanceof AcquireError)
|
|
181
|
+
throw err;
|
|
182
|
+
throw new AcquireError(sha256, 'origin fetch failed', err);
|
|
183
|
+
}
|
|
184
|
+
if (!bytes) {
|
|
185
|
+
throw new AcquireError(sha256, 'origin returned null (404 / not found)');
|
|
186
|
+
}
|
|
187
|
+
const actualSha = sha256Hex(bytes);
|
|
188
|
+
if (actualSha !== sha256) {
|
|
189
|
+
throw new HashMismatchError(sha256, actualSha, 'origin', ownerSafe);
|
|
190
|
+
}
|
|
191
|
+
const ts = now();
|
|
192
|
+
store.saveNetworkArtifact({
|
|
193
|
+
sha256,
|
|
194
|
+
artifactType,
|
|
195
|
+
envelopeCid: envelopeCid ?? null,
|
|
196
|
+
content: bytes,
|
|
197
|
+
source: 'origin',
|
|
198
|
+
sourceOperator: ownerSafe ?? null,
|
|
199
|
+
sourceEndpoint: access.endpoint,
|
|
200
|
+
paidAmountUsdc: access.priceUsdc,
|
|
201
|
+
fetchedAt: ts,
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
sha256,
|
|
205
|
+
bytes,
|
|
206
|
+
artifactType,
|
|
207
|
+
source: 'origin',
|
|
208
|
+
paidAmountUsdc: access.priceUsdc,
|
|
209
|
+
fetchedAt: ts,
|
|
210
|
+
sourceOperator: ownerSafe,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus cache helpers.
|
|
3
|
+
*
|
|
4
|
+
* The corpus library reuses the existing `network_artifacts` and `served_artifacts`
|
|
5
|
+
* tables on the daemon's `Store`. There is no separate corpus cache layer — the
|
|
6
|
+
* acquire chain in `acquire.ts` is the only writer/reader. This module exposes
|
|
7
|
+
* thin convenience helpers for higher-level code that wants to inspect or
|
|
8
|
+
* pre-populate the cache without going through `acquireArtifactContent`.
|
|
9
|
+
*
|
|
10
|
+
* Spec: spec/2026-04-30-phase-a-umbrella.md §2.4 (caching semantics).
|
|
11
|
+
*/
|
|
12
|
+
import type { CorpusStorePort, NetworkArtifactRow } from './types.js';
|
|
13
|
+
export declare function getCachedArtifact(store: CorpusStorePort, sha256: string): NetworkArtifactRow | null;
|
|
14
|
+
export declare function hasCachedArtifact(store: CorpusStorePort, sha256: string): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus cache helpers.
|
|
3
|
+
*
|
|
4
|
+
* The corpus library reuses the existing `network_artifacts` and `served_artifacts`
|
|
5
|
+
* tables on the daemon's `Store`. There is no separate corpus cache layer — the
|
|
6
|
+
* acquire chain in `acquire.ts` is the only writer/reader. This module exposes
|
|
7
|
+
* thin convenience helpers for higher-level code that wants to inspect or
|
|
8
|
+
* pre-populate the cache without going through `acquireArtifactContent`.
|
|
9
|
+
*
|
|
10
|
+
* Spec: spec/2026-04-30-phase-a-umbrella.md §2.4 (caching semantics).
|
|
11
|
+
*/
|
|
12
|
+
export function getCachedArtifact(store, sha256) {
|
|
13
|
+
return store.getNetworkArtifact(sha256);
|
|
14
|
+
}
|
|
15
|
+
export function hasCachedArtifact(store, sha256) {
|
|
16
|
+
return store.getNetworkArtifact(sha256) !== null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CaptureMetaHit {
|
|
2
|
+
manifestCid: string;
|
|
3
|
+
taskSummary: string;
|
|
4
|
+
tags: string[];
|
|
5
|
+
repositorySlug?: string;
|
|
6
|
+
synthesis?: string;
|
|
7
|
+
retrievalVisible?: boolean;
|
|
8
|
+
provenance: string;
|
|
9
|
+
verifiabilityTier: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function queryCaptureMeta({ url, query, limit, fetchImpl, }: {
|
|
12
|
+
url: string;
|
|
13
|
+
query: string;
|
|
14
|
+
limit: number;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
}): Promise<CaptureMetaHit[]>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function queryCaptureMeta({ url, query, limit, fetchImpl = globalThis.fetch, }) {
|
|
2
|
+
if (!url || query === '')
|
|
3
|
+
return [];
|
|
4
|
+
try {
|
|
5
|
+
const response = await fetchImpl(`${url}?q=${encodeURIComponent(query)}&limit=${limit}`);
|
|
6
|
+
if (!response.ok)
|
|
7
|
+
return [];
|
|
8
|
+
const body = (await response.json());
|
|
9
|
+
if (!Array.isArray(body))
|
|
10
|
+
return [];
|
|
11
|
+
return body.filter((hit) => hit !== null
|
|
12
|
+
&& typeof hit === 'object'
|
|
13
|
+
&& typeof hit.manifestCid === 'string');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { acquireArtifactContent } from './acquire.js';
|
|
2
|
+
import { fetchManifest } from './fetch.js';
|
|
3
|
+
export function createCorpus(options, deps = {}) {
|
|
4
|
+
async function query(query) {
|
|
5
|
+
let refs;
|
|
6
|
+
if (options.discovery) {
|
|
7
|
+
try {
|
|
8
|
+
refs = await options.discovery.queryEnvelopes(query);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw new Error(`corpus query failed (discovery): ${error instanceof Error ? error.message : String(error)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else if (options.legacyQuery) {
|
|
15
|
+
try {
|
|
16
|
+
refs = await options.legacyQuery(query);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`corpus query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
refs = [];
|
|
24
|
+
}
|
|
25
|
+
const deduped = new Map();
|
|
26
|
+
for (const ref of refs) {
|
|
27
|
+
if (!deduped.has(ref.manifestCid))
|
|
28
|
+
deduped.set(ref.manifestCid, ref);
|
|
29
|
+
}
|
|
30
|
+
return [...deduped.values()];
|
|
31
|
+
}
|
|
32
|
+
function fetchOne(ref) {
|
|
33
|
+
return fetchManifest(ref, options.ipfsGatewayUrl, {
|
|
34
|
+
...(deps.fetchFromIpfs ? { fetchFromIpfs: deps.fetchFromIpfs } : {}),
|
|
35
|
+
parseEnvelope: options.parseEnvelope,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function acquire(manifest) {
|
|
39
|
+
const contents = new Map();
|
|
40
|
+
for (const artifact of manifest.envelope.artifacts) {
|
|
41
|
+
const content = await acquireArtifactContent({
|
|
42
|
+
sha256: artifact.sha256,
|
|
43
|
+
artifactType: artifact.artifactType,
|
|
44
|
+
access: artifact.access,
|
|
45
|
+
store: options.store,
|
|
46
|
+
selfSafeAddress: options.selfSafeAddress,
|
|
47
|
+
privateKey: options.signer.privateKey,
|
|
48
|
+
routeResolver: options.routeResolver,
|
|
49
|
+
envelopeCid: manifest.ref.manifestCid,
|
|
50
|
+
sources: artifact.sources,
|
|
51
|
+
ipfsGatewayUrl: options.ipfsGatewayUrl,
|
|
52
|
+
ownerSafe: manifest.envelope.participant.safeAddress,
|
|
53
|
+
acquireFn: deps.acquireFn,
|
|
54
|
+
fetchFromIpfs: deps.fetchFromIpfs,
|
|
55
|
+
});
|
|
56
|
+
contents.set(artifact.sha256, content);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ref: manifest.ref,
|
|
60
|
+
envelope: manifest.envelope,
|
|
61
|
+
artifactContents: contents,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function acquireBySha256(sha256, access, hint = {}) {
|
|
65
|
+
return acquireArtifactContent({
|
|
66
|
+
sha256,
|
|
67
|
+
artifactType: hint.artifactType ?? 'unknown',
|
|
68
|
+
access,
|
|
69
|
+
store: options.store,
|
|
70
|
+
selfSafeAddress: options.selfSafeAddress,
|
|
71
|
+
privateKey: options.signer.privateKey,
|
|
72
|
+
routeResolver: options.routeResolver,
|
|
73
|
+
envelopeCid: hint.envelopeCid,
|
|
74
|
+
sources: hint.sources,
|
|
75
|
+
ipfsGatewayUrl: options.ipfsGatewayUrl,
|
|
76
|
+
ownerSafe: hint.ownerSafe,
|
|
77
|
+
acquireFn: deps.acquireFn,
|
|
78
|
+
fetchFromIpfs: deps.fetchFromIpfs,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function read(args) {
|
|
82
|
+
const refs = await query(args.query);
|
|
83
|
+
const previews = [];
|
|
84
|
+
for (const ref of refs)
|
|
85
|
+
previews.push(await fetchOne(ref));
|
|
86
|
+
const solverType = args.query.solverType;
|
|
87
|
+
const matching = solverType
|
|
88
|
+
? previews.filter((preview) => preview.envelope.solverType === solverType)
|
|
89
|
+
: previews;
|
|
90
|
+
const selected = args.select ? args.select(matching) : matching;
|
|
91
|
+
const envelopes = [];
|
|
92
|
+
for (const preview of selected)
|
|
93
|
+
envelopes.push(await acquire(preview));
|
|
94
|
+
return envelopes;
|
|
95
|
+
}
|
|
96
|
+
return { read, query, fetchManifest: fetchOne, acquire, acquireBySha256 };
|
|
97
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free artifact fetch — pulls remote artifact bytes over plain HTTP.
|
|
3
|
+
*
|
|
4
|
+
* Knowledge is a free, opt-in public good (tokenless-OLAS pivot): artifact
|
|
5
|
+
* acquisition is an unauthenticated GET, no payment wrapper. The server
|
|
6
|
+
* returns raw bytes; the consumer hashes them locally to verify integrity
|
|
7
|
+
* against the envelope's `artifact.sha256` field.
|
|
8
|
+
*
|
|
9
|
+
* Keys artifacts by sha256 (not IPFS CID) — artifacts no longer have IPFS
|
|
10
|
+
* CIDs (post jinn-mono-vy37.1.2).
|
|
11
|
+
*/
|
|
12
|
+
export type AcquireResult = {
|
|
13
|
+
ok: true;
|
|
14
|
+
content: Buffer;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
reason: 'not_found' | 'network_error';
|
|
18
|
+
message?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function buildArtifactUrl(endpoint: string, sha256: string): string;
|
|
21
|
+
export declare function fetchArtifactContent(endpoint: string, sha256: string): Promise<AcquireResult>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free artifact fetch — pulls remote artifact bytes over plain HTTP.
|
|
3
|
+
*
|
|
4
|
+
* Knowledge is a free, opt-in public good (tokenless-OLAS pivot): artifact
|
|
5
|
+
* acquisition is an unauthenticated GET, no payment wrapper. The server
|
|
6
|
+
* returns raw bytes; the consumer hashes them locally to verify integrity
|
|
7
|
+
* against the envelope's `artifact.sha256` field.
|
|
8
|
+
*
|
|
9
|
+
* Keys artifacts by sha256 (not IPFS CID) — artifacts no longer have IPFS
|
|
10
|
+
* CIDs (post jinn-mono-vy37.1.2).
|
|
11
|
+
*/
|
|
12
|
+
export function buildArtifactUrl(endpoint, sha256) {
|
|
13
|
+
return `${endpoint.replace(/\/$/, '')}/v1/artifacts/${sha256}/content`;
|
|
14
|
+
}
|
|
15
|
+
export async function fetchArtifactContent(endpoint, sha256) {
|
|
16
|
+
const url = buildArtifactUrl(endpoint, sha256);
|
|
17
|
+
try {
|
|
18
|
+
const response = await globalThis.fetch(url);
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
if (response.status === 404) {
|
|
21
|
+
return { ok: false, reason: 'not_found', message: `HTTP 404 for ${url}` };
|
|
22
|
+
}
|
|
23
|
+
return { ok: false, reason: 'network_error', message: `HTTP ${response.status} for ${url}` };
|
|
24
|
+
}
|
|
25
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
26
|
+
return { ok: true, content: buf };
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30
|
+
return { ok: false, reason: 'network_error', message };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch a manifest envelope from IPFS by CID and parse it under the v1 schema.
|
|
3
|
+
*
|
|
4
|
+
* Spec §2.3 step 2.
|
|
5
|
+
*/
|
|
6
|
+
import type { CorpusManifest, EnvelopeRef, ManifestPreview } from './types.js';
|
|
7
|
+
type FetchFromIpfs = (gatewayUrl: string, cid: string) => Promise<unknown>;
|
|
8
|
+
export declare function fetchManifest<TEnvelope extends CorpusManifest>(ref: EnvelopeRef, ipfsGatewayUrl: string, ports: {
|
|
9
|
+
fetchFromIpfs?: FetchFromIpfs;
|
|
10
|
+
parseEnvelope(input: unknown): TEnvelope;
|
|
11
|
+
}): Promise<ManifestPreview<TEnvelope>>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch a manifest envelope from IPFS by CID and parse it under the v1 schema.
|
|
3
|
+
*
|
|
4
|
+
* Spec §2.3 step 2.
|
|
5
|
+
*/
|
|
6
|
+
import { fetchFromIpfs } from './ipfs.js';
|
|
7
|
+
import { ManifestFetchError } from './types.js';
|
|
8
|
+
export async function fetchManifest(ref, ipfsGatewayUrl, ports) {
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = await (ports.fetchFromIpfs ?? fetchFromIpfs)(ipfsGatewayUrl, ref.manifestCid);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new ManifestFetchError(ref.manifestCid, 'IPFS fetch failed', err);
|
|
15
|
+
}
|
|
16
|
+
let envelope;
|
|
17
|
+
try {
|
|
18
|
+
envelope = ports.parseEnvelope(raw);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new ManifestFetchError(ref.manifestCid, `schema parse failed: ${error instanceof Error ? error.message : String(error)}`, error);
|
|
22
|
+
}
|
|
23
|
+
return { ref, envelope };
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CorpusDiscoveryPort } from './types.js';
|
|
2
|
+
export interface HttpCorpusDiscoveryOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
retryDelaysMs?: readonly number[];
|
|
6
|
+
fetchTimeoutMs?: number;
|
|
7
|
+
readyProbeTtlMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function createHttpCorpusDiscovery(options: HttpCorpusDiscoveryOptions): CorpusDiscoveryPort;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { DiscoveryUnavailableError } from './types.js';
|
|
2
|
+
const QUERY_ENVELOPES = `
|
|
3
|
+
query QueryEnvelopes($where: envelopeFilter, $limit: Int!) {
|
|
4
|
+
envelopes(
|
|
5
|
+
where: $where,
|
|
6
|
+
limit: $limit,
|
|
7
|
+
orderBy: "publishedAtBlock",
|
|
8
|
+
orderDirection: "desc"
|
|
9
|
+
) {
|
|
10
|
+
items {
|
|
11
|
+
agentId
|
|
12
|
+
manifestCid
|
|
13
|
+
manifestHash
|
|
14
|
+
evidenceTier
|
|
15
|
+
publishedAtBlock
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
const DEFAULT_RETRY_DELAYS_MS = [200, 500];
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
22
|
+
const DEFAULT_READY_TTL_MS = 20_000;
|
|
23
|
+
function wait(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
export function createHttpCorpusDiscovery(options) {
|
|
27
|
+
const gqlUrl = options.url.endsWith('/graphql')
|
|
28
|
+
? options.url
|
|
29
|
+
: `${options.url.replace(/\/$/, '')}/graphql`;
|
|
30
|
+
const readyUrl = `${options.url.replace(/\/graphql\/?$/, '').replace(/\/$/, '')}/ready`;
|
|
31
|
+
const baseFetch = options.fetchImpl ?? globalThis.fetch;
|
|
32
|
+
const retryDelays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
33
|
+
const timeoutMs = options.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
34
|
+
const readyTtlMs = options.readyProbeTtlMs ?? DEFAULT_READY_TTL_MS;
|
|
35
|
+
let readyCache;
|
|
36
|
+
async function request(input, init) {
|
|
37
|
+
let lastError;
|
|
38
|
+
for (let attempt = 0; attempt <= retryDelays.length; attempt += 1) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await baseFetch(input, {
|
|
41
|
+
...init,
|
|
42
|
+
signal: init.signal ?? AbortSignal.timeout(timeoutMs),
|
|
43
|
+
});
|
|
44
|
+
if ((response.status === 502 || response.status === 503)
|
|
45
|
+
&& attempt < retryDelays.length) {
|
|
46
|
+
await wait(retryDelays[attempt]);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
if (attempt >= retryDelays.length)
|
|
54
|
+
break;
|
|
55
|
+
await wait(retryDelays[attempt]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new DiscoveryUnavailableError(`Ponder GraphQL network error: ${String(lastError)}`, lastError);
|
|
59
|
+
}
|
|
60
|
+
async function ensureReady() {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
if (readyCache && now - readyCache.checkedAt < readyTtlMs) {
|
|
63
|
+
if (!readyCache.ok) {
|
|
64
|
+
throw new DiscoveryUnavailableError(`indexer not ready: ${readyCache.status}`);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let response;
|
|
69
|
+
try {
|
|
70
|
+
response = await request(readyUrl, { method: 'GET' });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
readyCache = { checkedAt: now, ok: false, status: 0 };
|
|
74
|
+
const cause = error instanceof DiscoveryUnavailableError
|
|
75
|
+
? (error.cause ?? error)
|
|
76
|
+
: error;
|
|
77
|
+
throw new DiscoveryUnavailableError(`indexer /ready probe failed: ${String(cause)}`, cause);
|
|
78
|
+
}
|
|
79
|
+
readyCache = { checkedAt: now, ok: response.ok, status: response.status };
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new DiscoveryUnavailableError(`indexer not ready: ${response.status}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
async queryEnvelopes(query) {
|
|
86
|
+
await ensureReady();
|
|
87
|
+
const where = {};
|
|
88
|
+
if (query.evidenceTier)
|
|
89
|
+
where['evidenceTier'] = query.evidenceTier;
|
|
90
|
+
if (query.manifestHash)
|
|
91
|
+
where['manifestHash'] = query.manifestHash;
|
|
92
|
+
const response = await request(gqlUrl, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { 'content-type': 'application/json' },
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
query: QUERY_ENVELOPES,
|
|
97
|
+
variables: {
|
|
98
|
+
where,
|
|
99
|
+
limit: Math.min(500, Math.max(1, query.limit ?? 50)),
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new DiscoveryUnavailableError(`Ponder GraphQL HTTP ${response.status} ${response.statusText}`);
|
|
105
|
+
}
|
|
106
|
+
let body;
|
|
107
|
+
try {
|
|
108
|
+
body = await response.json();
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new DiscoveryUnavailableError(`Ponder GraphQL response parse error: ${String(error)}`, error);
|
|
112
|
+
}
|
|
113
|
+
if (body.errors?.length) {
|
|
114
|
+
throw new DiscoveryUnavailableError(`Ponder GraphQL error: ${body.errors.map((error) => error.message ?? 'unknown').join('; ')}`);
|
|
115
|
+
}
|
|
116
|
+
if (!body.data) {
|
|
117
|
+
throw new DiscoveryUnavailableError('Ponder GraphQL response missing data field');
|
|
118
|
+
}
|
|
119
|
+
return (body.data.envelopes?.items ?? []).map((row) => ({
|
|
120
|
+
manifestCid: row.manifestCid,
|
|
121
|
+
manifestHash: row.manifestHash,
|
|
122
|
+
operator: { agentId: row.agentId, safeAddress: '' },
|
|
123
|
+
evidenceTier: row.evidenceTier ?? 'unknown',
|
|
124
|
+
publishedAt: Number(row.publishedAtBlock),
|
|
125
|
+
}));
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './acquire.js';
|
|
2
|
+
export * from './cache.js';
|
|
3
|
+
export * from './capture-meta.js';
|
|
4
|
+
export * from './create-corpus.js';
|
|
5
|
+
export * from './fetch-artifact.js';
|
|
6
|
+
export * from './fetch.js';
|
|
7
|
+
export * from './http-discovery.js';
|
|
8
|
+
export * from './ipfs.js';
|
|
9
|
+
export * from './route-resolver.js';
|
|
10
|
+
export * from './types.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './acquire.js';
|
|
2
|
+
export * from './cache.js';
|
|
3
|
+
export * from './capture-meta.js';
|
|
4
|
+
export * from './create-corpus.js';
|
|
5
|
+
export * from './fetch-artifact.js';
|
|
6
|
+
export * from './fetch.js';
|
|
7
|
+
export * from './http-discovery.js';
|
|
8
|
+
export * from './ipfs.js';
|
|
9
|
+
export * from './route-resolver.js';
|
|
10
|
+
export * from './types.js';
|