Generator (gen_kg_index.py): parse the @acl block → emit facts:
e=<class> a=kind v=acl_class, e=<role> a=can_see v=@<class> (one per class),
e=<person> a=role v=@<role>. The block syntax (already authored):
classes=cls_a,cls_b,cls_c
grant <role> <class> <class> …
person <Full Name> = <role> ← note the = delimiter (names contain spaces).
Query engine (kg_query.py):
Make cls fold as a SET (union), not latest-wins scalar — add it to the relation-like
union path, or special-case it. (Today it collapses to one class — see bugs.)
Add a viewer parameter to the query/Index. Compute closure(can_see(viewer)).
Apply the mask as the first operation: drop every fact whose cls set doesn't intersect
the viewer's visible classes. A fact with no cls → denied (default-deny).
Verify no leak: as role_ext, follow/incoming/diff must never surface an
internal/eng fact, even transitively.
UI: add a "View as <role/person>" entry to the existing View dropdown (same surface
as State / Open Items / Persona). On select → set viewer → re-render the masked graph.
Hidden nodes/edges are silently omitted (default-deny; no tombstone in consumption views).
A flat dropdown entry is enough — no nested menu needed.
Vyasa KG — ACL (viewer-mask) Engine + UI Handoff Prompt