From 895b5f0532473d880a25cb1c37118a21a2f41fa8 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 19 Aug 2026 13:06:39 +0300 Subject: [PATCH 1/3] Refresh source-backed documents in place on a FULL rebuild FULL rebuild deleted a document before re-ingesting it from its URI, and the handler around that logged and continued. A 404, a timeout or any conversion error therefore removed the document permanently. Deleting after a successful create is not an alternative: create_document_from_source resolves the same URI to the existing document and updates it in place, so a trailing delete would remove the freshly rebuilt row. Refresh in place instead. create_document_from_source takes an internal force flag that skips the revision and MD5 short-circuits, so an unchanged source is still re-converted, re-chunked and re-embedded into the existing document, and the document id survives a rebuild. A failed refresh now falls through to the stored-content path rather than skipping the document: FULL recreates the chunks table before the loop, so skipping left the document present but unsearchable until the next rebuild. The pending-batch flush moves out of the try. A failed flush is a lost write and should abort the rebuild, not be logged and skipped. --- CHANGELOG.md | 1 + haiku_rag_slim/haiku/rag/client/documents.py | 14 +++- haiku_rag_slim/haiku/rag/client/rebuild.py | 47 +++++++---- ..._failure_falls_back_to_stored_content.yaml | 82 +++++++++++++++++++ ..._source_failure_is_logged_and_skipped.yaml | 42 ---------- tests/test_rebuild.py | 66 +++++++++------ 6 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml delete mode 100644 tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1daa4601..0731b93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Fixed +- A FULL rebuild no longer deletes a source-backed document before re-ingesting it: it refreshes the document in place, so the document id is preserved and a failed fetch or conversion falls back to rebuilding from stored content instead of losing the document. - `DocumentRepository.delete_all` recreated `document_items` from `DocumentItemRecord` instead of `get_document_items_arrow_schema()`, returning `picture_data` as `binary` rather than `large_binary`. - `server.json` runtime arguments are `mcp --stdio`, was `serve --mcp`. diff --git a/haiku_rag_slim/haiku/rag/client/documents.py b/haiku_rag_slim/haiku/rag/client/documents.py index 7257fcbd..d3256a69 100644 --- a/haiku_rag_slim/haiku/rag/client/documents.py +++ b/haiku_rag_slim/haiku/rag/client/documents.py @@ -677,6 +677,7 @@ async def create_document_from_source( sources: "list[Source] | None" = None, source_id: str | None = None, metadata_provider: "MetadataProvider | None" = None, + force: bool = False, ) -> Document | list[Document]: """Create or update document(s) from a file path, directory, or URL. @@ -685,6 +686,10 @@ async def create_document_from_source( - If MD5 changed, updates the document - If no document exists, creates a new one + ``force`` skips both freshness checks so an unchanged source is re-converted, + re-chunked and re-embedded into the existing document. Internal: rebuild uses + it to refresh a document in place instead of deleting and recreating it. + If ``uri`` is provided, it overrides the URI auto-derived from the source (which is normally ``file://`` for local files or the URL for remote sources). Not supported for directory sources, which produce one document @@ -730,6 +735,7 @@ async def create_document_from_source( sources=sources, source_id=source_id, metadata_provider=metadata_provider, + force=force, ) assert isinstance(doc, Document) documents.append(doc) @@ -784,7 +790,7 @@ async def create_document_from_source( stored_revision = ( (existing_doc.metadata or {}).get("source_revision") if existing_doc else None ) - if existing_doc and stored_revision: + if existing_doc and stored_revision and not force: current_revision = await fetcher.head(source_str) if current_revision == stored_revision: return await _refresh_doc_metadata( @@ -808,7 +814,11 @@ async def create_document_from_source( # MD5 short-circuit: the bytes are unchanged even if the revision wasn't. # Refresh the source-derived metadata (revision may have rolled) but skip # convert/embed/store entirely. - if existing_doc and existing_doc.metadata.get("md5") == result.content_hash: + if ( + existing_doc + and not force + and existing_doc.metadata.get("md5") == result.content_hash + ): source_meta: dict = { "content_type": result.content_type, "md5": result.content_hash, diff --git a/haiku_rag_slim/haiku/rag/client/rebuild.py b/haiku_rag_slim/haiku/rag/client/rebuild.py index 9374a2f4..9193123e 100644 --- a/haiku_rag_slim/haiku/rag/client/rebuild.py +++ b/haiku_rag_slim/haiku/rag/client/rebuild.py @@ -8,7 +8,10 @@ from typing import TYPE_CHECKING from docling_core.types.doc.document import DescriptionMetaField, PictureMeta from lancedb.pydantic import LanceModel -from haiku.rag.client.documents import check_source_accessible +from haiku.rag.client.documents import ( + check_source_accessible, + create_document_from_source, +) from haiku.rag.converters import get_converter from haiku.rag.store.compression import compress_docling_split from haiku.rag.store.engine import ChunkRecordBase @@ -807,28 +810,38 @@ async def _rebuild_full( # Try to rebuild from source if available — uses the light listing # directly, no need to load the stored content/blobs first. if light_doc.uri and check_source_accessible(light_doc.uri): - try: - # Flush pending batch before source rebuild (creates new doc) - if pending_docs: - await _flush_rebuild_batch(client, pending_docs, pending_chunks) - pending_chunks = [] - pending_docs = [] + # The refresh writes through the client, not the batch buffer, so + # anything pending has to land first. + if pending_docs: + await _flush_rebuild_batch(client, pending_docs, pending_chunks) + pending_chunks = [] + pending_docs = [] - await client.delete_document(light_doc.id) - new_doc = await client.create_document_from_source( - source=light_doc.uri, metadata=light_doc.metadata or {} + try: + # force=True: the source bytes are usually unchanged, and the + # point of a FULL rebuild is to re-convert them anyway. Updates + # in place, so a failure here cannot cost the document. + refreshed = await create_document_from_source( + client, + source=light_doc.uri, + metadata=light_doc.metadata or {}, + force=True, ) - assert isinstance(new_doc, Document) - assert new_doc.id is not None - yield new_doc.id + assert isinstance(refreshed, Document) + assert refreshed.id is not None + yield refreshed.id continue except Exception as e: - logger.error( - "Error recreating document from source %s: %s", + logger.warning( + "Rebuilding %s from source failed (%s), " + "falling back to stored content", light_doc.uri, e, ) - continue + elif light_doc.uri: + logger.warning( + "Source missing for %s, re-embedding from content", light_doc.uri + ) # Fallback: rebuild from stored content. Now we need the full # record (content + docling_pages for the round-trip write). @@ -838,8 +851,6 @@ async def _rebuild_full( if doc is None: continue assert doc.id is not None - if doc.uri: - logger.warning("Source missing for %s, re-embedding from content", doc.uri) docling_document = await converter.convert_text(doc.content, format="md") chunks = await client.chunk(docling_document) diff --git a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml new file mode 100644 index 00000000..ff7f37ab --- /dev/null +++ b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '111' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Content that will vanish by rebuild time. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 9 + total_tokens: 9 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '111' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Content that will vanish by rebuild time. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: FdmnuUuXiDv2wcw8tsOFvKMs7bqOjgc9FjgePU52nrwzfJE8q/XcO+5Fkr2NmDI98/O0O7whF7x+UQK9dNK0vF7ZAr1SEPo86qWVOma7XTmxCb68k6QbPT5eJz3f+/u42BoIvS9/WLtXDdm8WQFdvTZUOj0DuNO8/60Yva2Bl72J3mo8mj+KvHq+hTudIwa7Aj6nO9gmUbxScbk8ED3mPAtKYLszLoU8FDdEOhumG7ynxMW7kimxPG8AEzzB3Au9t9dvvB6EVLzh3Q88VYCJPDa8dzzAjM+8/8a9vOkEcDwU0K85T831Ol/wr7yc6R68PmK8u9eiurpS7h+7xm66vCbSRruTdIq8pin8O71DPzwjnX47jTJfu5KEBz32xAe730lKOzPDgz0bMi28Sn0RvfGgsroboZ08w5EaPb1enjp7Noc8X27BO1iTl7te1vm72A9aO7hhfL25U7K8aHe4OvOAV71EYcy7BiQKPGMEzbxyt2U84QJqPHPYFrr1wkA7Ra5Mu/KD6Lprwoa8Q4SFPEiIHzzSjR474YjyPBcBxbviNyC8AjmcvMVzW7xI0Io7yQkUPEIkNjzIYSg8d4ktPBqB0btMDCq9fGfHvNFwRbqV43Q8QwmTOS8Klzx0fOc885sUvFPakzzP8D07yVaavCD33zuBcQq9BbBGuoa0xjn3j6M7t2w7PEz1QjySgaI7c++XPPoxtbvF7Ya8+0x8PEa4vzzybxC8IwUNu/engjx+PTe8J3sBu4nUoDxMZou8ZesgvO5jMDzzLG08LuqAPBQuezsKnHo8B9HRPP1mwbwT3fq7XY7oO6E8pLzLoNA8PMyGu0cxUDyq8XU7V6oju2nfODw9XaU8gxflu5Pz3jxFumM8mdb9uwM0brzXZqG8lsjFvOaaRToy1YU7WgQXvOI1PTytCLy89pPuu/Rx7ruCKsm8lQQZOjh/cbyFKN48wRoivATa2zun+oS89lqcvFreDD30tO67OUp6vFhGaDsXWXw71gOTPCRMgDzRkHk8EW6NO17eHzunYH68n43/vDa1Ab0FiK48hQ2QvD7ZlTwwsZg8Oz5Ruxw8Hz3HDny7okUOPM0e17z0W+0780syvE9GuDsWzuW8zoAtPAfktrxI0Aq8lIE6vJWMtDoc0oE8SyaTvO0Vm7uqO6o8uYSDPdM6Drz2dPk7X2y3vGpPVTzVC7S8QtiUPLFHObtHc4i73LJgPEJM2rphQLg8RyDvO+qPubsjj1e8f/4Uup9mfLysFj27QDFvPLgo/TvbYNO89jskOmfVGDyr8IE7FjfLOwVBRzx+l2+8HlI8O7jZsbulfDq8BRuHvFvRPrzxMjY8dtSKO/jTtTto5Ne8pLWGOxYfELxpKIs8t8eXvAHJ4bvEkPE7vzmJOwoZhro9VgO8hoASvM1Xbr1u4b68DyCNvGaur7t7Ap28WQhFPWHzFLoCtAQ8438cPJxjr7nDEEi7/XYlPOcq4jtf9gM8XxeOPNKXwLy9sG48pXHJvLHMCzwvcSM8UlpWvEsHoLzPbQk8uUjAvKTOqLqEYbQ8jgvFvLlNlTyfeKi6kiHyuyuNI70CXky8wnf9u4D8Hbwdv5a7RJqcuzrV5LukR1a8CPAbvHy7BD2kp9C8kf+COz/KhjzVeai8S2vBPBuHTLorSdG7bAoNvHn30btg4P28XRk1vSMZbTy4trE7eNACvZ3iVb3VNIC7K+PPuw/tKrxP0hm7IlHfu8Kz7Tuu1jQ8j/KTPI5es7wJY5Y8FAsZPF6K9TzYBuQ7V48svDzdnLzPACi8jM+NPGALRz0pgWi8VYaSu8/6i7xjXiS8IO5uO12a9Dx3ZJG8R+GHvDsfFT1HXGA7gUE5PCwLbjqeRiM6tm/POiy4i72DSM68aXMIvG859TyHItu8cRuUPFCWfzztsX089ZClO/Bn6rsxbqM7aaJdum2qa7wYzTe74/BIvFFaEz0/Gpk816UivcqxkDwOghU9lCoOPUPY1Ds08QA9DKi2O48xcDzgCJe63SqJvFYD3DxpGpk8LccCvQ6QvjvFpWQ84I8OvQouIbxp1ZK8oUVdO3sKGj2DqII8kpyGvFyUWrxSqFM8TMTiu4Wh5jsCepS8a+JpPDtQHT2vFMe7YsBIvKp1hTyXzxC8s8dyvP/tsbvGlqa7s+B0vOMVTjwtE548PscFPdjYObybkSa8XDT4PIdexzuTgxu9+18mvBKOnjm5NXg7HtGNvMwRi71mB248lqShundbJ7yHx8m6vePru2VSCTw1fti7Tn35OxZMkTzbFWS8NTSvvIP5QrzA/ZE7HmOJPFHc0zypkeC8oDtePV6JVzxR8rY85TIru9Le/7wW/oG70Lm4vBd2Xrx9AGM8SxP1vN4zcjy0SX48PFvMO+Z2Azzfaho7mwaYvP9oZrwD4aA7VJAAO3AqMrpJPgW8SjSsO10k7rsRJ7K8NhSJPEhB072WpVc7KuY9Par0kLzcJa06KVaQvFNXMbzxaQ68ZC+PPDlEi7q6QAe7a9OFvC30DzyawFk9qeVQvAiDiDyaoq48sTTSO2xsljxqepW7DdH+Oz08M71wxjw8WZJBPbenkTwFVRI91EIIPYMo1Dz3O3o8VvjIu4eqE7svKZY8q3ytNouaH7xj9Hu8ZOwTOimm0TtqdoK8Eaehu4pPtzyCz3A8y237vE81+bxLkgG7MdxZvCgMW7xL9Nw8a2uMvFVwDzuIHvo8bRcdPd5RUrwROKa8P5x1vZfX/zylHE07GxVBvNGP7rxtofS8BXKxO3hR3btQ0/48v/AaPVnR67xn01m8EJllPFGDKD12cNY8kMiNPCR+Bj0QEpu7vm6iPKWqTzw5gpi8nYN7vG6tNbw0hDo7Ke07O0D2FjzJ6ha7Z/LMvGCNhTzAZH28t3cgu6HoATqxLYy5Iam/vJq4C7yJ5wm9Bk1PO8VhZbwyY5c86OIAvLR9RbqE2xY8u1utPCv5XzxuIeo5c2BZPC4Qjjs55+s6LqIRul/HLD0eBmc8VohJPTdKh7yPxYO8iWlqPMwiMLy8Hya8ELjLPLUTDz1P65E8a0IwvF4URzrktAC8b/eMvFxunLtOgyq86uqfPK75O7uLXsc6Ld0dPMZX0zy5X9U765llPO/djLzUCLi6xy65uyC9BLzMvd289NVVPG//uLpGJ7Q8qkZ9vPy3ULxMrQG9jaAsvelYMzvezfo8c4YPPZv4M7w3B1W97Rrku8d1S7z5e4K7zHByPFeNFj2Vc1K9ne2pu96a9rygXw28XuGBvNm/kTtCgEs82zdaPNhBZbw2liS9cU1lPHzGqLwPsTG8MpUDPRcuzTvvM3u8qH0BvPzLSLzbHcy8VPXavCD2mDyx53e6Pw1pvMJK5zq27xA9C2TfvMdbJDysfHq8sgwKvTqDqTzEBHc775x3vHH1Fz2LIrY8ESyRPEi4C7u5Ikg8X5TCu55vd7zbhCS8TXDCuxudxzxyJSi91eADPfsMiTwkINS7+7ACPMPFJr0S4zM9YH8GvXmmt7loB6O3PYHSOx0VFrvGQBs9q/HnPCSue7wvSPo7B2kJvdq9lbtjiGM9Q4HivA/o9LzYyeI8d6FWPC9Z3zzSmNQ8ma1uvZg75zgJ+dM8sFv4u4TgLztHMwA9iniFPPXC+zxDMgs8hidBu1Zsszx8+sQ8YOnmPGxW5jyvdBc9dMGMPEyh9zz6ygI9HEOdPKf7GDzgoq4827HAu5cuXryJfti87oAtvHZigDx+mNI8+5jHPPpGCj3tNdi7j8S7Oh6nEb0KG4a818bhvM1kAz2jA4M8j143va9f5Lu7eTM7v5+evJX2HbxCAjO8gFDWuwcwjLsHjMk8TDc5uwLsUz28is68C0K/O7ed4bu4XC486w0NPbfPKjzIgMu8NB+5PLuDaTxCI/64cn82uRMpIj064Sy8XlNyPCoi2bxoHbS8N7ygu65MJjwTEbO7pTZWPJUAjjyoQGI8jZ8KvcwbjbtTTLK8SNEJvYIRFzsuoKu8i5mkPBG5lDw79yM8s3mbvBQ/s7yQ7Bs9KVguPbgngTvki1k7TyJgO74lOLuGA2c8vEoaPLuMEr3xh8K8ox2QvMIgE7yaPQi8dINwPAYhpDq+IKK8PeI6Pfz9BD227mE8BVIPvN9ckDskM8g5rUAjPPOd0TzXRJo8o/qsPOD1RbwOP/o7zRxEvNXl+bwtBkk8yWAkvb6Dhruh4d07CGoTveQ52TxDhAE7IqPQuhcQhrs5y6K7d8Ovu5cN/DyXa6K8Z/OtvPUx4LtqGFY8urClvFmN3rxiCBA9KoELPASaszoF8gE9Z0CBu+KHU7yVqgk9v0x9POGRA70YVgy8tjXPOygyc7uR7km88tMLPexJIrzmjRa8/QM6PbRzrbzK4sE74ZxeOwYFgbsRGH08HyGWuwc2KD2qII+75GYHN23YeTtSUrw7xedYO9qMgjy2deE7v5TqupzwgLzW2CW8msmsvBNtA72WjLk7sg+0vNj6ujq+bz89YK+kvHIkgrsJuau5eYayvMVcwTw+p6q8O8mguycAAbz0qiw9DV51PExeKzzxR2A8o/BzPFdOxTzg14u8pn8XPRwbpzyTCZA7PSzFu0p5uLwviSE9vdA6O+udTLxWki88TZn3PMNo5jx5PyC92YeIu5h6bTshERE8ywpgPEPhtTsPG4A6BEihu/MPNztHG+K8X5eFOkOWgjx9tWI8mL3VPKftCrzVC4i8OTC9ukzgnjzSM9w8MddaPbMiUTxUmtK8ZVRnPTlioDtM/ly8ROSRO5Wc/Tsb0mq8DvDYuuaXJrzWzca88D4EOyG5/jsyqPg7IHK2vLbuKLw66RY93/IJvZXGIDwd8UM8X4A2vDPB07xahMG8aLe2OzOapDyQ8Z68jM3TvEbTML1MM548ITl5vGhu3bsmBwc9QTTiuyJaD73OyRG8jxEjuxCLGbyDe4y84alJPH5bfbvD13y8660XPD65/TugTke9n76/u50x4bw//yI8zmsEPRxUojw+oUY8fdaIvPVJmLz7wzu7X9/VPH6SCD12Cge71T8bPEO5RjwX4rC8zRyGPNd7UDxi5K86r9aIvIEMxby1dSc8f9uEvJ+hJrz2W0O7BpXyuxEigLwA81y7Vm42vGYRm7vzvL48UUSvucF5Iz2peKe66+pCPOfXHTsfwVk8tx2zvKTcvLxQeIc8b3W0ulYVQjxHruS8E6m2vLSrXrxcJeY8iSugPD8Wlzqgy5U765VMPDRysTz6JIG8OsDoO0mvMrw4nxa9GlyivJ4tWzzO0bK8lcFevNqvxzzgThg8SZMCO/xiDjtdChU9fldsPMbvjbklP9M7PbvbPI5F9LxtKx89lWeyPGP78Lsbngm9goINvUC9JDzQ7lq7U6kiPca6Wryomcm8o/9vvMSb+LpLTHC83lMcvaMLGjsQKq28g/fkPKIWTTy0pCc8T55fvPk+hjyw99G8VoyNvKmEAryivxI7J/exPAYPfbqW10y6Xa8iPHm7drv88PY6SSMEvbVZGbrXl9g8ketxvNGPkzxfbgU97fqku0WYbDwr2Fi8fsz6PEdYZ7wyvp478JQRPCsyIDwi5V28r0IvO5sewbyJh6E8A7k5PIF7mzs0gZ08JuMHO9bRSLwhpKU8lJgCPf8n5rsfwOY8JNvBPDMWI7r2B/G8n6ATvL0ltbyLEhe8BVuFO136JDyFvzo8sQ1xuwcO07tzsqI8Zr2vu2rYPbsEhlc8UI8iPE5/hTxlDBm9wH+aPNLUyLxH/o47v0i7vKfUNzsSWyu7eIV0vBRSurzK8hy9fL+FPIRZRTtftim97r12PDh+mrtew7C7yYDWvJE7frwMXYE8iA+Du8M3HrzTfW4731YYO8iQYDs5Da459EHNvBOT5Tufsns8Vi08u3AJEbxxZw0749qfPFEVxbsfqyE99WwlvRka5TwhlZ88XgXTvFAjQzxc9CC7pStmvGu7HT1pbwO9DRtCvAFVMrx+PFU8PSwkvMYpibtyiwC8Mg8DPEfMbLtn8/+8/tWfPDnmDz0UbCu8ncHTu4WWRzzgYKW7UJAkOoObOTyXSqG8Z4MdO3qW2zyfZFO7zNsKPK99XTydf4a7LDvEvC+X8LtE9QE9+wGhvDjYNTyKexO8eqVSu8alerxdeKA887xPvOR8cjzxU7S7nszJO2jIyTzj+Q89kTsivEDYI7zi8n28tuHeu06jGDyu8sa8VeYju/A+UTzafJc4cPnCu8/stzrPuH47v3DFvIyiwby9eZs833nlvJaR3zzBuDk5Y94CPfwW6DuCxjs91y8RvS7v1bwrDd43TUiCPGPt97xWpR87XWSxPDumGTy+qnU8jo9uvBqPp7z6PXE9WYuYPEZ1BbyNUJY8gNTAPMIIYbtK3e67wfuiu4KtMbxD+cS7k2QePH2xDb2BeDU7WyWAvGENprxUT6Y8XOGePP7alzzY9Tm8vyIlPcvwKbsp40q8JfFRuWWiA7xeH/O8D+QbvAtvGbwGydC7+qpevFiyMbwc8rO7L8GHPL4jgbuK/CC8hqmKu32wpLw9wnK8tjSWPOxODj1tNde6M+IuOgt3FDpNGEM5MoOGPMt2Lrxoon08wOOTOxIGp7zWvYq8FBkSu23IejxCDou8GvGQPIBMo7t4ZPg8RKPoPIUZqrtJU708oRK0PDnXS7yW3ze8OIJOPC3j17zUguk7cCD2uprclDxxvTg9FLUuvMGBHjy7Ysg8QNkIu+i1kTr9oCK8MUB8vHN0SDsWxEq8dZaVPOfqUrwkkxs936/VvFsH5Tz5T8I7jY8HPDVAGj1rS3u84X7OOj02pbyM5fm8dPROvHXMhzw2wNi7GmATPe2VIDxPFMU7Q59hu9XMGr1XWX+8e3Knu85Ae7zo6MS8civLvEMvnzzV9b28e8m4PKAfaDwdbWm8nW29O+xAkryrQYw71ndju6oDvLwpp2G8UttuuzQSo7u1A1E8oGVMvDVnartxZoo6m3+1u8Iokzw8pxg8tS+Tu6fzjzxBdRg9LlggPDCJO7zS6Vm8eHWAvNaKHDxUYne8HHVTvX1surwv6yC9qXfTO3zqobziDgW85B0svOSfFrz+rUw8YvxwvN3xc7zYSxG7ti6zurQAC7yrFKq7Ua01vAYFkru2lms8FptCO5wYDbyxLpS8B3s8vY/zGr2gH647Lruhu7uAHTysIjW9E852vPMBET2BnCa9Y6A+PMsS6ry5BJa8o468Oymy9LXGOog8aOtPO+MsCj294Qy8oZWJu2+D4DzjMgW7/6vWOqvakjyPsXQ8ZwfZPGGsGj3DLsc7pjFPO41AFjxwyou8BqbvPMr4xrtGiGU8+JdHPdIW7TsoJwA7TsITu1mzOzzTmv+7SrNnuwWBlbyJIcQ4a/EdvAifqTxQYRM7Tuw9vIYlB7s28DA8BY9zPA1SFbyYyF+8FQ31vDwbVjuXxoM8xdUuvDjzfzwUolq81kBgPBMuBb1uyLs7COGGO/rLKL1H6Xa8M4QaPVmPGb2Yq8I5NtegvDT1P7yPUgm9/X8TPbikAD1h/N28pzpkvN7riDw3yLM8ffcQvIynt7wUBhU8kWMMvRDZHD2ed1C8tP+cO53uBbopnSO7DF80vHO5jjwXWN85lwYAvBlUSLxzmnQ8gxjnu9ae2LuYJts7eOopPNuxgjvQp7S78MluPKaF2DxH9uw8v3GdO6y+JrvT/4k76etSPO429bwdMRK8TOXKvMvevbsxGlI7oTt1vG+k+LyX9ig8w3XiuygVprzTdmK8I0oIPQBqCT0TbdS8Dy+yOy9beDxqmA28YomNPL4W4Lsq4DQ96tNXPTXHkboAimQ7H43DPBx9VzxttPq8LDiSu1TiS7wW4Ve777dDPHTq6DwjI+c7CYKJPJTFSjoVXvk7HFzHvBVK57w8as08+0d8OZ0itrsyVby8WBw+u3G1xTtDHUE6KseLPFWzGLuhu226i3nJux7Fozxf9wS8Y0BBPVCJFrvsTG854RQ0PA7iDbwmCiC8477Lu4QgRLzvU4u8azo0O1V3HzwkzIa80/MgvAWYX7xKx1a85RrFuy4IBj0HhGk8XoVqO2e1qrvLquK8wnSuvAAM/Dyngfq6J9KwO7KDL7yl2zC9itsTvEJtiLwSRua743CoOwpq2LzArWA7ll4OPYG7HT3m5H08yoJIPHe3B7vlc2W7nlSTvI9Wjby6hzw88qDQPMQJxTvFuXk8GTJcuBMupjzM/cK7ePtHO1nZezup8hk9hJEBO/VFsDzlEhI7TaeSvFPCXD089Iq8iH2JPIOg/7yC9888v1z6vJM3brrNxNI8AZQbvKbRvzyD8xi6GmGbuwJYCz0sKym8/aPRuyPCID0yY/y86RGju5vTLDxyBaK8iuj3vDiLsbyr1Dk8/gP9PHiyRj18CGO8u/EgPKEWubpHUze8Kg5KvJ7V8zy67Je8KzG8u9igjzxwA4a7HMp5OjOt6zuVJvg7X/1lPI/tjDxnaAq8hl9iPC0sN7uLlso827z/u0burTsyRzY7+RukvDDPlzyKV9c8u5K9vFmzy7vDOja9qlwyO+4X4jwzM8W8xKQoPCS9/zqFeCm9991MvGlMarwsrAc80zEDu/gIsDuh+we6mcS+PPKdoTtM+/m8ruOWPLy9kDy5z4w6jtsIPEiJXTtxnIA7mvRtvAVVAzxbx1M8GLjsO/Rhv7ylJEa8XCeCvPhChz0nQZu8/SH+uxRNgzxg2Sm9ccfavPvOm7snEOA8nkC7uyPYzDzUNIE7iDsavHfxNjx0foC8rjwcvNGmS7tU/j074SXbPPi8ZLwt7lK7FroaPU2BejzcPmK8P8U9vDGT2btTxoY83l5wOz4RO7yL/y67cmYXPDbV6zzP0eS8WhQMOq/1PTp/f3688yLZOzz1vTyxirm8UTDeuoE1HT0kXgq8ZyG1vG+ZBrwNZ348tNtivKFQ37zlf9U7JhZAvHfLJb2eTo87+1qiuz3itbwsh3A8E16jPHaqsTsf7/W81PO5PCQM5Tx/Ve67lvvDuw+0WbwgQji8jd2BuyuP2zxo1Cy835udPJ9Jbzw2X5E8sUGNvPOV+rvpLx+9l2qXPO2oBzxJvY27xEWoPHc0Urud5VW9tkvhOwG4iLzrKBs8e3+MPOojBjs18RW9S4GdvOHnF7ytQbY8hgkVve3V07uIcKE8ogmNvOjnEj1tZks9QgbSO5OHt7yJW6q4VrMFPeoNJrtv6aw8anNiPBHz3TznGDE33yaCOpFbGbu0Nqc7aOocPI+ne7vCGay85fVzvDKnSbwkaZU7trFUO9nw4TmprRw8YSAYvMk0+TpoOSy7z7k3PNpmFzxgzK46ANiKu/LePLwoDwa7Pj8avfV+pLw1cUi89X3/PKUdmjwRNO48EMglvHQSXrzDrPY7lzrAu7+2Bzw/oSm8dlYPu0VyWLy81xA8wcS/PB3WE73tz2U8P9+5vC+xHzyzBLs7fvUwPXZGBTxEOTo8nNnKOxp/Bj3cvJM8g9kKu0sqfTrXgYe8zdwGvNgOujwnnMW79lefu85zl7zjuTY8ay8JPOO8/bzo+KC74NODuz8ZnTwKIM+8JwExvJhWAj3/0xw8nAszPVkNmrz2IKU6BQXQvCx6HLyO1wE8MDP8vJXsHD0Gg7g8GJ0NvCqq8zqyouY5C1/hvGV0I7wAefo86dRqvAK8+TsIJBQ8gOxxPA0AAzu/gUo8mFK/uw/sBT0hxhc5OXC8vEjiJL1yPJw8zGZDvGS9Jb0htJU8F1OUPBcTHrt4kBY8vl7QvBIMurtunQe9ybbFvBbhFj20h1G8k+tivCX+GL1zUcE8ocalvI9XkTp6quk8uwOXu1bc0DyCaTe8FlsiPFRAzbsorkU75MBFvBxWojzPxmw8+xYOvYRcZbwOGWm8oJHrO8i+krubjp48tAFTu+oidbyC3Pg7EqhDu+hbuLxOH5g7eczAvG8MyzhOfIE8LioZvInf3DobnRO9RxuGu5ETFz1DLRC9zbycu4tNQjxmWuS8nR4WvB5BhDyztIs7KyFUvNRayzzfHw89IpWRux4kMLtBvc88W1KHPARGcTss3DC8XfK0PEQjET1090076HvFOnGf1jxcQiY8Ps9Pux87ajwG19U8Yg4VvD113zykkZ+77TSXPDL/rbtY0L288861vBbfYju+8w28cb1ZurB6VLwS/ye9Rne6vIOsjjxpAnI8W2RAO3VJXrxk9TY7rOAFvUnktjz/4se8nfZZPHaTSTxJakw89J//Ozc9tzwh6cg7I2tKvPHfwjwI3No8up0LPBJZRTpGyQY7nLqIvBlw/bvceL885QJeO1et5ryBpv+79u36O1E4hjyDyja8pNJKPE/wUTw5agA70E8bPTRp3bz3O666aJVAPJH5FL2r7gC88SMLN1vpo7xt05g80sD6vORNxbzfZ5K80+rUPEsCBT1V2gU8f+BtvCda5bxXOYg6jYXRPMtwVbtbCrm8Aedqvbdhe7yaGNS8cCJBPIRWgzy+USO9SzftO2eytrtb35q8X/knvFy8CDviHAk8JOmUvLoBlDxzegq95djSuy7c4rvN9FO9gAxFPTXfLzlPRlq7/UCAPBV3DzwXmPe70+tGOSjJJr3mEae8+7F/PHDx3DzlCNS8U8XavADOBz0HF6A8e973Ox+0irwcaoE9F3i7OmuJ1Tzrsvu8vUUvvAPlMDtkQqy5AHezO3Lkhby4qs83hwxfPB0IJb2PIPO8dJAjO/Z/5zssvxa8ykP9vMsa/rxT6XQ7yTu5OqyqO7kIdV68UlghvKQHIzki1xi65+mfPCtDCzw62Zu8vm6DvBmdBjwFQLK8QRDtvKEsPjyAicE7vWceO01LPTtLmo48wHDTvGwiHjyk56O8gxlMvH22VTsiaOu7pgG4vPEVDTtINOa7+jeePFiinTtuJ4S8RcStvKDou7xQ6bK8a3T8O7j1gTsNQ8c5e6HJuhnqMLo1weo7tOPnu32kGD2AgIK5CTzDvKNQFroHLeG8ouaKPJtgAj0FkqW8JvFfvG7tjjupTOA8CX6puwbi5DwLfCS857n+OnK33ztZRcS8+Cu4PG+N4DrubL28eCJoPFtR77uRWdm88HgjPLUcJr3Ynye919yPtz1QWTvfP6u8B/24vAuuHT3+9PA7vqRkvI8m0LzIfTm8kkGtu82NLrzhUew7n8sgPJkskjwF6gE8StNgO7p3GLyg89O7kIIpvMmbVDx/ykY8r8sYunZorDvT2la7Y/XRPNBJnjzXfXY8kkoZPFIEAr0IgoQ8xzTXvHkapjwhYzU6kaeavH6zgDygGJI7Xw1MvPWuWT3thHC8B5aIO7T/7bvAnYg8rrUGPfAphjwVHMI7D4acPIHXsrzHeYy80mWKvA3qBr0HDg48SbqxuNoEHDwnd7e8ruRxO/1xibws15S7d1KMPOAnL7zmH/q71Wm3O9+DI7zuw7A8xcxtvNkQizxS0pA8Y8OeO29pk7txVAW9YD7BO0RUwbzmeem7iOnkO/E+Jr0boFW8LIS9PAeJSrwWR1Y76G+nPBKSm7q05xi8KL//u8jgOLwZWze9XADLvOn4JL3zx7667aa2vPJoMztm+4o8BL6dvASwVzu/ByC8bakevYaJCruSMgs6ZPa5uONqtbv7W5G8dhUzO/TT/rseNMu8hvOAO111nrwEdw88vm0ePdCYGj3TJcs8mvKjPDtvI7y5ZXQ8fGC0vMJkqTxSM5o8kAoHPRR60ruOcju9Di8jPDzMjrsHDJg8Y6EavQ40hTzkBJs8kjZ6PBimIz15qC+9TBc5PCwJIzy3mVu80N+rvG77SL2f9Kk81OemPI97PzzlANM8cXgluoAnpLq1WC48WHkPvOKYqbzsj8Y8bA5TvAC9izxr7tK8iznYvGvwCbtMU6g8LjCiPI6ehbsR2m68UKnjO7+jODuV3Pk87NDRPLSNYLzLbOY7X6Mfu3swBT3aZsE59GfzPGOzN739Key62ZZ9u9eX3LvpNFi47zzIu720QLwnvrw8SqKMvOR+Nb3Xkgu8mmfKO55LB70+ERE8bIgmvHmcbruvpBA91mkhO8uRezv22gG9KPiPPMSI5TxsYQG8dT/NvJt9eTtL70m8BmmBu83gLbs27yS8xX6ovLGddjs6Cc08uu+cO82eJD0vYBe9qRitPIP1gDpkOp87R9spPcw0K7xqg/k87CqJPCPKgryTMBG9GAzBu9j9Lrzv05O8VN0lPI60XbySIfM87652PIuftzvpEcS8Exq7O0aN5zpLO3Y7C3EgvRldEzsdO988zuBgPMmxVzshK4O8ZCWYPEXWXzxJX4+8/RazO67uM7093d48bGFuOSCDvjmDY2Y8t1dYuh+Fqbz2FC+827j1vDLSmTz7pyA6bWiRvGI0QzwXyKu7yFYhu8E4nbrnDWk8vLyqvCl3xzynRXU8/kNCuj40Fjy5u/a8j1L/vLYCqDtMO6e7aoJYPGu0/zwBqlc8YrWevAWz+Ttdnos81GgdOYwAjzutdHs8FxRSvDy4ajyfg5y8jIOhPGawY7zRsHI7dtC5O4iI2Ltz36q8bT7Qu7tVuDusZky8jjRXvcocHzysXsk8UkSBO4oYAT3DXZa8AoH/uvW94zyMJMs5oGQ3PFcaoLy5t+E87W10PE2DxrwkLWs6KoL6vPxWDLtTagc8jLOHPD3WkTwQEH48MtRfOsbberxkeg882gfgvNrVLDznhhI8UOfUOiKWWDxiASC9t1T/vBmDRzwKcqW8dL3AvEY5GTxqlOs8BiyfO0BMLjxob8C7FZK4PD2J6Ly/UYo8AM4sO+c5TzwW6Zg7DTqFO497ADzJ80q8JOWCPCbkNzsoozC9OhY7vXKl0TzvjkE9x6T/vMFdnjz0rZw8B5ftvJAsSrzzdgq7LMOjuxABHj1cD5u86ze6vL0Mq7z3gLA6kI4hvM5YKLxA2B89A38mvCm1Ar0GJsq8aCEJvPPuADzx7Ua8vu+xOrOKDDvUtjm8LJocvfveh7wWFRm8z0VRPT9sRrzuCNe7jTe6u+21Gz0a54o60YSmvBrcT7yK9648A5pIurgmS7y9ynY8xUMRPCTGND2YUWu7SwrJuzSxlbyQ7UU7iKQDPL9cgrx+lQo9b/x5POnuXDzzEYc8ALvAvB7MXbw1KAM9v9Q6PC+rr7te3IK4w0c9vGYEkjylQGI84RkCvIN9fTv+QZE8RucFvUq8ujyXe5e8a3gxu4jE/zyAioc8fsq+u+nZMj17Uyq8a8W6PJM7GDsb38Y6aJ6evCsTC7zm0G28wX58u1OQXDs6J0i8D+kBOz/kn7zSbKE8bDODPLLoNbmubUY8ZYAbPOyRkjsbS927d6yHPJBDbjyzIK67Z3axPMSoKDxpCgc76au8vOG+hTxvqaC7hbnWOhL9FjzaXSk5bE24PMGQ4zu/2548bbm5u1huDTwDTi28bN08OWbVCjv7yo67PJ2PvL4wK7rQZfA8MsY4PC4wErzvslm8+vFIu2QhlLzF76k8JHkuvKr587uWZAu95JIEvemcYbseMFe9iNVtPBu26jziTA48zUr6OvZn1rw/jqE8/o3+PEf5oLzTbgI9es4EPVM/hTvvKho71cOFuxafVTeQmQC8lvirvI1HzDuxiqa8ekKnu+n9Pjxb8028nB+muzoc/bxfn7U5LPCIvA== + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 9 + total_tokens: 9 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml deleted file mode 100644 index 6ff93083..00000000 --- a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml +++ /dev/null @@ -1,42 +0,0 @@ -interactions: -- request: - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate, zstd - connection: - - keep-alive - content-length: - - '111' - content-type: - - application/json - host: - - localhost:11434 - method: POST - parsed_body: - encoding_format: base64 - input: - - Content that will vanish by rebuild time. - model: qwen3-embedding:4b - uri: http://localhost:11434/v1/embeddings - response: - headers: - content-type: - - application/json - transfer-encoding: - - chunked - parsed_body: - data: - - embedding: 9SWouYfkgjuhjcw8mOuFvEq27bqPLgc9dcoePVXDnrwEUJE85YffO0uPkr28ezI9F420O1vVFLzThwK9yr+1vJiZAr1t2/g8QkChOmO1ZDljDb68wasbPStaJz3dE/o2G1gIvTtNS7tgG9m8SV9cvYbKOj3N9tS8d1QYvXujl71OI2s8NtyKvDo+hzsnmAG7E1ynO3egUbx5h7g80z3mPJvKabuAC4Q8vC5rOg0SG7zeL8G7CMexPBNTEzxl5wu9AzpuvOt+VLx5uQ88gfiJPEsWeDzcv8+8zrK9vEeHcTwKxKA5f6L5Oqw0sLxlax28vGq7u2SNuLq3lQq7SEe6vOa9SLvBvou8nHD7OxdfPzxVCXk7kyNmu2XiBz0VGgC7oa1SO0Nmgz3Pmi68hWkRvdDLtbo7eJ4883caPZKKpDofP4c8ByO/O+himLuXwfq7FBNhO8WBfL3ShLK8qta3OuGEVr0iQcq7PxwLPJcEzryE32Y8C91rPHDnLLo2sz472wtFu/u417r43oW8Lg2GPCDCHzyk0R47MwbyPFj/xrt/qBu86qqdvEnmW7zyg4o74e0TPPe2ODzXIic8UDMuPH5F07ugaym9w+7GvKZfMbpDAHY8RVXkOW8AmDx2/ek8mAsUvIH7kzwHpzs7NNqZvKP53Dsb5gq9tP5GusGn4jkFv6Q7N/k9PFbbQzy+waM7k2iWPMLStrvIooi8FUZ7PH6tvjwpRhG8doQTuxRCgjyjFzi84pQPu1itoDzQ3Yu8EuAfvN98LDxSSW08HqmAPAQ5dztZ5Ho8+BbSPNgiwrwuN/+7aqDrO3Cpo7yRy9E8Yp6Gu2U2UDwGkG07u8ciu7JWNzx3saY8G/Dlu9wc4Dw1q2I8hI35uwfYcLwREKC8HlPGvCp9hzqMv4g7pGQWvG42Pjzgwru8HmnsuzEl67sgz8i8Ki1UOlBGcrxNTt48rRIivFvr3DvreYe8MkGcvERqDD28N/G7cLR6vCrfZDtYtoI7oTGSPGeCgDzdOXc8BmCNO+55JzuJoH28XHD/vPaEAb3HYa88lNuQvO2mlDwx/Jg8g7xJu24ZHz26cXu71iQPPK/71rz3AOs7kGkwvMmktjvxtua8/O0wPHmZt7zjdw28zGY7vCKltTpphoI83haSvMBHnbuFBKs8frKDPbM5Erwf0fg7qPO3vJRIVTzz77O8QKWUPE2wObtcWoy7CiZhPPFl1bo7Lbg8kvT1OzN1u7vTtVW8FHAFugPRfby2WT6711BuPKO8/jtpVdK8Va0JOjzgFzwui4M7cHrNO/3kRzw5tW68NF9CO9PUrru2Jzu8gtaGvGOqO7xPCzQ8kASHOzVxszu2t9m8daWAO7LUEryj3Io8yeyYvPt56rt3pfE7BJ6JO151abpojAO8UokPvFlcbr2Ymr+8iByNvLhQtbvaspy8H4hEPQexObqubwQ8gOMaPEE6nbmvk0i7l+4jPHml4Duu8QQ8tdyOPGFZwLxfEW88Xx7JvD3jCTwOgCM8hOZTvPEOoLyFrAY8umnAvPCisLo5h7U8HcDGvD+mlTwzLrC6y6H3u1UFJL1ijUq8hPn9u0NUH7ypXpq70Z+Su82M47s2mlS8utsXvNZ3BD3yZtG8JG6CO3c6hjzyB6m86O7BPKLPMLrk/tW7obMNvM0Q1bv5SP28vjs1vcFLbDy/qrM7KvoCvaMsVr1Lj3y76f/Nu2OyKbxObB+7aNDfuyUz7TsTvzU8d6GTPKpetLwVWpc8UzMaPIXR9TzofOY79OwuvJfcm7zFQCq8uUmNPJ5bRz0Mzmm8M4+Qu9SQi7x0yCG8clhqO8fT9TxWLZC85uuGvPOSFT2ovVw76Bk3POF4eDqfkwU6M9y6OiOoi707MM28SrILvHA49TwaItu8aFGVPBKOgTytOn88GkKpOxGP6ruLg6I728lcurjDbrzNCjm7raBJvOccEz2qQpg8VQEjvWlqkTzMQxU9KCcOPRlw1TuzyAA9/k2zO+dJcjwg2qW6UV2JvMf63DzM4pg8hX4CvcrFvTt9i2M8rz4PvUEuILy0bZO82IxcOwdZGT0juYI89+SGvBXmWrzWG1Q88LbmuzAh4TtFJJW8vPJoPCuRHT2fssO73adLvAG+hzzpCBO8dOxxvCMbr7tBy5+7PSx2vJkzTjwjJZ48p0kFPS8DObx2fii80J32PIvnxzsHDBy9V48kvJsa0zlh43k7ag2PvFlDi71d22s8/FSZuvPdKbx7rL+6udbpu/x4CjxGfdm7vJf7O7qxkTwHBGe8KNSwvLA6QbzXvpM7bsSJPKeq1TzM1t+8Ym1dPRPMWTzeVrY8B9Equ04h/rz4FIW7zPO3vJrJX7wtkmM8u6L1vIP1cjxsA388e9XOO1HwATzI4xc7ttaXvACJZrwpHKM7MWIEO3c1Lrp6cQW8s42vO4+z9bv7erK8VZ+IPIwH070woVU7sfE9PSaZjry1cK06BpWPvEeZMLzQ5Q282jeQPDopgrp7+gW7kPqGvCkxETx1kFk9wNRRvA5kiTyYBK88tkLSO8eIlzxsf5K7g+35O4WQM70DuTw8YLJBPdGgkjwmFBI9YJcIPWDI1DzGSXw8gzHMu1elErvsv5U8Al9Mt67WHbwxPXy8bmPOOY0V1jvKVoO8EKWjuwJjuDzY3m88FbL6vLaB+Ly5Zfu6r4xavABCXLyQk9w8qVCLvFyGHDvxqPg8G1AdPWy3ULzrHqa8eod1vdhzAD2fJFE7xCxAvHWE7rz9kvW8HcmuO6kc37uLkf88UhIbPSqL7bzEElm8Tl5lPLQKKD1bB9c8hrSOPNzTBj2vPJW70qihPK3FTjyE6Je8jmx5vIu4NLyzqTY7JlRGOzHPFzx0DBW7hnbMvLHBhTx8kn+8qXAau9GdHDqvjk+5P8m+vEvZDbx7Bwq9ZZxQOzsIZLypBpc8cB//u/ehDbpynRY80YmtPIkcYTzBOcM5cwVZPF1sizvoNuY6VbHTufeNLD24m2Y8V+lJPRxyhrwoeIO8v4VoPEulLrzAuCS8KA3LPGi3Dz0p3ZE8QUguvC7/ZjrkdgG8mvGLvEYdlruMoSy8kg2gPMM7QbvL1s06dBcfPEZ90zyRitI7t5tjPOIMi7yx+ba6cM67u+FhA7zvit280x5WPKndy7o8nbQ83Jh+vEKITrxnEAK9x48svRrfKztk6/s8l+IPPc4NM7y+mFS9I6fnuyVUTLwcNX+7foN0PIaIFj0K1lK9yUOlu7ZO97xzMQy8JV+BvCg8jjtwBEw8nC1ZPO1qZbwqeSW9LC5oPPGwqbyJYTS80TYDPZ4d2TuoXXy8la39uziiSLxJicy8TbPcvGVfmDyQ5Ie68vxpvKaF7zoH5RA9e3ffvIVaIjwbE3q8s4UKvTd6qTw+dHg7A/F3vBwPGD1iNrc8oM2QPEh2CrsVjEk8HBvJu0d9eLxEViS8uynBux/OxjxaLSi9QHoDPbTRiDySStS7DmkAPGZNJ71KQzQ9wlwGvWcL6bntVC25zJXQO1lyEruxAxs9wjrnPNljfbwIAgA8OzMKvWLblLvag2M98IvhvFFb9LwqL+I8E+5WPEc63zzya9Q8psZuvZIKk7gCotQ8c2n3u+sLOzsXzf88Lc6EPAsy+jx/wQg8De47u7dcszx8jcQ8RrjmPNXF5jw2Chc9MgaMPAWQ9TzXlQM9x2icPHb2GzxYc7A8eeXCuyx6XLwT1de8m3AtvCZIfzyVxNI8EdzGPL43Cj0CM967Rm+4On/KEb1Tn4W8X+3gvM7AAj3sBIQ89Bc3vZI757u+kC07E36fvFRjG7wnDjK84AnZu5FMh7sa6Mg8NYI6uxrBVD0RwM68F8zBOwEd47srCy48qJ4NPTODKjwGncu8luy4PEv3aTzilc22saotuV/RIT0GtC28wUdzPA1g2LxFA7a8PC+eu7eZJDwBWrW720pYPKzMjTw8t2M8EjAKvYF1i7tVb7O8i0sKvcgAHDtqnKu8/IukPGdhlDx6SCU86kqcvGu5tLxbexs99vktPScogTsu+Vc7obJbO98ANbvjPGg8V2AbPJgHEr2Nq8K8MrWPvOkKE7wyiQi8xGFxPNOVmToSbKO82fY6Pah4BD0rWWA8uI4TvKiPjztW4aQ5OK8jPGkN0zym15c8HBetPC05Sbxjtfs7rCJGvOp0+LxtVkk8MOojverahLuJA9w7iToUvW6j1zyew+06KDrxupS6f7saI5+7p9yvuwnn+zxAsaO8HKqtvL6e3LsNg1k8jwmkvCUT4LwZAhA9s+UJPAu/sDqs8wE97F14u+AvVbxAiwg9W099PHobBL1niAm8vrvSO4E0eruJsEe8howMPZCKIbyGxxi8+YQ6PU47rrzpUsY7dmlmO+twgLsOPnw84kKXu+naKD1PpI277PAsuQu7eTsmgsY7idVfO4GbgTzRs+I7FG74uo3YgLzkZim8AcKtvFmpA71sgrw7U7KzvAeJpzozgUA9va6ivB8ngLsl7Ki5iRmyvPSmwTxQ3aq8zLOduyCTAbzo5ys9MaByPHN5KzwVmWM82PlyPHwdxTxqtou8Cx4YPVAfpzyueZY7zazEu5s2tryGjCE9jWA7O3gVTrwreDI8tOL4POed5TyPqh+9vs2OuzgfezupdhQ8rrhfPOe3sztz0Hk6A/Siu5rdMzuejuK84NeYOrT+gTwY8WE84tnUPC0DDLxW3Ie8m/HFui28njy6uNo8Gb9aPfVFUjxphdK80BtnPWaOnTvEIl68NTCQO9mz+Tt1n2u8b7/Buu5wJLx8VMi8gsgBO1aQADwK6/k7EJ+1vHdAKbzYLBc9eTgKvWecHjxOvUQ8I0I6vGbj07x5esK8HLOzO9HHpTzM95+8WUXUvElpML0/V588ubd5vITB2Lut2QY9y67auzAND71JJBK8MF0pu2KNGrwGeou8nfBJPO6ZgbvYDX28pj8aPK1i9DttyUe9A63Cu+FJ4LxuCCI8UaoEPT8Gozxpq0Q8G2eJvOacmbxgmzi7BMnVPE5NCD1+jwq7XtwZPAJJSDySa7G8pgCGPGyIUDwNusU6BuuHvLsrxLyYPyc8y8CDvFpXJrzqJD+71Uf2uyJpgbyjklq7Jrc2vIKEnrvBRb48mm2AuTGiIz2cQq+64HRFPK2oFTtrC1o8wxK0vL26vbx6yYc8m5GxukcoRDwjQOW83my3vDgXYLxjluU88M2gPOWyizoHOJg7tsdMPA4BsTyZ03+8qebnO1OsNrxFhxa9ubqgvLOlWzwfNbO8QuRdvOFGxzwAQBc8aH4EOygpDDt1JxU9eb9sPF2L4rm+VNU7JtPbPIUY87woPx89JmCyPA9+67sPdQm9QyUNvSt/JDzY9V67/5oiPciUWbzDIMm8KnFvvMCdA7tfZ228uzYcvWroFjvO+Ky8NHbkPIA2TzwYaiU8Lh9jvOwihjxAUdK8Wy6PvD2ZA7yhDA47VNmxPFEVZbptiy66XzolPL0WfbulvOA6cTUEvc6kILqvbdc85oRzvEVGlDxWrQU9wKOlu7Kmazxjalm8u9L5PFaPZ7z83p47V2IPPInfHDwFx1m8nGwqO0SWwLxq+Z88REg6PCH6nTsot508JosFO7VqR7yGA6c8v9UCPaxY5LtECOc8u/7CPPulIroBlvC8Lt4VvF4LtLzcIxi8EPqDOwXwIzxW2To8dKB1uxYZ1LtOTKM8ZTKxu0rSPLv79Fc8djMjPHt7hTyxChm9BRGaPF/qxrzGa5Y7jya8vLwgQjt8ECm7XTJ3vKuAurw6Ph29o1iGPF+0RTv5aCq9It50PNEOnrsMVK67HC3WvLdDgLyY+IE8XdGAu9J1Grw5DG07HJ0YO5dPbTsM6t05FsjNvNkr5jtMYXk8VS46u3tTDrxPof86ZxegPIkPxrsyaiE9J3klvRAa5jwsJ5889kvUvB7rQjybtiC7IgZjvJ2JHT2tUAO9BXVBvE+aMbzZ8Fc8qzEkvJngiruUtQC8tEL9OwCobLshEv+8bYmfPERWED1AQy28q3vNuzglRzysYai7cjMxOidrOzypfKG8RVopO4u+2Tzc9la7TTkLPGn6XjxGT4O7VX/EvL1P87vWLQI9p06gvBZyNjxm/hG8w3BauyEserwi7J48+S9RvPbWczzWM7i7OxjLOyJPyTxusQ89EecjvL7aIryrEn28ijDiuzBEFzzYm8W8G50ZuxnnUDzEP022ISbCu/hQoTpXzXg7vUHFvN+Ewrw8oJw8403lvOlf3zxYRp45F6EDPbuH6TtfcDs94V0RvVoe1rxxfm85e9OAPHuy9rxCRBI77VyxPMUVGDyconc808JsvHYBqLwthXE9IvCZPLOmBrygfJU8Dem/PLlUVbtikei73puhu3pCMrzB7sG7oWIdPPj5Db37Tjw7nLKBvFnQpbzF5aY8XIeePDUZmDwz/zy8180kPSX1MLuaEky8/HCBudMpBrx0tPK8fWwYvMdfGLzdyM67m6RfvFoKMrwgYrS7sZmGPI2yf7s/YCO8ZsqGu0rlpLyDknK8ZFKVPAD9DT2zZ+W6XHInOhWJFjph10Q5O9OFPCiuLbx+in086hiSO1O3przm54u8WegLu8OWdzxBJIu82euRPOeFpLtdEPg8wTboPI2fqLu+27s8jGGzPNqWS7yzpzm8vM5NPGR52LzXo+o7g/UAu8JelTzUYDk9HgwtvOZ0HzwTrcg864oFuz8XmDqM7SK8swGAvHiuTDtHK0m89duVPCeiVbw19Ro9ySrXvJs/5TzYY8Y7Gb0IPPwTGj1HMnu8cLbUOt82prwjove8BDROvH5BiTwbRNi7ijUTPRTQHjya7ME7mrBhu/qAG71W93+8DtKjuxEyfrz5t8W8HY/LvPGGnjyinr28pHC4PNoqaTyV02q8iwe7OziIkrzFDZA7Ri1cu9TxvLzmmWG8SKp0u73bo7trH088RrRLvAPUarvsgIo6bIC3u2WBkjytzBg8nbSVuz1tjzyJ4xc9tRggPGeMPLyrxVi80OeAvGkfGzwtS3O8epJTveZMu7z8dSC9voXPOzkworytnwG8+lMpvDIEFrwpFU08w3ZwvD1kc7yUfQ27SV+9uoFjCbwHm6q7Tjs2vCgSkbsTAWw8q/9BO/cYELzVHpS8tZQ8vWRLGr3GJ7M7M+6fu0IJHzwVbDW96Gt2vGZWET2DUya95W4/POuK6byHdpa8Gk+7O236E7kyO4k8iQ1TO5igCT2QiQu8aJaKu1Wv4TwICxC75vLcOtg+kzw6zHY8ejLYPA2ZGT1+bck7DJdMO7vrFTxrFYu8KtfuPGtNyru+rmE843BHPbBt6zvJ9/w69zoOu7bROjzlaAC81yRiu7gulLwYFCk5XSYdvAsZqzyblw076YE+vGm3AbvEbTI8+Bl0PIA2FbyrbF+819z0vHicUjvPQIQ82oYwvC4AgTyaxlq89+ZfPC/JBL2gWcA7QE+GOxcFKb0sAnm8w9IZPYNFGb1jpgA69dOhvIsuPLw5OQm9cm8UPYu6AD0ZZ96839xnvJNghzyQ/LM8HNgOvA1Mtrxf8hQ84GMMvYEQHD0q6lC8w36eO+JK0rmcoR+7zYAzvCepjzwpGt05nMn8uw5gSbyOWHU8gzHiuwlR2LuwM9g7/XUoPCGngDv1sLi7cc9sPC6u2DwE0ew8sHWcOwcTMbudTZI7/vNTPKpn87xq3hW8Kt3JvDZgwLvtsE47YeR1vC5697zBASs8WKnju5HFpbzoU2a8AKUIPcmSCD19dNW8jTWzOyxveTxgxg687UCMPBUD4rtcojQ9D59XPX9Ilrr4n2Q7tVnEPBpAVzxg4vm84WePu+jaTLzPXE+7g0hDPKMz6Dy4ouc7ESiIPFvKPDon2vU7CH3FvFb+5rzcIc48mnD1OGUFtLtY5rq8FJk8u59xwDuWkVs60kuNPOdXH7tTT2C6Aw/Eu4BAozxUlgO8RgRBPa8JEruogC05QJE1PM13C7yxLCO8OgDNu8GJQbzgXou8kbU5O9O/HTzlkYe8x0MjvPtyXrysKFa83UjFux1HBj1OWG08/ZJrOzhFp7uVgOG8hpWuvEjD+zzhFgS7z+uxO3+QMLxQKTG94qESvNNziLyqb+a7XkemO/cm2LxG4GA7e9QOPXhfHT01tn08y1tFPN5CAbu+rm67uBqTvPzFjbwn+Dw8/CLQPNuovTsNZHk83ikMuL06pzzv6Me7t61NOzXzgjvMJRo9nrL1OsF5rzxiBRg7oUyRvLHbXD2ij4m8KOCJPIuE/7ytQtA8uKz5vOpZWbp1+9M8+k0ZvOXKvzw2MB+6yCSXux+ICz31sym8URjYuwM+IT3CWvu8jBufu/skLjz5Y6G8EC33vIl0sLyYBzk8Ovb8PBd+Rj1C62K8gdYdPFWar7o/9Dm8VWtJvBFQ8zwHKpa8k7S8u9arkDxZz467vmmHOrR16ztdMPo7HB9lPH3HjDxEEgu84gVfPNB3Obu6Ccs8mG35uxcgsDsFzz07KqmkvF1ZmTyqCtc8RAS9vLnJzLsn/jW9xiwzO/0d4zwd1cW8rD4pPLRJBTu8cim9wlpOvCHlZ7z06gc83LcEu7Z8szsvEwi6TYW9PLlcpTsMiPq89mWXPFj/jzwdNZc6w9EHPP1vXzvYcoI74q1uvFT5AzwDCFI8iZLvO4O4wLwch0W8LOKBvKoQhz1Mzpu8j2oAvAtBgzzFECq9aDbavCXInLuZPeE8+MC5uyf8zDyBw387XOocvAQ2Nzz9kYC8jIQcvCr/V7uw7zo7Z+/aPKOWZbxwzFq73agbPYs+ejygvWK8qANAvLCo3rt0w4Y82uxxOyKIPLxzqi67KhoYPDHr6zyLU+W8ZWgWOp4CRjolJn28KNDVOyqEvDzxdLi8qwzaum29HT0DGQq81321vKXXBbzRGH08lkFfvIXw3rydotM7OUVBvBItJr0HX5E7q1aju038trwQyG88DaOjPE7vqzuV8vS8ix+6PMx15TzI1Oi7TDbGu8GeWLxSxTm8dy6Fu04c3Dzo0Sq8UamdPGfAbDytOZA8xfuNvPe3/7tJOh+95nqVPFGXCDx8V4u7BsamPAVsYbsKM1W9Y8XhO19firzb6hw89zaNPNrI9TouohW9PFScvEAHF7zsvLY8h4QUvbB31LsNzKE8xtKMvCObEj0DiEs9TxLWO92zt7zO6kK5U2cGPSNIIbtII6080JtiPAOw3jz8EYE3Qot7OrYoErue+6g7Nn8dPNNKgLt/aqy8CBd1vHvqSLyf3ZU7q21YO8TEyDnTOR08pK8XvBo+AjspVCq7Rz04PNvvGjzsw7U6yQeGu/nNPLxaUAK7J4savVyTpbwoHEq8aFAAPSptmjyvRu08Zo0lvM6CXLxoZ/E7kJPAu+rmCDxagSq8hhYUu4brWbzA6RI8C8PBPChOE734IGU8mh+5vK8TITyw5r47H0AxPU0bAzzXIDo8sbLLOwigBT2s4ZI8JEARu3xHRzqKDoi8Ee0EvN4oujzTHMm7dKeduw4DmLzlYTg8GKcJPKcC/Lx05Zy7DrKHu3pXnTzpxM68vlYxvCr8AT15uR08TFYzPfnVmLw37ak6FOTQvIeZHrywHQM8tif8vB+aHD2RTrc84NoMvGOb6TpHpxg6xyHivAWwIrx9W/o8PVhsvIDx/TskXxU8NgNyPB3X+jrSdks8Lhu7uxsABj1f5Ki3IA+9vBuEJL0mUJ08VKVDvDpTJb0jAZU8kLiUPELiIbsDfRY8YjrRvOd1t7uXjAe9vX3FvO2RFj0IgFW8Vm9ivD6rGb13KsE8hnKnvLT/ljo9Juk89eqZu73KzzydQTa8oiwhPIdCz7sK0EE77ahGvPCuoDxbaG48BOENvWZlZ7yntWW84nvoO4J3krvlFZ48yOtju2qMc7zirfQ7SXxFu9AzubyPFps7l6u/vNcOXznciIA8PTAavIw44TqtchO9RBGHuysAFz2YKhC9CuSdu50OQTyTvuO8bk4XvG8HgzwICo87/jdUvFbYzDzkLw89xiiTu58HMLupi848vd2GPKmPbTssXS68FWK1PGQKET0f80k7RPPIOvnQ1DwoJyY8T9RMu+ozaDxU9tU89cEUvKFv3zyikJy70maWPIYTrrv27b28pGu1vL9MYDvpiw68dNhLurFcU7yICSi9+X25vFAxjzxhF3I80KNCO3J6XrwVzjw735AGvWawtzw02MW85A1cPHtJSjw/BUw841gAPFRmtzygQcc7iCZIvAK8wzx0ENw8fd8MPF0XMjpw5wg7b16IvGDc/buBUb48UipiO2jq5bzKKgO8XOn+O322hjy/eTi8kbNLPIjkUjwbgAc7xFYbPZdQ3bz0TK26/lw+PGmOFb3xdwG8ucjGOF8npLwXy5c8SR/8vLW8xLwmgJK8lxPWPBchBT2dOwc8ZiduvPN75Lzl6GY6j3zSPPyMS7vr6bm8KpxqvVo4fry5wNO8qz9APOnLgzxC2CK9zWboO3qcsrsqkZq89ukpvJkOCjtBNQs8aqmUvOURlDzHCwu9cXbUu8iq4rv1+1O99whFPQnsIjnweE+7nNJ/PNoADzweQPa7PjuJOQDcJr0+bKW8oHaAPB2i3DzRYtO8KGjavOmjBz0w0KA8omj1O04vi7zKrIE9+OrROvCJ1TzDcfq84PUsvPaZKTveP5W5neqzOySmhrwoG0M5/XFdPFdgJb36/fK8wOgcO5rG4DtYXhW8KbL9vDJc/byq43Q7R/e+Ojw2PrkWHl+8ua8hvGBZQzmCCgq6KvefPKBzCzyDq5y8dnmDvJQ+CDwmBbG8XxLsvEECPjxtLL87f2UlOzNcRzuPMY08CZvTvACgIDzW7aO8YhpOvCdPYTtDVe675e63vO2oGDt4/+O7QfucPKQNoDuSGoK86AatvIvGu7xcGLK8siL3O+mtfTvG57M5VtjLurAsZbrayeg7ZFPmu0veFz1c+Ju5kvXBvBMQ+rk3QOG8eyOKPEDiAT2PZae8VWNivBgPkzsMlN88lhKtu8az5DwwzCO8gHboOlZ73TtAWMK8E7O4PJSz0zoCU768g/tnPLlf7ru/Qdq8vs0kPDHaJr2bYie9tzQluAGnWDsbNqu8gU65vB1vHT1icu47aFRkvJEu0bwOjzm8naisuyhdMLyNk/U77nQgPO1ckjxJGwI8yfdaO3pBFrzF2s275AcrvLZ0Ujz3IUU8/Sgfug96rDvbhVC7+O/QPFB1nTy6OXU8NrMaPEpwAr3cS4M8aeLWvPRmpTy+uzM6nIuavFcSgTzhD5M77ShMvIkhWT2eiHG8EiCOO0+86btoFIg8GvYGPRuVhjxRTsQ7d6SbPGorsrwYk4y8dgiKvH+PB71k7Qw8B0uUt9S6GTwQIbe8QMN2OyHOibyVU5G7DK+NPPxpLbzg1fq7YTuyO7LIJLyoJLI8QLtsvACZijxTA5E8+KqgO0KKk7vmPQa9wKLAO5LlwLx7c+O7ES/lOz+vJb1zmVO8jQe9PJXgSrxbcls7ps2nPPEvrLrfJxm8rsoAvBShOrz24za94enKvCnhI70GTsK6WLW1vA8IMTtznIs8eyudvBJ/ZzsC5B68TTwfvVU7FLvmbAU6c8QuORRNs7u3lZK8lUkxOwmC/LuTucq8axaCO4zrnrxoRBA8Ie8dPQcPGj2mRMs8baGjPJUxIrwvIHM85kK1vBqSqTxQeZs8TEYHPb3U0bsfVTu9KDwjPDeBkbu1k5c8sVkavQMmhTwKY5o8VXh9PLCUIz26SDC9HWw5PCz4Izyvx1y80cWpvIjwSL1WW6g8TbmlPMbPQDwtstI8zGESurfKq7qh5zE8eSoQvOEvqryQP8Y8md5TvPR8jDwzkdO8m4PXvGq4C7vOP6c8b7SgPAOHhbshj3C8GEHnO0dqMjs5VPk8nPHPPN9jYLxmDOM7fyYiu+tGBT2t0ck5RBfzPBmEN70+FvK6bwh/u0gM27vwYCI4JgbCuxYTQrxQ0Ls8h8yKvJPSNb3R4Qq8u7HLO1O6Br0SPhE8LxEpvPCUabsa1xA9If0bOwzZgDua7wG9lOmOPHS95jwWpwG8L03OvG6udzt/nEy8U9aIu7FYJbvFHyW8fxWovI1IgTsro8480oOaO5YNJD2D4Re9H2ysPE6FajpQ+Js757kpPU7jKrwd2Pk8/WuIPDoFg7y0KBC9QeXBu0zuLrxMBZO8hS8nPKKzW7zNJvI8HV53PE3DuDvSccS8P6e+O9kA5zrVTm47YJ8gvfeDGDuHJN88y8BfPOLyXTucwoO8jKOYPD96Xzyf5o688bu0OzrnM72hk948gR/6OGIToTmY7GQ8NJhvurQ+qrzgoC68FTn1vDLplzybMio67qGQvDk5QTzA4K+7Hbkfu/g2mLoYyGc8NharvFUJxjy223c8rAVkuok1FTwgvPW8xWb/vAfJqjtpj6a7HgxYPLi//zy+11Y89nWevMP2+zs5coo8dQw4OUwPiTsRVXk87XJUvJiTazwf05u8tcOfPA/zYryKBno79ay+O3vE1LsDlaq8/1PLu682tjvCt028+CVXvSEUIjzUQso8+T5/OyvlAT2URJa8+a4Lu7A95DwU4/k5v/01PMIcobzgq+A8SPNxPOnsxbyx53M6wVP7vG/PFLumqwc8792GPLjckjzNJns88jNVOrRDf7yRTw88NRXhvIn7MDyfWBI8jXLJOsAUVzyqUx+9+0YAvQiLRjwuDae8kX7AvO4TGTwrV+w8duqnO3beLDwnisW77Xm5PAgB6bzjvok8NNkuO3qCTjyyQpQ7mAyDO1oDADyxVk28bSmCPN+JNztwtzC9n2g7vRx20Tyis0E9hsP/vJbinTzap5w8za/uvIbfSLyXyhS7T7aguyVGHj1LG5y8XWa6vB0prLwOe6s6470gvPq6JbzYPR89TlUmvLEoA72fV8q8hRoKvNNh/zsX/UW8J+usOj6jEDuN7jm89m0cvatFh7yqlhq8oPRQPT1sRbwf0Nu75Zm1u/rCHD1C9ZA6/RinvPe+Tby9hq48qOYmut+8TbxhZ3k8nEEPPEf/ND2AU2G7yD/Ku0BylLzMuEA7R/UAPH/Ogbz5wwo9Je97PNyaXzyfO4U8p2DAvAQvXbw+CgM996M5PHOHrrvP6w24prM9vJwUkTwORV489UsBvDaGcjvcoo88VMgGvcJIvTz1XZe8ffVCuyEnAD28uog87Zm6uzA2Mj3N9Se88Iu6PKPQFTv6Edc6CLuevM3jCrzzQW28RZqAuxc7ZjuGPUm8dc8HO8CwoLy90KI8l+2DPDiaDriEukg8P+QaPNkckTtLxta7G1aJPHu1bDxdHqy7aEuyPH6dKDzLHRE7zJW7vL2DhTyJIZ27U6nWOsjXFTz+jFk5+LC3PEg54ztXs548HVm9u5SLDjykQSy8+YwGOAESDTt0yJG7jLiOvNCUK7qSv/A8EsM5PM3JEbx3RVq8mxtAu9K/krxmfqs8iI8xvMKL77vqcAu90y0FvZajZrtYula96KZwPLUz6zwUTBE8gcYAO+Wr17wGtqM8Abj+PJ3OobzvQgI9/M0EPRGygjsocQ47Xg2JuzayBDkmi/67Lx+svOZpzTtvU6W80y+ru4pHQDzlDVC8L5Cqu+cC/rz4FLQ5fk+JvA== - index: 0 - object: embedding - model: qwen3-embedding:4b - object: list - usage: - prompt_tokens: 9 - total_tokens: 9 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/test_rebuild.py b/tests/test_rebuild.py index bcc41f4b..01c29a55 100644 --- a/tests/test_rebuild.py +++ b/tests/test_rebuild.py @@ -523,7 +523,10 @@ async def test_rebuild_full_with_accessible_source(temp_db_path): """FULL rebuild re-ingests from source when the URI is accessible. Covers the main path in _rebuild_full (source-accessible branch): the - document is deleted and re-created from its URI, producing a new ID. + document is refreshed in place, keeping its ID. The source bytes are + unchanged since ingestion, so this also pins that the refresh bypasses the + revision and MD5 short-circuits instead of returning the document + untouched. """ async with HaikuRAG(temp_db_path, create=True) as client: with tempfile.TemporaryDirectory() as temp_dir: @@ -540,15 +543,17 @@ async def test_rebuild_full_with_accessible_source(temp_db_path): async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - # Original doc was deleted and a new one created; the old ID - # must not appear, and exactly one new ID must have been yielded. - assert original_id not in processed_ids - assert len(processed_ids) == 1 + assert processed_ids == [original_id] - new_doc = await client.get_document_by_id(processed_ids[0]) - assert new_doc is not None - assert new_doc.uri == source_path.as_uri() - assert "Fresh content" in new_doc.content + refreshed = await client.get_document_by_id(original_id) + assert refreshed is not None + assert refreshed.uri == source_path.as_uri() + assert "Fresh content" in refreshed.content + + # The chunks table is recreated at the top of FULL, so the refresh + # must have written new chunks for the document to stay searchable. + chunks = await client.chunk_repository.get_by_document_id(original_id) + assert chunks async def test_rebuild_title_only_reads_structural_title(temp_db_path): @@ -622,14 +627,14 @@ async def test_rebuild_title_only_handles_llm_failure(temp_db_path, monkeypatch) @pytest.mark.vcr() -async def test_rebuild_full_source_failure_is_logged_and_skipped( +async def test_rebuild_full_source_failure_falls_back_to_stored_content( temp_db_path, monkeypatch ): - """FULL rebuild logs-and-continues when re-ingesting from source fails. + """A failed source refresh must never cost the document. - Covers _rebuild_full's `except Exception` branch: when - create_document_from_source raises, the doc is skipped (no yield) and - the error is logged. Regression guard against silent failures. + Covers _rebuild_full's `except Exception` branch: when the refresh raises, + the document keeps its stored row and is rebuilt from stored content, so it + is still readable and still searchable afterwards. """ import logging @@ -644,22 +649,31 @@ async def test_rebuild_full_source_failure_is_logged_and_skipped( assert not isinstance(original, list) assert original.id is not None - # Force the source rebuild branch to raise. - async def failing_create(*args, **kwargs): + # Force the source refresh to raise. + async def failing_refresh(*args, **kwargs): raise RuntimeError("simulated ingestion failure") - monkeypatch.setattr(client, "create_document_from_source", failing_create) + monkeypatch.setattr( + rebuild_module, "create_document_from_source", failing_refresh + ) - with capture_logs(rebuild_module.logger, logging.ERROR) as records: + with capture_logs(rebuild_module.logger, logging.WARNING) as records: processed_ids = [ doc_id async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - assert processed_ids == [] + assert processed_ids == [original.id] + + survivor = await client.get_document_by_id(original.id) + assert survivor is not None + assert "Content that will vanish" in survivor.content + + chunks = await client.chunk_repository.get_by_document_id(original.id) + assert chunks + assert any( - "Error recreating document from source" in rec.getMessage() - for rec in records + "falling back to stored content" in rec.getMessage() for rec in records ) @@ -1412,12 +1426,10 @@ async def test_rebuild_full_flushes_pending_before_source_rebuild(temp_db_path): async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - # The content-path document keeps its id and must survive the - # flush that precedes the source re-ingest; the source document - # is replaced by a freshly ingested one with a new id. - assert content_doc.id in processed - assert source_doc.id not in processed - assert len(processed) == 2 + # Both documents keep their ids: the content-path one must survive + # the flush that precedes the source refresh, and the source one is + # refreshed in place. + assert sorted(processed) == sorted([content_doc.id, source_doc.id]) assert await client.store.documents_table.count_rows() == 2 From 73944f91ea3ba51898928f06b5b587df3b3e3add Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 19 Aug 2026 13:14:21 +0300 Subject: [PATCH 2/3] Close the source adapter one-shot ingestion builds create_document_from_source resolves a fetcher per call and never closed it, so every one-shot URL or WebDAV ingest leaked an httpx connection pool. Close it, but only when we built it: resolve_adhoc_fetcher returns a caller-supplied source when one matches the URI, and the ingester keeps those open across jobs. Directory ingestion also yielded symlinked files resolving outside the directory it was given. rglob does not recurse into symlinked directories, so a symlinked file was the only way out of the tree; skip those, as FSSource.discover already does. The chunk repository docstring named client._ensure_chunks_embedded, which does not exist. --- CHANGELOG.md | 2 + haiku_rag_slim/haiku/rag/client/documents.py | 143 ++++++++++-------- .../haiku/rag/store/repositories/chunk.py | 3 +- ...t_adhoc_source_is_closed_after_ingest.yaml | 44 ++++++ ...red_source_is_not_closed_after_ingest.yaml | 44 ++++++ ...gest_skips_symlinks_escaping_the_tree.yaml | 82 ++++++++++ tests/test_client.py | 95 ++++++++++++ 7 files changed, 350 insertions(+), 63 deletions(-) create mode 100644 tests/cassettes/test_client/test_adhoc_source_is_closed_after_ingest.yaml create mode 100644 tests/cassettes/test_client/test_configured_source_is_not_closed_after_ingest.yaml create mode 100644 tests/cassettes/test_client/test_directory_ingest_skips_symlinks_escaping_the_tree.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0731b93b..c65e55ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ ### Fixed +- `create_document_from_source` closes the source adapter it builds for the call; adapters passed in through `sources` are left to their owner. +- Directory ingestion skips symlinked files resolving outside the given directory, matching `FSSource.discover`. - A FULL rebuild no longer deletes a source-backed document before re-ingesting it: it refreshes the document in place, so the document id is preserved and a failed fetch or conversion falls back to rebuilding from stored content instead of losing the document. - `DocumentRepository.delete_all` recreated `document_items` from `DocumentItemRecord` instead of `get_document_items_arrow_schema()`, returning `picture_data` as `binary` rather than `large_binary`. - `server.json` runtime arguments are `mcp --stdio`, was `serve --mcp`. diff --git a/haiku_rag_slim/haiku/rag/client/documents.py b/haiku_rag_slim/haiku/rag/client/documents.py index d3256a69..e3b23df1 100644 --- a/haiku_rag_slim/haiku/rag/client/documents.py +++ b/haiku_rag_slim/haiku/rag/client/documents.py @@ -726,6 +726,14 @@ async def create_document_from_source( documents: list[Document] = [] filter = FileFilter() for child in local_path.rglob("*"): + # rglob does not recurse into symlinked directories, but it does + # yield symlinked files. Skip the ones resolving outside the + # directory the caller named, as FSSource.discover does. + if child.is_symlink(): + resolved = child.resolve(strict=False) + if not resolved.is_relative_to(local_path.resolve()): + continue + child = resolved if child.is_file() and filter.include_file(str(child)): doc = await create_document_from_source( client, @@ -768,80 +776,91 @@ async def create_document_from_source( source_str, sources=sources, storage_options=storage_options ) - # The stored URI is what we look up + persist by. For an explicit uri - # override, use it as-is. For a file:// input the source string is - # already canonical (URL-encoded); round-tripping via Path.as_uri() - # would double-encode any escapes like %5B. For bare paths, canonicalize. - if uri is not None: - stored_uri = uri - elif parsed_url.scheme == "file": - stored_uri = source_str - elif parsed_url.scheme == "": - stored_uri = Path(source_str).absolute().as_uri() - else: - stored_uri = source_str + # A fetcher built for this call holds its own httpx pool (HTTP, WebDAV) and + # has to be closed here. One handed in through `sources` belongs to the + # caller: the ingester keeps its sources open across jobs. + owns_fetcher = all(fetcher is not configured for configured in sources or ()) - existing_doc = await client.get_document_by_uri(stored_uri) + try: + # The stored URI is what we look up + persist by. For an explicit uri + # override, use it as-is. For a file:// input the source string is + # already canonical (URL-encoded); round-tripping via Path.as_uri() + # would double-encode any escapes like %5B. For bare paths, canonicalize. + if uri is not None: + stored_uri = uri + elif parsed_url.scheme == "file": + stored_uri = source_str + elif parsed_url.scheme == "": + stored_uri = Path(source_str).absolute().as_uri() + else: + stored_uri = source_str - # Cheap revision-based short-circuit: only worth a HEAD when we have a - # stored revision to compare against. All sources persist their native - # revision (mtime_ns for FS, ETag for S3, ETag/Last-Modified for HTTP) - # under the canonical "source_revision" metadata key. - stored_revision = ( - (existing_doc.metadata or {}).get("source_revision") if existing_doc else None - ) - if existing_doc and stored_revision and not force: - current_revision = await fetcher.head(source_str) - if current_revision == stored_revision: + existing_doc = await client.get_document_by_uri(stored_uri) + + # Cheap revision-based short-circuit: only worth a HEAD when we have a + # stored revision to compare against. All sources persist their native + # revision (mtime_ns for FS, ETag for S3, ETag/Last-Modified for HTTP) + # under the canonical "source_revision" metadata key. + stored_revision = ( + (existing_doc.metadata or {}).get("source_revision") + if existing_doc + else None + ) + if existing_doc and stored_revision and not force: + current_revision = await fetcher.head(source_str) + if current_revision == stored_revision: + return await _refresh_doc_metadata( + client, + existing_doc, + title=title, + user_metadata=metadata, + source_metadata=None, + ) + + with logfire.span("document.fetch", uri=source_str) as fetch_span: + result = await fetcher.fetch(source_str) + fetch_span.set_attribute("bytes", len(result.body)) + fetch_span.set_attribute("content_hash", result.content_hash) + + provider_metadata = await _provider_metadata( + metadata_provider, source_id or fetcher.source_id, source_str, result + ) + user_metadata = {**metadata, **provider_metadata} + + # MD5 short-circuit: the bytes are unchanged even if the revision wasn't. + # Refresh the source-derived metadata (revision may have rolled) but skip + # convert/embed/store entirely. + if ( + existing_doc + and not force + and existing_doc.metadata.get("md5") == result.content_hash + ): + source_meta: dict = { + "content_type": result.content_type, + "md5": result.content_hash, + **result.extra_metadata, + } + if result.revision is not None: + source_meta["source_revision"] = result.revision return await _refresh_doc_metadata( client, existing_doc, title=title, - user_metadata=metadata, - source_metadata=None, + user_metadata=user_metadata, + source_metadata=source_meta, ) - with logfire.span("document.fetch", uri=source_str) as fetch_span: - result = await fetcher.fetch(source_str) - fetch_span.set_attribute("bytes", len(result.body)) - fetch_span.set_attribute("content_hash", result.content_hash) - - provider_metadata = await _provider_metadata( - metadata_provider, source_id or fetcher.source_id, source_str, result - ) - user_metadata = {**metadata, **provider_metadata} - - # MD5 short-circuit: the bytes are unchanged even if the revision wasn't. - # Refresh the source-derived metadata (revision may have rolled) but skip - # convert/embed/store entirely. - if ( - existing_doc - and not force - and existing_doc.metadata.get("md5") == result.content_hash - ): - source_meta: dict = { - "content_type": result.content_type, - "md5": result.content_hash, - **result.extra_metadata, - } - if result.revision is not None: - source_meta["source_revision"] = result.revision - return await _refresh_doc_metadata( + return await _ingest_fetch_result( client, - existing_doc, + result, title=title, user_metadata=user_metadata, - source_metadata=source_meta, + stored_uri=stored_uri, + existing_doc=existing_doc, ) - - return await _ingest_fetch_result( - client, - result, - title=title, - user_metadata=user_metadata, - stored_uri=stored_uri, - existing_doc=existing_doc, - ) + finally: + if owns_fetcher: + await fetcher.aclose() async def update_document( diff --git a/haiku_rag_slim/haiku/rag/store/repositories/chunk.py b/haiku_rag_slim/haiku/rag/store/repositories/chunk.py index ecfe9f49..d3005128 100644 --- a/haiku_rag_slim/haiku/rag/store/repositories/chunk.py +++ b/haiku_rag_slim/haiku/rag/store/repositories/chunk.py @@ -60,7 +60,8 @@ class ChunkRepository: """Create one or more chunks in the database. Chunks must have embeddings set before calling this method. - Use client._ensure_chunks_embedded() to embed chunks if needed. + Use haiku.rag.client.processing.ensure_chunks_embedded() to embed + chunks if needed. """ self.store._assert_writable() # Handle single chunk diff --git a/tests/cassettes/test_client/test_adhoc_source_is_closed_after_ingest.yaml b/tests/cassettes/test_client/test_adhoc_source_is_closed_after_ingest.yaml new file mode 100644 index 00000000..23b39e99 --- /dev/null +++ b/tests/cassettes/test_client/test_adhoc_source_is_closed_after_ingest.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '100' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - |- + Counting + Ad-hoc fetched body. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 10 + total_tokens: 10 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_client/test_configured_source_is_not_closed_after_ingest.yaml b/tests/cassettes/test_client/test_configured_source_is_not_closed_after_ingest.yaml new file mode 100644 index 00000000..bab50fd1 --- /dev/null +++ b/tests/cassettes/test_client/test_configured_source_is_not_closed_after_ingest.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '100' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - |- + Configured + Caller-owned body. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 8 + total_tokens: 8 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_client/test_directory_ingest_skips_symlinks_escaping_the_tree.yaml b/tests/cassettes/test_client/test_directory_ingest_skips_symlinks_escaping_the_tree.yaml new file mode 100644 index 00000000..791ad797 --- /dev/null +++ b/tests/cassettes/test_client/test_directory_ingest_skips_symlinks_escaping_the_tree.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '86' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Inside the tree. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 5 + total_tokens: 5 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '86' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Inside the tree. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 5 + total_tokens: 5 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index e6e379a2..2b29a146 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -19,6 +19,7 @@ from haiku.rag.client.documents import ( ) from haiku.rag.config import Config from haiku.rag.embeddings import EmbedderWrapper +from haiku.rag.ingester.sources.base import FetchResult from haiku.rag.store.compression import decompress_json from haiku.rag.store.models.chunk import Chunk from haiku.rag.store.models.document import Document @@ -444,6 +445,32 @@ async def test_client_create_document_from_directory(temp_db_path): assert not any("unsupported.xyz" in uri for uri in uris) +@pytest.mark.vcr() +async def test_directory_ingest_skips_symlinks_escaping_the_tree(temp_db_path): + """A symlinked file resolving outside the named directory is not ingested; + one resolving inside it is. Matches FSSource.discover.""" + import os + + async with HaikuRAG(temp_db_path, create=True) as client: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) / "tree" + outside = Path(temp_dir) / "outside" + root.mkdir() + outside.mkdir() + + (root / "real.txt").write_text("Inside the tree.") + (outside / "secret.txt").write_text("Outside the tree.") + os.symlink(outside / "secret.txt", root / "escape.txt") + os.symlink(root / "real.txt", root / "inside_link.txt") + + result = await client.create_document_from_source(root) + + assert isinstance(result, list) + uris = sorted(uri for doc in result if (uri := doc.uri)) + assert not any("secret" in uri or "escape" in uri for uri in uris) + assert any("real.txt" in uri for uri in uris) + + @pytest.mark.vcr() async def test_client_create_document_from_url(temp_db_path): """Test creating a document from a URL.""" @@ -2408,6 +2435,74 @@ def test_check_source_accessible_file_uri(tmp_path): assert check_source_accessible((tmp_path / "gone.txt").as_uri()) is False +class _CountingSource: + """A real Source over one in-memory document that counts its closes.""" + + def __init__(self, uri: str, body: bytes) -> None: + self.source_id = "counting" + self.supported_extensions = None + self.max_file_size = None + self._uri = uri + self._body = body + self.closes = 0 + + def supports(self, uri: str) -> bool: + return uri == self._uri + + async def head(self, uri: str) -> str | None: + return "v1" + + async def aclose(self) -> None: + self.closes += 1 + + async def fetch(self, uri: str) -> "FetchResult": + import hashlib + + return FetchResult( + uri=uri, + body=self._body, + content_type="text/markdown", + content_hash=hashlib.md5(self._body).hexdigest(), + revision="v1", + ) + + def discover(self, since=None, *, known_uris=None): + raise NotImplementedError + + +@pytest.mark.vcr() +async def test_adhoc_source_is_closed_after_ingest(temp_db_path, monkeypatch): + """An ad-hoc fetcher is built for this one call, so this call has to close + it — HTTP and WebDAV adapters hold an httpx connection pool.""" + from haiku.rag.ingester import sources as sources_module + + uri = "https://example.com/counting.md" + fetcher = _CountingSource(uri, b"# Counting\n\nAd-hoc fetched body.") + monkeypatch.setattr( + sources_module, "resolve_adhoc_fetcher", lambda *a, **kw: fetcher + ) + + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document_from_source(uri) + assert not isinstance(doc, list) + + assert fetcher.closes == 1 + + +@pytest.mark.vcr() +async def test_configured_source_is_not_closed_after_ingest(temp_db_path): + """A source handed in by the caller (the ingester's long-lived pool) is not + ours to close: closing it would tear down the pool mid-run.""" + uri = "https://example.com/configured.md" + fetcher = _CountingSource(uri, b"# Configured\n\nCaller-owned body.") + + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document_from_source(uri, sources=[fetcher]) + assert not isinstance(doc, list) + + assert fetcher.closes == 0 + + def _bbox_doc(*, with_page_image: bool, pages: tuple[int, ...] = (1,)): """DoclingDocument with one paragraph per page, each carrying a bbox. From 15868762b0ead86bd40f8082b13d2386c4cf1740 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 19 Aug 2026 13:17:46 +0300 Subject: [PATCH 3/3] Wire providers.docling_serve.timeout through to the client The setting was documented but did not exist on DoclingServeConfig, so it was silently dropped, and DoclingServeClient.from_config never forwarded the timeout parameter it already accepted. The per-request timeout was therefore pinned at the constructor default of 300s with no way to change it. Add the field, forward it, and reject a non-positive value. Also parametrize over the checked-in *.yaml.example files and validate each through AppConfig, so an example that no longer loads fails a test rather than a user's first run. --- CHANGELOG.md | 1 + docs/remote-processing.md | 1 + haiku_rag_slim/haiku/rag/config/models.py | 5 +++++ .../haiku/rag/providers/docling_serve.py | 1 + tests/test_config.py | 20 +++++++++++++++++++ tests/test_docling_serve_client.py | 2 ++ 6 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c65e55ac..f5bc35cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Added +- `providers.docling_serve.timeout` (default 300 seconds), forwarded to the docling-serve client's per-request timeout. - `evaluations run --filter/-f CLAUSE`: SQL `WHERE` clause over document columns, applied to the retrieval benchmark's searches and to every capability search during QA. Recorded as `document_filter` in experiment metadata. - `mtrag_clapnq` / `mtrag_clapnq_rewrite` / `mtrag_clapnq_live` / `mtrag_clapnq_live_uncompacted` evaluation datasets: multi-turn QA with gold-prefix and live-session conversation replay, live arms with and without `EvidenceCompactionCapability`, Recall@k/nDCG@k retrieval metrics, eligibility-aware citation scoring, refusal precision/recall, per-turn tool-traffic attributes, and `citation_status` / `turn_citation_status` eval attributes. - Raw chunk metadata is now exposed to search and citation results, through `SearchResult.chunk_meta` and `Citation.chunk_meta`. For context-expanded results, the metadata is that of the anchor chunk. diff --git a/docs/remote-processing.md b/docs/remote-processing.md index 73cefaf9..9c5b9783 100644 --- a/docs/remote-processing.md +++ b/docs/remote-processing.md @@ -64,6 +64,7 @@ providers: docling_serve: base_url: http://localhost:5001 api_key: "" # Optional API key for authentication + timeout: 300 # Per-request timeout in seconds ``` For converter / chunker config options (chunking strategy, tokenizer, diff --git a/haiku_rag_slim/haiku/rag/config/models.py b/haiku_rag_slim/haiku/rag/config/models.py index 2fa58a8e..b122ae8d 100644 --- a/haiku_rag_slim/haiku/rag/config/models.py +++ b/haiku_rag_slim/haiku/rag/config/models.py @@ -295,6 +295,11 @@ class DoclingServeConfig(BaseModel): description="Max attempts per request across the fleet before giving up; " "each retry fails over to another instance.", ) + timeout: float = Field( + default=300, + gt=0, + description="Per-request timeout in seconds for submit, poll and result calls.", + ) circuit_breaker: CircuitBreakerConfig = Field( default_factory=lambda: CircuitBreakerConfig( failure_threshold=3, cooldown_s=30.0 diff --git a/haiku_rag_slim/haiku/rag/providers/docling_serve.py b/haiku_rag_slim/haiku/rag/providers/docling_serve.py index a14cc0fe..23abd5d9 100644 --- a/haiku_rag_slim/haiku/rag/providers/docling_serve.py +++ b/haiku_rag_slim/haiku/rag/providers/docling_serve.py @@ -97,6 +97,7 @@ class DoclingServeClient: return cls( base_urls=config.base_urls, api_key=config.api_key, + timeout=config.timeout, circuit_breaker=config.circuit_breaker, max_attempts=config.max_attempts, ) diff --git a/tests/test_config.py b/tests/test_config.py index 3bcdef6d..19383411 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest import yaml @@ -557,3 +559,21 @@ def test_get_config_initialises_lazily_then_reuses(monkeypatch): first = config_module.get_config() assert isinstance(first, AppConfig) assert config_module.get_config() is first + + +# Every YAML config shipped in the repo has to load. These are what users copy. +_EXAMPLE_CONFIGS = sorted( + (Path(__file__).resolve().parent.parent).glob("**/*.yaml.example") +) + + +def test_example_configs_are_present(): + """Guard against the glob silently matching nothing.""" + assert _EXAMPLE_CONFIGS + + +@pytest.mark.parametrize( + "path", _EXAMPLE_CONFIGS, ids=lambda p: p.parent.name + "/" + p.name +) +def test_example_config_validates(path: Path): + AppConfig.model_validate(yaml.safe_load(path.read_text()) or {}) diff --git a/tests/test_docling_serve_client.py b/tests/test_docling_serve_client.py index 175ed53e..655f9f6c 100644 --- a/tests/test_docling_serve_client.py +++ b/tests/test_docling_serve_client.py @@ -474,11 +474,13 @@ def test_from_config_wires_retry_and_breaker(): ds = config.providers.docling_serve ds.base_url = "http://cfg-n:5001" ds.max_attempts = 7 + ds.timeout = 42.0 ds.circuit_breaker = CircuitBreakerConfig(failure_threshold=9, cooldown_s=90.0) for component in (DoclingServeConverter(config), DoclingServeChunker(config)): client = component.client assert client._max_attempts == 7 + assert client.timeout == 42.0 assert client._breaker_config.failure_threshold == 9 assert client._breaker_config.cooldown_s == 90.0