@gmickel/gno 1.12.0 → 1.12.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -40,6 +40,7 @@ const CFB_SIGNATURE = new Uint8Array([
40
40
  ]);
41
41
  const ENCRYPTION_INFO = utf16le("EncryptionInfo");
42
42
  const ENCRYPTED_PACKAGE = utf16le("EncryptedPackage");
43
+ const PDF_TRAILER_SCAN_BYTES = 2048;
43
44
  const MAX_MESSAGE_LENGTH = 200;
44
45
  const PASSWORD_ERROR_REGEX = /password(?:-protected)?|no password given/i;
45
46
 
@@ -100,6 +101,19 @@ function isPasswordProtectedPdf(bytes: Uint8Array): boolean {
100
101
  return /\/Encrypt\b/.test(tail);
101
102
  }
102
103
 
104
+ function hasCompletePdfTrailer(bytes: Uint8Array): boolean {
105
+ if (!hasPrefix(bytes, PDF_SIGNATURE)) {
106
+ return false;
107
+ }
108
+
109
+ const tailStart = Math.max(0, bytes.length - PDF_TRAILER_SCAN_BYTES);
110
+ const tail = Buffer.from(bytes.subarray(tailStart)).toString("latin1");
111
+ const eofIndex = tail.lastIndexOf("%%EOF");
112
+ const startXrefIndex = tail.lastIndexOf("startxref");
113
+
114
+ return startXrefIndex >= 0 && eofIndex > startXrefIndex;
115
+ }
116
+
103
117
  function isPasswordProtectedXlsx(bytes: Uint8Array): boolean {
104
118
  return (
105
119
  hasPrefix(bytes, CFB_SIGNATURE) &&
@@ -162,7 +176,20 @@ export const markitdownAdapter: Converter = {
162
176
  return { ok: false, error: tooLargeError(input, CONVERTER_ID) };
163
177
  }
164
178
 
165
- // 1b. Detect password-protected documents before calling markitdown-ts.
179
+ // 1b. Reject obviously truncated PDFs before markitdown-ts logs a parser
180
+ // stack trace. ISO 32000 requires the final %%EOF marker near the file end.
181
+ if (input.ext === ".pdf" && !hasCompletePdfTrailer(input.bytes)) {
182
+ return {
183
+ ok: false,
184
+ error: corruptError(
185
+ input,
186
+ CONVERTER_ID,
187
+ "Invalid or incomplete PDF structure"
188
+ ),
189
+ };
190
+ }
191
+
192
+ // 1c. Detect password-protected documents before calling markitdown-ts.
166
193
  // markitdown-ts logs dependency stack traces to stderr for these files.
167
194
  if (isPasswordProtected(input)) {
168
195
  return {
@@ -75,6 +75,12 @@ const MAX_CONCURRENCY = 16;
75
75
  export const INGEST_VERSION = 6;
76
76
  const EMPTY_CONTENT_TYPE_RULES_FINGERPRINT = fingerprintContentTypeRules([]);
77
77
  const RELATION_EDGE_TYPE_PATTERN = /^[a-z][a-z0-9_]*$/;
78
+ const NON_RETRYABLE_CONVERSION_ERROR_CODES = new Set([
79
+ "CORRUPT",
80
+ "PERMISSION",
81
+ "TOO_LARGE",
82
+ "UNSUPPORTED",
83
+ ]);
78
84
 
79
85
  type RelationMap = Record<string, string[]>;
80
86
 
@@ -219,6 +225,20 @@ function decideAction(
219
225
 
220
226
  // Source unchanged, but check for repair cases:
221
227
 
228
+ // Preserve non-retryable conversion failures until the source or ingest
229
+ // version changes. Re-running an unchanged corrupt/protected file on every
230
+ // sync only repeats expensive work and noisy diagnostics.
231
+ if (
232
+ existing.lastErrorCode &&
233
+ NON_RETRYABLE_CONVERSION_ERROR_CODES.has(existing.lastErrorCode) &&
234
+ existing.ingestVersion === INGEST_VERSION
235
+ ) {
236
+ return {
237
+ kind: "skip",
238
+ reason: "unchanged non-retryable conversion failure",
239
+ };
240
+ }
241
+
222
242
  // 1. Previous conversion failed (mirrorHash is null)
223
243
  if (!existing.mirrorHash) {
224
244
  return { kind: "repair", reason: "previous conversion failed" };
@@ -738,6 +758,8 @@ export class SyncService {
738
758
  sourceCtime,
739
759
  lastErrorCode: convertResult.error.code,
740
760
  lastErrorMessage: convertResult.error.message,
761
+ ingestVersion: INGEST_VERSION,
762
+ contentTypeRulesFingerprint,
741
763
  // mirrorHash intentionally omitted (will be null)
742
764
  });
743
765