feat(layer): read_out_of_scope scope gating for read_context via PermissionGate#455
Open
jlunder00 wants to merge 1 commit into
Open
feat(layer): read_out_of_scope scope gating for read_context via PermissionGate#455jlunder00 wants to merge 1 commit into
jlunder00 wants to merge 1 commit into
Conversation
Implements M-hop scope enforcement for read_context tool calls: - PermissionGate gains optional scope params (source_node_id, scope_radius, scope_mode, hop_distance_fn, resolve_node_path_fn); None = no enforcement, so all existing callers are unaffected. - When scope is active: read_context targets (node_ids + resolved paths) are checked against the session's source node via undirected tree distance. Targets beyond M hops (or unresolvable) trigger a permission_request with kind="read_out_of_scope" and await user approval before proceeding. - add get_node_hop_distance(conn, from_id, to_id) to db/pg_queries/nodes.py: LCA-based recursive CTE over context_nodes; returns int hops or None for unrelated trees. Performance note: add ltree/closure table for large trees. - Session options keys: scope_source_node_id, scope_radius. Bot dispatcher and frontend session-start will populate these in a follow-on PR. - scope_mode defaults to "tree_distance" (undirected); "descendant_only" flag is reserved for future use. - 24 new tests (16 PermissionGate, 8 hop distance); all 165 IAL tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scope_source_node_id,scope_radius,scope_mode,hop_distance_fn,resolve_node_path_fntoPermissionGate.__init__. All default toNone— existing callers are fully backwards-compatible.get_node_hop_distance(conn, from_node_id, to_node_id) -> int | Nonetodb/pg_queries/nodes.py. Uses an LCA-based recursive CTE overcontext_nodes. ReturnsNonewhen nodes are in separate trees.can_use_tool("read_context", ...)checks all target node_ids (and resolves paths) against the hop distance limit. First offender triggerspermission_request(kind="read_out_of_scope")and awaits user approval.Design decisions
IAL has no DB pool: Scope checking is injected as callables (
hop_distance_fn,resolve_node_path_fn) — pre-bound to a DB connection in production wiring, mocked in tests. Same pattern ascheck_grant_fn/insert_grant_fn.Path bypass prevention:
read_contextaccepts bothnode_idsandpaths. Both are scope-checked. Unresolvable paths (resolver returnsNone, or no resolver provided) are treated as out-of-scope to prevent bypass via path arguments.Distance metric: Undirected tree distance (shortest path via LCA).
scope_modedefaults to"tree_distance";"descendant_only"is reserved for future activation.Session options keys:
scope_source_node_idandscope_radiusinsession.options. No existing callers set these today — bot dispatcher and frontend session-start will populate them in a follow-on PR when scope gating is wired end-to-end.Performance note
get_node_hop_distanceuses a recursive CTE that scans O(depth) rows per node. For large context trees, consider adding anltreecolumn or a closure table to reduce this to O(1) lookups. Not an issue at current scale but worth tracking as the tree grows.Test plan
tests/interactive_agent_layer/test_scope_gating.py— no-scope pass-through, in-scope allow, out-of-scope prompt, None-distance prompt, path resolution, path bypass prevention, multi-target first-offender, timeout deny, pending cleanuptests/db/test_node_hop_distance.py— mocked asyncpg, no DATABASE_URL required; covers distance cases 0/1/2/4/None, Decimal coercion, UUID arg contract