Chain · Selective access — who may see what, frozen at issue
Parent: requirements-to-design
Status: DEEPEST CHAIN — design finalized, heaviest tests.
L0 · Vision
introduction — modes are roles: four apps sharing one corpus; ACL is the hard wall on what's reachable, role is the soft framing on what gets surfaced. boundaries — a "thought-leadership filter for selective engagement", not open broadcast, not anyone-can-book. design-principles — "serve those who already love it; keep some access friction".
L1 · Promise
The owner decides, per audience, which slice exists at all; a visitor's rules are fixed the moment their invitation is issued and can only ever be narrowed, never widened.
L2 · Requirements (journeys & features)
The owner code-management journeys and the visitor gate journeys (entry: Code / BYOAI / Request access), plus quota; admin surfaces Codes + Access Requests; the gate page /gate.
L3 · Design decisions
The finalized three-layer design (capability-acl-hierarchy, decisions locked 2026-06-23): pure-AND narrowing global(live) ∧ role(frozen) ∧ ¬code-deny(frozen) (acl-and-quota-granularity); tri-state overrides KILLED, sparse deny tables (a code can only subtract); freeze/live asymmetry (role-snapshot-frozen); AccessCode = invitation, ONE table, no parallel concept; identity via neutral _meta (trusted-identity-via-meta); BYOAI containment (byoai-envelope, byoai-browser-vault).
L4 · Engineering artifacts
Tables access_codes (assumed_role_id, prompt_id / inline_prompt, slug, microsite_id, limit_per_period), code_capability_denials, code_skill_denials, code_corpus_denials, code_members, capability_settings, roles/role_skills/role_mcp_servers/role_corpus_uris, embeds (code_id UNIQUE, key_id / public_key); backend/internal/conversation/usecase/visitor_role_snapshot.go; RoleSnapshot.deniedCapabilities for ACL=always caps; the quota system.
L5 · Verification
The heaviest e2e estate in the repo: acl-capability-matrix, acl-skill-matrix, acl-freeze-isolation, visitor-chat-permissions-deny, gate-access — plus the timing story (ACL at session establishment via ErrHidden; only monitor+secret-scan at call-time).
Status & gaps
Global layer landed; role/code deny per finalized design; corpus-glob ACL now LANDED (AllowsCorpusEntry enforced on every read; raw://** hard-denied; per-code corpus narrowing via code_corpus_denials, 2026-07-16 6395374b0); per-code prompt LANDED (access_codes.prompt_id or inline_prompt → codePromptBody frozen into the snapshot, layered after the role persona in visitor_chat_prompt.go). Since the 2026-08-30 sweep: system-derived codes carry a 64-bit random suffix instead of 16 (2026-09-01 1ca9d9564, access/entity/code_derive.go, driven by a pentest); a code exposed on a third-party site goes through embed-credential-never-carries-the-code (2026-09-01 2098975db); each code owns a landing path /c/<slug> (2026-09-06 4e383c2a7 + c6c54ce88, DeriveSlug, migration 2026-09-06-access-code-slug.sql); the code string can be rotated for leak recovery, codes.rotate → RotateCode purging the code's sessions (2026-09-07 505b3fc4f + 6ed1759bb, access/usecase/codes.go:113); a new code on the visitor page switches the session and an invalid one lands on /gate (2026-09-06 230a17025).