@gmickel/gno 1.27.1 → 1.28.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/README.md +2 -2
- package/assets/skill/SKILL.md +26 -1
- package/browser-extension/artifacts/{gno-browser-clipper-v1.27.1.zip → gno-browser-clipper-v1.28.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/spec/cli.md +41 -4
- package/spec/db/schema.sql +12 -0
- package/spec/mcp.md +5 -0
- package/spec/output-schemas/ask.schema.json +125 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +94 -1
- package/spec/output-schemas/get.schema.json +94 -0
- package/spec/output-schemas/mcp-job-status.schema.json +28 -0
- package/spec/output-schemas/multi-get.schema.json +128 -0
- package/spec/output-schemas/record-import.schema.json +193 -0
- package/spec/output-schemas/search-result.schema.json +94 -0
- package/spec/output-schemas/search-results.schema.json +125 -0
- package/spec/project-profile.schema.json +66 -0
- package/src/app/context-format.ts +1 -1
- package/src/cli/commands/get.ts +12 -2
- package/src/cli/commands/index-cmd.ts +13 -0
- package/src/cli/commands/multi-get.ts +9 -2
- package/src/cli/commands/shared.ts +28 -0
- package/src/cli/commands/update.ts +5 -0
- package/src/cli/program.ts +4 -0
- package/src/config/project-profile.ts +2 -0
- package/src/config/types.ts +16 -0
- package/src/converters/adapters/browser-export/adapter.ts +199 -0
- package/src/converters/adapters/browser-export/formats.ts +358 -0
- package/src/converters/adapters/email/adapter.ts +429 -0
- package/src/converters/adapters/email/html.ts +162 -0
- package/src/converters/adapters/email/mime.ts +454 -0
- package/src/converters/adapters/email/parameters.ts +77 -0
- package/src/converters/adapters/ical/adapter.ts +475 -0
- package/src/converters/adapters/ical/recurrence.ts +186 -0
- package/src/converters/adapters/jsonl/adapter.ts +238 -0
- package/src/converters/adapters/jsonl/config.ts +105 -0
- package/src/converters/adapters/shared/html-text.ts +165 -0
- package/src/converters/adapters/shared/record-utils.ts +79 -0
- package/src/converters/adapters/shared/utf8-lines.ts +141 -0
- package/src/converters/adapters/transcript/adapter.ts +295 -0
- package/src/converters/adapters/transcript/json.ts +184 -0
- package/src/converters/adapters/transcript/model.ts +171 -0
- package/src/converters/adapters/transcript/text.ts +58 -0
- package/src/converters/adapters/transcript/timed.ts +152 -0
- package/src/converters/index.ts +11 -1
- package/src/converters/mime.ts +8 -0
- package/src/converters/pipeline.ts +15 -1
- package/src/converters/registry.ts +37 -1
- package/src/converters/types.ts +136 -0
- package/src/core/context-capsule-schema.ts +87 -0
- package/src/core/context-capsule.ts +20 -0
- package/src/core/context-evidence.ts +7 -1
- package/src/core/document-capabilities.ts +12 -0
- package/src/core/project-profile-apply-state.ts +5 -0
- package/src/core/project-profile.ts +4 -0
- package/src/core/record-metadata.ts +49 -0
- package/src/ingestion/record-adapter-canonical.ts +433 -0
- package/src/ingestion/record-adapter.ts +437 -0
- package/src/ingestion/record-container.ts +688 -0
- package/src/ingestion/record-path.ts +20 -0
- package/src/ingestion/record-sync.ts +70 -0
- package/src/ingestion/sync.ts +228 -36
- package/src/ingestion/types.ts +69 -1
- package/src/ingestion/walker.ts +31 -11
- package/src/mcp/tools/get.ts +10 -2
- package/src/mcp/tools/multi-get.ts +9 -2
- package/src/mcp/tools/workspace-write.ts +2 -0
- package/src/pipeline/filters.ts +1 -1
- package/src/pipeline/graph-retrieval.ts +7 -4
- package/src/pipeline/hybrid.ts +7 -4
- package/src/pipeline/result-context.ts +12 -4
- package/src/pipeline/search.ts +11 -4
- package/src/pipeline/types.ts +3 -0
- package/src/pipeline/vsearch.ts +26 -8
- package/src/sdk/documents.ts +13 -5
- package/src/serve/browse-tree.ts +4 -2
- package/src/serve/routes/api.ts +63 -65
- package/src/store/migrations/022-record-export-lineage.ts +42 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +114 -7
- package/src/store/types.ts +40 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RecordAdapter,
|
|
3
|
+
RecordAdapterFailure,
|
|
4
|
+
RecordAdapterFailureCode,
|
|
5
|
+
RecordAdapterInput,
|
|
6
|
+
RecordAdapterRecord,
|
|
7
|
+
RecordAnchor,
|
|
8
|
+
RecordMetadata,
|
|
9
|
+
} from "../converters/types";
|
|
10
|
+
|
|
11
|
+
import { canonicalize, mirrorHash } from "../converters/canonicalize";
|
|
12
|
+
import { RECORD_METADATA_LIMITS } from "../converters/types";
|
|
13
|
+
|
|
14
|
+
const HASH_PATTERN = /^[a-f\d]{64}$/;
|
|
15
|
+
const CONTROL_PATTERN = new RegExp(
|
|
16
|
+
`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}]`
|
|
17
|
+
);
|
|
18
|
+
const CONTROL_REPLACE_PATTERN = new RegExp(CONTROL_PATTERN.source, "g");
|
|
19
|
+
const WINDOWS_ABSOLUTE_PATTERN = /^[a-zA-Z]:[\\/]/;
|
|
20
|
+
const MAX_ID_CHARS = 512;
|
|
21
|
+
const MAX_LOCATOR_CHARS = 512;
|
|
22
|
+
const MAX_ERROR_CHARS = 240;
|
|
23
|
+
const RECORD_ANCHOR_KINDS = new Set<RecordAnchor["kind"]>([
|
|
24
|
+
"line",
|
|
25
|
+
"cue",
|
|
26
|
+
"timestamp",
|
|
27
|
+
"message",
|
|
28
|
+
"event",
|
|
29
|
+
"record",
|
|
30
|
+
]);
|
|
31
|
+
const RECORD_ATTACHMENT_DISPOSITIONS = new Set<
|
|
32
|
+
NonNullable<NonNullable<RecordMetadata["attachments"]>[number]["disposition"]>
|
|
33
|
+
>(["inline", "attachment"]);
|
|
34
|
+
|
|
35
|
+
export interface CanonicalRecord {
|
|
36
|
+
recordKey: string;
|
|
37
|
+
stableId: string;
|
|
38
|
+
sourceLocator: string;
|
|
39
|
+
sourceHash: string;
|
|
40
|
+
mirrorHash: string;
|
|
41
|
+
markdown: string;
|
|
42
|
+
adapterId: string;
|
|
43
|
+
adapterVersion: string;
|
|
44
|
+
adapterFingerprint: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
languageHint?: string;
|
|
47
|
+
metadata?: RecordMetadata;
|
|
48
|
+
anchors?: RecordAnchor[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AccountedCanonicalRecord extends CanonicalRecord {
|
|
52
|
+
accountingChars: number;
|
|
53
|
+
metadataChars: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RecordAdapterIdentity {
|
|
57
|
+
id: string;
|
|
58
|
+
version: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hashText = (value: string): string => {
|
|
62
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
63
|
+
hasher.update(value);
|
|
64
|
+
return hasher.digest("hex");
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const canonicalJsonValue = (value: unknown): unknown => {
|
|
68
|
+
if (Array.isArray(value)) return value.map(canonicalJsonValue);
|
|
69
|
+
if (value && typeof value === "object") {
|
|
70
|
+
const result: Record<string, unknown> = {};
|
|
71
|
+
for (const key of Object.keys(value).sort()) {
|
|
72
|
+
const child = canonicalJsonValue((value as Record<string, unknown>)[key]);
|
|
73
|
+
if (child !== undefined) result[key] = child;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const normalizeText = (value: string): string => value.normalize("NFC").trim();
|
|
81
|
+
|
|
82
|
+
export const normalizeRecordAdapterIdentity = (
|
|
83
|
+
adapter: Pick<RecordAdapter, "id" | "version">
|
|
84
|
+
): RecordAdapterIdentity => {
|
|
85
|
+
const id =
|
|
86
|
+
typeof adapter.id === "string" ? normalizeText(adapter.id) : undefined;
|
|
87
|
+
const version =
|
|
88
|
+
typeof adapter.version === "string"
|
|
89
|
+
? normalizeText(adapter.version)
|
|
90
|
+
: undefined;
|
|
91
|
+
if (
|
|
92
|
+
!id ||
|
|
93
|
+
id.length > RECORD_METADATA_LIMITS.maxAdapterIdChars ||
|
|
94
|
+
CONTROL_PATTERN.test(id)
|
|
95
|
+
) {
|
|
96
|
+
throw new Error("invalid record adapter ID");
|
|
97
|
+
}
|
|
98
|
+
if (
|
|
99
|
+
!version ||
|
|
100
|
+
version.length > RECORD_METADATA_LIMITS.maxAdapterVersionChars ||
|
|
101
|
+
CONTROL_PATTERN.test(version)
|
|
102
|
+
) {
|
|
103
|
+
throw new Error("invalid record adapter version");
|
|
104
|
+
}
|
|
105
|
+
return { id, version };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const recordAdapterFingerprint = (adapter: RecordAdapter): string => {
|
|
109
|
+
const identity = normalizeRecordAdapterIdentity(adapter);
|
|
110
|
+
return hashText(
|
|
111
|
+
`gno-record-adapter-v1\0${identity.id}\0${identity.version}\0${adapter.configurationFingerprint ?? "default"}`
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const normalizeStableId = (value: string): string => {
|
|
116
|
+
const normalized = normalizeText(value);
|
|
117
|
+
if (
|
|
118
|
+
normalized.length === 0 ||
|
|
119
|
+
normalized.length > MAX_ID_CHARS ||
|
|
120
|
+
CONTROL_PATTERN.test(normalized) ||
|
|
121
|
+
normalized.startsWith("/") ||
|
|
122
|
+
WINDOWS_ABSOLUTE_PATTERN.test(normalized)
|
|
123
|
+
) {
|
|
124
|
+
throw new Error("invalid stable ID");
|
|
125
|
+
}
|
|
126
|
+
return normalized;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const normalizeLocator = (value: string): string => {
|
|
130
|
+
const normalized = normalizeText(value);
|
|
131
|
+
const pathSegments = normalized.replaceAll("\\", "/").split("/");
|
|
132
|
+
if (
|
|
133
|
+
normalized.length === 0 ||
|
|
134
|
+
normalized.length > MAX_LOCATOR_CHARS ||
|
|
135
|
+
CONTROL_PATTERN.test(normalized) ||
|
|
136
|
+
normalized.startsWith("/") ||
|
|
137
|
+
WINDOWS_ABSOLUTE_PATTERN.test(normalized) ||
|
|
138
|
+
pathSegments.includes("..")
|
|
139
|
+
) {
|
|
140
|
+
throw new Error("invalid source locator");
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const normalizeOptionalText = (
|
|
146
|
+
value: string | undefined
|
|
147
|
+
): string | undefined => {
|
|
148
|
+
if (value === undefined) return undefined;
|
|
149
|
+
const normalized = normalizeText(value).replace(CONTROL_REPLACE_PATTERN, "");
|
|
150
|
+
return normalized || undefined;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const boundedText = (
|
|
154
|
+
value: string | undefined,
|
|
155
|
+
maxChars: number
|
|
156
|
+
): string | undefined => {
|
|
157
|
+
const normalized = normalizeOptionalText(value);
|
|
158
|
+
if (normalized && normalized.length > maxChars) {
|
|
159
|
+
throw new Error("record metadata out of bounds");
|
|
160
|
+
}
|
|
161
|
+
return normalized;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const normalizeSha256 = (value: string | undefined): string | undefined => {
|
|
165
|
+
if (value === undefined) return undefined;
|
|
166
|
+
if (!HASH_PATTERN.test(value)) {
|
|
167
|
+
throw new Error("record metadata out of bounds");
|
|
168
|
+
}
|
|
169
|
+
return value;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const normalizeFailureReference = (
|
|
173
|
+
value: string | undefined
|
|
174
|
+
): string | undefined => {
|
|
175
|
+
const normalized = normalizeOptionalText(value);
|
|
176
|
+
if (!normalized) return undefined;
|
|
177
|
+
const pathSegments = normalized.replaceAll("\\", "/").split("/");
|
|
178
|
+
if (
|
|
179
|
+
normalized.startsWith("/") ||
|
|
180
|
+
WINDOWS_ABSOLUTE_PATTERN.test(normalized) ||
|
|
181
|
+
pathSegments.includes("..")
|
|
182
|
+
) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return normalized.slice(0, MAX_LOCATOR_CHARS);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const normalizeMetadata = (
|
|
189
|
+
metadata: RecordMetadata | undefined
|
|
190
|
+
): RecordMetadata | undefined => {
|
|
191
|
+
if (!metadata) return undefined;
|
|
192
|
+
const normalizeList = (
|
|
193
|
+
values: string[] | undefined,
|
|
194
|
+
maxChars: number
|
|
195
|
+
): string[] | undefined => {
|
|
196
|
+
if (!values) return undefined;
|
|
197
|
+
if (values.length > RECORD_METADATA_LIMITS.maxItems) {
|
|
198
|
+
throw new Error("record metadata out of bounds");
|
|
199
|
+
}
|
|
200
|
+
return values
|
|
201
|
+
.map((value) => boundedText(value, maxChars))
|
|
202
|
+
.filter((value): value is string => Boolean(value));
|
|
203
|
+
};
|
|
204
|
+
const dateEntries = Object.entries(metadata.dateFields ?? {});
|
|
205
|
+
if (dateEntries.length > RECORD_METADATA_LIMITS.maxItems) {
|
|
206
|
+
throw new Error("record metadata out of bounds");
|
|
207
|
+
}
|
|
208
|
+
const dateFields = metadata.dateFields
|
|
209
|
+
? Object.fromEntries(
|
|
210
|
+
dateEntries
|
|
211
|
+
.map(([key, value]) => [
|
|
212
|
+
boundedText(key, RECORD_METADATA_LIMITS.maxDateFieldKeyChars),
|
|
213
|
+
boundedText(value, RECORD_METADATA_LIMITS.maxDateFieldValueChars),
|
|
214
|
+
])
|
|
215
|
+
.filter(
|
|
216
|
+
(entry): entry is [string, string] =>
|
|
217
|
+
Boolean(entry[0]) && Boolean(entry[1])
|
|
218
|
+
)
|
|
219
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
220
|
+
)
|
|
221
|
+
: undefined;
|
|
222
|
+
if (
|
|
223
|
+
metadata.attachments &&
|
|
224
|
+
metadata.attachments.length > RECORD_METADATA_LIMITS.maxItems
|
|
225
|
+
) {
|
|
226
|
+
throw new Error("record metadata out of bounds");
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
author: boundedText(metadata.author, RECORD_METADATA_LIMITS.maxPersonChars),
|
|
230
|
+
participants: normalizeList(
|
|
231
|
+
metadata.participants,
|
|
232
|
+
RECORD_METADATA_LIMITS.maxPersonChars
|
|
233
|
+
),
|
|
234
|
+
categories: normalizeList(
|
|
235
|
+
metadata.categories,
|
|
236
|
+
RECORD_METADATA_LIMITS.maxCategoryChars
|
|
237
|
+
),
|
|
238
|
+
dateFields,
|
|
239
|
+
messageId: boundedText(
|
|
240
|
+
metadata.messageId,
|
|
241
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
242
|
+
),
|
|
243
|
+
inReplyTo: boundedText(
|
|
244
|
+
metadata.inReplyTo,
|
|
245
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
246
|
+
),
|
|
247
|
+
references: normalizeList(
|
|
248
|
+
metadata.references,
|
|
249
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
250
|
+
),
|
|
251
|
+
threadId: boundedText(
|
|
252
|
+
metadata.threadId,
|
|
253
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
254
|
+
),
|
|
255
|
+
eventId: boundedText(
|
|
256
|
+
metadata.eventId,
|
|
257
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
258
|
+
),
|
|
259
|
+
sessionId: boundedText(
|
|
260
|
+
metadata.sessionId,
|
|
261
|
+
RECORD_METADATA_LIMITS.maxIdentifierChars
|
|
262
|
+
),
|
|
263
|
+
attachments: metadata.attachments?.map((attachment) => {
|
|
264
|
+
if (
|
|
265
|
+
attachment.bytes !== undefined &&
|
|
266
|
+
(!Number.isSafeInteger(attachment.bytes) || attachment.bytes < 0)
|
|
267
|
+
) {
|
|
268
|
+
throw new Error("record metadata out of bounds");
|
|
269
|
+
}
|
|
270
|
+
if (
|
|
271
|
+
attachment.disposition !== undefined &&
|
|
272
|
+
!RECORD_ATTACHMENT_DISPOSITIONS.has(attachment.disposition)
|
|
273
|
+
) {
|
|
274
|
+
throw new Error("record metadata out of bounds");
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
name:
|
|
278
|
+
boundedText(
|
|
279
|
+
attachment.name,
|
|
280
|
+
RECORD_METADATA_LIMITS.maxAttachmentNameChars
|
|
281
|
+
) ?? "attachment",
|
|
282
|
+
mime: boundedText(
|
|
283
|
+
attachment.mime,
|
|
284
|
+
RECORD_METADATA_LIMITS.maxAttachmentMimeChars
|
|
285
|
+
),
|
|
286
|
+
bytes: attachment.bytes,
|
|
287
|
+
disposition: attachment.disposition,
|
|
288
|
+
sha256: normalizeSha256(attachment.sha256),
|
|
289
|
+
};
|
|
290
|
+
}),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const normalizeAnchors = (
|
|
295
|
+
anchors: RecordAnchor[] | undefined
|
|
296
|
+
): RecordAnchor[] | undefined => {
|
|
297
|
+
if (!anchors) return undefined;
|
|
298
|
+
if (anchors.length > RECORD_METADATA_LIMITS.maxItems) {
|
|
299
|
+
throw new Error("record metadata out of bounds");
|
|
300
|
+
}
|
|
301
|
+
return anchors.map((anchor) => {
|
|
302
|
+
if (!RECORD_ANCHOR_KINDS.has(anchor.kind)) {
|
|
303
|
+
throw new Error("record metadata out of bounds");
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
kind: anchor.kind,
|
|
307
|
+
value:
|
|
308
|
+
boundedText(anchor.value, RECORD_METADATA_LIMITS.maxAnchorChars) ??
|
|
309
|
+
"unknown",
|
|
310
|
+
endValue: boundedText(
|
|
311
|
+
anchor.endValue,
|
|
312
|
+
RECORD_METADATA_LIMITS.maxAnchorChars
|
|
313
|
+
),
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export const recordKeyFor = (adapterId: string, stableId: string): string =>
|
|
319
|
+
hashText(`gno-record-v1\0${adapterId}\0${normalizeStableId(stableId)}`);
|
|
320
|
+
|
|
321
|
+
export const safeFailure = (
|
|
322
|
+
input: RecordAdapterInput,
|
|
323
|
+
code: RecordAdapterFailureCode,
|
|
324
|
+
message: string,
|
|
325
|
+
retryable: boolean,
|
|
326
|
+
stableId?: string,
|
|
327
|
+
sourceLocator?: string
|
|
328
|
+
): RecordAdapterFailure => {
|
|
329
|
+
const withoutPath = message.replaceAll(input.sourcePath, "<source>");
|
|
330
|
+
const sanitized = withoutPath
|
|
331
|
+
.replace(CONTROL_REPLACE_PATTERN, " ")
|
|
332
|
+
.slice(0, MAX_ERROR_CHARS);
|
|
333
|
+
return {
|
|
334
|
+
code,
|
|
335
|
+
message: sanitized || "Record adapter failure.",
|
|
336
|
+
retryable,
|
|
337
|
+
stableId: normalizeFailureReference(stableId),
|
|
338
|
+
sourceLocator: normalizeFailureReference(sourceLocator),
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const canonicalRecord = (
|
|
343
|
+
adapter: RecordAdapter,
|
|
344
|
+
record: RecordAdapterRecord
|
|
345
|
+
): AccountedCanonicalRecord => {
|
|
346
|
+
const adapterIdentity = normalizeRecordAdapterIdentity(adapter);
|
|
347
|
+
const stableId = normalizeStableId(record.stableId);
|
|
348
|
+
const sourceLocator = normalizeLocator(record.sourceLocator);
|
|
349
|
+
const markdown = canonicalize(record.markdown);
|
|
350
|
+
const metadata = normalizeMetadata(record.metadata);
|
|
351
|
+
const anchors = normalizeAnchors(record.anchors);
|
|
352
|
+
const title = boundedText(record.title, RECORD_METADATA_LIMITS.maxTitleChars);
|
|
353
|
+
const languageHint = boundedText(
|
|
354
|
+
record.languageHint,
|
|
355
|
+
RECORD_METADATA_LIMITS.maxLanguageHintChars
|
|
356
|
+
);
|
|
357
|
+
const derivedSource = JSON.stringify(
|
|
358
|
+
canonicalJsonValue({
|
|
359
|
+
anchors,
|
|
360
|
+
languageHint,
|
|
361
|
+
markdown,
|
|
362
|
+
metadata,
|
|
363
|
+
sourceLocator,
|
|
364
|
+
stableId,
|
|
365
|
+
title,
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
const metadataChars = JSON.stringify(
|
|
369
|
+
canonicalJsonValue({
|
|
370
|
+
anchors,
|
|
371
|
+
languageHint,
|
|
372
|
+
metadata,
|
|
373
|
+
sourceLocator,
|
|
374
|
+
stableId,
|
|
375
|
+
title,
|
|
376
|
+
})
|
|
377
|
+
).length;
|
|
378
|
+
if (
|
|
379
|
+
record.sourceHash !== undefined &&
|
|
380
|
+
!HASH_PATTERN.test(record.sourceHash)
|
|
381
|
+
) {
|
|
382
|
+
throw new Error("invalid source hash");
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
recordKey: recordKeyFor(adapterIdentity.id, stableId),
|
|
386
|
+
stableId,
|
|
387
|
+
sourceLocator,
|
|
388
|
+
sourceHash:
|
|
389
|
+
record.sourceHash ?? hashText(`gno-record-source-v1\0${derivedSource}`),
|
|
390
|
+
mirrorHash: mirrorHash(markdown),
|
|
391
|
+
markdown,
|
|
392
|
+
adapterId: adapterIdentity.id,
|
|
393
|
+
adapterVersion: adapterIdentity.version,
|
|
394
|
+
adapterFingerprint: recordAdapterFingerprint(adapter),
|
|
395
|
+
title,
|
|
396
|
+
languageHint,
|
|
397
|
+
metadata,
|
|
398
|
+
anchors,
|
|
399
|
+
accountingChars: derivedSource.length,
|
|
400
|
+
metadataChars,
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export const adapterFailureMessage = (
|
|
405
|
+
code: RecordAdapterFailureCode
|
|
406
|
+
): string => {
|
|
407
|
+
switch (code) {
|
|
408
|
+
case "MALFORMED_RECORD":
|
|
409
|
+
return "Record adapter reported a malformed record.";
|
|
410
|
+
case "MISSING_ID":
|
|
411
|
+
return "Record adapter reported a missing stable identity.";
|
|
412
|
+
case "DUPLICATE_ID":
|
|
413
|
+
return "Record adapter reported a duplicate stable identity.";
|
|
414
|
+
case "RECORD_TOO_LARGE":
|
|
415
|
+
return "Record adapter reported an oversized record.";
|
|
416
|
+
case "SOURCE_TOO_LARGE":
|
|
417
|
+
return "Record adapter reported an oversized source.";
|
|
418
|
+
case "TIMEOUT":
|
|
419
|
+
return "Record adapter exceeded its time limit.";
|
|
420
|
+
case "RECORD_LIMIT":
|
|
421
|
+
return "Record adapter reported that its record limit was reached.";
|
|
422
|
+
case "FAILURE_LIMIT":
|
|
423
|
+
return "Record adapter reported that its failure limit was reached.";
|
|
424
|
+
case "INVALID_LOCATOR":
|
|
425
|
+
return "Record adapter reported an invalid source locator.";
|
|
426
|
+
case "INVALID_SOURCE_HASH":
|
|
427
|
+
return "Record adapter reported an invalid source hash.";
|
|
428
|
+
case "INVALID_SNAPSHOT":
|
|
429
|
+
return "Record adapter reported an invalid snapshot.";
|
|
430
|
+
default:
|
|
431
|
+
return "Record adapter reported a conversion failure.";
|
|
432
|
+
}
|
|
433
|
+
};
|