You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scope filtering is arriving piecemeal. src/vouch/scoping.py already provides is_visible(scope, viewer), filter_hits, and filter_audit_events, and kb.search / kb.context / kb.audit thread a ViewerContext through it. but the coverage is uneven: kb.synthesize and kb.neighbors take no project/agent params, and every kb.list_* / kb.read_* handler returns model_dump() straight off KBStore with no scope check at all. a private or wrong-project claim that kb.search correctly hides is still fully readable through kb.list_claims or kb.read_claim. once richer (visibility, project, agent) scopes land end-to-end (#189 / #100), the boundary needs a test that proves it holds across every read surface, not only the three that were wired first.
this issue is that test. it does not implement scoping — it fuzzes it. the target is a single property/fuzz suite that fails loudly the moment a new read method forgets to apply is_visible.
proposed surface
no new kb.* method — this is test-only, under tests/test_scope_leak_fuzz.py (mirrors the module convention).
a property-based generator seeds a kb with claims and sources spread across all four Visibility values (public, team, project, private) and a spread of project / agent scope tuples, plus a matching set of ViewerContext callers.
a driver enumerates every read method and calls it once per caller scope: kb.search, kb.context, kb.synthesize, kb.neighbors, kb.list_pages, kb.list_claims, kb.list_entities, kb.list_relations, kb.list_sources, kb.read_page, kb.read_claim, kb.read_entity, kb.read_relation, and kb.audit.
for each (method, viewer) pair, assert every artifact id in the result satisfies is_visible(artifact.scope, viewer) — zero ids that fail the predicate. audit results additionally assert against event_visible_to_viewer.
a registry guard: enumerate the read methods off capabilities.METHODS and fail if any read-side method is absent from the driver's coverage set, so a newly added reader cannot silently escape the sweep.
runs in the default pytest tests/ -q job with no embeddings dependency; the search backend under test is substring/fts5, so it stays in the non-embeddings ci gate.
review gate & scope
pure test code — it exercises read paths only, never proposes, approves, or writes approved artifacts, so the review gate is untouched. seeding goes through the normal propose_* → kb.approve path (or a fixture that drives proposals.approve) so the fixtures themselves respect the gate rather than writing yaml directly. everything runs against a temp .vouch/ on local disk with no network, keeping it local-first. the fuzzer asserts on the retrieval/read boundary only and never reaches into storage.py internals.
one known fail-open case the fuzzer must encode as expected behavior, not a bug: is_visible treats a project-visibility artifact whose scope.project is None as visible to everyone, and fails closed for private. the oracle is is_visible itself, so the test tracks the shipped semantics rather than a hand-rolled duplicate.
acceptance criteria
tests/test_scope_leak_fuzz.py seeds claims + sources across all four Visibility values and multiple project/agent tuples via the review-gated propose→approve path
driver exercises every read method (search, context, synthesize, neighbors, list_*, read_*, audit) under each caller ViewerContext
for every (method, viewer) pair, asserts zero result ids fail scoping.is_visible (audit uses event_visible_to_viewer)
registry guard fails if a read-side method in capabilities.METHODS is missing from the driver's coverage set
currently-unscoped readers (kb.read_*, kb.list_*, kb.synthesize, kb.neighbors) are covered; the test stays red until they filter, green once they do
runs in the default non-embeddings pytest tests/ -q gate; mypy src and ruff check stay clean
feat: conformance test suite for kb.* servers #98 (conformance test suite for kb.* servers) checks protocol shape and method parity across transports; this issue checks a different invariant — that read outputs never leak artifacts across visibility scopes — and asserts on the is_visible predicate rather than on wire conformance.
scope filtering is arriving piecemeal.
src/vouch/scoping.pyalready providesis_visible(scope, viewer),filter_hits, andfilter_audit_events, andkb.search/kb.context/kb.auditthread aViewerContextthrough it. but the coverage is uneven:kb.synthesizeandkb.neighborstake noproject/agentparams, and everykb.list_*/kb.read_*handler returnsmodel_dump()straight offKBStorewith no scope check at all. a private or wrong-project claim thatkb.searchcorrectly hides is still fully readable throughkb.list_claimsorkb.read_claim. once richer (visibility, project, agent) scopes land end-to-end (#189 / #100), the boundary needs a test that proves it holds across every read surface, not only the three that were wired first.this issue is that test. it does not implement scoping — it fuzzes it. the target is a single property/fuzz suite that fails loudly the moment a new read method forgets to apply
is_visible.proposed surface
no new
kb.*method — this is test-only, undertests/test_scope_leak_fuzz.py(mirrors the module convention).Visibilityvalues (public,team,project,private) and a spread ofproject/agentscope tuples, plus a matching set ofViewerContextcallers.kb.search,kb.context,kb.synthesize,kb.neighbors,kb.list_pages,kb.list_claims,kb.list_entities,kb.list_relations,kb.list_sources,kb.read_page,kb.read_claim,kb.read_entity,kb.read_relation, andkb.audit.is_visible(artifact.scope, viewer)— zero ids that fail the predicate. audit results additionally assert againstevent_visible_to_viewer.capabilities.METHODSand fail if any read-side method is absent from the driver's coverage set, so a newly added reader cannot silently escape the sweep.pytest tests/ -qjob with no embeddings dependency; the search backend under test issubstring/fts5, so it stays in the non-embeddings ci gate.review gate & scope
pure test code — it exercises read paths only, never proposes, approves, or writes approved artifacts, so the review gate is untouched. seeding goes through the normal
propose_* → kb.approvepath (or a fixture that drivesproposals.approve) so the fixtures themselves respect the gate rather than writing yaml directly. everything runs against a temp.vouch/on local disk with no network, keeping it local-first. the fuzzer asserts on the retrieval/read boundary only and never reaches intostorage.pyinternals.one known fail-open case the fuzzer must encode as expected behavior, not a bug:
is_visibletreats aproject-visibility artifact whosescope.project is Noneas visible to everyone, and fails closed forprivate. the oracle isis_visibleitself, so the test tracks the shipped semantics rather than a hand-rolled duplicate.acceptance criteria
tests/test_scope_leak_fuzz.pyseeds claims + sources across all fourVisibilityvalues and multiple project/agent tuples via the review-gated propose→approve pathsearch,context,synthesize,neighbors,list_*,read_*,audit) under each callerViewerContextscoping.is_visible(audit usesevent_visible_to_viewer)capabilities.METHODSis missing from the driver's coverage setkb.read_*,kb.list_*,kb.synthesize,kb.neighbors) are covered; the test stays red until they filter, green once they dopytest tests/ -qgate;mypy srcandruff checkstay cleandistinction from adjacent issues
(visibility, project, agent)[VEP] #100 (VEP-0005 richer scopes on Claim/Source) define and implement the scope model this test locks down — this issue is the conformance fuzzer that ships with them, not a second implementation.kb.*servers #98 (conformance test suite forkb.*servers) checks protocol shape and method parity across transports; this issue checks a different invariant — that read outputs never leak artifacts across visibility scopes — and asserts on theis_visiblepredicate rather than on wire conformance.OperationContext.remotefor MCP-facing callers #227 (trust-boundaryOperationContext.remotefor MCP callers) and feat(server): visibility-awarekb.auditqueries #232 (visibility-awarekb.audit) narrow how a caller's scope is derived; this fuzzer treats whateverViewerContextthose produce as input and asserts the read outputs never leak, regardless of how the viewer was resolved.