From 1e3996db4b6defe806aeefd52859169d02ac335c Mon Sep 17 00:00:00 2001 From: Lawliet2004 <168631200+Lawliet2004@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:01:46 +0530 Subject: [PATCH 1/3] fix(s3vectors): query directly and follow continuation tokens --- src/dynavec/stores/async_s3vectors.py | 35 ++++------ src/dynavec/stores/s3vectors.py | 37 ++++------- tests/test_async_s3vectors.py | 94 +++++++++++++++++++++------ tests/test_logging.py | 7 +- tests/test_s3vectors.py | 91 +++++++++++++++++++++----- tests/test_s3vectors_errors.py | 76 ++++++++++------------ 6 files changed, 211 insertions(+), 129 deletions(-) diff --git a/src/dynavec/stores/async_s3vectors.py b/src/dynavec/stores/async_s3vectors.py index f3fd66e..db3fc62 100644 --- a/src/dynavec/stores/async_s3vectors.py +++ b/src/dynavec/stores/async_s3vectors.py @@ -257,7 +257,7 @@ async def query_pages( return_distance: bool = True, page_size: int | None = None, ) -> AsyncIterator[list[dict[str, Any]]]: - """Yield result pages from the async S3 Vectors paginator.""" + """Yield async result pages, following S3 Vectors ``nextToken``.""" if top_k <= 0: return @@ -289,22 +289,13 @@ async def query_pages( yielded = 0 buffer: list[dict[str, Any]] = [] - with translate_client_errors(): - paginator = client.get_paginator("query_vectors") - page_iterator = paginator.paginate( - PaginationConfig={"MaxItems": top_k}, - **kwargs, - ).__aiter__() - while True: if self._query_limiter is not None: await self._query_limiter.acquire_async() - try: - with translate_client_errors(): - page = await page_iterator.__anext__() - except StopAsyncIteration: - break + # Older supported SDKs expose QueryVectors without a paginator. + with translate_client_errors(): + page = await client.query_vectors(**kwargs) vectors = page.get("vectors", []) @@ -325,16 +316,12 @@ async def query_pages( buffer.extend(vectors) while ( - len(buffer) >= effective_page_size - and yielded < top_k + yielded < top_k + and len(buffer) >= min(effective_page_size, top_k - yielded) ): - chunk = buffer[:effective_page_size] - buffer = buffer[effective_page_size:] - - remaining = top_k - yielded - - if len(chunk) > remaining: - chunk = chunk[:remaining] + chunk_size = min(effective_page_size, top_k - yielded) + chunk = buffer[:chunk_size] + buffer = buffer[chunk_size:] yield chunk yielded += len(chunk) @@ -342,8 +329,10 @@ async def query_pages( if yielded >= top_k: return - if page.get("NextToken") is None: + next_token = page.get("nextToken") + if not next_token: break + kwargs["nextToken"] = next_token if ( effective_page_size is not None diff --git a/src/dynavec/stores/s3vectors.py b/src/dynavec/stores/s3vectors.py index 79fb276..7de1bdc 100644 --- a/src/dynavec/stores/s3vectors.py +++ b/src/dynavec/stores/s3vectors.py @@ -195,7 +195,7 @@ def query_pages( return_distance: bool = True, page_size: int | None = None, ) -> Iterator[list[dict[str, Any]]]: - """Yield result pages as the S3 Vectors paginator returns them. + """Yield result pages from S3 Vectors, following ``nextToken``. This is what powers streaming search: an agent can begin consuming the first page while later pages are still in flight. @@ -233,24 +233,13 @@ def query_pages( yielded = 0 buffer: list[dict[str, Any]] = [] - with translate_client_errors(): - paginator = self._client.get_paginator("query_vectors") - page_iterator = iter( - paginator.paginate( - PaginationConfig={"MaxItems": top_k}, - **kwargs, - ) - ) - while True: if self._query_limiter is not None: self._query_limiter.acquire() - try: - with translate_client_errors(): - page = next(page_iterator) - except StopIteration: - break + # Older supported SDKs expose QueryVectors without a paginator. + with translate_client_errors(): + page = self._client.query_vectors(**kwargs) vectors = page.get("vectors", []) @@ -270,14 +259,12 @@ def query_pages( else: buffer.extend(vectors) - while len(buffer) >= effective_page_size and yielded < top_k: - chunk = buffer[:effective_page_size] - buffer = buffer[effective_page_size:] - - remaining = top_k - yielded - - if len(chunk) > remaining: - chunk = chunk[:remaining] + while yielded < top_k and len(buffer) >= min( + effective_page_size, top_k - yielded + ): + chunk_size = min(effective_page_size, top_k - yielded) + chunk = buffer[:chunk_size] + buffer = buffer[chunk_size:] yield chunk yielded += len(chunk) @@ -285,8 +272,10 @@ def query_pages( if yielded >= top_k: return - if page.get("NextToken") is None: + next_token = page.get("nextToken") + if not next_token: break + kwargs["nextToken"] = next_token if effective_page_size is not None and buffer and yielded < top_k: remaining = top_k - yielded diff --git a/tests/test_async_s3vectors.py b/tests/test_async_s3vectors.py index 9a46cba..5c95174 100644 --- a/tests/test_async_s3vectors.py +++ b/tests/test_async_s3vectors.py @@ -6,24 +6,14 @@ from typing import Any from unittest.mock import AsyncMock +import aioboto3 import pytest +from aiobotocore.stub import AioStubber from dynavec.config import DynavecConfig from dynavec.stores.async_s3vectors import AsyncS3VectorsStore -class FakeAsyncPaginator: - def __init__(self, pages: list[dict[str, Any]]) -> None: - self.pages = pages - self.calls: list[dict[str, Any]] = [] - - async def paginate(self, **kwargs: Any): - self.calls.append(kwargs) - - for page in self.pages: - yield page - - def _config() -> DynavecConfig: return DynavecConfig( vector_bucket="test-bucket", @@ -71,14 +61,15 @@ def __init__( ) -> None: self.put_calls: list[dict[str, Any]] = [] self.get_calls: list[dict[str, Any]] = [] - self.paginator = FakeAsyncPaginator(pages or []) + self.pages = iter(pages or [{"vectors": []}]) + self.query_calls: list[dict[str, Any]] = [] async def put_vectors(self, **kwargs: Any) -> None: self.put_calls.append(kwargs) - def get_paginator(self, name: str) -> FakeAsyncPaginator: - assert name == "query_vectors" - return self.paginator + async def query_vectors(self, **kwargs: Any) -> dict[str, Any]: + self.query_calls.append(kwargs) + return next(self.pages) async def get_vectors(self, **kwargs: Any) -> dict[str, Any]: self.get_calls.append(kwargs) @@ -188,7 +179,7 @@ async def test_query_returns_paginated_results(): {"key": f"k{i}", "distance": i * 0.01} for i in range(100) ], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [ @@ -210,6 +201,26 @@ async def test_query_returns_paginated_results(): assert len(results) == 150 assert results[0]["key"] == "k0" assert results[-1]["key"] == "k149" + assert "nextToken" not in client.query_calls[0] + assert client.query_calls[1]["nextToken"] == "token-1" + + +async def test_query_with_real_async_sdk_client(): + session = aioboto3.Session( + aws_access_key_id="testing", aws_secret_access_key="testing", region_name="us-east-1" + ) + vectors = [{"key": "doc-1", "distance": 0.25}] + + async with AsyncS3VectorsStore(_config(), session) as store: + with AioStubber(store._client) as stubber: + stubber.add_response("query_vectors", {"vectors": vectors}, { + "vectorBucketName": "test-bucket", "indexName": "test-index", "topK": 1, + "queryVector": {"float32": [1.0, 2.0, 3.0, 4.0]}, + "returnMetadata": True, "returnDistance": True, + }) + + assert await store.query([1.0, 2.0, 3.0, 4.0], top_k=1) == vectors + stubber.assert_no_pending_responses() async def test_query_truncates_at_top_k(): @@ -219,7 +230,7 @@ async def test_query_truncates_at_top_k(): {"key": f"k{i}"} for i in range(100) ], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [ @@ -283,6 +294,49 @@ async def test_query_rejects_excessive_top_k(): ) +@pytest.mark.parametrize("page_size", [None, 2]) +async def test_query_pages_continues_after_empty_page_with_next_token(page_size): + client = FakeS3Client([ + {"vectors": [{"key": "k0"}], "nextToken": "token-1"}, + {"vectors": [], "nextToken": "token-2"}, + {"vectors": [{"key": "k1"}, {"key": "k2"}]}, + ]) + limiter = AsyncMock() + + async with AsyncS3VectorsStore(_config(), FakeSession(client)) as store: + store._query_limiter = limiter + pages = [ + page async for page in store.query_pages( + [0.1, 0.2, 0.3, 0.4], top_k=5, page_size=page_size + ) + ] + + assert [hit["key"] for page in pages for hit in page] == ["k0", "k1", "k2"] + assert [len(page) for page in pages] == ([1, 2] if page_size is None else [2, 1]) + assert limiter.acquire_async.await_count == 3 + + +async def test_query_pages_stops_at_top_k_with_large_page_size(): + client = FakeS3Client([ + {"vectors": [{"key": "k0"}], "nextToken": "token-1"}, + {"vectors": [{"key": "k1"}, {"key": "k2"}], "nextToken": "unused"}, + ]) + + async with AsyncS3VectorsStore(_config(), FakeSession(client)) as store: + pages = [page async for page in store.query_pages( + [1.0, 2.0, 3.0, 4.0], top_k=2, page_size=100, + filter={"kind": "test"}, return_metadata=False, return_distance=False, + )] + + assert pages == [[{"key": "k0"}, {"key": "k1"}]] + assert len(client.query_calls) == 2 + for call in client.query_calls: + assert call["topK"] == 2 + assert call["filter"] == {"kind": "test"} + assert call["returnMetadata"] is False + assert call["returnDistance"] is False + + async def test_put_batch_uses_rate_limiter(): config = DynavecConfig( vector_bucket="test-bucket", @@ -319,11 +373,11 @@ async def test_query_pages_uses_rate_limiter_per_page(): pages = [ { "vectors": [{"key": "k0"}], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [{"key": "k1"}], - "NextToken": "token-2", + "nextToken": "token-2", }, { "vectors": [{"key": "k2"}], diff --git a/tests/test_logging.py b/tests/test_logging.py index 90a4311..08c3459 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -130,10 +130,9 @@ def test_dynamodb_store_structured_logging(caplog): def test_s3vectors_store_structured_logging(caplog): session = MagicMock() client = session.client.return_value - paginator = client.get_paginator.return_value - paginator.paginate.return_value = [ - {"vectors": [{"key": "ns#1", "data": {"float32": [0.1] * 8}, "distance": 0.05}]} - ] + client.query_vectors.return_value = { + "vectors": [{"key": "ns#1", "data": {"float32": [0.1] * 8}, "distance": 0.05}] + } # 1. Opt-in: When False, no logs cfg_disabled = DynavecConfig( diff --git a/tests/test_s3vectors.py b/tests/test_s3vectors.py index 32e1797..48eb538 100644 --- a/tests/test_s3vectors.py +++ b/tests/test_s3vectors.py @@ -1,7 +1,10 @@ import time +from contextlib import closing from unittest.mock import MagicMock +import boto3 import pytest +from botocore.stub import Stubber from dynavec.config import DynavecConfig from dynavec.stores.s3vectors import _MAX_TOP_K, S3VectorsStore @@ -49,34 +52,50 @@ def _store_with_pages(pages, config=None): store._put_limiter = None store._query_limiter = None - paginator = MagicMock() - paginator.paginate.return_value = pages - store._client.get_paginator.return_value = paginator - return store, paginator + store._client.query_vectors.side_effect = pages or [{"vectors": []}] + return store, store._client.query_vectors def test_query_single_page(): page = [{"key": f"k{i}", "distance": 0.01 * i} for i in range(50)] - store, paginator = _store_with_pages([{"vectors": page}]) + store, query = _store_with_pages([{"vectors": page}]) results = store.query(query_vector=[0.1, 0.2, 0.3, 0.4], top_k=50) assert len(results) == 50 assert results[0]["key"] == "k0" assert results[-1]["key"] == "k49" - store._client.get_paginator.assert_called_once_with("query_vectors") - assert paginator.paginate.call_args.kwargs["PaginationConfig"] == {"MaxItems": 50} - assert paginator.paginate.call_args.kwargs["topK"] == 50 + store._client.get_paginator.assert_not_called() + assert query.call_args.kwargs["topK"] == 50 + assert "nextToken" not in query.call_args.kwargs + + +def test_query_with_real_sdk_client(): + session = boto3.Session( + aws_access_key_id="testing", aws_secret_access_key="testing", region_name="us-east-1" + ) + store = S3VectorsStore(_config(), session) + vectors = [{"key": "doc-1", "distance": 0.25}] + + with closing(store._client), Stubber(store._client) as stubber: + stubber.add_response("query_vectors", {"vectors": vectors}, { + "vectorBucketName": "test-bucket", "indexName": "test-index", "topK": 1, + "queryVector": {"float32": [1.0, 2.0, 3.0, 4.0]}, + "returnMetadata": True, "returnDistance": True, + }) + + assert store.query([1.0, 2.0, 3.0, 4.0], top_k=1) == vectors + stubber.assert_no_pending_responses() def test_query_multi_page_pagination(): pages = [ { "vectors": [{"key": f"k{i}"} for i in range(100)], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [{"key": f"k{i}"} for i in range(100, 200)], - "NextToken": "token-2", + "nextToken": "token-2", }, { "vectors": [{"key": f"k{i}"} for i in range(200, 250)], @@ -97,6 +116,9 @@ def test_query_multi_page_pagination(): "k248", "k249", ] + calls = store._client.query_vectors.call_args_list + assert "nextToken" not in calls[0].kwargs + assert [call.kwargs["nextToken"] for call in calls[1:]] == ["token-1", "token-2"] def test_query_fewer_results_than_top_k(): store, _ = _store_with_pages([{"vectors": [{"key": "k0"}, {"key": "k1"}]}]) @@ -111,7 +133,7 @@ def test_query_zero_or_negative_top_k(): assert store.query(query_vector=[0.1, 0.2, 0.3, 0.4], top_k=0) == [] assert store.query(query_vector=[0.1, 0.2, 0.3, 0.4], top_k=-5) == [] - store._client.get_paginator.assert_not_called() + store._client.query_vectors.assert_not_called() def test_query_exceeding_service_limit(): @@ -200,7 +222,7 @@ def test_query_truncation_at_top_k(): pages = [ { "vectors": [{"key": f"k{i}"} for i in range(100)], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [{"key": f"k{i}"} for i in range(100, 200)], @@ -222,7 +244,7 @@ def test_query_pages_raw_chunks(): pages = [ { "vectors": [{"key": f"k{i}"} for i in range(100)], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [{"key": f"k{i}"} for i in range(100, 150)], @@ -273,15 +295,52 @@ def test_query_pages_uses_config_top_k_page_size(): assert [len(p) for p in result_pages] == [10, 10, 5] +@pytest.mark.parametrize("page_size", [None, 2]) +def test_query_pages_continues_after_empty_page_with_next_token(page_size): + store, _ = _store_with_pages([ + {"vectors": [{"key": "k0"}], "nextToken": "token-1"}, + {"vectors": [], "nextToken": "token-2"}, + {"vectors": [{"key": "k1"}, {"key": "k2"}]}, + ]) + limiter = MagicMock() + store._query_limiter = limiter + + pages = list(store.query_pages([0.1, 0.2, 0.3, 0.4], top_k=5, page_size=page_size)) + + assert [hit["key"] for page in pages for hit in page] == ["k0", "k1", "k2"] + assert [len(page) for page in pages] == ([1, 2] if page_size is None else [2, 1]) + assert limiter.acquire.call_count == 3 + + +def test_query_pages_stops_at_top_k_with_large_page_size(): + store, query = _store_with_pages([ + {"vectors": [{"key": "k0"}], "nextToken": "token-1"}, + {"vectors": [{"key": "k1"}, {"key": "k2"}], "nextToken": "unused"}, + ]) + + pages = list(store.query_pages( + [1.0, 2.0, 3.0, 4.0], top_k=2, page_size=100, + filter={"kind": "test"}, return_metadata=False, return_distance=False, + )) + + assert pages == [[{"key": "k0"}, {"key": "k1"}]] + assert query.call_count == 2 + for call in query.call_args_list: + assert call.kwargs["topK"] == 2 + assert call.kwargs["filter"] == {"kind": "test"} + assert call.kwargs["returnMetadata"] is False + assert call.kwargs["returnDistance"] is False + + def test_query_pages_uses_rate_limiter_per_page(): pages = [ { "vectors": [{"key": "k0"}], - "NextToken": "token-1", + "nextToken": "token-1", }, { "vectors": [{"key": "k1"}], - "NextToken": "token-2", + "nextToken": "token-2", }, { "vectors": [{"key": "k2"}], @@ -324,4 +383,4 @@ def test_query_pages_does_not_rate_limit_after_paginator_exhaustion(): ) assert [len(page) for page in result_pages] == [1] - assert limiter.acquire.call_count == 1 \ No newline at end of file + assert limiter.acquire.call_count == 1 diff --git a/tests/test_s3vectors_errors.py b/tests/test_s3vectors_errors.py index 5d7a2b7..2da1d75 100644 --- a/tests/test_s3vectors_errors.py +++ b/tests/test_s3vectors_errors.py @@ -42,6 +42,7 @@ def _async_store(): client = MagicMock() client.put_vectors = AsyncMock() client.get_vectors = AsyncMock() + client.query_vectors = AsyncMock() session = MagicMock() session.client.return_value.__aenter__.return_value = client return AsyncS3VectorsStore(_config(), session), client @@ -118,6 +119,7 @@ def test_new_exceptions_are_public(error_type): ("_put_batch", ([],), "put_vectors"), ("get_vectors", (["key"],), "get_vectors"), ("delete_vectors", (["key"],), "delete_vectors"), + ("query", ([0.1, 0.2], 2), "query_vectors"), ], ) def test_sync_direct_operations_translate( @@ -138,7 +140,11 @@ def test_sync_direct_operations_translate( @pytest.mark.parametrize("code,error_type", MAPPINGS) @pytest.mark.parametrize( "operation,args,aws_operation", - [("_put_batch", ([],), "put_vectors"), ("get_vectors", (["key"],), "get_vectors")], + [ + ("_put_batch", ([],), "put_vectors"), + ("get_vectors", (["key"],), "get_vectors"), + ("query", ([0.1, 0.2], 2), "query_vectors"), + ], ) async def test_async_direct_operations_translate(code, error_type, operation, args, aws_operation): store, client = _async_store() @@ -155,24 +161,24 @@ async def test_async_direct_operations_translate(code, error_type, operation, ar def _failing_pages(original): - yield {"vectors": [{"key": "first"}], "NextToken": "next"} + yield {"vectors": [{"key": "first"}], "nextToken": "next"} raise original -async def _async_pages(original=None): - yield {"vectors": [{"key": "first"}], "NextToken": "next"} - if original is not None: - raise original - - @pytest.mark.parametrize("operation,args", [("query_pages", ([0.1, 0.2], 2)), ("list_pages", ())]) def test_sync_pagination_translates_later_page_errors(sync_store, operation, args): store, client = sync_store original = _client_error("NotFoundException") - client.get_paginator.return_value.paginate.return_value = _failing_pages(original) + if operation == "query_pages": + client.query_vectors.side_effect = [ + {"vectors": [{"key": "first"}], "nextToken": "next"}, original, + ] + else: + client.get_paginator.return_value.paginate.return_value = _failing_pages(original) pages = getattr(store, operation)(*args) client.get_paginator.assert_not_called() + client.query_vectors.assert_not_called() assert next(pages) == [{"key": "first"}] with pytest.raises(NotFoundError) as caught: next(pages) @@ -183,11 +189,13 @@ def test_sync_pagination_translates_later_page_errors(sync_store, operation, arg async def test_async_pagination_translates_later_page_errors(): store, client = _async_store() original = _client_error("NotFoundException") - client.get_paginator.return_value.paginate.return_value = _async_pages(original) + client.query_vectors.side_effect = [ + {"vectors": [{"key": "first"}], "nextToken": "next"}, original, + ] async with store: pages = store.query_pages([0.1, 0.2], 2) - client.get_paginator.assert_not_called() + client.query_vectors.assert_not_called() assert await pages.__anext__() == [{"key": "first"}] with pytest.raises(NotFoundError) as caught: await pages.__anext__() @@ -195,9 +203,8 @@ async def test_async_pagination_translates_later_page_errors(): assert caught.value.__cause__ is original -@pytest.mark.parametrize("operation,args", [("query", ([0.1, 0.2], 2)), ("list_pages", ())]) @pytest.mark.parametrize("boundary", ["get_paginator", "paginate"]) -def test_sync_paginator_setup_errors_translate(sync_store, operation, args, boundary): +def test_sync_list_paginator_setup_errors_translate(sync_store, boundary): store, client = sync_store original = _client_error("ValidationException") call = ( @@ -208,26 +215,7 @@ def test_sync_paginator_setup_errors_translate(sync_store, operation, args, boun call.side_effect = original with pytest.raises(ValidationError) as caught: - list(getattr(store, operation)(*args)) - - assert caught.value.__cause__ is original - call.assert_called_once() - - -@pytest.mark.parametrize("boundary", ["get_paginator", "paginate"]) -async def test_async_paginator_setup_errors_translate(boundary): - store, client = _async_store() - original = _client_error("ValidationException") - call = ( - client.get_paginator - if boundary == "get_paginator" - else client.get_paginator.return_value.paginate - ) - call.side_effect = original - - async with store: - with pytest.raises(ValidationError) as caught: - await store.query([0.1, 0.2], 2) + list(store.list_pages()) assert caught.value.__cause__ is original call.assert_called_once() @@ -287,32 +275,36 @@ async def test_async_batch_retries_then_succeeds(monkeypatch, code): def test_sync_query_retries_page_iteration_failure(sync_store, monkeypatch): store, client = sync_store - client.get_paginator.return_value.paginate.side_effect = [ - _failing_pages(_client_error("ThrottlingException")), - [{"vectors": [{"key": "success"}]}], + client.query_vectors.side_effect = [ + {"vectors": [{"key": "first"}], "nextToken": "next"}, + _client_error("ThrottlingException"), + {"vectors": [{"key": "success"}]}, ] sleep = MagicMock() monkeypatch.setattr("dynavec.utils.time.sleep", sleep) assert store.query([0.1, 0.2], 2) == [{"key": "success"}] - assert client.get_paginator.call_count == 2 + assert client.query_vectors.call_count == 3 + assert "nextToken" not in client.query_vectors.call_args.kwargs sleep.assert_called_once() async def test_async_query_retries_page_iteration_failure(monkeypatch): store, client = _async_store() - client.get_paginator.return_value.paginate.side_effect = [ - _async_pages(_client_error("ThrottlingException")), - _async_pages(), + client.query_vectors.side_effect = [ + {"vectors": [{"key": "first"}], "nextToken": "next"}, + _client_error("ThrottlingException"), + {"vectors": [{"key": "success"}]}, ] sleep = AsyncMock() monkeypatch.setattr("dynavec.utils.asyncio.sleep", sleep) async with store: - assert await store.query([0.1, 0.2], 2) == [{"key": "first"}] + assert await store.query([0.1, 0.2], 2) == [{"key": "success"}] - assert client.get_paginator.call_count == 2 + assert client.query_vectors.await_count == 3 + assert "nextToken" not in client.query_vectors.call_args.kwargs sleep.assert_awaited_once() From d7b1531e8c980e1c98ce60bc484d54fbd1531de1 Mon Sep 17 00:00:00 2001 From: Lawliet2004 <168631200+Lawliet2004@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:03:00 +0530 Subject: [PATCH 2/3] docs: audit open issues and prioritize resolution work --- docs/ISSUE_AUDIT_2026-11-06.md | 165 +++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/ISSUE_AUDIT_2026-11-06.md diff --git a/docs/ISSUE_AUDIT_2026-11-06.md b/docs/ISSUE_AUDIT_2026-11-06.md new file mode 100644 index 0000000..d541a9c --- /dev/null +++ b/docs/ISSUE_AUDIT_2026-11-06.md @@ -0,0 +1,165 @@ +# Issue audit — 6 November 2026 + +## Scope and evidence + +Audit baseline: `development` at `b9c90d7`. GitHub inventory was read on 6 November 2026: **192 issues, 79 open and 113 closed; 158 pull requests, 7 open** before this audit PR. Every open issue appears exactly once in the inventory below. Closed issues were sampled against relevant code and PRs; this is not a reproduction of every closed issue. + +Assessments compare issue acceptance criteria, current source/tests, and merged/open PR metadata. A linked or closed PR is not proof that a feature shipped. In particular, the comment on #29 links provisioning-test PR #315, not an edge-storage implementation. Static code presence is not an end-to-end production guarantee. + +[Issue tracker](https://github.com/codeforstartups/dynavec/issues) · [Pull requests](https://github.com/codeforstartups/dynavec/pulls) · [Contribution/assignment policy](../CONTRIBUTING.md#finding--claiming-issues). Ownership is the snapshot assignee shown on each row; “unassigned” is not an assignment. Coordinate existing work before claiming an issue. This PR fixes a regression discovered during the audit rather than implementing another contributor's assigned feature. + +| Assessment | Open issues | Meaning | +|---|---:|---| +| Review closure | 11 | Implementation evidence exists; maintainer acceptance or a narrow follow-up is still required. | +| Partial | 28 | Some requested behavior exists, with explicit gaps listed below. | +| Open PR | 4 | Implementation is being proposed; review/merge and acceptance checks remain. | +| Missing | 31 | The requested public flow or essential deliverable was not found in the inspected code. | +| Epic | 5 | Parent tracking issue; resolve its children rather than treating it as one patch. | + +Priorities are audit recommendations: **P1** correctness/security or a blocking integration; **P2** functional completion; **P3** documentation, polish or later architecture. They are not repository labels. + +## Recommended order + +| Order | Work | Completion check | +|---|---|---| +| 1 | Merge the QueryVectors fix in this PR. | Real boto3/aioboto3 stubs work without a query paginator; all pages are consumed using `nextToken`; top-k, buffering, errors and rate limiting remain correct. | +| 2 | Coordinate correctness/security gaps #8, #28, #97, #100, #137 and #342 with their owners. | Cross-model caches are isolated; partial writes have a repair strategy; sample IAM is scoped; formatted secrets are redacted; dashboard APIs enforce their access model; routing is invoked once. | +| 3 | Review pending PRs: #348 → #349, #351, and draft #316. | Verify acceptance cases before merging; retire superseded #332/#336 in coordination with their authors. | +| 4 | Review the closure candidates below and reconcile #130/#141. | Update issue state only after maintainer acceptance; retain specific remaining gaps as follow-ups. | +| 5 | Finish persistent telemetry/run history and the shared graph schema. | #127/#128/#299/#301/#302 and #282/#284 provide stable data contracts before building dependent UI. | +| 6 | Build visual/eval/deployment flows on those contracts. | Dataset/gate/run links, canvas round-trip and deployment each have an executable acceptance scenario; expand to GCP/Azure afterward. | + +## Confirmed defects found while auditing + +### Selected fix: S3 Vectors queries stop early or cannot start + +Both stores requested a `query_vectors` paginator. The environment produced by `make install` resolves boto3/botocore **1.40.61**, where `client.can_paginate("query_vectors")` is false and `get_paginator` raises `OperationNotPageableError`. Newer AWS APIs support continuation, but both stores checked `NextToken` instead of the documented **`nextToken`**. This silently discarded later results. Earlier tests used the same incorrect uppercase key. + +The fix calls `query_vectors` directly in sync and async stores and forwards a nonempty `nextToken`. This supports a single response on older SDKs and continuation on SDK/service versions exposing it, without changing dependency constraints. A caller still needs an SDK supporting any request fields/features it uses; this change does not add pagination support to an old service model. + +Regression coverage includes real SDK clients with Stubber/AioStubber, multiple service pages, empty intermediate pages, client chunk buffering (including a page size larger than top-k), cursor forwarding, top-k limits, one rate-limit acquisition per request, permanent error translation and retry restart. Tests with realistic lowercase tokens reproduced 15 failures before the production fix. The historical pagination issue [#16](https://github.com/codeforstartups/dynavec/issues/16) is closed, so its tracker state alone did not establish correctness. + +Sources: [AWS QueryVectors request/response](https://docs.aws.amazon.com/AmazonS3/latest/API/API_S3VectorBuckets_QueryVectors.html), [boto3 query_vectors](https://docs.aws.amazon.com/boto3/latest/reference/services/s3vectors/client/query_vectors.html), and the installed SDK service/paginator model inspected offline. + +### Follow-ups reproduced without external services + +- **#8:** two CachedEmbedder instances of the same backend class, with different model identities and a shared cache, reuse the first model's vector under the default class-based key. Use explicit distinct `model_key` values as the current workaround; a full fix must encode actual embedding identity. +- **#342:** one Supervisor.run call invokes a configured router **twice**, as shown by a counting router. A stateful router can produce different selections between validation/execution. Reuse the first selected route and add an invocation-count regression. + +These findings are resolution work for the relevant issues, not claims that those issues are fixed by this PR. Security-related reproduction details are excluded from this public report in accordance with [SECURITY.md](../SECURITY.md). + +## Open-issue inventory + +Each row states source evidence and a concrete next action. “Review closure” is a recommendation for maintainer review, not an automatic closure request. UI acceptance and cloud deployment checks remain separate from offline Python tests. + +### Review closure (11) + +| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | +|---|---|---|---| +| [#7 — Retry/backoff for OpenAI embedder rate limits](https://github.com/codeforstartups/dynavec/issues/7)
@tikhandesanket | P2 | [src/dynavec/embeddings/openai.py](../src/dynavec/embeddings/openai.py)
[tests/test_openai_embeddings.py](../tests/test_openai_embeddings.py)
[PR #242 merged](https://github.com/codeforstartups/dynavec/pull/242) | 429/5xx retries and numeric Retry-After handling exist. Compare the tested header formats with the intended acceptance criteria, then close or narrow the issue. | +| [#39 — TTL jitter for DynamoDBCache](https://github.com/codeforstartups/dynavec/issues/39)
@be-student | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[tests/test_cache.py](../tests/test_cache.py)
[tools/build_docs.py](../tools/build_docs.py) | DynamoDBCache has bounded configurable TTL jitter, validation, deterministic tests and a caching guide. Review the existing behavior and close; a closed-unmerged PR alone would not establish completion. | +| [#43 — Cache warmup utility](https://github.com/codeforstartups/dynavec/issues/43)
@arshsk16 | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[tests/test_cache.py](../tests/test_cache.py)
[CHANGELOG.md](../CHANGELOG.md) | warm_cache(client, queries) already populates a configured cache and has tests. Confirm the accepted public name relative to warm(queries), finish any dedicated usage docs, then close or narrow. | +| [#69 — Semantic Kernel memory connector](https://github.com/codeforstartups/dynavec/issues/69)
@tikhandesanket | P2 | [src/dynavec/integrations/semantic_kernel.py](../src/dynavec/integrations/semantic_kernel.py)
[tests/test_semantic_kernel.py](../tests/test_semantic_kernel.py)
[examples/semantic_kernel_vector_store.py](../examples/semantic_kernel_vector_store.py) | A Semantic Kernel vector-store adapter, example and tests exist. Confirm that this fulfills the intended memory connector API on supported Semantic Kernel versions; close or specify the remaining interface gap. | +| [#75 — Optimized Product Quantization (OPQ)](https://github.com/codeforstartups/dynavec/issues/75)
@tikhandesanket | P2 | [src/dynavec/quantization.py](../src/dynavec/quantization.py)
[tests/test_quantization.py](../tests/test_quantization.py)
[benchmarks/benchmark_opq.py](../benchmarks/benchmark_opq.py) | OPQRotation and OptimizedProductQuantizer, tests and a recall benchmark exist. Review benchmark methodology/results against the acceptance criteria and close or narrow; PR #198 being closed is not sufficient evidence on its own. | +| [#79 — Hot/cold sync on writes](https://github.com/codeforstartups/dynavec/issues/79)
@Isha-Zaka | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[tests/test_hot_tier.py](../tests/test_hot_tier.py) | The sync client fans successful writes/deletes into the hot tier, with write-through and delete consistency tests. Verify the intended scope, then close; failure recovery across stores remains separate work under #28. | +| [#103 — Cookbook: production RAG with namespaces](https://github.com/codeforstartups/dynavec/issues/103)
@tikhandesanket | P3 | [examples/multi_tenant_rag.py](../examples/multi_tenant_rag.py)
[tools/build_docs.py](../tools/build_docs.py)
[PR #239 merged](https://github.com/codeforstartups/dynavec/pull/239) | The multi-tenant RAG example and namespaces documentation exist. Review the recipe and tenant-authentication guidance against production expectations, then close or narrow. | +| [#138 — Dashboard: dark mode + theme parity with the landing page](https://github.com/codeforstartups/dynavec/issues/138)
@tikhandesanket | P3 | [dashboard/components/ThemeToggle.tsx](../dashboard/components/ThemeToggle.tsx)
[dashboard/app/layout.tsx](../dashboard/app/layout.tsx)
[dashboard/app/globals.css](../dashboard/app/globals.css)
[PR #187 merged](https://github.com/codeforstartups/dynavec/pull/187) | Dark palette and persisted theme toggle are implemented. Capture the requested parity screenshots and obtain design acceptance before closing; no new theme implementation is needed. | +| [#176 — dynavec complete landing page revamp](https://github.com/codeforstartups/dynavec/issues/176)
@tikhandesanket | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[opensource/dynavec/styles.css](../opensource/dynavec/styles.css)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The latest merged PR redesigns the landing page and docs. Request visual acceptance of that implementation and close or list concrete remaining defects; avoid starting another full redesign. | +| [#310 — Landing page: "How they fit together" diagram](https://github.com/codeforstartups/dynavec/issues/310)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[opensource/dynavec/styles.css](../opensource/dynavec/styles.css)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The landing page now includes a four-stage ecosystem cycle and SVG artwork. Verify the single flow/caption and desktop/mobile readability against the issue, then close or specify the visual gap. | +| [#311 — Landing page: positioning vs LangChain / LangGraph (parity + differentiators)](https://github.com/codeforstartups/dynavec/issues/311)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[docs/roadmap.md](../docs/roadmap.md)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The landing page includes a capability comparison with availability markers. Cross-check every status against the actual SDK/visual/deployment flows and approve the wording before closure. | + +### Open PR (4) + +| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | +|---|---|---|---| +| [#40 — Redis Cluster / TLS support](https://github.com/codeforstartups/dynavec/issues/40)
@sriram1900 | P2 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[PR #316 open](https://github.com/codeforstartups/dynavec/pull/316) | PR #316 describes itself as work in progress. Coordinate with its author to add cluster-client selection, TLS options and docs, then run backend parity tests. Keep the issue open until implementation is merged. | +| [#290 — Conversation / short-term memory store on DynamoDB](https://github.com/codeforstartups/dynavec/issues/290)
@shivamm-gupta | P2 | [PR #349 open](https://github.com/codeforstartups/dynavec/pull/349)
[PR #336 open](https://github.com/codeforstartups/dynavec/pull/336)
[src/dynavec/agents/react.py](../src/dynavec/agents/react.py) | Review #348 first, then the maintainer port #349 stacked on it. The #349 description supersedes #336; avoid another implementation and verify multi-turn memory, trimming, restart and interrupt/resume together. | +| [#296 — Human-in-the-loop / interrupts](https://github.com/codeforstartups/dynavec/issues/296)
@Uzmaa7 | P1 | [PR #348 open](https://github.com/codeforstartups/dynavec/pull/348)
[PR #332 open](https://github.com/codeforstartups/dynavec/pull/332)
[src/dynavec/agents/react.py](../src/dynavec/agents/react.py) | Review maintainer port #348, which supersedes #332. Verify approve/reject, provider-valid tool message ordering and restart-safe resume; separately verify the issue's timeout/expiry criterion before closure. | +| [#305 — Regression runs & CI gating (fail when a metric drops)](https://github.com/codeforstartups/dynavec/issues/305)
unassigned | P1 | [src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py)
[PR #351 open](https://github.com/codeforstartups/dynavec/pull/351) | Review #351 instead of rebuilding its gate. Verify threshold polarity, zero/NaN/missing metric handling, persisted baselines, CLI exit codes and the GitHub Actions example; the unmerged PR does not resolve the issue. | + +### Partial (28) + +| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | +|---|---|---|---| +| [#8 — Optional embedding cache to avoid re-embedding identical text](https://github.com/codeforstartups/dynavec/issues/8)
@tikhandesanket | P1 | [src/dynavec/embeddings/cached.py](../src/dynavec/embeddings/cached.py)
[src/dynavec/embeddings/cache.py](../src/dynavec/embeddings/cache.py)
[tests/test_embedding_cache.py](../tests/test_embedding_cache.py) | Caching exists, but the default model key is the embedder class name. Include provider/model/dimension identity or require an explicit key when sharing a cache; test two models of the same class sharing one backend. | +| [#26 — Support provisioned billing + autoscaling docs](https://github.com/codeforstartups/dynavec/issues/26)
unassigned | P3 | [src/dynavec/provisioning.py](../src/dynavec/provisioning.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[ARCHITECTURE.md](../ARCHITECTURE.md) | PROVISIONED mode already creates 5 RCU/5 WCU. Document capacity planning, application autoscaling setup/IAM and an example config; distinguish the current fixed defaults from automatic scaling. | +| [#51 — Client-side rate limiting / backpressure](https://github.com/codeforstartups/dynavec/issues/51)
@GenZ-CODER-X | P2 | [src/dynavec/utils.py](../src/dynavec/utils.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores/s3vectors.py](../src/dynavec/stores/s3vectors.py)
[tests/test_s3vectors.py](../tests/test_s3vectors.py)
[tests/test_async_s3vectors.py](../tests/test_async_s3vectors.py) | TokenBucket, put_rps/query_rps and sync/async hooks already exist. Add deterministic burst/refill and concurrent contention tests, document limits, and audit remaining put/query paths before closure. | +| [#62 — Incremental / delta ingestion](https://github.com/codeforstartups/dynavec/issues/62)
@Pratham1603 | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[src/dynavec/metadata.py](../src/dynavec/metadata.py) | Within-call deduplication exists, but there is no stored-hash comparison across runs. Add an opt-in per-chunk skip_unchanged path, preserve unconditional overwrite by default, and test changed metadata/text plus records without hashes. | +| [#64 — Ingest MCP tools & prompts primitives](https://github.com/codeforstartups/dynavec/issues/64)
@GenZ-CODER-X | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[src/dynavec/agents/connectors.py](../src/dynavec/agents/connectors.py) | MCPResourceSource ingests resources and agent connectors expose tools, but ingestion adapters for tool/prompt definitions are absent. Add duck-typed source adapters and fake-session tests without executing tools. | +| [#85 — dynavec CLI: provision/upsert/query/stats](https://github.com/codeforstartups/dynavec/issues/85)
unassigned | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[tests/test_cli.py](../tests/test_cli.py)
[pyproject.toml](../pyproject.toml) | The CLI already has version/doctor/export/import/mcp. Add provision/upsert/query/stats commands, explicit config/.env precedence and offline parser/error tests. | +| [#89 — moto-based DynamoDB integration tests](https://github.com/codeforstartups/dynavec/issues/89)
unassigned | P2 | [tests/test_provisioning.py](../tests/test_provisioning.py)
[tests/test_dynamodb.py](../tests/test_dynamodb.py)
[tests/test_dynamodb_ttl.py](../tests/test_dynamodb_ttl.py)
[tests/test_async_dynamodb_moto.py](../tests/test_async_dynamodb_moto.py)
[.github/workflows/ci.yml](../.github/workflows/ci.yml) | Moto provisioning/versioning/TTL and async round-trip tests already run through pytest in CI. Complete explicit batch put/get/delete round-trips and failure cases, then close or narrow instead of adding a duplicate suite. | +| [#93 — Credentials assume-role test](https://github.com/codeforstartups/dynavec/issues/93)
unassigned | P2 | [src/dynavec/credentials.py](../src/dynavec/credentials.py)
[tests/test_credentials.py](../tests/test_credentials.py) | The async test mocks AWSCredentials.session(), bypassing STS. Stub STS assume_role directly and assert role/session/external ID forwarding and temporary credentials, including failures and source-profile region behavior. | +| [#95 — Release automation notes / changelog](https://github.com/codeforstartups/dynavec/issues/95)
unassigned | P3 | [CHANGELOG.md](../CHANGELOG.md)
[.github/workflows/publish.yml](../.github/workflows/publish.yml)
[tools/build_docs.py](../tools/build_docs.py) | Changelog, release publishing workflow and release notes already exist. Add a maintainer release checklist covering version/tag consistency, build checks and verification of the published package. | +| [#96 — Coverage + PyPI + CI badges in README](https://github.com/codeforstartups/dynavec/issues/96)
unassigned | P3 | [README.md](../README.md)
[opensource/dynavec/index.html](../opensource/dynavec/index.html)
[.github/workflows/ci.yml](../.github/workflows/ci.yml) | README PyPI/CI badges exist; coverage publishing and a coverage badge are absent. Define the coverage source/threshold, publish its report and verify all requested badge links on both surfaces. | +| [#99 — KMS encryption docs for table + bucket](https://github.com/codeforstartups/dynavec/issues/99)
unassigned | P2 | [ARCHITECTURE.md](../ARCHITECTURE.md)
[docs/iam-policy.json](../docs/iam-policy.json)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | Encryption has a brief architecture note, but no SSE-KMS recipe or key-policy guidance. Document table/bucket creation settings, required KMS/IAM permissions and an example; do not imply provisioning currently accepts KMS configuration. | +| [#100 — Redact secrets from any logging/errors](https://github.com/codeforstartups/dynavec/issues/100)
@tenten48tenten | P1 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[tests/test_logging.py](../tests/test_logging.py)
[tests/test_run_logging.py](../tests/test_run_logging.py) | Redaction helpers and tests exist. Complete an audit and synthetic-secret coverage across messages, tracebacks and AWS/provider errors; coordinate remediation with the assignee and follow SECURITY.md for any vulnerability details. | +| [#101 — Auto-generate API reference (pdoc)](https://github.com/codeforstartups/dynavec/issues/101)
@raunak3208 | P3 | [tools/build_docs.py](../tools/build_docs.py)
[.github/workflows/pages.yml](../.github/workflows/pages.yml) | The static docs are hand-authored in build_docs.py. Add a reproducible pdoc API-reference build/publish step, optional dependency handling and a public-symbol smoke check. | +| [#128 — Client telemetry hook (opt-in) to record query events](https://github.com/codeforstartups/dynavec/issues/128)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[tests/test_telemetry.py](../tests/test_telemetry.py) | Search telemetry is opt-in via a recorder and omits query text by default. Add stage timings/result IDs as approved by the privacy contract, and a nonblocking persistent sink tied to #127; test enabled/disabled behavior. | +| [#132 — Dashboard view: Query analytics (latency, QPS, cache hit-rate)](https://github.com/codeforstartups/dynavec/issues/132)
@raunak3208 | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[dashboard/components/Kpis.tsx](../dashboard/components/Kpis.tsx)
[dashboard/components/LatencyChart.tsx](../dashboard/components/LatencyChart.tsx)
[dashboard/components/VolumeChart.tsx](../dashboard/components/VolumeChart.tsx) | KPIs, percentiles, cache hit-rate and a range selector already exist. Add the requested latency percentile time-series and verify real-data behavior; coordinate with the current assignee. | +| [#133 — Dashboard view: Per-query drill-down](https://github.com/codeforstartups/dynavec/issues/133)
unassigned | P2 | [dashboard/components/TraceDrawer.tsx](../dashboard/components/TraceDrawer.tsx)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[src/dynavec/client.py](../src/dynavec/client.py) | The query drawer and trace endpoint exist, but the drawer lacks stage waterfalls and result links/IDs. Persist stage timings and approved result details, then test drill-down with text capture both enabled and disabled. | +| [#136 — Dashboard view: Retrieval-quality trends over time](https://github.com/codeforstartups/dynavec/issues/136)
@tikhandesanket | P2 | [src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py)
[src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | EvalRunStore trends/comparison and eval API endpoints exist. Add the dashboard score/trend panel with dataset/model filters and run comparison; backend support alone does not finish the UI issue. | +| [#137 — Dashboard: security — local-only, server-side creds, API token](https://github.com/codeforstartups/dynavec/issues/137)
unassigned | P1 | [src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/lib/api.ts](../dashboard/lib/api.ts) | The server binds to 127.0.0.1 by default, but APIs have no token check and serve() allows another host. Add a server-side local API authentication/origin model, document it and test unauthorized requests and credential exclusion. | +| [#281 — Snapshots for resume, time-travel & replay](https://github.com/codeforstartups/dynavec/issues/281)
unassigned | P2 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[tests/test_checkpointer.py](../tests/test_checkpointer.py) | Checkpoint history and failed-run resume exist. Add explicit immutable snapshot/fork/replay APIs with input overrides and tests proving historical snapshots stay unchanged. | +| [#282 — Typed node system / node schema (ReactFlow-compatible)](https://github.com/codeforstartups/dynavec/issues/282)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py) | Typed port declarations, dictionaries and a canvas palette exist. Add a versioned graph JSON loader/round-trip and validate port compatibility across edges; declarations alone do not enforce types. | +| [#284 — Custom nodes via the SDK](https://github.com/codeforstartups/dynavec/issues/284)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py)
[tests/test_nodes.py](../tests/test_nodes.py) | Node subclassing/registration is already possible. Document a public custom-node example, execute it in CI and verify graph serialization/restoration through the shared schema from #282. | +| [#295 — SDK & CLI: run / deploy / inspect with code⇄visual parity](https://github.com/codeforstartups/dynavec/issues/295)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/cli.py](../src/dynavec/cli.py) | SDK graph execution exists; orchestration run/inspect/deploy CLI and code/canvas parity do not. Land the shared schema first, then add offline-tested commands; deployment also depends on #298. | +| [#299 — Per-node traces/spans assembled into a run tree](https://github.com/codeforstartups/dynavec/issues/299)
@Isha-Zaka | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[tests/test_run_logging.py](../tests/test_run_logging.py) | Run/node/span correlation IDs exist, but a parented run tree is missing. Add parent_span_id, start/end timing and stable span events, then test nested/parallel propagation and tree assembly. | +| [#301 — Run history: searchable / filterable list with drill-down to a trace](https://github.com/codeforstartups/dynavec/issues/301)
unassigned | P2 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[src/dynavec/logging.py](../src/dynavec/logging.py)
[dashboard/components/TracesTable.tsx](../dashboard/components/TracesTable.tsx) | The UI lists query events; checkpoints/log sinks hold pieces of agent runs. Add a durable, indexed run-list API plus run detail/tree UI with status/time/model/tag filters and pagination. | +| [#302 — Metrics: latency percentiles, QPS, cache hit-rate, error rate, token/cost per run](https://github.com/codeforstartups/dynavec/issues/302)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[dashboard/components/Kpis.tsx](../dashboard/components/Kpis.tsx)
[src/dynavec/logging.py](../src/dynavec/logging.py) | Query latency, traffic, cache and error aggregates exist. Add per-run model token/cost accounting, parented run attribution and consistent aggregates; test real usage values and unknown-price behavior. | +| [#304 — Test cases & datasets (labeled inputs → expected outputs/relevance)](https://github.com/codeforstartups/dynavec/issues/304)
unassigned | P2 | [src/dynavec/eval/retrieval.py](../src/dynavec/eval/retrieval.py)
[src/dynavec/eval/runner.py](../src/dynavec/eval/runner.py)
[tests/test_retrieval_eval.py](../tests/test_retrieval_eval.py) | LabeledQuery, JSON/JSONL loading and per-example/aggregate retrieval scores exist. Add dataset saving/round-trip, tags/expected-output schema and graph-suite support without duplicating the retrieval runner. | +| [#306 — In-loop eval: attach evals to the same run/trace view](https://github.com/codeforstartups/dynavec/issues/306)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py) | Eval metric fields and endpoints exist, but no complete run/span-tree association and score drill-down UI. Join eval results to execution run IDs and test query-by-run plus dashboard visibility. | +| [#342 — feat(dynaflow): introduce multi-agent supervisor and handoff primitives](https://github.com/codeforstartups/dynavec/issues/342)
@tikhandesanket | P1 | [src/dynavec/agents/supervisor.py](../src/dynavec/agents/supervisor.py)
[src/dynavec/agents/handoff.py](../src/dynavec/agents/handoff.py)
[src/dynavec/agents/state.py](../src/dynavec/agents/state.py)
[tests/test_supervisor_routing.py](../tests/test_supervisor_routing.py)
[PR #345 merged](https://github.com/codeforstartups/dynavec/pull/345) | Supervisor/state/handoff implementations exist under dynavec. Supervisor.run currently calls its router twice per run: call once and test a stateful router. Separately agree whether the dynaflow package-boundary requirement remains before closure. | + +### Missing (31) + +| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | +|---|---|---|---| +| [#17 — Add delete-by-filter helper](https://github.com/codeforstartups/dynavec/issues/17)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/namespace.py](../src/dynavec/namespace.py)
[tests/test_list_vectors.py](../tests/test_list_vectors.py) | Define supported filter semantics, enumerate the complete namespace, evaluate matching metadata, and batch delete both stores with cache/hot-tier invalidation. Test more than one batch, empty matches, failures and tenant isolation; ANN top-k is not exhaustive deletion. | +| [#21 — Optional GSI for metadata access patterns](https://github.com/codeforstartups/dynavec/issues/21)
unassigned | P2 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | Design explicit metadata access patterns and projected/indexed fields; add opt-in GSI provisioning and a query helper. Cover existing-table migration, pagination and the no-GSI default. | +| [#28 — Transactional write across both stores (best-effort)](https://github.com/codeforstartups/dynavec/issues/28)
unassigned | P1 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/async_client.py](../src/dynavec/async_client.py) | Document the two-store failure model and implement a bounded repair/journal strategy for partial writes. Test failure of either store and retries over existing records; blind deletion as rollback can destroy an earlier version. | +| [#29 — Sort-key adjacency design for high fan-out nodes](https://github.com/codeforstartups/dynavec/issues/29)
@tikhandesanket | P2 | [src/dynavec/graph.py](../src/dynavec/graph.py)
[PR #315 merged](https://github.com/codeforstartups/dynavec/pull/315) | Adjacency still lives in embedded lists. Propose separate edge items and a migration/backfill path, then benchmark high fan-out and pagination. PR #315 tests table provisioning; it does not implement this issue despite the issue comment linking it. | +| [#42 — Memcached backend](https://github.com/codeforstartups/dynavec/issues/42)
@GenZ-CODER-X | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[pyproject.toml](../pyproject.toml) | Add an optional Memcached client extra and BaseCache implementation, including namespace invalidation and TTL/stats parity tests. | +| [#45 — Cohere Rerank API integration](https://github.com/codeforstartups/dynavec/issues/45)
@troublerone | P2 | [src/dynavec/retrieval.py](../src/dynavec/retrieval.py)
[src/dynavec/client.py](../src/dynavec/client.py)
[pyproject.toml](../pyproject.toml) | Add a pluggable hosted reranker with caller-supplied Cohere credentials and an optional extra. Test ordered result mapping, empty inputs and provider errors; existing cross-encoder reranking is a different backend. | +| [#52 — Thread-safety tests for stores](https://github.com/codeforstartups/dynavec/issues/52)
unassigned | P2 | [tests/test_client_inmemory.py](../tests/test_client_inmemory.py)
[tests/test_connection_pool.py](../tests/test_connection_pool.py) | Add barrier-driven concurrent upsert/search scenarios against the fakes, asserting complete payloads and namespace isolation. Separate fake-store thread safety from guarantees of the AWS-backed client. | +| [#53 — Process pool for CPU-bound PQ training](https://github.com/codeforstartups/dynavec/issues/53)
@tikhandesanket | P3 | [src/dynavec/quantization.py](../src/dynavec/quantization.py)
[benchmarks/benchmark_quantization.py](../benchmarks/benchmark_quantization.py) | Add opt-in process-based PQ fitting only after benchmarking serialization/startup costs. Keep deterministic seeds, a sequential default and a benchmark demonstrating where the process pool helps. | +| [#82 — OpenTelemetry tracing](https://github.com/codeforstartups/dynavec/issues/82)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[src/dynavec/client.py](../src/dynavec/client.py)
[pyproject.toml](../pyproject.toml) | Add optional OpenTelemetry spans for embed/query/hydrate/rerank, preserve caller trace context, and supply a collector example. Test span parentage and operation without the extra installed. | +| [#83 — CloudWatch metrics emission](https://github.com/codeforstartups/dynavec/issues/83)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[docs/iam-policy.json](../docs/iam-policy.json) | Add an opt-in CloudWatch emitter with batching, bounded buffers and documented IAM. Test dimension/cardinality limits and ensure emitter failure does not fail retrieval. | +| [#88 — CLI: migrate from Pinecone](https://github.com/codeforstartups/dynavec/issues/88)
@Isha-Zaka | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[examples/pinecone_vs_dynavec.py](../examples/pinecone_vs_dynavec.py) | Add a resumable, batched Pinecone migration command preserving IDs, namespaces, metadata and vector dimensions. Test source pagination and interrupted migration; comparison examples are not an importer. | +| [#97 — Tighten S3 Vectors resource ARNs in the sample policy](https://github.com/codeforstartups/dynavec/issues/97)
unassigned | P1 | [README.md](../README.md)
[docs/iam-policy.json](../docs/iam-policy.json)
[ARCHITECTURE.md](../ARCHITECTURE.md) | Separate account-level list/create permissions from bucket/index data permissions using the current AWS authorization resource table. Scope data actions to the configured bucket/index and test documented runtime/provisioning policies. | +| [#102 — Migrating from Pinecone guide](https://github.com/codeforstartups/dynavec/issues/102)
unassigned | P3 | [examples/pinecone_vs_dynavec.py](../examples/pinecone_vs_dynavec.py)
[tools/build_docs.py](../tools/build_docs.py) | Write a migration guide with a concepts table, namespace/metric/filter differences, batched export/import examples and a verification checklist. Keep documentation separate from the importer in #88. | +| [#114 — Add Python 3.13 to the CI matrix](https://github.com/codeforstartups/dynavec/issues/114)
unassigned | P2 | [.github/workflows/ci.yml](../.github/workflows/ci.yml)
[pyproject.toml](../pyproject.toml)
[CONTRIBUTING.md](../CONTRIBUTING.md) | Add Python 3.13 to CI and run the suite plus dependency installation there. Update supported-version claims only after the run is green; retain coverage of existing supported versions. | +| [#115 — Dependency vulnerability audit in CI](https://github.com/codeforstartups/dynavec/issues/115)
unassigned | P1 | [.github/workflows/ci.yml](../.github/workflows/ci.yml)
[pyproject.toml](../pyproject.toml) | Add a dependency vulnerability audit job with a documented triage/expiry process for exceptions. Define which base and optional extras it covers; demonstrate the job fails for a known vulnerable fixture or controlled check. | +| [#125 — Dashboard: `dynavec dashboard` CLI to launch it locally](https://github.com/codeforstartups/dynavec/issues/125)
unassigned | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/next.config.mjs](../dashboard/next.config.mjs) | Add dynavec dashboard --port, documented telemetry wiring and static bundle serving/browser launch. Test startup, port conflicts and API integration; depend on the security model in #137. | +| [#127 — Telemetry data model: DynamoDB events table](https://github.com/codeforstartups/dynavec/issues/127)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | TelemetryRecorder is an in-memory ring buffer. Add an opt-in DynamoDB event schema/store with TTL, bounded asynchronous writes, provisioning and privacy documentation. | +| [#129 — Dashboard view: Resources (buckets, indexes, tables)](https://github.com/codeforstartups/dynavec/issues/129)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Reuse describe/list helpers in a resources API and dashboard cards for buckets/indexes/tables. Add pagination, empty/error states and tests; there is no /api/resources route today. | +| [#130 — Dashboard view: Namespace explorer](https://github.com/codeforstartups/dynavec/issues/130)
unassigned | P2 | [src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Share one namespace-explorer implementation with #141. Add paginated counts/size summaries and detail/search UI, avoiding an unbounded full scan for each dashboard refresh. | +| [#131 — Dashboard view: Cost panel](https://github.com/codeforstartups/dynavec/issues/131)
@tikhandesanket | P2 | [benchmarks/cost_model.py](../benchmarks/cost_model.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Expose the existing cost model through a cost API using live size/volume inputs, then build a breakdown panel. Show assumptions, pricing date and an explicit estimate label. | +| [#141 — Dashboard: Add Namespace Explorer](https://github.com/codeforstartups/dynavec/issues/141)
@osamashabih6960 | P2 | [dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py) | This overlaps #130. Agree on one owner/acceptance checklist and implement one namespace list, counts, filter and detail flow; link or consolidate the duplicate issue. | +| [#286 — Prompt templates & output parsers](https://github.com/codeforstartups/dynavec/issues/286)
@tikhandesanket | P2 | [src/dynavec/chat](../src/dynavec/chat)
[src/dynavec/nodes/builtin.py](../src/dynavec/nodes/builtin.py) | LLMNode accepts messages, but a reusable template/output-parser module is absent. Add message templates and JSON/Pydantic parsers with clear validation errors and rendering/parser tests. | +| [#291 — Visual builder: ReactFlow canvas to compose orchestrations](https://github.com/codeforstartups/dynavec/issues/291)
unassigned | P2 | [dashboard/package.json](../dashboard/package.json)
[dashboard/app/page.tsx](../dashboard/app/page.tsx)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py) | Choose dashboard versus standalone ownership, then implement a ReactFlow canvas using the node registry and #282 graph schema. Test create/connect/configure/save/reload and incompatible ports. | +| [#292 — Code ⇄ visual round-trip (import/export graph as code)](https://github.com/codeforstartups/dynavec/issues/292)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py) | Define one versioned graph representation first (#282), then implement SDK/code-to-canvas import/export. Add semantic round-trip tests for nodes, edges, config and custom nodes; do not execute arbitrary source to import it. | +| [#293 — Live run visualization on the canvas](https://github.com/codeforstartups/dynavec/issues/293)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | After #291/#299, stream the same run/span events to canvas nodes. Test start/finish/error state, reconnects and inputs/outputs without creating a second telemetry format. | +| [#294 — Debug & iterate: step through, re-run a single node, pin test inputs](https://github.com/codeforstartups/dynavec/issues/294)
unassigned | P3 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | Build on #281/#291: add step/pause, pinned inputs and single-node replay with inspectable edge state. Test reproducibility and preservation of the original run. | +| [#297 — Durable execution on Step Functions / Workflows](https://github.com/codeforstartups/dynavec/issues/297)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py) | Specify a durable execution backend around stable graph/state interfaces, then implement Step Functions/Workflows adapters with idempotent steps, retries and resume tests. DynamoDB checkpoints alone do not provide managed execution. | +| [#298 — One-command serverless deploy (Lambda / Cloud Run), in-account](https://github.com/codeforstartups/dynavec/issues/298)
unassigned | P2 | [docs/DEPLOY.md](../docs/DEPLOY.md)
[src/dynavec/cli.py](../src/dynavec/cli.py) | The deployment document is not a one-command agent deployment flow. Add packaging and a deploy command with an explicit resource/IAM plan; verify provision/run/log/teardown in a dedicated cloud test environment. | +| [#307 — Multi-cloud: pluggable GCP backends (Vertex, Firestore, Cloud Run, Workflows)](https://github.com/codeforstartups/dynavec/issues/307)
unassigned | P3 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores](../src/dynavec/stores)
[pyproject.toml](../pyproject.toml) | Stabilize provider-neutral store/model/execution interfaces, then implement GCP adapters and a parity matrix. Keep GCP dependencies optional and add contract tests plus a documented cloud smoke run. | +| [#308 — Multi-cloud: pluggable Azure backends (Azure OpenAI, Cosmos DB, Functions, Durable Functions)](https://github.com/codeforstartups/dynavec/issues/308)
unassigned | P3 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores](../src/dynavec/stores)
[pyproject.toml](../pyproject.toml) | Reuse the backend contracts from #307, add Azure model/store/compute/durable/secrets adapters as optional extras, and verify an example graph against the published parity matrix. | +| [#321 — feat(ingest): Support cursor pagination in MCPResourceSource](https://github.com/codeforstartups/dynavec/issues/321)
@neha-5456 | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[tests/test_ingest.py](../tests/test_ingest.py) | MCPResourceSource still calls list_resources once. Coordinate with the assignee to follow nextCursor/next_cursor in objects and mappings, forward cursor and test multiple/empty pages plus repeated-cursor protection. | + +### Epic (5) + +| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | +|---|---|---|---| +| [#122 — [EPIC] Telemetry & resource dashboard](https://github.com/codeforstartups/dynavec/issues/122)
unassigned | P2 | [dashboard/README.md](../dashboard/README.md)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | Track child completion individually: persistence #127, launch/security #125/#137, resources/namespaces #129/#130/#141, cost #131 and eval UI #136. Reconcile the original Vite plan with the shipped Next.js app. | +| [#273 — [EPIC] dynaflow — serverless agent orchestration engine + visual builder](https://github.com/codeforstartups/dynavec/issues/273)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[docs/roadmap.md](../docs/roadmap.md) | Graph runtimes, nodes, agents and checkpointing have landed. Track remaining schema/round-trip, snapshots, visual builder and deployment children; update the stale roadmap claim that the engine is only pseudocode. | +| [#274 — [EPIC] dynalogs — in-account observability for agent runs](https://github.com/codeforstartups/dynavec/issues/274)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py) | Structured run/node logs and query telemetry exist. Complete parented spans, durable run history, metrics and dashboard integration under #299/#301/#302 rather than recreating logging. | +| [#275 — [EPIC] dynaevals — evaluation, datasets & CI gating](https://github.com/codeforstartups/dynavec/issues/275)
unassigned | P2 | [src/dynavec/eval](../src/dynavec/eval)
[PR #351 open](https://github.com/codeforstartups/dynavec/pull/351) | Retrieval/RAG metrics and trend tracking exist. Finish dataset round-trip #304, review CI gate #305 and attach evals to runs #306; keep the epic open until those flows are verified. | +| [#276 — [EPIC] Ecosystem launch — landing page & cross-library story (dynavec / dynaflow / dynalogs / dynaevals)](https://github.com/codeforstartups/dynavec/issues/276)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[tools/build_docs.py](../tools/build_docs.py)
[docs/roadmap.md](../docs/roadmap.md) | Ecosystem pages and landing-page sections exist after #347. Review #310/#311, then reconcile capability claims with shipped runtime/visual/deployment status before closing the ecosystem epic. | + +## Validation and limits + +- Python 3.12.15, installed via `uv venv --python 3.12` and the repository's `make install` target. +- Focused query/store/error tests pass, including real SDK stubs; lint passes for the changed tests. +- `make run-ci` passed: ruff, strict mypy (87 checked files), **1,125 tests passed, 2 opt-in live AWS tests skipped**, and 1 snapshot passed. The focused store/error/logging selection passed all 100 tests; changed-test lint and `git diff --check` also passed. +- No live AWS resources, credentialed model calls, external Redis/Memcached clusters, browser layout acceptance, or deployed orchestration were exercised. Missing/partial assessments concern inspected deliverables and explicitly identified acceptance gaps. +- This is a dated snapshot. Recheck current issue assignments, PR state and source before starting follow-up work. This PR does not close the entire backlog or automatically change issue ownership. From d8a1c8b2e4efb99b02905e78aa5364bc66cec80b Mon Sep 17 00:00:00 2001 From: Lawliet2004 <168631200+Lawliet2004@users.noreply.github.com> Date: Sun, 8 Nov 2026 13:00:43 +0530 Subject: [PATCH 3/3] docs: remove issue audit from pagination PR --- docs/ISSUE_AUDIT_2026-11-06.md | 165 --------------------------------- 1 file changed, 165 deletions(-) delete mode 100644 docs/ISSUE_AUDIT_2026-11-06.md diff --git a/docs/ISSUE_AUDIT_2026-11-06.md b/docs/ISSUE_AUDIT_2026-11-06.md deleted file mode 100644 index d541a9c..0000000 --- a/docs/ISSUE_AUDIT_2026-11-06.md +++ /dev/null @@ -1,165 +0,0 @@ -# Issue audit — 6 November 2026 - -## Scope and evidence - -Audit baseline: `development` at `b9c90d7`. GitHub inventory was read on 6 November 2026: **192 issues, 79 open and 113 closed; 158 pull requests, 7 open** before this audit PR. Every open issue appears exactly once in the inventory below. Closed issues were sampled against relevant code and PRs; this is not a reproduction of every closed issue. - -Assessments compare issue acceptance criteria, current source/tests, and merged/open PR metadata. A linked or closed PR is not proof that a feature shipped. In particular, the comment on #29 links provisioning-test PR #315, not an edge-storage implementation. Static code presence is not an end-to-end production guarantee. - -[Issue tracker](https://github.com/codeforstartups/dynavec/issues) · [Pull requests](https://github.com/codeforstartups/dynavec/pulls) · [Contribution/assignment policy](../CONTRIBUTING.md#finding--claiming-issues). Ownership is the snapshot assignee shown on each row; “unassigned” is not an assignment. Coordinate existing work before claiming an issue. This PR fixes a regression discovered during the audit rather than implementing another contributor's assigned feature. - -| Assessment | Open issues | Meaning | -|---|---:|---| -| Review closure | 11 | Implementation evidence exists; maintainer acceptance or a narrow follow-up is still required. | -| Partial | 28 | Some requested behavior exists, with explicit gaps listed below. | -| Open PR | 4 | Implementation is being proposed; review/merge and acceptance checks remain. | -| Missing | 31 | The requested public flow or essential deliverable was not found in the inspected code. | -| Epic | 5 | Parent tracking issue; resolve its children rather than treating it as one patch. | - -Priorities are audit recommendations: **P1** correctness/security or a blocking integration; **P2** functional completion; **P3** documentation, polish or later architecture. They are not repository labels. - -## Recommended order - -| Order | Work | Completion check | -|---|---|---| -| 1 | Merge the QueryVectors fix in this PR. | Real boto3/aioboto3 stubs work without a query paginator; all pages are consumed using `nextToken`; top-k, buffering, errors and rate limiting remain correct. | -| 2 | Coordinate correctness/security gaps #8, #28, #97, #100, #137 and #342 with their owners. | Cross-model caches are isolated; partial writes have a repair strategy; sample IAM is scoped; formatted secrets are redacted; dashboard APIs enforce their access model; routing is invoked once. | -| 3 | Review pending PRs: #348 → #349, #351, and draft #316. | Verify acceptance cases before merging; retire superseded #332/#336 in coordination with their authors. | -| 4 | Review the closure candidates below and reconcile #130/#141. | Update issue state only after maintainer acceptance; retain specific remaining gaps as follow-ups. | -| 5 | Finish persistent telemetry/run history and the shared graph schema. | #127/#128/#299/#301/#302 and #282/#284 provide stable data contracts before building dependent UI. | -| 6 | Build visual/eval/deployment flows on those contracts. | Dataset/gate/run links, canvas round-trip and deployment each have an executable acceptance scenario; expand to GCP/Azure afterward. | - -## Confirmed defects found while auditing - -### Selected fix: S3 Vectors queries stop early or cannot start - -Both stores requested a `query_vectors` paginator. The environment produced by `make install` resolves boto3/botocore **1.40.61**, where `client.can_paginate("query_vectors")` is false and `get_paginator` raises `OperationNotPageableError`. Newer AWS APIs support continuation, but both stores checked `NextToken` instead of the documented **`nextToken`**. This silently discarded later results. Earlier tests used the same incorrect uppercase key. - -The fix calls `query_vectors` directly in sync and async stores and forwards a nonempty `nextToken`. This supports a single response on older SDKs and continuation on SDK/service versions exposing it, without changing dependency constraints. A caller still needs an SDK supporting any request fields/features it uses; this change does not add pagination support to an old service model. - -Regression coverage includes real SDK clients with Stubber/AioStubber, multiple service pages, empty intermediate pages, client chunk buffering (including a page size larger than top-k), cursor forwarding, top-k limits, one rate-limit acquisition per request, permanent error translation and retry restart. Tests with realistic lowercase tokens reproduced 15 failures before the production fix. The historical pagination issue [#16](https://github.com/codeforstartups/dynavec/issues/16) is closed, so its tracker state alone did not establish correctness. - -Sources: [AWS QueryVectors request/response](https://docs.aws.amazon.com/AmazonS3/latest/API/API_S3VectorBuckets_QueryVectors.html), [boto3 query_vectors](https://docs.aws.amazon.com/boto3/latest/reference/services/s3vectors/client/query_vectors.html), and the installed SDK service/paginator model inspected offline. - -### Follow-ups reproduced without external services - -- **#8:** two CachedEmbedder instances of the same backend class, with different model identities and a shared cache, reuse the first model's vector under the default class-based key. Use explicit distinct `model_key` values as the current workaround; a full fix must encode actual embedding identity. -- **#342:** one Supervisor.run call invokes a configured router **twice**, as shown by a counting router. A stateful router can produce different selections between validation/execution. Reuse the first selected route and add an invocation-count regression. - -These findings are resolution work for the relevant issues, not claims that those issues are fixed by this PR. Security-related reproduction details are excluded from this public report in accordance with [SECURITY.md](../SECURITY.md). - -## Open-issue inventory - -Each row states source evidence and a concrete next action. “Review closure” is a recommendation for maintainer review, not an automatic closure request. UI acceptance and cloud deployment checks remain separate from offline Python tests. - -### Review closure (11) - -| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | -|---|---|---|---| -| [#7 — Retry/backoff for OpenAI embedder rate limits](https://github.com/codeforstartups/dynavec/issues/7)
@tikhandesanket | P2 | [src/dynavec/embeddings/openai.py](../src/dynavec/embeddings/openai.py)
[tests/test_openai_embeddings.py](../tests/test_openai_embeddings.py)
[PR #242 merged](https://github.com/codeforstartups/dynavec/pull/242) | 429/5xx retries and numeric Retry-After handling exist. Compare the tested header formats with the intended acceptance criteria, then close or narrow the issue. | -| [#39 — TTL jitter for DynamoDBCache](https://github.com/codeforstartups/dynavec/issues/39)
@be-student | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[tests/test_cache.py](../tests/test_cache.py)
[tools/build_docs.py](../tools/build_docs.py) | DynamoDBCache has bounded configurable TTL jitter, validation, deterministic tests and a caching guide. Review the existing behavior and close; a closed-unmerged PR alone would not establish completion. | -| [#43 — Cache warmup utility](https://github.com/codeforstartups/dynavec/issues/43)
@arshsk16 | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[tests/test_cache.py](../tests/test_cache.py)
[CHANGELOG.md](../CHANGELOG.md) | warm_cache(client, queries) already populates a configured cache and has tests. Confirm the accepted public name relative to warm(queries), finish any dedicated usage docs, then close or narrow. | -| [#69 — Semantic Kernel memory connector](https://github.com/codeforstartups/dynavec/issues/69)
@tikhandesanket | P2 | [src/dynavec/integrations/semantic_kernel.py](../src/dynavec/integrations/semantic_kernel.py)
[tests/test_semantic_kernel.py](../tests/test_semantic_kernel.py)
[examples/semantic_kernel_vector_store.py](../examples/semantic_kernel_vector_store.py) | A Semantic Kernel vector-store adapter, example and tests exist. Confirm that this fulfills the intended memory connector API on supported Semantic Kernel versions; close or specify the remaining interface gap. | -| [#75 — Optimized Product Quantization (OPQ)](https://github.com/codeforstartups/dynavec/issues/75)
@tikhandesanket | P2 | [src/dynavec/quantization.py](../src/dynavec/quantization.py)
[tests/test_quantization.py](../tests/test_quantization.py)
[benchmarks/benchmark_opq.py](../benchmarks/benchmark_opq.py) | OPQRotation and OptimizedProductQuantizer, tests and a recall benchmark exist. Review benchmark methodology/results against the acceptance criteria and close or narrow; PR #198 being closed is not sufficient evidence on its own. | -| [#79 — Hot/cold sync on writes](https://github.com/codeforstartups/dynavec/issues/79)
@Isha-Zaka | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[tests/test_hot_tier.py](../tests/test_hot_tier.py) | The sync client fans successful writes/deletes into the hot tier, with write-through and delete consistency tests. Verify the intended scope, then close; failure recovery across stores remains separate work under #28. | -| [#103 — Cookbook: production RAG with namespaces](https://github.com/codeforstartups/dynavec/issues/103)
@tikhandesanket | P3 | [examples/multi_tenant_rag.py](../examples/multi_tenant_rag.py)
[tools/build_docs.py](../tools/build_docs.py)
[PR #239 merged](https://github.com/codeforstartups/dynavec/pull/239) | The multi-tenant RAG example and namespaces documentation exist. Review the recipe and tenant-authentication guidance against production expectations, then close or narrow. | -| [#138 — Dashboard: dark mode + theme parity with the landing page](https://github.com/codeforstartups/dynavec/issues/138)
@tikhandesanket | P3 | [dashboard/components/ThemeToggle.tsx](../dashboard/components/ThemeToggle.tsx)
[dashboard/app/layout.tsx](../dashboard/app/layout.tsx)
[dashboard/app/globals.css](../dashboard/app/globals.css)
[PR #187 merged](https://github.com/codeforstartups/dynavec/pull/187) | Dark palette and persisted theme toggle are implemented. Capture the requested parity screenshots and obtain design acceptance before closing; no new theme implementation is needed. | -| [#176 — dynavec complete landing page revamp](https://github.com/codeforstartups/dynavec/issues/176)
@tikhandesanket | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[opensource/dynavec/styles.css](../opensource/dynavec/styles.css)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The latest merged PR redesigns the landing page and docs. Request visual acceptance of that implementation and close or list concrete remaining defects; avoid starting another full redesign. | -| [#310 — Landing page: "How they fit together" diagram](https://github.com/codeforstartups/dynavec/issues/310)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[opensource/dynavec/styles.css](../opensource/dynavec/styles.css)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The landing page now includes a four-stage ecosystem cycle and SVG artwork. Verify the single flow/caption and desktop/mobile readability against the issue, then close or specify the visual gap. | -| [#311 — Landing page: positioning vs LangChain / LangGraph (parity + differentiators)](https://github.com/codeforstartups/dynavec/issues/311)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[docs/roadmap.md](../docs/roadmap.md)
[PR #347 merged](https://github.com/codeforstartups/dynavec/pull/347) | The landing page includes a capability comparison with availability markers. Cross-check every status against the actual SDK/visual/deployment flows and approve the wording before closure. | - -### Open PR (4) - -| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | -|---|---|---|---| -| [#40 — Redis Cluster / TLS support](https://github.com/codeforstartups/dynavec/issues/40)
@sriram1900 | P2 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[PR #316 open](https://github.com/codeforstartups/dynavec/pull/316) | PR #316 describes itself as work in progress. Coordinate with its author to add cluster-client selection, TLS options and docs, then run backend parity tests. Keep the issue open until implementation is merged. | -| [#290 — Conversation / short-term memory store on DynamoDB](https://github.com/codeforstartups/dynavec/issues/290)
@shivamm-gupta | P2 | [PR #349 open](https://github.com/codeforstartups/dynavec/pull/349)
[PR #336 open](https://github.com/codeforstartups/dynavec/pull/336)
[src/dynavec/agents/react.py](../src/dynavec/agents/react.py) | Review #348 first, then the maintainer port #349 stacked on it. The #349 description supersedes #336; avoid another implementation and verify multi-turn memory, trimming, restart and interrupt/resume together. | -| [#296 — Human-in-the-loop / interrupts](https://github.com/codeforstartups/dynavec/issues/296)
@Uzmaa7 | P1 | [PR #348 open](https://github.com/codeforstartups/dynavec/pull/348)
[PR #332 open](https://github.com/codeforstartups/dynavec/pull/332)
[src/dynavec/agents/react.py](../src/dynavec/agents/react.py) | Review maintainer port #348, which supersedes #332. Verify approve/reject, provider-valid tool message ordering and restart-safe resume; separately verify the issue's timeout/expiry criterion before closure. | -| [#305 — Regression runs & CI gating (fail when a metric drops)](https://github.com/codeforstartups/dynavec/issues/305)
unassigned | P1 | [src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py)
[PR #351 open](https://github.com/codeforstartups/dynavec/pull/351) | Review #351 instead of rebuilding its gate. Verify threshold polarity, zero/NaN/missing metric handling, persisted baselines, CLI exit codes and the GitHub Actions example; the unmerged PR does not resolve the issue. | - -### Partial (28) - -| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | -|---|---|---|---| -| [#8 — Optional embedding cache to avoid re-embedding identical text](https://github.com/codeforstartups/dynavec/issues/8)
@tikhandesanket | P1 | [src/dynavec/embeddings/cached.py](../src/dynavec/embeddings/cached.py)
[src/dynavec/embeddings/cache.py](../src/dynavec/embeddings/cache.py)
[tests/test_embedding_cache.py](../tests/test_embedding_cache.py) | Caching exists, but the default model key is the embedder class name. Include provider/model/dimension identity or require an explicit key when sharing a cache; test two models of the same class sharing one backend. | -| [#26 — Support provisioned billing + autoscaling docs](https://github.com/codeforstartups/dynavec/issues/26)
unassigned | P3 | [src/dynavec/provisioning.py](../src/dynavec/provisioning.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[ARCHITECTURE.md](../ARCHITECTURE.md) | PROVISIONED mode already creates 5 RCU/5 WCU. Document capacity planning, application autoscaling setup/IAM and an example config; distinguish the current fixed defaults from automatic scaling. | -| [#51 — Client-side rate limiting / backpressure](https://github.com/codeforstartups/dynavec/issues/51)
@GenZ-CODER-X | P2 | [src/dynavec/utils.py](../src/dynavec/utils.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores/s3vectors.py](../src/dynavec/stores/s3vectors.py)
[tests/test_s3vectors.py](../tests/test_s3vectors.py)
[tests/test_async_s3vectors.py](../tests/test_async_s3vectors.py) | TokenBucket, put_rps/query_rps and sync/async hooks already exist. Add deterministic burst/refill and concurrent contention tests, document limits, and audit remaining put/query paths before closure. | -| [#62 — Incremental / delta ingestion](https://github.com/codeforstartups/dynavec/issues/62)
@Pratham1603 | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[src/dynavec/metadata.py](../src/dynavec/metadata.py) | Within-call deduplication exists, but there is no stored-hash comparison across runs. Add an opt-in per-chunk skip_unchanged path, preserve unconditional overwrite by default, and test changed metadata/text plus records without hashes. | -| [#64 — Ingest MCP tools & prompts primitives](https://github.com/codeforstartups/dynavec/issues/64)
@GenZ-CODER-X | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[src/dynavec/agents/connectors.py](../src/dynavec/agents/connectors.py) | MCPResourceSource ingests resources and agent connectors expose tools, but ingestion adapters for tool/prompt definitions are absent. Add duck-typed source adapters and fake-session tests without executing tools. | -| [#85 — dynavec CLI: provision/upsert/query/stats](https://github.com/codeforstartups/dynavec/issues/85)
unassigned | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[tests/test_cli.py](../tests/test_cli.py)
[pyproject.toml](../pyproject.toml) | The CLI already has version/doctor/export/import/mcp. Add provision/upsert/query/stats commands, explicit config/.env precedence and offline parser/error tests. | -| [#89 — moto-based DynamoDB integration tests](https://github.com/codeforstartups/dynavec/issues/89)
unassigned | P2 | [tests/test_provisioning.py](../tests/test_provisioning.py)
[tests/test_dynamodb.py](../tests/test_dynamodb.py)
[tests/test_dynamodb_ttl.py](../tests/test_dynamodb_ttl.py)
[tests/test_async_dynamodb_moto.py](../tests/test_async_dynamodb_moto.py)
[.github/workflows/ci.yml](../.github/workflows/ci.yml) | Moto provisioning/versioning/TTL and async round-trip tests already run through pytest in CI. Complete explicit batch put/get/delete round-trips and failure cases, then close or narrow instead of adding a duplicate suite. | -| [#93 — Credentials assume-role test](https://github.com/codeforstartups/dynavec/issues/93)
unassigned | P2 | [src/dynavec/credentials.py](../src/dynavec/credentials.py)
[tests/test_credentials.py](../tests/test_credentials.py) | The async test mocks AWSCredentials.session(), bypassing STS. Stub STS assume_role directly and assert role/session/external ID forwarding and temporary credentials, including failures and source-profile region behavior. | -| [#95 — Release automation notes / changelog](https://github.com/codeforstartups/dynavec/issues/95)
unassigned | P3 | [CHANGELOG.md](../CHANGELOG.md)
[.github/workflows/publish.yml](../.github/workflows/publish.yml)
[tools/build_docs.py](../tools/build_docs.py) | Changelog, release publishing workflow and release notes already exist. Add a maintainer release checklist covering version/tag consistency, build checks and verification of the published package. | -| [#96 — Coverage + PyPI + CI badges in README](https://github.com/codeforstartups/dynavec/issues/96)
unassigned | P3 | [README.md](../README.md)
[opensource/dynavec/index.html](../opensource/dynavec/index.html)
[.github/workflows/ci.yml](../.github/workflows/ci.yml) | README PyPI/CI badges exist; coverage publishing and a coverage badge are absent. Define the coverage source/threshold, publish its report and verify all requested badge links on both surfaces. | -| [#99 — KMS encryption docs for table + bucket](https://github.com/codeforstartups/dynavec/issues/99)
unassigned | P2 | [ARCHITECTURE.md](../ARCHITECTURE.md)
[docs/iam-policy.json](../docs/iam-policy.json)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | Encryption has a brief architecture note, but no SSE-KMS recipe or key-policy guidance. Document table/bucket creation settings, required KMS/IAM permissions and an example; do not imply provisioning currently accepts KMS configuration. | -| [#100 — Redact secrets from any logging/errors](https://github.com/codeforstartups/dynavec/issues/100)
@tenten48tenten | P1 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[tests/test_logging.py](../tests/test_logging.py)
[tests/test_run_logging.py](../tests/test_run_logging.py) | Redaction helpers and tests exist. Complete an audit and synthetic-secret coverage across messages, tracebacks and AWS/provider errors; coordinate remediation with the assignee and follow SECURITY.md for any vulnerability details. | -| [#101 — Auto-generate API reference (pdoc)](https://github.com/codeforstartups/dynavec/issues/101)
@raunak3208 | P3 | [tools/build_docs.py](../tools/build_docs.py)
[.github/workflows/pages.yml](../.github/workflows/pages.yml) | The static docs are hand-authored in build_docs.py. Add a reproducible pdoc API-reference build/publish step, optional dependency handling and a public-symbol smoke check. | -| [#128 — Client telemetry hook (opt-in) to record query events](https://github.com/codeforstartups/dynavec/issues/128)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[tests/test_telemetry.py](../tests/test_telemetry.py) | Search telemetry is opt-in via a recorder and omits query text by default. Add stage timings/result IDs as approved by the privacy contract, and a nonblocking persistent sink tied to #127; test enabled/disabled behavior. | -| [#132 — Dashboard view: Query analytics (latency, QPS, cache hit-rate)](https://github.com/codeforstartups/dynavec/issues/132)
@raunak3208 | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[dashboard/components/Kpis.tsx](../dashboard/components/Kpis.tsx)
[dashboard/components/LatencyChart.tsx](../dashboard/components/LatencyChart.tsx)
[dashboard/components/VolumeChart.tsx](../dashboard/components/VolumeChart.tsx) | KPIs, percentiles, cache hit-rate and a range selector already exist. Add the requested latency percentile time-series and verify real-data behavior; coordinate with the current assignee. | -| [#133 — Dashboard view: Per-query drill-down](https://github.com/codeforstartups/dynavec/issues/133)
unassigned | P2 | [dashboard/components/TraceDrawer.tsx](../dashboard/components/TraceDrawer.tsx)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[src/dynavec/client.py](../src/dynavec/client.py) | The query drawer and trace endpoint exist, but the drawer lacks stage waterfalls and result links/IDs. Persist stage timings and approved result details, then test drill-down with text capture both enabled and disabled. | -| [#136 — Dashboard view: Retrieval-quality trends over time](https://github.com/codeforstartups/dynavec/issues/136)
@tikhandesanket | P2 | [src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py)
[src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | EvalRunStore trends/comparison and eval API endpoints exist. Add the dashboard score/trend panel with dataset/model filters and run comparison; backend support alone does not finish the UI issue. | -| [#137 — Dashboard: security — local-only, server-side creds, API token](https://github.com/codeforstartups/dynavec/issues/137)
unassigned | P1 | [src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/lib/api.ts](../dashboard/lib/api.ts) | The server binds to 127.0.0.1 by default, but APIs have no token check and serve() allows another host. Add a server-side local API authentication/origin model, document it and test unauthorized requests and credential exclusion. | -| [#281 — Snapshots for resume, time-travel & replay](https://github.com/codeforstartups/dynavec/issues/281)
unassigned | P2 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[tests/test_checkpointer.py](../tests/test_checkpointer.py) | Checkpoint history and failed-run resume exist. Add explicit immutable snapshot/fork/replay APIs with input overrides and tests proving historical snapshots stay unchanged. | -| [#282 — Typed node system / node schema (ReactFlow-compatible)](https://github.com/codeforstartups/dynavec/issues/282)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py) | Typed port declarations, dictionaries and a canvas palette exist. Add a versioned graph JSON loader/round-trip and validate port compatibility across edges; declarations alone do not enforce types. | -| [#284 — Custom nodes via the SDK](https://github.com/codeforstartups/dynavec/issues/284)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py)
[tests/test_nodes.py](../tests/test_nodes.py) | Node subclassing/registration is already possible. Document a public custom-node example, execute it in CI and verify graph serialization/restoration through the shared schema from #282. | -| [#295 — SDK & CLI: run / deploy / inspect with code⇄visual parity](https://github.com/codeforstartups/dynavec/issues/295)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/cli.py](../src/dynavec/cli.py) | SDK graph execution exists; orchestration run/inspect/deploy CLI and code/canvas parity do not. Land the shared schema first, then add offline-tested commands; deployment also depends on #298. | -| [#299 — Per-node traces/spans assembled into a run tree](https://github.com/codeforstartups/dynavec/issues/299)
@Isha-Zaka | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[tests/test_run_logging.py](../tests/test_run_logging.py) | Run/node/span correlation IDs exist, but a parented run tree is missing. Add parent_span_id, start/end timing and stable span events, then test nested/parallel propagation and tree assembly. | -| [#301 — Run history: searchable / filterable list with drill-down to a trace](https://github.com/codeforstartups/dynavec/issues/301)
unassigned | P2 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[src/dynavec/logging.py](../src/dynavec/logging.py)
[dashboard/components/TracesTable.tsx](../dashboard/components/TracesTable.tsx) | The UI lists query events; checkpoints/log sinks hold pieces of agent runs. Add a durable, indexed run-list API plus run detail/tree UI with status/time/model/tag filters and pagination. | -| [#302 — Metrics: latency percentiles, QPS, cache hit-rate, error rate, token/cost per run](https://github.com/codeforstartups/dynavec/issues/302)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[dashboard/components/Kpis.tsx](../dashboard/components/Kpis.tsx)
[src/dynavec/logging.py](../src/dynavec/logging.py) | Query latency, traffic, cache and error aggregates exist. Add per-run model token/cost accounting, parented run attribution and consistent aggregates; test real usage values and unknown-price behavior. | -| [#304 — Test cases & datasets (labeled inputs → expected outputs/relevance)](https://github.com/codeforstartups/dynavec/issues/304)
unassigned | P2 | [src/dynavec/eval/retrieval.py](../src/dynavec/eval/retrieval.py)
[src/dynavec/eval/runner.py](../src/dynavec/eval/runner.py)
[tests/test_retrieval_eval.py](../tests/test_retrieval_eval.py) | LabeledQuery, JSON/JSONL loading and per-example/aggregate retrieval scores exist. Add dataset saving/round-trip, tags/expected-output schema and graph-suite support without duplicating the retrieval runner. | -| [#306 — In-loop eval: attach evals to the same run/trace view](https://github.com/codeforstartups/dynavec/issues/306)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[src/dynavec/eval/trend.py](../src/dynavec/eval/trend.py) | Eval metric fields and endpoints exist, but no complete run/span-tree association and score drill-down UI. Join eval results to execution run IDs and test query-by-run plus dashboard visibility. | -| [#342 — feat(dynaflow): introduce multi-agent supervisor and handoff primitives](https://github.com/codeforstartups/dynavec/issues/342)
@tikhandesanket | P1 | [src/dynavec/agents/supervisor.py](../src/dynavec/agents/supervisor.py)
[src/dynavec/agents/handoff.py](../src/dynavec/agents/handoff.py)
[src/dynavec/agents/state.py](../src/dynavec/agents/state.py)
[tests/test_supervisor_routing.py](../tests/test_supervisor_routing.py)
[PR #345 merged](https://github.com/codeforstartups/dynavec/pull/345) | Supervisor/state/handoff implementations exist under dynavec. Supervisor.run currently calls its router twice per run: call once and test a stateful router. Separately agree whether the dynaflow package-boundary requirement remains before closure. | - -### Missing (31) - -| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | -|---|---|---|---| -| [#17 — Add delete-by-filter helper](https://github.com/codeforstartups/dynavec/issues/17)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/namespace.py](../src/dynavec/namespace.py)
[tests/test_list_vectors.py](../tests/test_list_vectors.py) | Define supported filter semantics, enumerate the complete namespace, evaluate matching metadata, and batch delete both stores with cache/hot-tier invalidation. Test more than one batch, empty matches, failures and tenant isolation; ANN top-k is not exhaustive deletion. | -| [#21 — Optional GSI for metadata access patterns](https://github.com/codeforstartups/dynavec/issues/21)
unassigned | P2 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | Design explicit metadata access patterns and projected/indexed fields; add opt-in GSI provisioning and a query helper. Cover existing-table migration, pagination and the no-GSI default. | -| [#28 — Transactional write across both stores (best-effort)](https://github.com/codeforstartups/dynavec/issues/28)
unassigned | P1 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/async_client.py](../src/dynavec/async_client.py) | Document the two-store failure model and implement a bounded repair/journal strategy for partial writes. Test failure of either store and retries over existing records; blind deletion as rollback can destroy an earlier version. | -| [#29 — Sort-key adjacency design for high fan-out nodes](https://github.com/codeforstartups/dynavec/issues/29)
@tikhandesanket | P2 | [src/dynavec/graph.py](../src/dynavec/graph.py)
[PR #315 merged](https://github.com/codeforstartups/dynavec/pull/315) | Adjacency still lives in embedded lists. Propose separate edge items and a migration/backfill path, then benchmark high fan-out and pagination. PR #315 tests table provisioning; it does not implement this issue despite the issue comment linking it. | -| [#42 — Memcached backend](https://github.com/codeforstartups/dynavec/issues/42)
@GenZ-CODER-X | P3 | [src/dynavec/cache.py](../src/dynavec/cache.py)
[pyproject.toml](../pyproject.toml) | Add an optional Memcached client extra and BaseCache implementation, including namespace invalidation and TTL/stats parity tests. | -| [#45 — Cohere Rerank API integration](https://github.com/codeforstartups/dynavec/issues/45)
@troublerone | P2 | [src/dynavec/retrieval.py](../src/dynavec/retrieval.py)
[src/dynavec/client.py](../src/dynavec/client.py)
[pyproject.toml](../pyproject.toml) | Add a pluggable hosted reranker with caller-supplied Cohere credentials and an optional extra. Test ordered result mapping, empty inputs and provider errors; existing cross-encoder reranking is a different backend. | -| [#52 — Thread-safety tests for stores](https://github.com/codeforstartups/dynavec/issues/52)
unassigned | P2 | [tests/test_client_inmemory.py](../tests/test_client_inmemory.py)
[tests/test_connection_pool.py](../tests/test_connection_pool.py) | Add barrier-driven concurrent upsert/search scenarios against the fakes, asserting complete payloads and namespace isolation. Separate fake-store thread safety from guarantees of the AWS-backed client. | -| [#53 — Process pool for CPU-bound PQ training](https://github.com/codeforstartups/dynavec/issues/53)
@tikhandesanket | P3 | [src/dynavec/quantization.py](../src/dynavec/quantization.py)
[benchmarks/benchmark_quantization.py](../benchmarks/benchmark_quantization.py) | Add opt-in process-based PQ fitting only after benchmarking serialization/startup costs. Keep deterministic seeds, a sequential default and a benchmark demonstrating where the process pool helps. | -| [#82 — OpenTelemetry tracing](https://github.com/codeforstartups/dynavec/issues/82)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[src/dynavec/client.py](../src/dynavec/client.py)
[pyproject.toml](../pyproject.toml) | Add optional OpenTelemetry spans for embed/query/hydrate/rerank, preserve caller trace context, and supply a collector example. Test span parentage and operation without the extra installed. | -| [#83 — CloudWatch metrics emission](https://github.com/codeforstartups/dynavec/issues/83)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/config.py](../src/dynavec/config.py)
[docs/iam-policy.json](../docs/iam-policy.json) | Add an opt-in CloudWatch emitter with batching, bounded buffers and documented IAM. Test dimension/cardinality limits and ensure emitter failure does not fail retrieval. | -| [#88 — CLI: migrate from Pinecone](https://github.com/codeforstartups/dynavec/issues/88)
@Isha-Zaka | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[examples/pinecone_vs_dynavec.py](../examples/pinecone_vs_dynavec.py) | Add a resumable, batched Pinecone migration command preserving IDs, namespaces, metadata and vector dimensions. Test source pagination and interrupted migration; comparison examples are not an importer. | -| [#97 — Tighten S3 Vectors resource ARNs in the sample policy](https://github.com/codeforstartups/dynavec/issues/97)
unassigned | P1 | [README.md](../README.md)
[docs/iam-policy.json](../docs/iam-policy.json)
[ARCHITECTURE.md](../ARCHITECTURE.md) | Separate account-level list/create permissions from bucket/index data permissions using the current AWS authorization resource table. Scope data actions to the configured bucket/index and test documented runtime/provisioning policies. | -| [#102 — Migrating from Pinecone guide](https://github.com/codeforstartups/dynavec/issues/102)
unassigned | P3 | [examples/pinecone_vs_dynavec.py](../examples/pinecone_vs_dynavec.py)
[tools/build_docs.py](../tools/build_docs.py) | Write a migration guide with a concepts table, namespace/metric/filter differences, batched export/import examples and a verification checklist. Keep documentation separate from the importer in #88. | -| [#114 — Add Python 3.13 to the CI matrix](https://github.com/codeforstartups/dynavec/issues/114)
unassigned | P2 | [.github/workflows/ci.yml](../.github/workflows/ci.yml)
[pyproject.toml](../pyproject.toml)
[CONTRIBUTING.md](../CONTRIBUTING.md) | Add Python 3.13 to CI and run the suite plus dependency installation there. Update supported-version claims only after the run is green; retain coverage of existing supported versions. | -| [#115 — Dependency vulnerability audit in CI](https://github.com/codeforstartups/dynavec/issues/115)
unassigned | P1 | [.github/workflows/ci.yml](../.github/workflows/ci.yml)
[pyproject.toml](../pyproject.toml) | Add a dependency vulnerability audit job with a documented triage/expiry process for exceptions. Define which base and optional extras it covers; demonstrate the job fails for a known vulnerable fixture or controlled check. | -| [#125 — Dashboard: `dynavec dashboard` CLI to launch it locally](https://github.com/codeforstartups/dynavec/issues/125)
unassigned | P2 | [src/dynavec/cli.py](../src/dynavec/cli.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/next.config.mjs](../dashboard/next.config.mjs) | Add dynavec dashboard --port, documented telemetry wiring and static bundle serving/browser launch. Test startup, port conflicts and API integration; depend on the security model in #137. | -| [#127 — Telemetry data model: DynamoDB events table](https://github.com/codeforstartups/dynavec/issues/127)
unassigned | P2 | [src/dynavec/telemetry.py](../src/dynavec/telemetry.py)
[src/dynavec/provisioning.py](../src/dynavec/provisioning.py) | TelemetryRecorder is an in-memory ring buffer. Add an opt-in DynamoDB event schema/store with TTL, bounded asynchronous writes, provisioning and privacy documentation. | -| [#129 — Dashboard view: Resources (buckets, indexes, tables)](https://github.com/codeforstartups/dynavec/issues/129)
unassigned | P2 | [src/dynavec/client.py](../src/dynavec/client.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Reuse describe/list helpers in a resources API and dashboard cards for buckets/indexes/tables. Add pagination, empty/error states and tests; there is no /api/resources route today. | -| [#130 — Dashboard view: Namespace explorer](https://github.com/codeforstartups/dynavec/issues/130)
unassigned | P2 | [src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Share one namespace-explorer implementation with #141. Add paginated counts/size summaries and detail/search UI, avoiding an unbounded full scan for each dashboard refresh. | -| [#131 — Dashboard view: Cost panel](https://github.com/codeforstartups/dynavec/issues/131)
@tikhandesanket | P2 | [benchmarks/cost_model.py](../benchmarks/cost_model.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx) | Expose the existing cost model through a cost API using live size/volume inputs, then build a breakdown panel. Show assumptions, pricing date and an explicit estimate label. | -| [#141 — Dashboard: Add Namespace Explorer](https://github.com/codeforstartups/dynavec/issues/141)
@osamashabih6960 | P2 | [dashboard/components/Sidebar.tsx](../dashboard/components/Sidebar.tsx)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py) | This overlaps #130. Agree on one owner/acceptance checklist and implement one namespace list, counts, filter and detail flow; link or consolidate the duplicate issue. | -| [#286 — Prompt templates & output parsers](https://github.com/codeforstartups/dynavec/issues/286)
@tikhandesanket | P2 | [src/dynavec/chat](../src/dynavec/chat)
[src/dynavec/nodes/builtin.py](../src/dynavec/nodes/builtin.py) | LLMNode accepts messages, but a reusable template/output-parser module is absent. Add message templates and JSON/Pydantic parsers with clear validation errors and rendering/parser tests. | -| [#291 — Visual builder: ReactFlow canvas to compose orchestrations](https://github.com/codeforstartups/dynavec/issues/291)
unassigned | P2 | [dashboard/package.json](../dashboard/package.json)
[dashboard/app/page.tsx](../dashboard/app/page.tsx)
[src/dynavec/nodes/registry.py](../src/dynavec/nodes/registry.py) | Choose dashboard versus standalone ownership, then implement a ReactFlow canvas using the node registry and #282 graph schema. Test create/connect/configure/save/reload and incompatible ports. | -| [#292 — Code ⇄ visual round-trip (import/export graph as code)](https://github.com/codeforstartups/dynavec/issues/292)
unassigned | P2 | [src/dynavec/nodes/base.py](../src/dynavec/nodes/base.py)
[src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py) | Define one versioned graph representation first (#282), then implement SDK/code-to-canvas import/export. Add semantic round-trip tests for nodes, edges, config and custom nodes; do not execute arbitrary source to import it. | -| [#293 — Live run visualization on the canvas](https://github.com/codeforstartups/dynavec/issues/293)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | After #291/#299, stream the same run/span events to canvas nodes. Test start/finish/error state, reconnects and inputs/outputs without creating a second telemetry format. | -| [#294 — Debug & iterate: step through, re-run a single node, pin test inputs](https://github.com/codeforstartups/dynavec/issues/294)
unassigned | P3 | [src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | Build on #281/#291: add step/pause, pinned inputs and single-node replay with inspectable edge state. Test reproducibility and preservation of the original run. | -| [#297 — Durable execution on Step Functions / Workflows](https://github.com/codeforstartups/dynavec/issues/297)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py) | Specify a durable execution backend around stable graph/state interfaces, then implement Step Functions/Workflows adapters with idempotent steps, retries and resume tests. DynamoDB checkpoints alone do not provide managed execution. | -| [#298 — One-command serverless deploy (Lambda / Cloud Run), in-account](https://github.com/codeforstartups/dynavec/issues/298)
unassigned | P2 | [docs/DEPLOY.md](../docs/DEPLOY.md)
[src/dynavec/cli.py](../src/dynavec/cli.py) | The deployment document is not a one-command agent deployment flow. Add packaging and a deploy command with an explicit resource/IAM plan; verify provision/run/log/teardown in a dedicated cloud test environment. | -| [#307 — Multi-cloud: pluggable GCP backends (Vertex, Firestore, Cloud Run, Workflows)](https://github.com/codeforstartups/dynavec/issues/307)
unassigned | P3 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores](../src/dynavec/stores)
[pyproject.toml](../pyproject.toml) | Stabilize provider-neutral store/model/execution interfaces, then implement GCP adapters and a parity matrix. Keep GCP dependencies optional and add contract tests plus a documented cloud smoke run. | -| [#308 — Multi-cloud: pluggable Azure backends (Azure OpenAI, Cosmos DB, Functions, Durable Functions)](https://github.com/codeforstartups/dynavec/issues/308)
unassigned | P3 | [src/dynavec/config.py](../src/dynavec/config.py)
[src/dynavec/stores](../src/dynavec/stores)
[pyproject.toml](../pyproject.toml) | Reuse the backend contracts from #307, add Azure model/store/compute/durable/secrets adapters as optional extras, and verify an example graph against the published parity matrix. | -| [#321 — feat(ingest): Support cursor pagination in MCPResourceSource](https://github.com/codeforstartups/dynavec/issues/321)
@neha-5456 | P2 | [src/dynavec/ingest.py](../src/dynavec/ingest.py)
[tests/test_ingest.py](../tests/test_ingest.py) | MCPResourceSource still calls list_resources once. Coordinate with the assignee to follow nextCursor/next_cursor in objects and mappings, forward cursor and test multiple/empty pages plus repeated-cursor protection. | - -### Epic (5) - -| Issue and snapshot owner | Priority | Evidence | Resolution and acceptance | -|---|---|---|---| -| [#122 — [EPIC] Telemetry & resource dashboard](https://github.com/codeforstartups/dynavec/issues/122)
unassigned | P2 | [dashboard/README.md](../dashboard/README.md)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py)
[dashboard/app/page.tsx](../dashboard/app/page.tsx) | Track child completion individually: persistence #127, launch/security #125/#137, resources/namespaces #129/#130/#141, cost #131 and eval UI #136. Reconcile the original Vite plan with the shipped Next.js app. | -| [#273 — [EPIC] dynaflow — serverless agent orchestration engine + visual builder](https://github.com/codeforstartups/dynavec/issues/273)
unassigned | P2 | [src/dynavec/agents/graph.py](../src/dynavec/agents/graph.py)
[src/dynavec/nodes/graph.py](../src/dynavec/nodes/graph.py)
[src/dynavec/checkpoint.py](../src/dynavec/checkpoint.py)
[docs/roadmap.md](../docs/roadmap.md) | Graph runtimes, nodes, agents and checkpointing have landed. Track remaining schema/round-trip, snapshots, visual builder and deployment children; update the stale roadmap claim that the engine is only pseudocode. | -| [#274 — [EPIC] dynalogs — in-account observability for agent runs](https://github.com/codeforstartups/dynavec/issues/274)
unassigned | P2 | [src/dynavec/logging.py](../src/dynavec/logging.py)
[src/dynavec/dashboard.py](../src/dynavec/dashboard.py) | Structured run/node logs and query telemetry exist. Complete parented spans, durable run history, metrics and dashboard integration under #299/#301/#302 rather than recreating logging. | -| [#275 — [EPIC] dynaevals — evaluation, datasets & CI gating](https://github.com/codeforstartups/dynavec/issues/275)
unassigned | P2 | [src/dynavec/eval](../src/dynavec/eval)
[PR #351 open](https://github.com/codeforstartups/dynavec/pull/351) | Retrieval/RAG metrics and trend tracking exist. Finish dataset round-trip #304, review CI gate #305 and attach evals to runs #306; keep the epic open until those flows are verified. | -| [#276 — [EPIC] Ecosystem launch — landing page & cross-library story (dynavec / dynaflow / dynalogs / dynaevals)](https://github.com/codeforstartups/dynavec/issues/276)
unassigned | P3 | [opensource/dynavec/index.html](../opensource/dynavec/index.html)
[tools/build_docs.py](../tools/build_docs.py)
[docs/roadmap.md](../docs/roadmap.md) | Ecosystem pages and landing-page sections exist after #347. Review #310/#311, then reconcile capability claims with shipped runtime/visual/deployment status before closing the ecosystem epic. | - -## Validation and limits - -- Python 3.12.15, installed via `uv venv --python 3.12` and the repository's `make install` target. -- Focused query/store/error tests pass, including real SDK stubs; lint passes for the changed tests. -- `make run-ci` passed: ruff, strict mypy (87 checked files), **1,125 tests passed, 2 opt-in live AWS tests skipped**, and 1 snapshot passed. The focused store/error/logging selection passed all 100 tests; changed-test lint and `git diff --check` also passed. -- No live AWS resources, credentialed model calls, external Redis/Memcached clusters, browser layout acceptance, or deployed orchestration were exercised. Missing/partial assessments concern inspected deliverables and explicitly identified acceptance gaps. -- This is a dated snapshot. Recheck current issue assignments, PR state and source before starting follow-up work. This PR does not close the entire backlog or automatically change issue ownership.