@gmickel/gno 1.27.1 → 1.29.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 (195) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +59 -1
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/browser-extension/dist/preview.html +1 -1
  8. package/browser-extension/dist/service-worker.js +6 -6
  9. package/package.json +2 -2
  10. package/spec/cli.md +82 -5
  11. package/spec/db/schema.sql +31 -0
  12. package/spec/mcp.md +71 -0
  13. package/spec/output-schemas/ask.schema.json +156 -1
  14. package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
  15. package/spec/output-schemas/collection-egress-check.schema.json +91 -0
  16. package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
  17. package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +183 -3
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
  20. package/spec/output-schemas/egress-audit-management.schema.json +88 -0
  21. package/spec/output-schemas/get.schema.json +94 -0
  22. package/spec/output-schemas/mcp-http-error.schema.json +113 -2
  23. package/spec/output-schemas/mcp-job-status.schema.json +28 -0
  24. package/spec/output-schemas/multi-get.schema.json +128 -0
  25. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  26. package/spec/output-schemas/record-import.schema.json +223 -0
  27. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  28. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  29. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  30. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  31. package/spec/output-schemas/search-result.schema.json +94 -0
  32. package/spec/output-schemas/search-results.schema.json +155 -0
  33. package/spec/project-profile.schema.json +66 -0
  34. package/src/app/context-format.ts +1 -1
  35. package/src/app/context-runtime-contract.ts +16 -7
  36. package/src/cli/commands/ask.ts +7 -0
  37. package/src/cli/commands/collection/index.ts +5 -0
  38. package/src/cli/commands/collection/policy.ts +126 -0
  39. package/src/cli/commands/context-build.ts +5 -0
  40. package/src/cli/commands/daemon.ts +45 -3
  41. package/src/cli/commands/egress-audit.ts +68 -0
  42. package/src/cli/commands/embed.ts +2 -0
  43. package/src/cli/commands/get.ts +12 -2
  44. package/src/cli/commands/index-cmd.ts +13 -0
  45. package/src/cli/commands/multi-get.ts +9 -2
  46. package/src/cli/commands/query.ts +12 -0
  47. package/src/cli/commands/replay.ts +13 -3
  48. package/src/cli/commands/shared.ts +28 -0
  49. package/src/cli/commands/update.ts +5 -0
  50. package/src/cli/commands/vsearch.ts +3 -1
  51. package/src/cli/program.ts +127 -0
  52. package/src/config/index.ts +9 -0
  53. package/src/config/project-profile.ts +2 -0
  54. package/src/config/types.ts +66 -0
  55. package/src/converters/adapters/browser-export/adapter.ts +199 -0
  56. package/src/converters/adapters/browser-export/formats.ts +358 -0
  57. package/src/converters/adapters/email/adapter.ts +429 -0
  58. package/src/converters/adapters/email/html.ts +162 -0
  59. package/src/converters/adapters/email/mime.ts +454 -0
  60. package/src/converters/adapters/email/parameters.ts +77 -0
  61. package/src/converters/adapters/ical/adapter.ts +475 -0
  62. package/src/converters/adapters/ical/recurrence.ts +186 -0
  63. package/src/converters/adapters/jsonl/adapter.ts +238 -0
  64. package/src/converters/adapters/jsonl/config.ts +105 -0
  65. package/src/converters/adapters/shared/html-text.ts +165 -0
  66. package/src/converters/adapters/shared/record-utils.ts +79 -0
  67. package/src/converters/adapters/shared/utf8-lines.ts +141 -0
  68. package/src/converters/adapters/transcript/adapter.ts +295 -0
  69. package/src/converters/adapters/transcript/json.ts +184 -0
  70. package/src/converters/adapters/transcript/model.ts +171 -0
  71. package/src/converters/adapters/transcript/text.ts +58 -0
  72. package/src/converters/adapters/transcript/timed.ts +152 -0
  73. package/src/converters/index.ts +11 -1
  74. package/src/converters/mime.ts +8 -0
  75. package/src/converters/pipeline.ts +15 -1
  76. package/src/converters/registry.ts +37 -1
  77. package/src/converters/types.ts +136 -0
  78. package/src/core/browser-clip.ts +16 -1
  79. package/src/core/collection-egress-policy-projection.ts +28 -0
  80. package/src/core/collection-egress-policy-service.ts +443 -0
  81. package/src/core/collection-egress-policy-validation.ts +242 -0
  82. package/src/core/config-mutation.ts +31 -19
  83. package/src/core/context-capsule-schema.ts +198 -1
  84. package/src/core/context-capsule-validation.ts +4 -4
  85. package/src/core/context-capsule-verification.ts +5 -1
  86. package/src/core/context-capsule.ts +177 -112
  87. package/src/core/context-evidence.ts +93 -15
  88. package/src/core/destination-classifier.ts +402 -0
  89. package/src/core/document-capabilities.ts +12 -0
  90. package/src/core/egress-audit.ts +239 -0
  91. package/src/core/egress-authorization.ts +50 -0
  92. package/src/core/egress-enforcement.ts +264 -0
  93. package/src/core/egress-policy.ts +440 -0
  94. package/src/core/egress-provenance.ts +336 -0
  95. package/src/core/job-manager.ts +37 -0
  96. package/src/core/network-boundary-inventory.ts +261 -0
  97. package/src/core/project-profile-apply-state.ts +5 -0
  98. package/src/core/project-profile.ts +4 -0
  99. package/src/core/record-metadata.ts +49 -0
  100. package/src/core/retrieval-qrels.ts +6 -0
  101. package/src/core/retrieval-replay.ts +18 -4
  102. package/src/core/retrieval-trace-export.ts +23 -4
  103. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  104. package/src/core/retrieval-trace-management-types.ts +3 -0
  105. package/src/core/retrieval-trace-management.ts +9 -1
  106. package/src/core/retrieval-trace-session.ts +94 -0
  107. package/src/core/retrieval-trace.ts +22 -0
  108. package/src/ingestion/record-adapter-canonical.ts +433 -0
  109. package/src/ingestion/record-adapter.ts +448 -0
  110. package/src/ingestion/record-container.ts +704 -0
  111. package/src/ingestion/record-path.ts +20 -0
  112. package/src/ingestion/record-sync.ts +70 -0
  113. package/src/ingestion/sync.ts +243 -36
  114. package/src/ingestion/types.ts +71 -1
  115. package/src/ingestion/walker.ts +31 -11
  116. package/src/llm/errors.ts +10 -0
  117. package/src/llm/http-inference.ts +175 -0
  118. package/src/llm/http-policy.ts +537 -0
  119. package/src/llm/httpEmbedding.ts +47 -28
  120. package/src/llm/httpGeneration.ts +31 -18
  121. package/src/llm/httpRerank.ts +30 -18
  122. package/src/llm/index.ts +1 -0
  123. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  124. package/src/llm/pinned-http-connection.ts +177 -0
  125. package/src/mcp/context.ts +53 -1
  126. package/src/mcp/http-egress.ts +220 -0
  127. package/src/mcp/http-security.ts +32 -21
  128. package/src/mcp/http-session.ts +13 -2
  129. package/src/mcp/http-transport.ts +96 -6
  130. package/src/mcp/sync-egress.ts +24 -0
  131. package/src/mcp/tools/add-collection.ts +4 -0
  132. package/src/mcp/tools/ask.ts +1 -1
  133. package/src/mcp/tools/context.ts +9 -2
  134. package/src/mcp/tools/egress.ts +247 -0
  135. package/src/mcp/tools/embed.ts +1 -0
  136. package/src/mcp/tools/get.ts +10 -2
  137. package/src/mcp/tools/index-cmd.ts +7 -0
  138. package/src/mcp/tools/index.ts +98 -0
  139. package/src/mcp/tools/multi-get.ts +9 -2
  140. package/src/mcp/tools/query.ts +10 -0
  141. package/src/mcp/tools/remove-collection.ts +4 -0
  142. package/src/mcp/tools/sync.ts +6 -0
  143. package/src/mcp/tools/trace.ts +7 -1
  144. package/src/mcp/tools/vsearch.ts +1 -0
  145. package/src/mcp/tools/workspace-write.ts +2 -0
  146. package/src/pipeline/egress-lineage.ts +124 -0
  147. package/src/pipeline/filters.ts +1 -1
  148. package/src/pipeline/graph-retrieval.ts +7 -4
  149. package/src/pipeline/hybrid.ts +21 -4
  150. package/src/pipeline/result-context.ts +12 -4
  151. package/src/pipeline/search.ts +23 -4
  152. package/src/pipeline/types.ts +6 -0
  153. package/src/pipeline/vsearch.ts +67 -28
  154. package/src/publish/artifact.ts +31 -3
  155. package/src/publish/export-service.ts +25 -0
  156. package/src/sdk/client.ts +119 -2
  157. package/src/sdk/documents.ts +13 -5
  158. package/src/sdk/embed.ts +1 -0
  159. package/src/sdk/types.ts +33 -0
  160. package/src/serve/browse-tree.ts +4 -2
  161. package/src/serve/clipper-capture.ts +5 -1
  162. package/src/serve/closed-json.ts +61 -0
  163. package/src/serve/config-sync.ts +4 -1
  164. package/src/serve/context.ts +1 -0
  165. package/src/serve/doc-events.ts +110 -39
  166. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  167. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  168. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  169. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  170. package/src/serve/public/globals.built.css +1 -1
  171. package/src/serve/public/pages/Collections.tsx +3 -0
  172. package/src/serve/resident-request.ts +80 -3
  173. package/src/serve/resident-runtime.ts +63 -1
  174. package/src/serve/routes/api.ts +237 -65
  175. package/src/serve/routes/clipper.ts +64 -1
  176. package/src/serve/routes/mcp.ts +6 -0
  177. package/src/serve/routes/traces.ts +27 -4
  178. package/src/serve/server.ts +118 -7
  179. package/src/store/migrations/022-record-export-lineage.ts +42 -0
  180. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  181. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  182. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  183. package/src/store/migrations/index.ts +8 -0
  184. package/src/store/retrieval-trace-codec.ts +15 -0
  185. package/src/store/sqlite/adapter.ts +284 -14
  186. package/src/store/sqlite/change-journal-store.ts +41 -2
  187. package/src/store/sqlite/egress-audit-store.ts +485 -0
  188. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  189. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  190. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  191. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  192. package/src/store/types.ts +166 -1
  193. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip +0 -0
  194. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
  195. package/browser-extension/dist/chunk-vn5f663b.js +0 -50
@@ -0,0 +1,448 @@
1
+ import type {
2
+ RecordAdapter,
3
+ RecordAdapterFailure,
4
+ RecordAdapterFailureCode,
5
+ RecordAdapterEvent,
6
+ RecordAdapterInput,
7
+ } from "../converters/types";
8
+ import type { EgressLineage } from "../core/egress-provenance";
9
+
10
+ import {
11
+ egressLineageSchema,
12
+ legacyLocalOnlyEgressLineage,
13
+ } from "../core/egress-provenance";
14
+ import {
15
+ type AccountedCanonicalRecord,
16
+ adapterFailureMessage,
17
+ canonicalRecord,
18
+ type CanonicalRecord,
19
+ normalizeRecordAdapterIdentity,
20
+ recordKeyFor,
21
+ recordAdapterFingerprint,
22
+ safeFailure,
23
+ } from "./record-adapter-canonical";
24
+
25
+ export type { CanonicalRecord } from "./record-adapter-canonical";
26
+ export {
27
+ recordAdapterFingerprint,
28
+ recordKeyFor,
29
+ } from "./record-adapter-canonical";
30
+
31
+ export interface RecordAdapterRunResult {
32
+ adapterId: string;
33
+ adapterVersion: string;
34
+ adapterFingerprint: string;
35
+ egressLineage: EgressLineage;
36
+ records: CanonicalRecord[];
37
+ failures: RecordAdapterFailure[];
38
+ failedRecordKeys: string[];
39
+ snapshotState: "complete" | "partial";
40
+ authoritative: boolean;
41
+ stoppedByCap: boolean;
42
+ sourceBytesRead: number;
43
+ }
44
+
45
+ class SourceLimitError extends Error {
46
+ constructor() {
47
+ super("Record adapter source exceeded its byte limit.");
48
+ this.name = "SourceLimitError";
49
+ }
50
+ }
51
+
52
+ const TIMEOUT = Symbol("record-adapter-timeout");
53
+
54
+ const nextBeforeDeadline = async <T>(
55
+ iterator: AsyncIterator<T>,
56
+ deadlineMs: number
57
+ ): Promise<IteratorResult<T> | typeof TIMEOUT> => {
58
+ const remainingMs = deadlineMs - Date.now();
59
+ if (remainingMs <= 0) return TIMEOUT;
60
+ return await new Promise<IteratorResult<T> | typeof TIMEOUT>(
61
+ (resolve, reject) => {
62
+ const timer = setTimeout(() => resolve(TIMEOUT), remainingMs);
63
+ void iterator.next().then(
64
+ (value) => {
65
+ clearTimeout(timer);
66
+ resolve(value);
67
+ },
68
+ (error: unknown) => {
69
+ clearTimeout(timer);
70
+ reject(error);
71
+ }
72
+ );
73
+ }
74
+ );
75
+ };
76
+
77
+ const closeIterator = async (
78
+ iterator: AsyncIterator<unknown>
79
+ ): Promise<boolean> => {
80
+ if (!iterator.return) return true;
81
+ try {
82
+ await iterator.return();
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ };
88
+
89
+ /** Consume one adapter snapshot while centrally enforcing every global cap. */
90
+ export async function runRecordAdapter(
91
+ adapter: RecordAdapter,
92
+ input: RecordAdapterInput,
93
+ options: { egressLineage?: EgressLineage } = {}
94
+ ): Promise<RecordAdapterRunResult> {
95
+ const adapterIdentity = normalizeRecordAdapterIdentity(adapter);
96
+ const adapterFingerprint = recordAdapterFingerprint(adapter);
97
+ const egressLineage = egressLineageSchema.parse(
98
+ options.egressLineage ?? legacyLocalOnlyEgressLineage(input.collection)
99
+ );
100
+ const accepted = new Map<string, AccountedCanonicalRecord>();
101
+ const seenKeys = new Set<string>();
102
+ const failedRecordKeys = new Set<string>();
103
+ const failures: RecordAdapterFailure[] = [];
104
+ let totalChars = 0;
105
+ let sourceBytesRead = 0;
106
+ let sourceOpened = false;
107
+ let sourceLimitExceeded = false;
108
+ let sourceReadFailed = false;
109
+ let stoppedByCap = false;
110
+ let terminalCount = 0;
111
+ let terminalState: "complete" | "partial" = "partial";
112
+ let iteratorCloseFailed = false;
113
+ let stoppedByFailureLimit = false;
114
+ let timedOut = false;
115
+ const abortController = new AbortController();
116
+ const deadlineMs = Date.now() + (input.limits.timeoutMs ?? 60_000);
117
+
118
+ const appendFailure = (failure: RecordAdapterFailure): boolean => {
119
+ if (stoppedByFailureLimit) return true;
120
+ if (input.limits.maxFailures > 0) failures.push(failure);
121
+ if (failures.length < input.limits.maxFailures) return false;
122
+ failures.push(
123
+ safeFailure(
124
+ input,
125
+ "FAILURE_LIMIT",
126
+ "Record snapshot reached its failure limit.",
127
+ true
128
+ )
129
+ );
130
+ stoppedByFailureLimit = true;
131
+ stoppedByCap = true;
132
+ return true;
133
+ };
134
+
135
+ const boundedOpen = (): AsyncIterable<Uint8Array> => ({
136
+ [Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
137
+ if (sourceOpened)
138
+ throw new Error("Record adapter source may be opened once.");
139
+ sourceOpened = true;
140
+ let iterator: AsyncIterator<Uint8Array>;
141
+ try {
142
+ iterator = input.open(abortController.signal)[Symbol.asyncIterator]();
143
+ } catch {
144
+ sourceReadFailed = true;
145
+ throw new Error("Record adapter source could not be opened.");
146
+ }
147
+ return {
148
+ async next() {
149
+ let next: IteratorResult<Uint8Array>;
150
+ try {
151
+ next = await iterator.next();
152
+ } catch {
153
+ sourceReadFailed = true;
154
+ throw new Error("Record adapter source read failed.");
155
+ }
156
+ if (next.done) return next;
157
+ sourceBytesRead += next.value.byteLength;
158
+ if (sourceBytesRead > input.limits.maxSourceBytes) {
159
+ sourceLimitExceeded = true;
160
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
161
+ throw new SourceLimitError();
162
+ }
163
+ return next;
164
+ },
165
+ async return() {
166
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
167
+ return { done: true, value: undefined };
168
+ },
169
+ };
170
+ },
171
+ });
172
+
173
+ let iterator: AsyncIterator<RecordAdapterEvent> | undefined;
174
+ try {
175
+ iterator = adapter
176
+ .records({
177
+ ...input,
178
+ open: boundedOpen,
179
+ signal: abortController.signal,
180
+ })
181
+ [Symbol.asyncIterator]();
182
+ while (true) {
183
+ const next = await nextBeforeDeadline(iterator, deadlineMs);
184
+ if (next === TIMEOUT) {
185
+ timedOut = true;
186
+ stoppedByCap = true;
187
+ abortController.abort();
188
+ failures.push(
189
+ safeFailure(
190
+ input,
191
+ "TIMEOUT",
192
+ "Record adapter exceeded its time limit.",
193
+ true
194
+ )
195
+ );
196
+ break;
197
+ }
198
+ if (next.done) break;
199
+ const event = next.value;
200
+ if (event.type === "snapshot") {
201
+ terminalCount += 1;
202
+ if (terminalCount === 1) terminalState = event.state;
203
+ else {
204
+ const reachedFailureLimit = appendFailure(
205
+ safeFailure(
206
+ input,
207
+ "INVALID_SNAPSHOT",
208
+ "Record adapter emitted more than one terminal snapshot.",
209
+ false
210
+ )
211
+ );
212
+ if (reachedFailureLimit) break;
213
+ }
214
+ continue;
215
+ }
216
+ if (terminalCount > 0) {
217
+ const reachedFailureLimit = appendFailure(
218
+ safeFailure(
219
+ input,
220
+ "INVALID_SNAPSHOT",
221
+ "Record adapter emitted data after its terminal snapshot.",
222
+ false
223
+ )
224
+ );
225
+ if (reachedFailureLimit) break;
226
+ continue;
227
+ }
228
+ if (event.type === "failure") {
229
+ const reachedFailureLimit = appendFailure(
230
+ safeFailure(
231
+ input,
232
+ event.failure.code,
233
+ adapterFailureMessage(event.failure.code),
234
+ event.failure.retryable,
235
+ event.failure.stableId,
236
+ event.failure.sourceLocator
237
+ )
238
+ );
239
+ if (reachedFailureLimit) break;
240
+ } else {
241
+ let record: AccountedCanonicalRecord;
242
+ try {
243
+ record = canonicalRecord(adapter, event.record);
244
+ } catch (error) {
245
+ const code: RecordAdapterFailureCode =
246
+ error instanceof Error && error.message === "invalid source locator"
247
+ ? "INVALID_LOCATOR"
248
+ : error instanceof Error &&
249
+ error.message === "invalid source hash"
250
+ ? "INVALID_SOURCE_HASH"
251
+ : error instanceof Error &&
252
+ error.message === "record metadata out of bounds"
253
+ ? "RECORD_TOO_LARGE"
254
+ : "MISSING_ID";
255
+ const reachedFailureLimit = appendFailure(
256
+ safeFailure(
257
+ input,
258
+ code,
259
+ "Record identity or provenance was invalid.",
260
+ false,
261
+ event.record.stableId
262
+ )
263
+ );
264
+ if (reachedFailureLimit) break;
265
+ continue;
266
+ }
267
+ if (seenKeys.has(record.recordKey)) {
268
+ const previous = accepted.get(record.recordKey);
269
+ if (previous) totalChars -= previous.accountingChars;
270
+ accepted.delete(record.recordKey);
271
+ failedRecordKeys.add(record.recordKey);
272
+ const reachedFailureLimit = appendFailure(
273
+ safeFailure(
274
+ input,
275
+ "DUPLICATE_ID",
276
+ "Duplicate stable record identity; every duplicate was isolated.",
277
+ false,
278
+ record.stableId,
279
+ record.sourceLocator
280
+ )
281
+ );
282
+ if (reachedFailureLimit) break;
283
+ continue;
284
+ }
285
+ seenKeys.add(record.recordKey);
286
+ if (
287
+ record.markdown.length > input.limits.maxRecordChars ||
288
+ record.metadataChars > input.limits.maxMetadataChars
289
+ ) {
290
+ const reachedFailureLimit = appendFailure(
291
+ safeFailure(
292
+ input,
293
+ "RECORD_TOO_LARGE",
294
+ "Record exceeded the canonical character limit.",
295
+ false,
296
+ record.stableId,
297
+ record.sourceLocator
298
+ )
299
+ );
300
+ failedRecordKeys.add(record.recordKey);
301
+ if (reachedFailureLimit) break;
302
+ continue;
303
+ }
304
+ if (accepted.size >= input.limits.maxRecords) {
305
+ failures.push(
306
+ safeFailure(
307
+ input,
308
+ "RECORD_LIMIT",
309
+ "Record snapshot exceeded its record limit.",
310
+ true
311
+ )
312
+ );
313
+ stoppedByCap = true;
314
+ break;
315
+ }
316
+ if (totalChars + record.accountingChars > input.limits.maxTotalChars) {
317
+ failures.push(
318
+ safeFailure(
319
+ input,
320
+ "RECORD_LIMIT",
321
+ "Record snapshot exceeded its total character limit.",
322
+ true
323
+ )
324
+ );
325
+ stoppedByCap = true;
326
+ break;
327
+ }
328
+ accepted.set(record.recordKey, record);
329
+ totalChars += record.accountingChars;
330
+ }
331
+ }
332
+ } catch (error) {
333
+ failures.push(
334
+ safeFailure(
335
+ input,
336
+ error instanceof SourceLimitError
337
+ ? "SOURCE_TOO_LARGE"
338
+ : "ADAPTER_FAILURE",
339
+ error instanceof SourceLimitError
340
+ ? error.message
341
+ : "Record adapter stopped before a terminal snapshot.",
342
+ true
343
+ )
344
+ );
345
+ stoppedByCap ||= error instanceof SourceLimitError;
346
+ } finally {
347
+ if (iterator) {
348
+ if (timedOut) {
349
+ void closeIterator(iterator);
350
+ } else {
351
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
352
+ }
353
+ }
354
+ }
355
+
356
+ if (iteratorCloseFailed) {
357
+ failures.push(
358
+ safeFailure(
359
+ input,
360
+ "ADAPTER_FAILURE",
361
+ "Record adapter iterator did not close cleanly.",
362
+ true
363
+ )
364
+ );
365
+ }
366
+
367
+ if (
368
+ sourceLimitExceeded &&
369
+ !failures.some((failure) => failure.code === "SOURCE_TOO_LARGE")
370
+ ) {
371
+ failures.push(
372
+ safeFailure(
373
+ input,
374
+ "SOURCE_TOO_LARGE",
375
+ "Record adapter source exceeded its byte limit.",
376
+ true
377
+ )
378
+ );
379
+ stoppedByCap = true;
380
+ }
381
+
382
+ if (
383
+ sourceReadFailed &&
384
+ !failures.some((failure) => failure.code === "ADAPTER_FAILURE")
385
+ ) {
386
+ failures.push(
387
+ safeFailure(
388
+ input,
389
+ "ADAPTER_FAILURE",
390
+ "Record adapter source could not be read completely.",
391
+ true
392
+ )
393
+ );
394
+ }
395
+
396
+ if (terminalCount !== 1) {
397
+ failures.push(
398
+ safeFailure(
399
+ input,
400
+ "INVALID_SNAPSHOT",
401
+ "Record adapter did not emit exactly one terminal snapshot.",
402
+ true
403
+ )
404
+ );
405
+ }
406
+
407
+ const authoritative =
408
+ terminalCount === 1 &&
409
+ terminalState === "complete" &&
410
+ failures.length === 0 &&
411
+ !stoppedByCap;
412
+ const failureLimit = Math.max(0, input.limits.maxFailures);
413
+ const boundedFailures =
414
+ failures.length <= failureLimit
415
+ ? failures
416
+ : failureLimit === 0
417
+ ? []
418
+ : [
419
+ ...failures.slice(0, failureLimit - 1),
420
+ safeFailure(
421
+ input,
422
+ "FAILURE_LIMIT",
423
+ "Record snapshot reached its failure limit.",
424
+ true
425
+ ),
426
+ ];
427
+ return {
428
+ adapterId: adapterIdentity.id,
429
+ adapterVersion: adapterIdentity.version,
430
+ adapterFingerprint,
431
+ egressLineage,
432
+ records: [...accepted.values()]
433
+ .sort((left, right) => left.recordKey.localeCompare(right.recordKey))
434
+ .map(
435
+ ({
436
+ accountingChars: _accountingChars,
437
+ metadataChars: _metadataChars,
438
+ ...record
439
+ }) => record
440
+ ),
441
+ failures: boundedFailures,
442
+ failedRecordKeys: [...failedRecordKeys].sort(),
443
+ snapshotState: terminalState,
444
+ authoritative,
445
+ stoppedByCap,
446
+ sourceBytesRead,
447
+ };
448
+ }