Coverage for node / src / stigmem_node / memory_garden_acl_gate.py: 91%
37 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-18 05:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-18 05:34 +0000
1"""Garden ACL recall filtering (graduated to core) + remaining experimental gates."""
3from __future__ import annotations
5from logging import Logger
6from typing import Any
8from .db import db
11def _live_settings() -> Any:
12 import sys
14 return sys.modules["stigmem_node.settings"].settings
17def oidc_permission_ceiling_enabled() -> bool:
18 """Garden-membership-derived OIDC permission ceiling — graduated to core.
20 Off by default (see ``settings.oidc_permission_ceiling``): enabling it caps
21 OIDC-issued permissions to what the caller's garden memberships grant.
22 """
23 return bool(_live_settings().oidc_permission_ceiling)
26def recall_filter_enabled() -> bool:
27 """Cross-surface garden ACL recall filtering — graduated to core (default-on).
29 Closes the cross-garden read leak (F-CONF-1): tenant-wide recall, query, graph
30 traversal, and subscription delivery are restricted to gardens the caller is a
31 member of. Single-tenant installs are unaffected (their facts have
32 ``garden_id`` NULL). Opt out via ``STIGMEM_MEMORY_GARDEN_ACL_RECALL_FILTER=false``.
33 """
34 return bool(_live_settings().memory_garden_acl_recall_filter)
37def memory_garden_acl_filtering_state() -> str:
38 """Return the operator-visible advanced ACL filtering posture.
40 ``disabled`` means default core behavior is active: direct garden reads and
41 writes are guarded, but tenant-wide query, recall, graph, OIDC ceiling, and
42 subscription-delivery filtering are not all enabled.
43 """
44 if not garden_acl_enforced(): 44 ↛ 45line 44 didn't jump to line 45 because the condition on line 44 was never true
45 return "disabled"
46 if oidc_permission_ceiling_enabled(): 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true
47 return "enabled-full"
48 return "enabled-partial"
51def gardens_with_members_exist() -> bool:
52 """Return True when at least one garden membership row exists."""
53 with db() as conn:
54 row = conn.execute("SELECT 1 FROM garden_members LIMIT 1").fetchone()
55 return row is not None
58def garden_acl_enforced() -> bool:
59 """True when the garden access boundary must be enforced on read surfaces.
61 Fail-closed: enforced when the operator flag is on OR — regardless of the
62 flag — whenever any garden-with-members exists. The flag can never *disable*
63 the boundary once gardens exist; it can only be a no-op when there is nothing
64 to protect (no garden-with-members → every fact's ``garden_id`` is NULL, so
65 filtering changes nothing). Call once per request, not per fact.
66 """
67 if bool(_live_settings().memory_garden_acl_recall_filter):
68 return True
69 return gardens_with_members_exist()
72def caller_visible_gardens(identity: Any) -> frozenset[str]:
73 """Garden ids the caller is a member of — one query, for in-memory filtering.
75 Lets callers batch the membership check (O(1) per fact against this set)
76 instead of one DB lookup per candidate fact, so there is no performance
77 reason to ever disable the boundary.
78 """
79 entity_uri = getattr(identity, "entity_uri", None)
80 if entity_uri is None:
81 return frozenset()
82 with db() as conn:
83 rows = conn.execute(
84 "SELECT garden_id FROM garden_members WHERE entity_uri = ?",
85 (entity_uri,),
86 ).fetchall()
87 return frozenset(row["garden_id"] for row in rows)
90def warn_if_memory_garden_acl_filtering_disabled(logger: Logger) -> None:
91 """Inform at startup when the disable flag is set but ACL stays enforced.
93 The flag cannot create a leak: once gardens-with-members exist the boundary
94 is enforced regardless (see ``garden_acl_enforced``). This logs that the
95 operator's opt-out is being overridden for safety, rather than warning of a
96 leak that can no longer happen.
97 """
98 flag_off = not bool(_live_settings().memory_garden_acl_recall_filter)
99 if not flag_off or not gardens_with_members_exist():
100 return
101 logger.warning(
102 "Garden ACL recall filtering flag is OFF "
103 "(STIGMEM_MEMORY_GARDEN_ACL_RECALL_FILTER=false) but gardens with members "
104 "exist, so the garden access boundary remains ENFORCED on recall, query, "
105 "graph, and subscription delivery (fail-closed: the flag cannot disable it "
106 "once gardens exist). Remove the override to silence this notice."
107 )