{"record":{"id":"020b240d84be44e4","repo":"JuliusBrussee/caveman","slug":"invalid-recovery","errorCode":"invalid_recovery","errorMessage":"invalid_recovery","messagePattern":"invalid_recovery","errorType":"error_code","errorClass":"MiddlewareError","httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/middleware/validate.ts","lineNumber":80,"sourceCode":"    reduction += r.tokens_before - r.tokens_after;\n    if (r.unique_original) { unique += r.tokens_before - r.tokens_after; credited.add(r.original_sha256); }\n  }\n  for (const skipped of p.skipped) {\n    if (!segments.has(skipped.segment_id) || seen.has(skipped.segment_id) || !isToken(skipped.reason)) invalid();\n    seen.add(skipped.segment_id);\n  }\n  if (seen.size !== segments.size || m.tokens_before - m.tokens_after !== reduction || m.unique_tokens_reduced !== unique ||\n    (p.replacements.length > 0 && (p.status !== 'optimized' || reduction <= m.recovery_overhead_tokens)) ||\n    (p.status === 'optimized' && p.replacements.length === 0)) invalid();\n  return p;\n}\n\nexport async function validatePage(value: unknown, args: RetrieveArgs, maxBytes: number): Promise<RecoveryPage> {\n  const p = value as RecoveryPage;\n  if (!p || p.schema_version !== 1 || p.handle !== args.handle || !isToken(p.source_id) || !isHash(p.original_sha256) ||\n    typeof p.text !== 'string' || byteLength(p.text) > maxBytes || !integer(p.total_bytes) || !integer(p.offset) || p.offset !== (args.offset ?? 0) ||\n    !['original_page','excerpt'].includes(p.kind) || typeof p.complete !== 'boolean' ||\n    !(p.next_offset === null || integer(p.next_offset))) throw new MiddlewareError('invalid_recovery');\n  const length = byteLength(p.text);\n  if (p.kind === 'original_page' && (p.offset + length > p.total_bytes ||\n    p.next_offset !== (p.offset + length < p.total_bytes ? p.offset + length : null) ||\n    p.complete !== (p.offset === 0 && length === p.total_bytes))) throw new MiddlewareError('invalid_recovery');\n  if (p.kind === 'excerpt' && (p.complete || !args.query || p.next_offset !== 0)) throw new MiddlewareError('invalid_recovery');\n  if (p.complete && await sha256(p.text) !== p.original_sha256) throw new MiddlewareError('invalid_recovery');\n  return p;\n}\n","sourceCodeStart":62,"sourceCodeEnd":89,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/middleware/validate.ts#L62-L89","documentation":"MiddlewareError 'invalid_recovery' is thrown by validatePage when a RecoveryPage fails a structural check: schema_version, handle matching the RetrieveArgs, token-shaped source_id, hash-shaped original_sha256, string text within maxBytes, integer total_bytes/offset matching the requested offset, allowed kind ('original_page'|'excerpt'), boolean complete, and next_offset either null or an integer. It is the first of several recovery-page integrity gates before text is returned to the caller.","triggerScenarios":"Calling validatePage (via retrieve) with a page whose handle differs from args.handle, offset not equal to args.offset ?? 0, kind outside the two allowed values, non-integer or negative total_bytes/offset, next_offset that is neither null nor an integer, or text larger than maxBytes.","commonSituations":"Pages returned from a cache keyed by an older handle, a storage adapter returning excerpt data with original_page metadata, byte-offset paging logic off by one so the returned offset differs from the request, or oversized pages after a maxBytes configuration change.","solutions":["Ensure the page's handle exactly equals args.handle and offset equals args.offset ?? 0","Fix the source so kind is 'original_page' or 'excerpt' and next_offset is null or an integer","Check total_bytes/offset are integers and text byte length does not exceed maxBytes","Regenerate the page from storage instead of replaying a cached one","Verify SDK/storage adapter versions agree on the RecoveryPage schema"],"exampleFix":"// before\nconst page = await store.fetch(anyHandle, requestedOffset + 1);\n// after: echo back exactly the requested handle/offset\nconst page = await store.fetch(args.handle, args.offset ?? 0);","handlingStrategy":"validation","validationCode":"function precheckPage(p, args) {\n  return p && p.schema_version === 1 && p.handle === args.handle &&\n    typeof p.source_id === 'string' && typeof p.original_sha256 === 'string' &&\n    typeof p.text === 'string' && Number.isInteger(p.total_bytes) && Number.isInteger(p.offset) &&\n    p.offset === (args.offset ?? 0) &&\n    ['original_page','excerpt'].includes(p.kind) &&\n    (p.next_offset === null || Number.isInteger(p.next_offset));\n}\nif (!precheckPage(page, args)) throw new Error('recovery page fails local precheck');","typeGuard":"const isRecoveryPage = (p) => !!p && p.schema_version === 1 && typeof p.handle === 'string' &&\n  typeof p.text === 'string' && (p.kind === 'original_page' || p.kind === 'excerpt') &&\n  typeof p.complete === 'boolean' && (p.next_offset === null || Number.isInteger(p.next_offset));","tryCatchPattern":"try { const page = await retrieve(args); } catch (e) {\n  if (e.code === 'invalid_recovery') {\n    // refetch from canonical storage; do not serve unvalidated recovery data\n    cache.invalidate(args.handle);\n    page = await retrieve(args);\n  } else throw e;\n}","preventionTips":["Ensure storage adapters echo back the exact handle and offset from the request","Compute all offsets/sizes in bytes, not characters","Invalidate caches when handles or schema versions change","Test adapters for first/middle/last-page boundaries before shipping"],"tags":["validation","recovery","paging","middleware"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}