@jinn-network/jinn-layer 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.
Files changed (131) hide show
  1. package/dist/adapters/contribution-adapter.d.ts +18 -0
  2. package/dist/adapters/contribution-adapter.js +134 -0
  3. package/dist/adapters/corpus-adapter.d.ts +12 -0
  4. package/dist/adapters/corpus-adapter.js +212 -0
  5. package/dist/adapters/index.d.ts +17 -0
  6. package/dist/adapters/index.js +17 -0
  7. package/dist/adapters/json-map-store.d.ts +4 -0
  8. package/dist/adapters/json-map-store.js +31 -0
  9. package/dist/adapters/local-learning-adapter.d.ts +25 -0
  10. package/dist/adapters/local-learning-adapter.js +49 -0
  11. package/dist/adapters/skills-adapter.d.ts +8 -0
  12. package/dist/adapters/skills-adapter.js +55 -0
  13. package/dist/admission-policy.d.ts +38 -0
  14. package/dist/admission-policy.js +17 -0
  15. package/dist/bin/jinn-distill-mcp.d.ts +2 -0
  16. package/dist/bin/jinn-distill-mcp.js +10 -0
  17. package/dist/bin/jinn-layer.d.ts +7 -0
  18. package/dist/bin/jinn-layer.js +15 -0
  19. package/dist/bridge-fetch-evidence.d.ts +102 -0
  20. package/dist/bridge-fetch-evidence.js +677 -0
  21. package/dist/bridge-verdict-source.d.ts +47 -0
  22. package/dist/bridge-verdict-source.js +216 -0
  23. package/dist/bridge.d.ts +247 -0
  24. package/dist/bridge.js +439 -0
  25. package/dist/capture.d.ts +98 -0
  26. package/dist/capture.js +617 -0
  27. package/dist/cli.d.ts +148 -0
  28. package/dist/cli.js +2221 -0
  29. package/dist/cluster-selection.d.ts +34 -0
  30. package/dist/cluster-selection.js +135 -0
  31. package/dist/cluster.d.ts +89 -0
  32. package/dist/cluster.js +142 -0
  33. package/dist/consume.d.ts +139 -0
  34. package/dist/consume.js +280 -0
  35. package/dist/corpus-probes.d.ts +61 -0
  36. package/dist/corpus-probes.js +120 -0
  37. package/dist/corpus-store.d.ts +16 -0
  38. package/dist/corpus-store.js +101 -0
  39. package/dist/distill-captures.d.ts +56 -0
  40. package/dist/distill-captures.js +283 -0
  41. package/dist/distill-feedback.d.ts +20 -0
  42. package/dist/distill-feedback.js +30 -0
  43. package/dist/distill-llm.d.ts +138 -0
  44. package/dist/distill-llm.js +438 -0
  45. package/dist/distill-mcp-server.d.ts +30 -0
  46. package/dist/distill-mcp-server.js +281 -0
  47. package/dist/distill-mode.d.ts +62 -0
  48. package/dist/distill-mode.js +107 -0
  49. package/dist/distill-progress.d.ts +78 -0
  50. package/dist/distill-progress.js +69 -0
  51. package/dist/distill-prompt.d.ts +34 -0
  52. package/dist/distill-prompt.js +85 -0
  53. package/dist/distill-render.d.ts +123 -0
  54. package/dist/distill-render.js +303 -0
  55. package/dist/distill-runs.d.ts +28 -0
  56. package/dist/distill-runs.js +39 -0
  57. package/dist/distill-traces.d.ts +67 -0
  58. package/dist/distill-traces.js +211 -0
  59. package/dist/distill.d.ts +180 -0
  60. package/dist/distill.js +337 -0
  61. package/dist/distiller.d.ts +118 -0
  62. package/dist/distiller.js +142 -0
  63. package/dist/envelope.d.ts +9 -0
  64. package/dist/envelope.js +9 -0
  65. package/dist/eval-prep.d.ts +129 -0
  66. package/dist/eval-prep.js +523 -0
  67. package/dist/execution-publish.d.ts +62 -0
  68. package/dist/execution-publish.js +72 -0
  69. package/dist/gate.d.ts +39 -0
  70. package/dist/gate.js +106 -0
  71. package/dist/index.d.ts +49 -0
  72. package/dist/index.js +50 -0
  73. package/dist/ipfs-cid.d.ts +18 -0
  74. package/dist/ipfs-cid.js +149 -0
  75. package/dist/layer-default.d.ts +3 -0
  76. package/dist/layer-default.js +10 -0
  77. package/dist/ledger.d.ts +62 -0
  78. package/dist/ledger.js +104 -0
  79. package/dist/measurement.d.ts +90 -0
  80. package/dist/measurement.js +134 -0
  81. package/dist/path-safety.d.ts +19 -0
  82. package/dist/path-safety.js +303 -0
  83. package/dist/pipeline.d.ts +94 -0
  84. package/dist/pipeline.js +124 -0
  85. package/dist/plugin-wiring.d.ts +4 -0
  86. package/dist/plugin-wiring.js +54 -0
  87. package/dist/preview.d.ts +31 -0
  88. package/dist/preview.js +28 -0
  89. package/dist/process-contract.d.ts +303 -0
  90. package/dist/process-contract.js +118 -0
  91. package/dist/publish-skill.d.ts +86 -0
  92. package/dist/publish-skill.js +116 -0
  93. package/dist/publish.d.ts +294 -0
  94. package/dist/publish.js +1090 -0
  95. package/dist/scripts/scan-skills-registry.d.ts +24 -0
  96. package/dist/scripts/scan-skills-registry.js +207 -0
  97. package/dist/seed-import/curated-batch.d.ts +36 -0
  98. package/dist/seed-import/curated-batch.js +103 -0
  99. package/dist/seed-import/episode-execute.d.ts +50 -0
  100. package/dist/seed-import/episode-execute.js +235 -0
  101. package/dist/seed-import/episode-fetch.d.ts +119 -0
  102. package/dist/seed-import/episode-fetch.js +137 -0
  103. package/dist/seed-import/episode-plan.d.ts +11 -0
  104. package/dist/seed-import/episode-plan.js +27 -0
  105. package/dist/seed-import/episode-report.d.ts +39 -0
  106. package/dist/seed-import/episode-report.js +36 -0
  107. package/dist/seed-import/execute.d.ts +61 -0
  108. package/dist/seed-import/execute.js +245 -0
  109. package/dist/seed-import/fetch.d.ts +62 -0
  110. package/dist/seed-import/fetch.js +82 -0
  111. package/dist/seed-import/licence.d.ts +17 -0
  112. package/dist/seed-import/licence.js +36 -0
  113. package/dist/seed-import/plan.d.ts +10 -0
  114. package/dist/seed-import/plan.js +21 -0
  115. package/dist/seed-import/report.d.ts +34 -0
  116. package/dist/seed-import/report.js +31 -0
  117. package/dist/seed-import/state.d.ts +72 -0
  118. package/dist/seed-import/state.js +120 -0
  119. package/dist/signal.d.ts +36 -0
  120. package/dist/signal.js +48 -0
  121. package/dist/signing.d.ts +8 -0
  122. package/dist/signing.js +21 -0
  123. package/dist/skill-package.d.ts +70 -0
  124. package/dist/skill-package.js +141 -0
  125. package/dist/skill.d.ts +21 -0
  126. package/dist/skill.js +77 -0
  127. package/dist/snapshot-transcript.d.ts +54 -0
  128. package/dist/snapshot-transcript.js +117 -0
  129. package/dist/three-arm-run.d.ts +27 -0
  130. package/dist/three-arm-run.js +41 -0
  131. package/package.json +60 -0
@@ -0,0 +1,677 @@
1
+ /**
2
+ * The bridge's `fetchEvidence` adapter (spec/2026-07-06-distillation-v1.md §8, D5).
3
+ *
4
+ * Implements the VERIFIED verdict→solution join that reaches
5
+ * the SOLVER's coding patch — the reference implementation is `fetchEvidence` in
6
+ * client/scripts/distill-run-live.ts:
7
+ *
8
+ * verdict(ref.requestId, ref.chainId) → authoritative (taskId, attemptIndex, evaluator)
9
+ * + bounded verdictEnvelopeMeta candidates → historical publisher,
10
+ * participant, signed-hash, request, and polarity binding
11
+ * → authenticated evaluation wrapper → signed original task CID
12
+ * → TaskCreated task-CID / creator / manifest commitments
13
+ * → authenticated original task (description + verifier facts)
14
+ * → attempt(taskId, attemptIndex, chainId) → the SOLVE request
15
+ * → bounded attemptEnvelopeMeta candidates → publisher + signed-hash binding
16
+ * → authenticated solution envelope (IPFS) → .payload.patch (the coding diff)
17
+ * problem statement = authenticated original task description
18
+ * typed history (hop 4, best-effort, #1472/#1473) = the solution's system_snapshot
19
+ * artifact → donation unwrap → gunzip+untar → the harness stdout
20
+ * transcript → canonical core stream parser → typed trajectory spans
21
+ *
22
+ * The predecessor selected the solution with the task document's mutable
23
+ * `restorationRequestId`. That allowed an otherwise-valid signed task to be
24
+ * relabelled onto another solution. The selection path now comes exclusively
25
+ * from chain-indexed verdict + attempt rows. Any outer task copy is only a
26
+ * consistency assertion and can never select the patch.
27
+ *
28
+ * All I/O and authentication boundaries are injected ports so this stays
29
+ * unit-testable. Production uses the Autonolas gateway, Ponder GraphQL,
30
+ * canonical envelope verification, and an IdentityRegistry wallet read; tests
31
+ * inject fakes. The returned function is directly usable as
32
+ * `BridgeDeps.fetchEvidence`.
33
+ */
34
+ import { extractSnapshotTranscript, parseSolveTranscript, } from './snapshot-transcript.js';
35
+ /** Cap the fetched patch / problem so one huge diff does not blow the prompt. */
36
+ const MAX_PATCH_CHARS = 6000;
37
+ const MAX_PROBLEM_CHARS = 1500;
38
+ function nonEmptyString(value) {
39
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
40
+ }
41
+ function stringArray(value) {
42
+ if (!Array.isArray(value))
43
+ return undefined;
44
+ const strings = value.filter((item) => typeof item === 'string' && item.length > 0);
45
+ return strings.length === value.length ? strings : undefined;
46
+ }
47
+ function generatorModel(value) {
48
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
49
+ return undefined;
50
+ const raw = value;
51
+ const id = nonEmptyString(raw['id']);
52
+ const source = raw['source'];
53
+ if (!id || (source !== 'stream' && source !== 'config'))
54
+ return undefined;
55
+ const provider = nonEmptyString(raw['provider']);
56
+ const openWeights = typeof raw['openWeights'] === 'boolean'
57
+ ? raw['openWeights']
58
+ : undefined;
59
+ return {
60
+ id,
61
+ source,
62
+ ...(provider ? { provider } : {}),
63
+ ...(openWeights !== undefined ? { openWeights } : {}),
64
+ };
65
+ }
66
+ function record(value) {
67
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
68
+ ? value
69
+ : undefined;
70
+ }
71
+ function exactAddress(value, label) {
72
+ if (typeof value !== 'string' || !/^0x[0-9a-fA-F]{40}$/.test(value)) {
73
+ throw new Error(`${label} must be an exact 20-byte address`);
74
+ }
75
+ return value.toLowerCase();
76
+ }
77
+ function exactHash(value, label) {
78
+ if (typeof value !== 'string' || !/^0x[0-9a-fA-F]{64}$/.test(value)) {
79
+ throw new Error(`${label} must be an exact bytes32 hash`);
80
+ }
81
+ return value.toLowerCase();
82
+ }
83
+ function exactAgentId(value, label) {
84
+ if (typeof value !== 'string' || !/^[1-9][0-9]*$/.test(value)) {
85
+ throw new Error(`${label} must be a positive decimal agent id`);
86
+ }
87
+ return value;
88
+ }
89
+ function exactBlockNumber(value, label) {
90
+ if (typeof value !== 'string' || !/^[1-9][0-9]*$/.test(value)) {
91
+ throw new Error(`${label} must be a positive decimal block number`);
92
+ }
93
+ return BigInt(value);
94
+ }
95
+ function assertAuthenticatedEnvelopeKind(envelope, sourceName, expectedRole) {
96
+ if (envelope.role !== expectedRole) {
97
+ throw new Error(`${sourceName}.role must be exactly ${expectedRole}`);
98
+ }
99
+ if (envelope.solverType === undefined) {
100
+ throw new Error(`${sourceName}.solverType requires exact authenticated task provenance`);
101
+ }
102
+ if (envelope.solverType !== 'swe-rebench-v2.v1') {
103
+ throw new Error(`${sourceName}.solverType must be exactly swe-rebench-v2.v1`);
104
+ }
105
+ }
106
+ async function assertEnvelopeAuthority(args) {
107
+ const participantSafe = exactAddress(record(args.envelope['participant'])?.['safeAddress'], `${args.sourceName} participant.safeAddress`);
108
+ if (participantSafe !== args.expectedSafe) {
109
+ throw new Error(`${args.sourceName} participant Safe does not match on-chain ${args.onchainRole}`);
110
+ }
111
+ const publisherSafe = exactAddress(await args.resolvePublisherSafe(args.chainId, args.publisherAgentId, args.publishedAtBlock), `${args.sourceName} publisher Safe`);
112
+ if (publisherSafe !== args.expectedSafe) {
113
+ throw new Error(`${args.sourceName} publisher does not match on-chain ${args.onchainRole}`);
114
+ }
115
+ const signedHash = exactHash(record(args.envelope['signature'])?.['hash'], `${args.sourceName} signature.hash`);
116
+ if (signedHash !== args.manifestHash) {
117
+ throw new Error(`${args.sourceName} signature hash does not match its MetadataSet manifest hash`);
118
+ }
119
+ }
120
+ function hasExactSweTaskType(taskDoc) {
121
+ const outer = record(taskDoc);
122
+ const task = record(outer?.['signedTask']) ?? outer;
123
+ return task?.['solverType'] === 'swe-rebench-v2.v1';
124
+ }
125
+ function validateVerifierFacts(value, instanceId) {
126
+ const facts = record(value);
127
+ if (!facts) {
128
+ throw new Error(`authenticated verifier facts for ${instanceId} must be an object`);
129
+ }
130
+ const failToPass = stringArray(facts['failToPass']);
131
+ const passToPass = stringArray(facts['passToPass']);
132
+ const evalSemanticsVersion = nonEmptyString(facts['evalSemanticsVersion']);
133
+ if (!failToPass || !passToPass || !evalSemanticsVersion) {
134
+ throw new Error(`authenticated verifier facts for ${instanceId} require exact failToPass, passToPass, and evalSemanticsVersion`);
135
+ }
136
+ const rawTask = record(facts['task']);
137
+ const solverType = rawTask?.['solverType'];
138
+ const authenticatedInstanceId = nonEmptyString(rawTask?.['instanceId']);
139
+ const repo = nonEmptyString(rawTask?.['repo']);
140
+ const baseCommit = nonEmptyString(rawTask?.['baseCommit']);
141
+ const createdAt = rawTask?.['createdAt'];
142
+ const originalTaskCid = nonEmptyString(rawTask?.['originalTaskCid']);
143
+ const creatorSafe = exactAddress(rawTask?.['creatorSafe'], `authenticated verifier facts for ${instanceId} task.creatorSafe`);
144
+ const manifestDigest = exactHash(rawTask?.['manifestDigest'], `authenticated verifier facts for ${instanceId} task.manifestDigest`);
145
+ const description = nonEmptyString(rawTask?.['description']);
146
+ if (solverType !== 'swe-rebench-v2.v1'
147
+ || !authenticatedInstanceId
148
+ || !repo
149
+ || !baseCommit
150
+ || !originalTaskCid
151
+ || !description
152
+ || !Number.isInteger(createdAt)
153
+ || createdAt < 0) {
154
+ throw new Error(`authenticated verifier facts for ${instanceId} require an exact task provenance tuple`);
155
+ }
156
+ return {
157
+ failToPass,
158
+ passToPass,
159
+ evalSemanticsVersion,
160
+ task: {
161
+ solverType,
162
+ instanceId: authenticatedInstanceId,
163
+ repo,
164
+ baseCommit,
165
+ createdAt: createdAt,
166
+ originalTaskCid,
167
+ creatorSafe,
168
+ manifestDigest,
169
+ description,
170
+ },
171
+ };
172
+ }
173
+ function assertExactEnvelopeTaskProvenance(sourceName, value, authenticated) {
174
+ const envelope = record(value);
175
+ if (!envelope) {
176
+ throw new Error(`${sourceName} requires exact authenticated task provenance`);
177
+ }
178
+ const solverType = envelope['solverType'];
179
+ if (solverType === undefined) {
180
+ throw new Error(`${sourceName}.solverType requires exact authenticated task provenance`);
181
+ }
182
+ if (solverType !== authenticated.solverType) {
183
+ throw new Error(`authenticated task provenance mismatch at ${sourceName}.solverType: `
184
+ + `expected ${JSON.stringify(authenticated.solverType)}, got ${JSON.stringify(solverType)}`);
185
+ }
186
+ const source = record(envelope['task']);
187
+ if (!source) {
188
+ throw new Error(`${sourceName}.task requires exact authenticated task provenance`);
189
+ }
190
+ for (const field of [
191
+ 'instanceId',
192
+ 'repo',
193
+ 'baseCommit',
194
+ ]) {
195
+ const candidate = source[field];
196
+ if (candidate !== undefined && candidate !== authenticated[field]) {
197
+ throw new Error(`authenticated task provenance mismatch at ${sourceName}.${field}: `
198
+ + `expected ${JSON.stringify(authenticated[field])}, got ${JSON.stringify(candidate)}`);
199
+ }
200
+ }
201
+ }
202
+ function assertEnvelopeRequestId(sourceName, value, expectedRequestId) {
203
+ const task = record(record(value)?.['task']);
204
+ const requestId = nonEmptyString(task?.['requestId']);
205
+ if (!requestId || requestId.toLowerCase() !== expectedRequestId.toLowerCase()) {
206
+ throw new Error(`${sourceName}.task.requestId must match authoritative requestId ${expectedRequestId}`);
207
+ }
208
+ }
209
+ function exactItems(value, label) {
210
+ const items = record(value)?.['items'];
211
+ if (!Array.isArray(items) || items.length !== 1 || !record(items[0])) {
212
+ throw new Error(`expected exactly one ${label} row; found ${Array.isArray(items) ? items.length : 0}`);
213
+ }
214
+ return record(items[0]);
215
+ }
216
+ const MAX_METADATA_PAGES = 20;
217
+ async function* walkMetadataCandidatePages(label, fetchPage) {
218
+ let candidateCount = 0;
219
+ let after = null;
220
+ for (let page = 0; page < MAX_METADATA_PAGES; page += 1) {
221
+ const value = record(await fetchPage(after));
222
+ const items = value?.['items'];
223
+ if (!Array.isArray(items)) {
224
+ throw new Error(`${label} page ${page + 1} has no items array`);
225
+ }
226
+ const candidates = items.flatMap((item) => {
227
+ const parsed = record(item);
228
+ return parsed ? [parsed] : [];
229
+ });
230
+ const pageInfo = record(value?.['pageInfo']);
231
+ if (!pageInfo || typeof pageInfo['hasNextPage'] !== 'boolean') {
232
+ throw new Error(`${label} page ${page + 1} has malformed pageInfo`);
233
+ }
234
+ candidateCount += candidates.length;
235
+ yield candidates;
236
+ if (pageInfo['hasNextPage'] === false) {
237
+ if (candidateCount === 0) {
238
+ throw new Error(`expected at least one ${label} row; found 0`);
239
+ }
240
+ return;
241
+ }
242
+ const endCursor = pageInfo['endCursor'];
243
+ if (typeof endCursor !== 'string' || endCursor.length === 0) {
244
+ throw new Error(`${label} page ${page + 1} has no continuation cursor`);
245
+ }
246
+ after = endCursor;
247
+ }
248
+ throw new Error(`incomplete ${label} walk after ${MAX_METADATA_PAGES} pages`);
249
+ }
250
+ const AUTHORITATIVE_VERDICT_QUERY = `
251
+ query AuthoritativeVerdict($requestId: String!, $chainId: Int!) {
252
+ verdicts(
253
+ where: { requestId: $requestId, chainId: $chainId },
254
+ limit: 2
255
+ ) {
256
+ items { requestId chainId taskId attemptIndex evaluator }
257
+ }
258
+ }
259
+ `;
260
+ const AUTHORITATIVE_TASK_QUERY = `
261
+ query AuthoritativeTask($taskId: String!, $chainId: Int!) {
262
+ tasks(
263
+ where: { id: $taskId, chainId: $chainId },
264
+ limit: 2
265
+ ) {
266
+ items { id chainId taskCidDigest manifestDigest creator }
267
+ }
268
+ }
269
+ `;
270
+ const VERDICT_ENVELOPE_META_CANDIDATES_QUERY = `
271
+ query AuthoritativeVerdictEnvelopeMetaCandidates(
272
+ $requestId: String!
273
+ $chainId: Int!
274
+ $manifestCid: String!
275
+ $after: String
276
+ ) {
277
+ verdictEnvelopeMetas(
278
+ where: {
279
+ requestId: $requestId
280
+ chainId: $chainId
281
+ manifestCid: $manifestCid
282
+ },
283
+ orderBy: "enrichedAtBlock"
284
+ orderDirection: "desc"
285
+ limit: 1000
286
+ after: $after
287
+ ) {
288
+ items { requestId chainId manifestCid publisherAgentId manifestHash enrichedAtBlock }
289
+ pageInfo { hasNextPage endCursor }
290
+ }
291
+ }
292
+ `;
293
+ const AUTHORITATIVE_ATTEMPT_QUERY = `
294
+ query AuthoritativeAttempt($taskId: String!, $attemptIndex: Int!, $chainId: Int!) {
295
+ attempts(
296
+ where: { taskId: $taskId, attemptIndex: $attemptIndex, chainId: $chainId },
297
+ limit: 2
298
+ ) {
299
+ items { requestId chainId taskId attemptIndex operator }
300
+ }
301
+ }
302
+ `;
303
+ const SOLUTION_ENVELOPE_META_CANDIDATES_QUERY = `
304
+ query SolutionEnvelopeMetaCandidates($requestId: String!, $chainId: Int!, $after: String) {
305
+ attemptEnvelopeMetas(
306
+ where: { requestId: $requestId, chainId: $chainId },
307
+ orderBy: "enrichedAtBlock"
308
+ orderDirection: "desc"
309
+ limit: 1000
310
+ after: $after
311
+ ) {
312
+ items { requestId chainId manifestCid publisherAgentId manifestHash enrichedAtBlock }
313
+ pageInfo { hasNextPage endCursor }
314
+ }
315
+ }
316
+ `;
317
+ /**
318
+ * Fetch + parse the solver's reasoning for one solution envelope (§8, #1472).
319
+ *
320
+ * The decision path does NOT live in `jinn.trajectory.v1` (that carries only
321
+ * the packaging step's 2 `jinn.artifact.emit` spans — #1473 tracks making it
322
+ * truthful at solve time). It lives in the `system_snapshot` artifact: a
323
+ * donation-wrapped gzipped tar of the solve working dir containing the
324
+ * harness's raw stdout transcript. Resolve that artifact, unwrap (sha256-
325
+ * verified), decompress (bomb-guarded), locate the transcript, and parse it
326
+ * with the same canonical typed-span implementation as the live path.
327
+ *
328
+ * Best-effort: ANY failure (no snapshot, unresolvable CID, sha mismatch,
329
+ * corrupt bytes, no transcript — e.g. hermes) degrades to `undefined` — the
330
+ * trace is enrichment, never a gate on the evidence fetch. Absence is later
331
+ * recorded as a `patch-only` tag.
332
+ */
333
+ async function fetchTrajectorySpans(ipfs, solutionEnv) {
334
+ try {
335
+ const arts = solutionEnv?.artifacts;
336
+ if (!Array.isArray(arts))
337
+ return undefined;
338
+ const snap = arts.find((a) => a?.artifactType === 'system_snapshot');
339
+ const cid = snap?.sources?.[0]?.cid;
340
+ const sha256 = typeof snap?.sha256 === 'string' ? snap.sha256 : snap?.sources?.[0]?.sha256;
341
+ if (typeof cid !== 'string' || !cid || typeof sha256 !== 'string' || !sha256)
342
+ return undefined;
343
+ const wrapper = await ipfs(cid); // the donation wrapper is itself JSON
344
+ const transcript = extractSnapshotTranscript(wrapper, sha256);
345
+ if (!transcript)
346
+ return undefined;
347
+ const spans = parseSolveTranscript(transcript);
348
+ return spans.length > 0 ? spans : undefined;
349
+ }
350
+ catch {
351
+ return undefined;
352
+ }
353
+ }
354
+ function rawSnapshotRef(solutionEnv) {
355
+ const snapshot = solutionEnv.artifacts?.find((artifact) => record(artifact)?.['artifactType'] === 'system_snapshot');
356
+ const sources = record(snapshot)?.['sources'];
357
+ return Array.isArray(sources)
358
+ ? nonEmptyString(record(sources[0])?.['cid'])
359
+ : undefined;
360
+ }
361
+ /**
362
+ * Build the bridge's `fetchEvidence`. Consumes injected `ipfs` / `gql` ports
363
+ * (fakes in tests; a gateway + indexer in production). Throws a clear error at
364
+ * each missing hop so a per-instance miss is diagnosable and skipped, not silent.
365
+ */
366
+ export function createEvidenceFetcher(ports) {
367
+ const authoritativeVerdicts = new Map();
368
+ const authoritativeTasks = new Map();
369
+ const authoritativeAttempts = new Map();
370
+ const selectedSolutions = new Map();
371
+ const cachedAuthority = (cache, key, query, variables) => {
372
+ const existing = cache.get(key);
373
+ if (existing)
374
+ return existing;
375
+ const pending = ports.gql(query, variables);
376
+ cache.set(key, pending);
377
+ return pending;
378
+ };
379
+ return async (ref) => {
380
+ if (!ref.verdictManifestCid) {
381
+ throw new Error(`AttemptRef ${ref.instanceId} has no verdictManifestCid (join entry point)`);
382
+ }
383
+ if (!Number.isSafeInteger(ref.chainId) || ref.chainId <= 0) {
384
+ throw new Error(`AttemptRef ${ref.instanceId} has invalid chainId ${ref.chainId}`);
385
+ }
386
+ // Hop 1a: the on-chain verdict row authoritatively identifies the task
387
+ // attempt. A metadata envelope cannot choose a different task tuple.
388
+ const verdictData = await cachedAuthority(authoritativeVerdicts, `${ref.chainId}:${ref.requestId.toLowerCase()}`, AUTHORITATIVE_VERDICT_QUERY, {
389
+ requestId: ref.requestId,
390
+ chainId: ref.chainId,
391
+ });
392
+ const verdictRow = exactItems(record(verdictData)?.['verdicts'], 'authoritative verdict');
393
+ const verdictRequestId = nonEmptyString(verdictRow['requestId']);
394
+ const verdictChainId = verdictRow['chainId'];
395
+ const taskId = nonEmptyString(verdictRow['taskId']);
396
+ const attemptIndex = verdictRow['attemptIndex'];
397
+ const evaluator = exactAddress(verdictRow['evaluator'], 'authoritative verdict evaluator');
398
+ if (!verdictRequestId
399
+ || verdictRequestId.toLowerCase() !== ref.requestId.toLowerCase()
400
+ || verdictChainId !== ref.chainId
401
+ || !taskId
402
+ || !Number.isInteger(attemptIndex)
403
+ || attemptIndex < 0) {
404
+ throw new Error('authoritative verdict row does not match the requested chain tuple');
405
+ }
406
+ // Hop 1b: retain the immutable TaskCreated commitments that the
407
+ // authenticated evaluation wrapper and original restoration task must
408
+ // prove. The evaluator-supplied task description is never authoritative.
409
+ const taskData = await cachedAuthority(authoritativeTasks, `${ref.chainId}:${taskId}`, AUTHORITATIVE_TASK_QUERY, {
410
+ taskId,
411
+ chainId: ref.chainId,
412
+ });
413
+ const taskRow = exactItems(record(taskData)?.['tasks'], 'authoritative task');
414
+ const taskCidDigest = exactHash(taskRow['taskCidDigest'], 'authoritative task taskCidDigest');
415
+ const manifestDigest = exactHash(taskRow['manifestDigest'], 'authoritative task manifestDigest');
416
+ const creatorSafe = exactAddress(taskRow['creator'], 'authoritative task creator');
417
+ if (taskRow['id'] !== taskId || taskRow['chainId'] !== ref.chainId) {
418
+ throw new Error('authoritative task row does not match the verdict task tuple');
419
+ }
420
+ const taskAuthority = {
421
+ taskId,
422
+ chainId: ref.chainId,
423
+ taskCidDigest,
424
+ manifestDigest,
425
+ creatorSafe,
426
+ evaluatorSafe: evaluator,
427
+ };
428
+ // Hop 1c: a request can have many enrichment rows because MetadataSet is
429
+ // permissionless. Walk a finite, newest-first candidate set and accept
430
+ // only a cryptographically authenticated envelope whose historical
431
+ // publisher, participant, committed hash, request, and polarity all bind
432
+ // to the authoritative verdict. The ref CID is only a discovery seed.
433
+ const verdictMetaPages = walkMetadataCandidatePages('verdict envelope metadata', async (after) => record(await ports.gql(VERDICT_ENVELOPE_META_CANDIDATES_QUERY, {
434
+ requestId: ref.requestId,
435
+ chainId: ref.chainId,
436
+ manifestCid: ref.verdictManifestCid,
437
+ after,
438
+ }))?.['verdictEnvelopeMetas']);
439
+ let selectedVerdict;
440
+ let lastVerdictCandidateError;
441
+ for await (const verdictMetaRows of verdictMetaPages) {
442
+ for (const metaRow of verdictMetaRows) {
443
+ try {
444
+ const cid = nonEmptyString(metaRow['manifestCid']);
445
+ const publisherAgentId = exactAgentId(metaRow['publisherAgentId'], 'verdict envelope publisherAgentId');
446
+ const candidateManifestHash = exactHash(metaRow['manifestHash'], 'verdict envelope manifestHash');
447
+ const publishedAtBlock = exactBlockNumber(metaRow['enrichedAtBlock'], 'verdict envelope enrichedAtBlock');
448
+ if (!cid
449
+ || nonEmptyString(metaRow['requestId'])?.toLowerCase()
450
+ !== ref.requestId.toLowerCase()
451
+ || metaRow['chainId'] !== ref.chainId) {
452
+ throw new Error('verdict envelope metadata row does not match the requested chain tuple');
453
+ }
454
+ const publisherSafe = exactAddress(await ports.resolvePublisherSafe(ref.chainId, publisherAgentId, publishedAtBlock), 'verdict envelope publisher Safe');
455
+ if (publisherSafe !== evaluator) {
456
+ throw new Error('verdict envelope publisher does not match on-chain evaluator');
457
+ }
458
+ const envelope = await ports.authenticateEnvelope(await ports.ipfs(cid), 'verdictEnvelope');
459
+ assertAuthenticatedEnvelopeKind(envelope, 'verdictEnvelope', 'verdict');
460
+ const evaluationTaskCid = nonEmptyString(record(envelope['task'])?.['cid']);
461
+ if (!evaluationTaskCid) {
462
+ throw new Error(`no task.cid on verdict envelope ${cid}`);
463
+ }
464
+ await assertEnvelopeAuthority({
465
+ envelope,
466
+ sourceName: 'verdict envelope',
467
+ expectedSafe: evaluator,
468
+ publisherAgentId,
469
+ publishedAtBlock,
470
+ manifestHash: candidateManifestHash,
471
+ chainId: ref.chainId,
472
+ resolvePublisherSafe: ports.resolvePublisherSafe,
473
+ onchainRole: 'evaluator',
474
+ });
475
+ assertEnvelopeRequestId('verdictEnvelope', envelope, ref.requestId);
476
+ const signedPassed = record(envelope['payload'])?.['passed_match'];
477
+ if (typeof signedPassed !== 'boolean') {
478
+ throw new Error('verdictEnvelope.payload.passed_match must be an exact boolean');
479
+ }
480
+ if ((signedPassed ? 'pass' : 'fail') !== ref.polarity) {
481
+ throw new Error('signed verdict polarity does not match the indexed candidate');
482
+ }
483
+ selectedVerdict = { envelope, evaluationTaskCid, manifestCid: cid };
484
+ break;
485
+ }
486
+ catch (error) {
487
+ lastVerdictCandidateError = error;
488
+ }
489
+ }
490
+ if (selectedVerdict)
491
+ break;
492
+ }
493
+ if (!selectedVerdict) {
494
+ const reason = lastVerdictCandidateError instanceof Error
495
+ ? `: ${lastVerdictCandidateError.message}`
496
+ : '';
497
+ throw new Error(`no authenticated verdict envelope metadata candidate matched the on-chain evaluator${reason}`);
498
+ }
499
+ const verdictEnv = selectedVerdict.envelope;
500
+ // Hop 2a: the chain attempt row supplies the SOLVE request id. This is the
501
+ // only value allowed to select a solution envelope.
502
+ const attemptData = await cachedAuthority(authoritativeAttempts, `${ref.chainId}:${taskId}:${String(attemptIndex)}`, AUTHORITATIVE_ATTEMPT_QUERY, {
503
+ taskId,
504
+ attemptIndex,
505
+ chainId: ref.chainId,
506
+ });
507
+ const attemptRow = exactItems(record(attemptData)?.['attempts'], 'authoritative attempt');
508
+ const solveReq = nonEmptyString(attemptRow['requestId']);
509
+ const operator = exactAddress(attemptRow['operator'], 'authoritative attempt operator');
510
+ if (!solveReq
511
+ || attemptRow['chainId'] !== ref.chainId
512
+ || attemptRow['taskId'] !== taskId
513
+ || attemptRow['attemptIndex'] !== attemptIndex) {
514
+ throw new Error('authoritative attempt row does not match the verdict task tuple');
515
+ }
516
+ // Hop 2b: fetch the evaluator-signed wrapper. The verifier resolver must
517
+ // authenticate it, extract its signed original-task CID, and prove that
518
+ // original task against taskAuthority before any task fact is consumed.
519
+ const taskDoc = await ports.ipfs(selectedVerdict.evaluationTaskCid);
520
+ const outerTaskForRequestBinding = record(taskDoc);
521
+ const signedTaskForRequestBinding = record(outerTaskForRequestBinding?.['signedTask']);
522
+ for (const [sourceName, candidate] of [
523
+ ['mutable task', outerTaskForRequestBinding?.['restorationRequestId']],
524
+ ['signed task', signedTaskForRequestBinding?.['restorationRequestId']],
525
+ ]) {
526
+ if (candidate === undefined)
527
+ continue;
528
+ const requestId = nonEmptyString(candidate);
529
+ if (!requestId || requestId.toLowerCase() !== solveReq.toLowerCase()) {
530
+ throw new Error(`${sourceName} restorationRequestId does not match `
531
+ + `authoritative solution requestId ${solveReq}`);
532
+ }
533
+ }
534
+ // Hop 2c: authenticate the retained solution candidates in the same
535
+ // bounded way, selecting only the one bound to the historical operator.
536
+ const solutionMetadataKey = `${ref.chainId}:${solveReq.toLowerCase()}`;
537
+ let selectedSolutionPromise = selectedSolutions.get(solutionMetadataKey);
538
+ if (!selectedSolutionPromise) {
539
+ selectedSolutionPromise = (async () => {
540
+ let lastSolutionCandidateError;
541
+ const metaPages = walkMetadataCandidatePages('solution envelope metadata', async (after) => record(await ports.gql(SOLUTION_ENVELOPE_META_CANDIDATES_QUERY, {
542
+ requestId: solveReq,
543
+ chainId: ref.chainId,
544
+ after,
545
+ }))?.['attemptEnvelopeMetas']);
546
+ for await (const metaRows of metaPages) {
547
+ for (const metaRow of metaRows) {
548
+ try {
549
+ const cid = nonEmptyString(metaRow['manifestCid']);
550
+ const publisherAgentId = exactAgentId(metaRow['publisherAgentId'], 'solution envelope publisherAgentId');
551
+ const candidateManifestHash = exactHash(metaRow['manifestHash'], 'solution envelope manifestHash');
552
+ const publishedAtBlock = exactBlockNumber(metaRow['enrichedAtBlock'], 'solution envelope enrichedAtBlock');
553
+ if (!cid
554
+ || nonEmptyString(metaRow['requestId'])?.toLowerCase()
555
+ !== solveReq.toLowerCase()
556
+ || metaRow['chainId'] !== ref.chainId) {
557
+ throw new Error('solution envelope metadata row does not match the authoritative attempt');
558
+ }
559
+ const publisherSafe = exactAddress(await ports.resolvePublisherSafe(ref.chainId, publisherAgentId, publishedAtBlock), 'solution envelope publisher Safe');
560
+ if (publisherSafe !== operator) {
561
+ throw new Error('solution envelope publisher does not match on-chain operator');
562
+ }
563
+ const envelope = await ports.authenticateEnvelope(await ports.ipfs(cid), 'solutionEnvelope');
564
+ assertAuthenticatedEnvelopeKind(envelope, 'solutionEnvelope', 'solution');
565
+ await assertEnvelopeAuthority({
566
+ envelope,
567
+ sourceName: 'solution envelope',
568
+ expectedSafe: operator,
569
+ publisherAgentId,
570
+ publishedAtBlock,
571
+ manifestHash: candidateManifestHash,
572
+ chainId: ref.chainId,
573
+ resolvePublisherSafe: ports.resolvePublisherSafe,
574
+ onchainRole: 'operator',
575
+ });
576
+ assertEnvelopeRequestId('solutionEnvelope', envelope, solveReq);
577
+ const candidatePatch = record(envelope['payload'])?.['patch'];
578
+ if (!candidatePatch || typeof candidatePatch !== 'string') {
579
+ throw new Error(`no payload.patch on solution envelope ${cid}`);
580
+ }
581
+ return { envelope, patch: candidatePatch, manifestCid: cid };
582
+ }
583
+ catch (error) {
584
+ lastSolutionCandidateError = error;
585
+ }
586
+ }
587
+ }
588
+ const reason = lastSolutionCandidateError instanceof Error
589
+ ? `: ${lastSolutionCandidateError.message}`
590
+ : '';
591
+ throw new Error(`no authenticated solution envelope metadata candidate matched the on-chain operator${reason}`);
592
+ })();
593
+ selectedSolutions.set(solutionMetadataKey, selectedSolutionPromise);
594
+ }
595
+ const selectedSolution = await selectedSolutionPromise;
596
+ const solutionEnv = selectedSolution.envelope;
597
+ const patch = selectedSolution.patch;
598
+ // Hop 3 (best-effort, §8 v0.5): the solver's own trajectory off the SAME
599
+ // solution envelope. Never throws — absence is recorded as `patch-only`.
600
+ const trajectorySpans = await fetchTrajectorySpans(ports.ipfs, solutionEnv);
601
+ const snapshotRef = rawSnapshotRef(solutionEnv);
602
+ const outerTaskDocument = record(taskDoc) ?? {};
603
+ const signedTaskDocument = record(outerTaskDocument['signedTask']) ?? outerTaskDocument;
604
+ const signedTaskSpec = record(signedTaskDocument['spec']) ?? {};
605
+ const problem = String(signedTaskDocument['description']
606
+ ?? signedTaskSpec['problem_statement']
607
+ ?? '')
608
+ .replace(/\s+/g, ' ')
609
+ .trim()
610
+ .slice(0, MAX_PROBLEM_CHARS);
611
+ let verifier;
612
+ if (ports.resolveVerifierFacts) {
613
+ if (!hasExactSweTaskType(taskDoc)) {
614
+ throw new Error(`verifier resolution for ${ref.instanceId} requires an exact swe-rebench-v2.v1 task type`);
615
+ }
616
+ verifier = validateVerifierFacts(await ports.resolveVerifierFacts(taskDoc, ref.instanceId, taskAuthority), ref.instanceId);
617
+ }
618
+ else if (hasExactSweTaskType(taskDoc)) {
619
+ throw new Error(`identified SWE task ${ref.instanceId} requires authenticated verifier facts`);
620
+ }
621
+ if (verifier) {
622
+ // Execution-envelope task.createdAt is the on-chain TaskCreated block
623
+ // timestamp (epoch seconds), while the authenticated tuple carries the
624
+ // signed TaskV1 creation time (epoch milliseconds). They are distinct
625
+ // events, so envelope createdAt is deliberately ignored rather than
626
+ // compared or allowed to override the authenticated signed-task value.
627
+ assertExactEnvelopeTaskProvenance('verdictEnvelope', verdictEnv, verifier.task);
628
+ assertExactEnvelopeTaskProvenance('solutionEnvelope', solutionEnv, verifier.task);
629
+ if (verifier.task.creatorSafe !== creatorSafe) {
630
+ throw new Error('authenticated task creator Safe does not match TaskCreated creator');
631
+ }
632
+ if (verifier.task.manifestDigest !== manifestDigest) {
633
+ throw new Error('authenticated task manifest digest does not match TaskCreated manifestDigest');
634
+ }
635
+ const solutionTaskCid = nonEmptyString(record(solutionEnv['task'])?.['cid']);
636
+ if (solutionTaskCid !== verifier.task.originalTaskCid) {
637
+ throw new Error('solutionEnvelope.task.cid does not match authenticated original task CID');
638
+ }
639
+ }
640
+ const model = generatorModel(record(solutionEnv['executor'])?.['generatorModel']);
641
+ const distributionClass = solutionEnv['distributionClass'] === 'open'
642
+ || solutionEnv['distributionClass'] === 'restricted-tos'
643
+ || solutionEnv['distributionClass'] === 'unknown'
644
+ ? solutionEnv['distributionClass']
645
+ : undefined;
646
+ return {
647
+ taskSummary: (verifier?.task.description || problem || ref.instanceId)
648
+ .replace(/\s+/g, ' ')
649
+ .trim()
650
+ .slice(0, MAX_PROBLEM_CHARS) || ref.instanceId,
651
+ patch: patch.slice(0, MAX_PATCH_CHARS),
652
+ ...(verifier
653
+ ? {
654
+ repo: verifier.task.repo,
655
+ baseCommit: verifier.task.baseCommit,
656
+ taskCreatedAt: verifier.task.createdAt,
657
+ instanceId: verifier.task.instanceId,
658
+ }
659
+ : {}),
660
+ ...(model ? { generatorModel: model } : {}),
661
+ ...(distributionClass ? { distributionClass } : {}),
662
+ ...(verifier
663
+ ? {
664
+ verifier: {
665
+ failToPass: verifier.failToPass,
666
+ passToPass: verifier.passToPass,
667
+ evalSemanticsVersion: verifier.evalSemanticsVersion,
668
+ },
669
+ }
670
+ : {}),
671
+ verdictEnvelopeCid: selectedVerdict.manifestCid,
672
+ solutionEnvelopeCid: selectedSolution.manifestCid,
673
+ ...(snapshotRef ? { rawSnapshotRef: snapshotRef } : {}),
674
+ ...(trajectorySpans ? { trajectorySpans } : {}),
675
+ };
676
+ };
677
+ }