"""bw_accounts.py — the standard BW-Auth app admin framework (P4, external-users plan).

Every Pattern B app gets, out of the box: access LEVELS with per-level PERMISSIONS,
optional INSTANCES (projects/workspaces within the app) with per-user per-instance
levels, and the guarded mutations behind a standard account-management interface —
with the app owner as a standing, immutable SUPER ADMIN.

Self-check: `python3 bw_accounts.py`.

------------------------------------------------------------------------------------
THE MODEL

  super admin  — the app OWNER (config, default 'rian'). Not a row: code. Always
                 exists, holds every permission, cannot be edited/demoted/removed.
  levels       — app-defined ({name, permissions, assignable_levels}). The kit
                 ships RECOMMENDED templates (admin/manager/team/collaborator) the
                 super admin can adopt; apps add their own app-specific permission
                 strings freely (the standard set below is just guaranteed vocab).
  members      — BW usernames added to this app, each with one app-wide level.
  instances    — optional (has_instances=True): the app's projects/workspaces.
                 A member either has all_instances access (their app-wide level
                 applies everywhere) or per-instance grants, each with its own
                 level. Different users → different instances → different levels.

  can(actor, perm)                 — THE permission check (owner always True).
  effective_level(user, instance)  — what level a user has in one instance.

AUTHORITY: this store IS your app's access authority (decision D4 — apps own
assignments). The kit reports to central for VISIBILITY only, via bw_auth
(report_access / report_instances); nothing central grants anything here.

------------------------------------------------------------------------------------
WIRING (one page + one POST route; see ACCOUNTS.md for full recipes)

    import bw_accounts as bwa
    bwa.init("accounts.db", owner="rian", has_instances=True)

    @app.get("/admin/accounts")           # any route you like
    def accounts_page():
        user = current_bw_username()       # from your bw_auth session
        return bwa.admin_page_html(user, action_url="/admin/accounts")

    @app.post("/admin/accounts")
    def accounts_post():
        user = current_bw_username()
        msg = bwa.handle_admin_action(user, request.form)   # any mapping
        return redirect("/admin/accounts?msg=" + quote(msg))

Invites + resets flow through bw_auth (the gateway sends the email; your app
never sees the link): handle_admin_action calls bw_auth.invite_user /
bw_auth.send_reset when those actions arrive, and re-reports access after every
mutation so the access console and auth.bowden.works/account stay current.
"""
import html
import json
import sqlite3
import threading
import time

# The standard permission vocabulary (plan §5.1) — exactly the super-admin
# capability set. Apps may ADD their own strings (e.g. "reports.export"); these
# are the ones the standard admin UI knows how to render and enforce.
PERM_ACCOUNTS_VIEW = "accounts.view"
PERM_ACCOUNTS_ADD = "accounts.add"
PERM_ACCOUNTS_DELETE = "accounts.delete"
PERM_ACCOUNTS_RESET_PW = "accounts.reset_password"
PERM_ACCOUNTS_CHANGE_LEVEL = "accounts.change_level"
PERM_LEVELS_CREATE = "levels.create"
PERM_LEVELS_EDIT = "levels.edit_permissions"
PERM_INSTANCES_CREATE = "instances.create"
PERM_INSTANCES_GRANT = "instances.grant"
PERM_INSTANCES_DEFAULT_ALL = "instances.default_all"
PERM_INSTANCES_CHANGE_LEVEL = "instances.change_user_level"
PERM_ACCOUNTS_VIEW_AS = "accounts.view_as"   # G1: the standard "may impersonate" name

STANDARD_PERMISSIONS = [
    PERM_ACCOUNTS_VIEW, PERM_ACCOUNTS_ADD, PERM_ACCOUNTS_DELETE,
    PERM_ACCOUNTS_RESET_PW, PERM_ACCOUNTS_CHANGE_LEVEL, PERM_ACCOUNTS_VIEW_AS,
    PERM_LEVELS_CREATE, PERM_LEVELS_EDIT,
    PERM_INSTANCES_CREATE, PERM_INSTANCES_GRANT,
    PERM_INSTANCES_DEFAULT_ALL, PERM_INSTANCES_CHANGE_LEVEL,
]

# PERMISSION_INFO (T1, T2): label + one-line description + SCOPE for each standard
# permission. `scope` is the load-bearing addition: "app" = a capability that is
# app-wide (e.g. create a level); "instance" = a capability that is meant to be
# resolved against ONE instance (e.g. grant/change a member's level on a project) —
# so a level can carry it and it only bites where the holder actually has that level.
# A level editor renders from this instead of every app re-writing copy. `weight` is
# used to derive a permission-weighted impersonation rank (see bw_view_as, T10).
PERMISSION_INFO = {
    PERM_ACCOUNTS_VIEW:        {"label": "View accounts", "scope": "app", "weight": 1,
        "description": "See the people in this app and their levels."},
    PERM_ACCOUNTS_ADD:         {"label": "Add / invite people", "scope": "instance", "weight": 4,
        "description": "Invite or add accounts (app-wide, or scoped to instances they hold this on)."},
    PERM_ACCOUNTS_DELETE:      {"label": "Remove people", "scope": "instance", "weight": 4,
        "description": "Remove an account from this app (or from an instance)."},
    PERM_ACCOUNTS_RESET_PW:    {"label": "Send reset links", "scope": "instance", "weight": 3,
        "description": "Email a set-password / reset link to a person."},
    PERM_ACCOUNTS_CHANGE_LEVEL:{"label": "Change a person's level", "scope": "instance", "weight": 5,
        "description": "Change what level a person holds (app-wide or per-instance)."},
    PERM_ACCOUNTS_VIEW_AS:     {"label": "View as another user", "scope": "app", "weight": 6,
        "description": "Impersonate another user to see the app as they do (read-only by default)."},
    PERM_LEVELS_CREATE:        {"label": "Create levels", "scope": "app", "weight": 7,
        "description": "Define new access levels."},
    PERM_LEVELS_EDIT:          {"label": "Edit level permissions", "scope": "app", "weight": 8,
        "description": "Change which permissions a level carries, and which levels it may assign."},
    PERM_INSTANCES_CREATE:     {"label": "Create instances", "scope": "app", "weight": 4,
        "description": "Create new instances (projects / workspaces)."},
    PERM_INSTANCES_GRANT:      {"label": "Grant instance access", "scope": "instance", "weight": 4,
        "description": "Give a person access to an instance at a chosen level."},
    PERM_INSTANCES_DEFAULT_ALL:{"label": "Grant all-instance access", "scope": "app", "weight": 5,
        "description": "Give a person their app-wide level across every instance by default."},
    PERM_INSTANCES_CHANGE_LEVEL:{"label": "Change per-instance level", "scope": "instance", "weight": 4,
        "description": "Change a person's level on a specific instance."},
}


def permission_scope(perm):
    """'app' | 'instance' | None(unknown). Instance-scoped permissions are the ones a
    route should resolve against ONE instance (see the ACCOUNTS.md route-gate note)."""
    info = PERMISSION_INFO.get(perm)
    return info["scope"] if info else None

# Recommended level templates (plan D2). `assignable` = which levels a holder may
# assign to others — the property that stops an admin minting another admin.
LEVEL_TEMPLATES = {
    "admin": {
        "permissions": [PERM_ACCOUNTS_VIEW, PERM_ACCOUNTS_ADD, PERM_ACCOUNTS_DELETE,
                        PERM_ACCOUNTS_RESET_PW, PERM_ACCOUNTS_CHANGE_LEVEL,
                        PERM_ACCOUNTS_VIEW_AS,
                        PERM_INSTANCES_CREATE, PERM_INSTANCES_GRANT,
                        PERM_INSTANCES_DEFAULT_ALL, PERM_INSTANCES_CHANGE_LEVEL],
        "assignable": ["manager", "team", "collaborator", "member"],
    },
    "manager": {
        "permissions": [PERM_ACCOUNTS_VIEW, PERM_ACCOUNTS_ADD,
                        PERM_ACCOUNTS_RESET_PW, PERM_INSTANCES_GRANT,
                        PERM_INSTANCES_CHANGE_LEVEL],
        "assignable": ["team", "collaborator", "member"],
    },
    "team": {"permissions": [], "assignable": []},
    "collaborator": {"permissions": [], "assignable": []},
    # The scaffold default pair (D-seed): admin + member (member = plain app user).
    "member": {"permissions": [], "assignable": []},
}


class AccountsError(Exception):
    """A refused mutation. `.code` is stable for programmatic handling:
    NOT_INITIALIZED | FORBIDDEN | NO_SUCH_LEVEL | NO_SUCH_MEMBER | NO_SUCH_INSTANCE
    | NOT_ASSIGNABLE | OWNER_IMMUTABLE | EXISTS | BAD_INPUT | NO_INSTANCES"""

    def __init__(self, message, code="FORBIDDEN"):
        super().__init__(message)
        self.code = code


_state = {"store": None, "owner": "rian", "has_instances": False,
          "lock": threading.Lock(), "audit": None,
          # Enforced-permission registry (T3/T11): the set of permission strings the
          # APP actually checks somewhere. The kit's own STANDARD_PERMISSIONS are
          # always enforced by the kit; an app registers the extra strings IT gates on
          # so `known_permissions()` can mark "enforced" from fact, not hand-kept claims.
          "enforced": set()}


def register_enforced(*perms):
    """Declare permission strings THIS app actually checks (T3/T11). Anything a level
    carries that is neither a kit-standard permission nor registered here renders in
    the level editor as 'custom — not enforced yet', honestly and automatically."""
    for p in perms:
        if isinstance(p, str) and p:
            _state["enforced"].add(p)


def known_permissions():
    """Every permission the level editor should offer, each tagged with truth:
      [{name, label, description, scope, enforced}]
    Standard vocab (with PERMISSION_INFO metadata, enforced by the kit) first, then
    any app-registered extras (enforced=True), then — computed by the caller from a
    level's strings — unknown strings render enforced=False. Apps EXTEND, never
    duplicate (T2)."""
    out = []
    for p in STANDARD_PERMISSIONS:
        info = PERMISSION_INFO.get(p, {})
        out.append({"name": p, "label": info.get("label", p),
                    "description": info.get("description", ""),
                    "scope": info.get("scope", "app"), "enforced": True})
    for p in sorted(_state["enforced"] - set(STANDARD_PERMISSIONS)):
        out.append({"name": p, "label": p, "description": "",
                    "scope": "app", "enforced": True})
    return out


def is_enforced(perm):
    """True if `perm` is enforced somewhere — a kit-standard permission, or one the
    app registered. An unknown custom string (attached to a level but never checked)
    is False, which is what the honest 'not enforced yet' badge keys on."""
    return perm in STANDARD_PERMISSIONS or perm in _state["enforced"]


# ---------------------------------------------------------------- storage ------
# The kit is STORAGE-PLUGGABLE: the permission model, the guards, the admin UI and
# the central reporting are the standard; WHERE the rows live is the app's choice.
# Ship-default is SqliteStore (zero config). An app on Postgres/MySQL/an ORM (or
# with EXISTING accounts/projects tables it wants to keep) implements the same
# ~16 methods over its own schema and passes store=... to init(). See ACCOUNTS.md
# "Bring your own storage".
#
# Contract for every implementation:
#   * usernames arrive already normalized (lowercased/stripped) — store verbatim
#   * get_member() returns {"username","level","all_instances",bool,"grants":{iid:level}}
#     or None; get_level() returns {"name","permissions":[...],"assignable":[...]}
#     or None; list_instances() returns [{"id","label"}] sorted for display
#   * add_* raise nothing on success; the kit checks existence BEFORE calling, but
#     a store MAY also enforce uniqueness (the kit surfaces it as a failed action)
#   * no method performs authorization — the kit already did it

class SqliteStore:
    """Default store: the kit's own tables in a SQLite file (created if missing)."""

    def __init__(self, db_path):
        self.db_path = db_path

    def _conn(self):
        c = sqlite3.connect(self.db_path, timeout=5)
        c.execute("PRAGMA busy_timeout=5000")
        return c

    def ensure_schema(self):
        with self._conn() as c:
            c.execute("CREATE TABLE IF NOT EXISTS bwa_levels("
                      "name TEXT PRIMARY KEY, permissions TEXT NOT NULL, "
                      "assignable TEXT NOT NULL)")
            c.execute("CREATE TABLE IF NOT EXISTS bwa_members("
                      "username TEXT PRIMARY KEY, level TEXT NOT NULL, "
                      "all_instances INTEGER DEFAULT 0, "
                      "added_by TEXT, added_at INTEGER)")
            c.execute("CREATE TABLE IF NOT EXISTS bwa_instances("
                      "id TEXT PRIMARY KEY, label TEXT NOT NULL, "
                      "created_by TEXT, created_at INTEGER)")
            c.execute("CREATE TABLE IF NOT EXISTS bwa_instance_grants("
                      "username TEXT, instance_id TEXT, level TEXT NOT NULL, "
                      "PRIMARY KEY(username, instance_id))")

    # -- levels --
    def get_level(self, name):
        with self._conn() as c:
            row = c.execute("SELECT name, permissions, assignable FROM bwa_levels "
                            "WHERE name=?", ((name or "").strip(),)).fetchone()
        return ({"name": row[0], "permissions": json.loads(row[1]),
                 "assignable": json.loads(row[2])} if row else None)

    def list_levels(self):
        with self._conn() as c:
            rows = c.execute("SELECT name, permissions, assignable FROM bwa_levels "
                             "ORDER BY name").fetchall()
        return [{"name": r[0], "permissions": json.loads(r[1]),
                 "assignable": json.loads(r[2])} for r in rows]

    def add_level(self, name, permissions, assignable):
        with self._conn() as c:
            c.execute("INSERT INTO bwa_levels VALUES (?,?,?)",
                      (name, json.dumps(permissions), json.dumps(assignable)))

    def update_level(self, name, permissions, assignable):
        with self._conn() as c:
            c.execute("UPDATE bwa_levels SET permissions=?, assignable=? WHERE name=?",
                      (json.dumps(permissions), json.dumps(assignable), name))

    def delete_level(self, name):
        with self._conn() as c:
            c.execute("DELETE FROM bwa_levels WHERE name=?", (name,))

    def count_level_usage(self, name):
        with self._conn() as c:
            n = c.execute("SELECT COUNT(*) FROM bwa_members WHERE level=?",
                          (name,)).fetchone()[0]
            n += c.execute("SELECT COUNT(*) FROM bwa_instance_grants WHERE level=?",
                           (name,)).fetchone()[0]
        return n

    # -- members --
    def get_member(self, username):
        with self._conn() as c:
            row = c.execute("SELECT username, level, all_instances FROM bwa_members "
                            "WHERE username=?", (username,)).fetchone()
            if not row:
                return None
            grants = dict(c.execute("SELECT instance_id, level FROM "
                                    "bwa_instance_grants WHERE username=?",
                                    (username,)).fetchall())
        return {"username": row[0], "level": row[1],
                "all_instances": bool(row[2]), "grants": grants}

    def list_members(self):
        with self._conn() as c:
            rows = c.execute("SELECT username, level, all_instances FROM bwa_members "
                             "ORDER BY username").fetchall()
            grants = {}
            for un, iid, lvl in c.execute(
                    "SELECT username, instance_id, level FROM bwa_instance_grants"):
                grants.setdefault(un, {})[iid] = lvl
        return [{"username": r[0], "level": r[1], "all_instances": bool(r[2]),
                 "grants": grants.get(r[0], {})} for r in rows]

    def add_member(self, username, level, all_instances, added_by):
        with self._conn() as c:
            c.execute("INSERT INTO bwa_members VALUES (?,?,?,?,?)",
                      (username, level, 1 if all_instances else 0, added_by,
                       int(time.time())))

    def remove_member(self, username):
        with self._conn() as c:
            c.execute("DELETE FROM bwa_members WHERE username=?", (username,))
            c.execute("DELETE FROM bwa_instance_grants WHERE username=?", (username,))

    def set_member_level(self, username, level):
        with self._conn() as c:
            c.execute("UPDATE bwa_members SET level=? WHERE username=?",
                      (level, username))

    def set_all_instances(self, username, value):
        with self._conn() as c:
            c.execute("UPDATE bwa_members SET all_instances=? WHERE username=?",
                      (1 if value else 0, username))

    # -- instances --
    def list_instances(self):
        with self._conn() as c:
            rows = c.execute("SELECT id, label FROM bwa_instances "
                             "ORDER BY label").fetchall()
        return [{"id": r[0], "label": r[1]} for r in rows]

    def has_instance(self, instance_id):
        with self._conn() as c:
            return bool(c.execute("SELECT 1 FROM bwa_instances WHERE id=?",
                                  (instance_id,)).fetchone())

    def add_instance(self, instance_id, label, created_by):
        with self._conn() as c:
            c.execute("INSERT INTO bwa_instances VALUES (?,?,?,?)",
                      (instance_id, label, created_by, int(time.time())))

    def set_grant(self, username, instance_id, level):
        with self._conn() as c:
            c.execute("INSERT INTO bwa_instance_grants VALUES (?,?,?) "
                      "ON CONFLICT(username, instance_id) DO UPDATE SET "
                      "level=excluded.level", (username, instance_id, level))

    def remove_grant(self, username, instance_id):
        with self._conn() as c:
            c.execute("DELETE FROM bwa_instance_grants WHERE username=? "
                      "AND instance_id=?", (username, instance_id))


def init(db_path=None, owner="rian", has_instances=False, store=None, audit=None):
    """Wire up the kit. Call once at app startup.

    Args:
        db_path:       path to a SQLite file for the DEFAULT store (created if
                       missing). Ignored when `store` is given.
        owner:         the app owner = immutable super admin (BW username).
        has_instances: True if this app has instances (projects/workspaces).
        store:         OPTIONAL custom storage object implementing the store
                       contract above — use it to keep the kit's model on your
                       existing schema (Postgres/ORM/etc). `ensure_schema()` is
                       called if the object defines it (own-migrations stores can
                       omit it).
        audit:         OPTIONAL default audit sink fn(action, actor, target, detail)
                       (G5) called best-effort after every successful mutation.
                       A per-call `audit=` overrides it. Never blocks a mutation.
    """
    if store is None:
        if not db_path:
            raise AccountsError("init() needs db_path or store.", "NOT_INITIALIZED")
        store = SqliteStore(db_path)
    _state["store"] = store
    _state["owner"] = (owner or "rian").strip().lower()
    _state["has_instances"] = bool(has_instances)
    _state["audit"] = audit
    if hasattr(store, "ensure_schema"):
        store.ensure_schema()


def _store():
    st = _state["store"]
    if st is None:
        raise AccountsError("bw_accounts.init() has not been called.", "NOT_INITIALIZED")
    return st


def _norm(u):
    return (u or "").strip().lower()


def is_owner(username):
    return _norm(username) == _state["owner"]


# ---------------------------------------------------------------- reads --------

def level_def(name):
    """{name, permissions, assignable} or None."""
    return _store().get_level((name or "").strip())


def levels():
    return _store().list_levels()


def member(username):
    """{username, level, all_instances, grants:{iid: level}} or None. The owner is
    synthesized (super admin is code, not a row)."""
    username = _norm(username)
    if is_owner(username):
        return {"username": username, "level": "super admin", "all_instances": True,
                "grants": {}, "owner": True}
    m = _store().get_member(username)
    if not m:
        return None
    return {"username": m["username"], "level": m["level"],
            "all_instances": bool(m.get("all_instances")),
            "grants": dict(m.get("grants") or {}), "owner": False}


def members():
    return [{"username": m["username"], "level": m["level"],
             "all_instances": bool(m.get("all_instances")),
             "grants": dict(m.get("grants") or {})}
            for m in _store().list_members()]


def instances():
    return _store().list_instances()


def can(username, perm, instance_id=None):
    """THE permission check. Authority is the UNION of two sources:
      - the actor's APP-WIDE level's permissions — these apply on EVERY instance
        (an app-wide admin manages everywhere), and
      - when `instance_id` is given, the actor's per-instance GRANT level's
        permissions on that instance (a per-instance coordinator, T9).
    Owner → always True. With no `instance_id`, this is exactly the app-wide check
    (backwards-compatible). Note: authority is separate from `all_instances`, which
    governs DATA reach (effective_level), not management permission."""
    if is_owner(username):
        return True
    m = member(username)
    if not m:
        return False
    ld = level_def(m["level"])                       # app-wide authority (everywhere)
    if ld and perm in ld["permissions"]:
        return True
    if instance_id is not None and instance_id in m["grants"]:   # instance-scoped
        gld = level_def(m["grants"][instance_id])
        if gld and perm in gld["permissions"]:
            return True
    return False


def assignable_by(username, instance_id=None):
    """Which levels this actor may assign (T9). Owner → all. Otherwise the UNION of
    the app-wide level's assignable list (applies everywhere) and, when `instance_id`
    is given, the per-instance grant level's assignable list on that instance — so a
    coordinator whose authority is a per-instance grant can assign within it, without
    stripping an app-wide admin's reach."""
    if is_owner(username):
        return [l["name"] for l in levels()]
    m = member(username)
    if not m:
        return []
    out = []
    ld = level_def(m["level"])
    if ld:
        out += ld["assignable"]
    if instance_id is not None and instance_id in m["grants"]:
        gld = level_def(m["grants"][instance_id])
        if gld:
            out += [a for a in gld["assignable"] if a not in out]
    return out


def effective_level(username, instance_id=None):
    """The level a user acts at — app-wide, or within one instance:
    per-instance grant wins; else the app-wide level IF all_instances; else None
    (no access to that instance). With no instance_id: the app-wide level."""
    m = member(username)
    if not m:
        return None
    if instance_id is None:
        return m["level"]
    if instance_id in m["grants"]:
        return m["grants"][instance_id]
    return m["level"] if m["all_instances"] else None


# ------------------------------------------------------------- mutations -------
# Every mutation takes `actor` first and enforces the permission model itself,
# so an app route can pass form input straight through safely.

def _require(actor, perm, instance_id=None):
    if not can(actor, perm, instance_id):
        raise AccountsError(f"You need the '{perm}' permission for that.", "FORBIDDEN")


def _emit_audit(audit, action, actor, target, detail=""):
    """Best-effort audit (G5), symmetric with bw_view_as: call the per-call `audit`
    fn if given, else the module default set at init(audit=...); NEVER raise (a
    failing sink can't break a mutation that already succeeded)."""
    fn = audit or _state.get("audit")
    if not fn:
        return
    try:
        fn(action, _norm(actor), _norm(target) if target else target, detail)
    except Exception:
        pass


def _require_assignable(actor, target_level, instance_id=None):
    if is_owner(actor):
        if not level_def(target_level):
            raise AccountsError(f"No such level '{target_level}'.", "NO_SUCH_LEVEL")
        return
    if target_level not in assignable_by(actor, instance_id):
        raise AccountsError(
            f"Your level cannot assign '{target_level}'.", "NOT_ASSIGNABLE")


def _guard_target(actor, target):
    target = _norm(target)
    if is_owner(target):
        raise AccountsError("The app owner (super admin) cannot be modified.",
                            "OWNER_IMMUTABLE")
    return target


def create_level(actor, name, permissions=None, assignable=None, audit=None):
    _require(actor, PERM_LEVELS_CREATE)
    name = (name or "").strip()
    if not name or len(name) > 40 or name.lower() == "super admin":
        raise AccountsError("Invalid level name.", "BAD_INPUT")
    perms = [p for p in (permissions or []) if isinstance(p, str)][:64]
    assign = [a for a in (assignable or []) if isinstance(a, str)][:32]
    with _state["lock"]:
        if _store().get_level(name):
            raise AccountsError(f"Level '{name}' already exists.", "EXISTS")
        _store().add_level(name, perms, assign)
    _emit_audit(audit, "create_level", actor, name, ",".join(perms))
    return name


def adopt_template(actor, name, audit=None):
    """Create one of the recommended levels (admin/manager/team/collaborator)."""
    t = LEVEL_TEMPLATES.get((name or "").strip())
    if not t:
        raise AccountsError(f"No template '{name}'.", "BAD_INPUT")
    return create_level(actor, name, t["permissions"], t["assignable"], audit=audit)


def _guard_not_own_level(actor, level_name):
    """A non-owner may not edit/delete the level they themselves hold — otherwise a
    holder of levels.edit_permissions could add every permission to their own level
    and self-escalate to super-admin-equivalent. The owner is exempt (already
    all-powerful) and holds no editable level row."""
    if is_owner(actor):
        return
    m = member(actor)
    if m and m["level"] == level_name:
        raise AccountsError("You cannot edit your own access level.", "FORBIDDEN")


def set_level_def(actor, name, permissions=None, assignable=None, audit=None):
    """Edit a level's permissions and/or assignable list."""
    _require(actor, PERM_LEVELS_EDIT)
    ld = level_def(name)
    if not ld:
        raise AccountsError(f"No such level '{name}'.", "NO_SUCH_LEVEL")
    _guard_not_own_level(actor, ld["name"])
    perms = ld["permissions"] if permissions is None else \
        [p for p in permissions if isinstance(p, str)][:64]
    assign = ld["assignable"] if assignable is None else \
        [a for a in assignable if isinstance(a, str)][:32]
    with _state["lock"]:
        _store().update_level(ld["name"], perms, assign)
    _emit_audit(audit, "set_level_def", actor, ld["name"], ",".join(perms))


def delete_level(actor, name, audit=None):
    _require(actor, PERM_LEVELS_EDIT)
    _guard_not_own_level(actor, name)
    with _state["lock"]:
        used = _store().count_level_usage(name)
        if used:
            raise AccountsError(f"Level '{name}' is in use by {used} assignment(s).",
                                "EXISTS")
        _store().delete_level(name)
    _emit_audit(audit, "delete_level", actor, name)


def add_member(actor, username, level, all_instances=False, audit=None):
    _require(actor, PERM_ACCOUNTS_ADD)
    _require_assignable(actor, level)
    if all_instances and not is_owner(actor):
        _require(actor, PERM_INSTANCES_DEFAULT_ALL)
    username = _guard_target(actor, username)
    if not username:
        raise AccountsError("A username is required.", "BAD_INPUT")
    with _state["lock"]:
        if _store().get_member(username):
            raise AccountsError(f"'{username}' is already a member.", "EXISTS")
        _store().add_member(username, level, bool(all_instances), _norm(actor))
    _emit_audit(audit, "add_member", actor, username, level)
    return username


def remove_member(actor, username, audit=None):
    """Remove from THIS app only — never touches the central BW account."""
    _require(actor, PERM_ACCOUNTS_DELETE)
    username = _guard_target(actor, username)
    with _state["lock"]:
        _store().remove_member(username)
    _emit_audit(audit, "remove_member", actor, username)


def set_member_level(actor, username, level, audit=None):
    _require(actor, PERM_ACCOUNTS_CHANGE_LEVEL)
    _require_assignable(actor, level)
    username = _guard_target(actor, username)
    with _state["lock"]:
        if not _store().get_member(username):
            raise AccountsError(f"'{username}' is not a member.", "NO_SUCH_MEMBER")
        _store().set_member_level(username, level)
    _emit_audit(audit, "set_member_level", actor, username, level)


def set_all_instances(actor, username, value, audit=None):
    _require(actor, PERM_INSTANCES_DEFAULT_ALL)
    username = _guard_target(actor, username)
    with _state["lock"]:
        if not _store().get_member(username):
            raise AccountsError(f"'{username}' is not a member.", "NO_SUCH_MEMBER")
        _store().set_all_instances(username, bool(value))
    _emit_audit(audit, "set_all_instances", actor, username, str(bool(value)))


def create_instance(actor, instance_id, label=None, audit=None):
    if not _state["has_instances"]:
        raise AccountsError("This app has no instances.", "NO_INSTANCES")
    _require(actor, PERM_INSTANCES_CREATE)
    instance_id = (instance_id or "").strip()[:64]
    if not instance_id:
        raise AccountsError("An instance id is required.", "BAD_INPUT")
    with _state["lock"]:
        if _store().has_instance(instance_id):
            raise AccountsError(f"Instance '{instance_id}' already exists.", "EXISTS")
        _store().add_instance(instance_id, (label or instance_id).strip()[:120],
                              _norm(actor))
    _emit_audit(audit, "create_instance", actor, instance_id, label or instance_id)
    return instance_id


def grant_instance(actor, username, instance_id, level, audit=None):
    if not _state["has_instances"]:
        raise AccountsError("This app has no instances.", "NO_INSTANCES")
    # Instance-scoped authority (T9): a coordinator whose grant/assignability comes
    # from their level ON THIS instance passes here, not only an app-wide admin.
    _require(actor, PERM_INSTANCES_GRANT, instance_id)
    _require_assignable(actor, level, instance_id)
    username = _guard_target(actor, username)
    with _state["lock"]:
        if not _store().get_member(username):
            raise AccountsError(f"'{username}' is not a member.", "NO_SUCH_MEMBER")
        if not _store().has_instance(instance_id):
            raise AccountsError(f"No such instance '{instance_id}'.", "NO_SUCH_INSTANCE")
        _store().set_grant(username, instance_id, level)
    _emit_audit(audit, "grant_instance", actor, username, f"{instance_id}={level}")


def revoke_instance(actor, username, instance_id, audit=None):
    _require(actor, PERM_INSTANCES_GRANT, instance_id)
    username = _guard_target(actor, username)
    with _state["lock"]:
        _store().remove_grant(username, instance_id)
    _emit_audit(audit, "revoke_instance", actor, username, instance_id)


# ------------------------------------------------- derived views ---------------

def access_matrix():
    """Every member × every instance at a glance (G3) — ONE derivation of the
    resolution order, so an app's matrix view never re-implements it. Returns:

      {"instances": [{id,label}],
       "rows": [{"username", "level" (app-wide), "all_instances" (bool),
                 "cells": {instance_id: effective_level_or_None}}]}

    `cells` uses the SAME resolution as effective_level (per-instance grant →
    app-wide level if all_instances → None). Apps without instances get rows with
    empty `cells` and can render just the level column."""
    insts = instances() if _state["has_instances"] else []
    ids = [i["id"] for i in insts]
    rows = []
    for m in members():
        cells = {}
        if _state["has_instances"]:
            for iid in ids:
                if iid in m["grants"]:
                    cells[iid] = m["grants"][iid]
                elif m["all_instances"]:
                    cells[iid] = m["level"]
                else:
                    cells[iid] = None
        rows.append({"username": m["username"], "level": m["level"],
                     "all_instances": m["all_instances"], "cells": cells})
    return {"instances": insts, "rows": rows}


# ------------------------------------------------- central visibility ----------

def sync_reports(bw_auth):
    """Push the current state to central visibility (best-effort, display-only):
    the instance catalog + every member's access. Pass your imported bw_auth
    module. Call after mutations (handle_admin_action does this for you)."""
    try:
        if _state["has_instances"]:
            bw_auth.report_instances(instances())
        for m in members():
            inst = []
            label_of = {i["id"]: i["label"] for i in instances()}
            if m["all_instances"]:
                inst = [{"id": i["id"], "label": i["label"], "level": m["level"]}
                        for i in instances()]
            for iid, lvl in m["grants"].items():
                inst = [x for x in inst if x["id"] != iid]
                inst.append({"id": iid, "label": label_of.get(iid, iid), "level": lvl})
            bw_auth.report_access(m["username"], m["level"], inst)
    except Exception:
        pass    # visibility must never break the app's own admin flow


# ------------------------------------------------------- standard admin UI -----

_CSS = """
.bwa{font-family:system-ui,sans-serif;max-width:860px}
.bwa table{border-collapse:collapse;width:100%;margin:8px 0}
.bwa th,.bwa td{border:1px solid #ccc3;padding:6px 8px;text-align:left;font-size:14px}
.bwa input,.bwa select{padding:5px;margin:2px}
.bwa button{padding:5px 10px;cursor:pointer}
.bwa .muted{opacity:.65;font-size:12px}
.bwa details{margin:10px 0}
.bwa .lvl{display:inline-block;background:#8882;border-radius:9px;padding:1px 8px;font-size:12px}
"""


def admin_page_html(actor, action_url="/admin/accounts", message="",
                    csrf_token=None):
    """The standard account-management page, rendered for what `actor` may do.
    Non-viewers get a refusal. Mount it on any GET route; pair with ONE POST
    route that calls handle_admin_action(actor, form, expected_csrf=<same token>).

    CSRF: pass `csrf_token` (a per-session/per-request token from your app) and it
    is injected as a hidden `csrf` field into EVERY generated form; enforce it with
    handle_admin_action(expected_csrf=...). These are owner-level state changes on a
    cookie session, so protect them — especially on a shared registrable domain."""
    e = html.escape
    if not can(actor, PERM_ACCOUNTS_VIEW):
        return ('<div class="bwa"><style>' + _CSS + '</style>'
                '<p>You don’t have access to account management.</p></div>')
    msg = f'<p><b>{e(message)}</b></p>' if message else ''
    a = e(action_url)
    lvls = levels()
    lvl_names = [l["name"] for l in lvls]
    assignable = assignable_by(actor)
    inst = instances() if _state["has_instances"] else []

    def lvl_select(name, allowed):
        opts = ''.join(f'<option value="{e(x)}">{e(x)}</option>' for x in allowed)
        return f'<select name="{name}">{opts}</select>'

    rows = []
    for m in members():
        gr = ', '.join(f'{e(i)}: {e(l)}' for i, l in sorted(m["grants"].items()))
        scope = ('all instances' if m["all_instances"] else (gr or '—')) \
            if _state["has_instances"] else ''
        acts = []
        if can(actor, PERM_ACCOUNTS_CHANGE_LEVEL) and assignable:
            acts.append(f'<form method="POST" action="{a}" style="display:inline">'
                        f'<input type="hidden" name="action" value="set_level">'
                        f'<input type="hidden" name="username" value="{e(m["username"])}">'
                        + lvl_select("level", assignable)
                        + '<button>Set level</button></form>')
        if can(actor, PERM_ACCOUNTS_RESET_PW):
            acts.append(f'<form method="POST" action="{a}" style="display:inline">'
                        f'<input type="hidden" name="action" value="send_reset">'
                        f'<input type="hidden" name="username" value="{e(m["username"])}">'
                        '<button>Send reset link</button></form>')
        if can(actor, PERM_ACCOUNTS_DELETE):
            acts.append(f'<form method="POST" action="{a}" style="display:inline" '
                        'onsubmit="return confirm(\'Remove from this app?\')">'
                        f'<input type="hidden" name="action" value="remove_member">'
                        f'<input type="hidden" name="username" value="{e(m["username"])}">'
                        '<button>Remove</button></form>')
        if _state["has_instances"] and can(actor, PERM_INSTANCES_GRANT) and inst \
                and assignable:
            iopts = ''.join(f'<option value="{e(i["id"])}">{e(i["label"])}</option>'
                            for i in inst)
            acts.append(f'<form method="POST" action="{a}" style="display:inline">'
                        f'<input type="hidden" name="action" value="grant_instance">'
                        f'<input type="hidden" name="username" value="{e(m["username"])}">'
                        f'<select name="instance_id">{iopts}</select>'
                        + lvl_select("level", assignable)
                        + '<button>Grant</button></form>')
        rows.append(f'<tr><td>{e(m["username"])}</td>'
                    f'<td><span class="lvl">{e(m["level"])}</span></td>'
                    + (f'<td>{scope}</td>' if _state["has_instances"] else '')
                    + f'<td>{" ".join(acts)}</td></tr>')
    inst_th = '<th>Instances</th>' if _state["has_instances"] else ''
    table = ('<table><thead><tr><th>User</th><th>Level</th>' + inst_th
             + '<th></th></tr></thead><tbody>' + ''.join(rows) + '</tbody></table>')

    invite = ''
    if can(actor, PERM_ACCOUNTS_ADD) and assignable:
        invite = ('<details open><summary><b>Invite a user</b></summary>'
                  f'<form method="POST" action="{a}">'
                  '<input type="hidden" name="action" value="invite">'
                  '<input name="username" placeholder="username (lowercase)" required>'
                  '<input name="email" placeholder="email" type="email">'
                  '<input name="first" placeholder="First name">'
                  '<input name="last" placeholder="Last name">'
                  + lvl_select("level", assignable)
                  + '<button>Invite</button>'
                  '<div class="muted">New users get an email with a 48h '
                  'set-password link; existing BW users are just added.</div>'
                  '</form></details>')

    levels_ui = ''
    if can(actor, PERM_LEVELS_CREATE) or can(actor, PERM_LEVELS_EDIT):
        lvl_rows = ''.join(
            f'<tr><td>{e(l["name"])}</td>'
            f'<td class="muted">{e(", ".join(l["permissions"]) or "—")}</td>'
            f'<td class="muted">{e(", ".join(l["assignable"]) or "—")}</td></tr>'
            for l in lvls)
        tmpl_opts = ''.join(f'<option value="{e(t)}">{e(t)}</option>'
                            for t in LEVEL_TEMPLATES if t not in lvl_names)
        adopt = ''
        if can(actor, PERM_LEVELS_CREATE) and tmpl_opts:
            adopt = (f'<form method="POST" action="{a}" style="display:inline">'
                     '<input type="hidden" name="action" value="adopt_template">'
                     f'<select name="name">{tmpl_opts}</select>'
                     '<button>Adopt recommended level</button></form>')
        levels_ui = ('<details><summary><b>Access levels</b></summary>'
                     '<table><thead><tr><th>Level</th><th>Permissions</th>'
                     '<th>Can assign</th></tr></thead><tbody>' + lvl_rows
                     + '</tbody></table>' + adopt
                     + '<div class="muted">Level editing beyond templates: see '
                     'ACCOUNTS.md (create_level / set_level_def).</div></details>')

    inst_ui = ''
    if _state["has_instances"] and can(actor, PERM_INSTANCES_CREATE):
        inst_rows = ''.join(f'<tr><td>{e(i["id"])}</td><td>{e(i["label"])}</td></tr>'
                            for i in inst)
        inst_ui = ('<details><summary><b>Instances</b></summary>'
                   '<table><thead><tr><th>Id</th><th>Label</th></tr></thead>'
                   '<tbody>' + inst_rows + '</tbody></table>'
                   f'<form method="POST" action="{a}">'
                   '<input type="hidden" name="action" value="create_instance">'
                   '<input name="instance_id" placeholder="id (slug)" required>'
                   '<input name="label" placeholder="Label">'
                   '<button>Create instance</button></form></details>')

    page = ('<div class="bwa"><style>' + _CSS + '</style>'
            f'{msg}<h3>Accounts</h3>{table}{invite}{levels_ui}{inst_ui}</div>')
    if csrf_token is not None:
        # Inject the token into every form. Each generated form's first child is its
        # hidden `action` field, so prefixing that with a hidden `csrf` field puts
        # the token in all of them with one pass.
        field = f'<input type="hidden" name="csrf" value="{e(csrf_token)}">'
        page = page.replace('<input type="hidden" name="action"',
                            field + '<input type="hidden" name="action"')
    return page


def _one(form, key):
    v = form.get(key)
    if isinstance(v, (list, tuple)):
        v = v[0] if v else ''
    return (v or '').strip()


def handle_admin_action(actor, form, bw_auth=None, expected_csrf=None):
    """Route ONE admin-UI form POST to the guarded mutation it names. `form` is
    any mapping (Flask request.form, parsed qs dict, ...). Returns a short
    human message; raises AccountsError only for programmer errors (unknown
    action) — permission refusals come back as messages so the page can show
    them. Pass your bw_auth module to enable invite/reset + central reporting.

    CSRF: pass `expected_csrf` (the token you rendered via admin_page_html's
    csrf_token) and this rejects any POST whose `csrf` field doesn't match — these
    are owner-level state changes on a cookie session, so on a shared registrable
    domain (e.g. *.demoing.info) a sibling site could otherwise auto-submit them.
    STRONGLY recommended; omit only if your framework already enforces CSRF."""
    if expected_csrf is not None:
        import hmac as _hmac
        if not _hmac.compare_digest(_one(form, "csrf"), str(expected_csrf)):
            return "Security check failed — please reload and try again."
    action = _one(form, "action")
    try:
        if action == "invite":
            username = _one(form, "username")
            level = _one(form, "level")
            _require(actor, PERM_ACCOUNTS_ADD)
            _require_assignable(actor, level)
            # Validate LOCALLY before any central side effect (account creation /
            # email): otherwise a re-submit for an existing member would re-send the
            # invite email yet fail on add_member. Guard target + membership first.
            username = _guard_target(actor, username)
            if not username:
                raise AccountsError("A username is required.", "BAD_INPUT")
            if member(username):
                raise AccountsError(f"'{username}' is already a member.", "EXISTS")
            if bw_auth is not None:
                r = bw_auth.invite_user(username, _one(form, "email"),
                                        _one(form, "first"), _one(form, "last"))
                note = ("existing account added" if r.get("existing_with_password")
                        else f"invite emailed to {r.get('emailed_to', 'them')}")
            else:
                note = "added (no central invite — bw_auth not wired)"
            add_member(actor, username, level)
            msg = f"{username}: {note}."
        elif action == "remove_member":
            removed = _norm(_one(form, "username"))
            remove_member(actor, removed)
            # Clear the user's central access report so they stop appearing with app
            # access on the console + their own /account (sync_reports below only
            # RE-reports survivors; it never clears a departed member).
            if bw_auth is not None and removed:
                try:
                    bw_auth.report_access(removed, None)
                except Exception:
                    pass
            msg = f"{removed} removed from this app."
        elif action == "set_level":
            set_member_level(actor, _one(form, "username"), _one(form, "level"))
            msg = f"{_one(form, 'username')} → {_one(form, 'level')}."
        elif action == "send_reset":
            _require(actor, PERM_ACCOUNTS_RESET_PW)
            if bw_auth is None:
                raise AccountsError("bw_auth not wired.", "BAD_INPUT")
            bw_auth.send_reset(_one(form, "username"))
            msg = f"Reset link sent for {_one(form, 'username')}."
        elif action == "grant_instance":
            grant_instance(actor, _one(form, "username"),
                           _one(form, "instance_id"), _one(form, "level"))
            msg = "Instance access granted."
        elif action == "revoke_instance":
            revoke_instance(actor, _one(form, "username"), _one(form, "instance_id"))
            msg = "Instance access revoked."
        elif action == "create_instance":
            create_instance(actor, _one(form, "instance_id"), _one(form, "label"))
            msg = f"Instance '{_one(form, 'instance_id')}' created."
        elif action == "adopt_template":
            adopt_template(actor, _one(form, "name"))
            msg = f"Level '{_one(form, 'name')}' adopted."
        else:
            raise AccountsError(f"Unknown action '{action}'.", "BAD_INPUT")
    except AccountsError as exc:
        return str(exc)
    except Exception as exc:      # e.g. a BWAuthError from invite/send_reset
        return f"Failed: {exc}"
    if bw_auth is not None:
        sync_reports(bw_auth)
    return msg


# ------------------------------------------------------------- self-check ------

if __name__ == "__main__":
    import os
    import tempfile
    path = os.path.join(tempfile.mkdtemp(prefix="bwa-"), "t.db")
    init(path, owner="rian", has_instances=True)
    ok = lambda n, c: (print(("  ok  " if c else "  FAIL") + n),
                       (not c) and os._exit(1))
    # owner is super admin without any row
    ok("owner can everything", can("rian", PERM_LEVELS_CREATE))
    ok("stranger can nothing", not can("nobody", PERM_ACCOUNTS_VIEW))
    # rian's worked example: member level + admin that can only assign member
    create_level("rian", "member", [], [])
    create_level("rian", "admin",
                 [PERM_ACCOUNTS_VIEW, PERM_ACCOUNTS_ADD, PERM_ACCOUNTS_DELETE,
                  PERM_ACCOUNTS_RESET_PW, PERM_ACCOUNTS_CHANGE_LEVEL,
                  PERM_INSTANCES_CREATE, PERM_INSTANCES_GRANT,
                  PERM_INSTANCES_DEFAULT_ALL, PERM_INSTANCES_CHANGE_LEVEL],
                 ["member"])
    add_member("rian", "alice", "admin")
    add_member("rian", "bob", "member")
    ok("admin can add accounts", can("alice", PERM_ACCOUNTS_ADD))
    ok("admin canNOT create levels", not can("alice", PERM_LEVELS_CREATE))
    ok("member has no admin perms", not can("bob", PERM_ACCOUNTS_ADD))
    # assignability: alice may assign member, never admin (no self-replication)
    add_member("alice", "carol", "member")
    ok("admin assigns member", member("carol")["level"] == "member")
    try:
        add_member("alice", "dave", "admin")
        ok("admin cannot mint another admin", False)
    except AccountsError as exc:
        ok("admin cannot mint another admin", exc.code == "NOT_ASSIGNABLE")
    # owner immutability
    try:
        set_member_level("alice", "rian", "member")
        ok("owner is immutable", False)
    except AccountsError as exc:
        ok("owner is immutable", exc.code == "OWNER_IMMUTABLE")
    # instances + effective levels
    create_instance("rian", "proj-a", "Project A")
    create_instance("alice", "proj-b", "Project B")
    grant_instance("alice", "carol", "proj-a", "member")
    ok("per-instance grant", effective_level("carol", "proj-a") == "member")
    ok("no grant, no all_instances -> None",
       effective_level("carol", "proj-b") is None)
    set_all_instances("rian", "bob", True)
    ok("all_instances falls back to app level",
       effective_level("bob", "proj-b") == "member")
    # member permission denied on level ops
    try:
        create_instance("bob", "x")
        ok("member cannot create instances", False)
    except AccountsError as exc:
        ok("member cannot create instances", exc.code == "FORBIDDEN")
    # in-use level cannot be deleted
    try:
        delete_level("rian", "member")
        ok("in-use level protected", False)
    except AccountsError as exc:
        ok("in-use level protected", exc.code == "EXISTS")
    # admin UI renders per-permission
    page_owner = admin_page_html("rian")
    page_member = admin_page_html("bob")
    ok("owner page shows invite form", 'value="invite"' in page_owner)
    ok("member (no accounts.view) page refuses",
       "don’t have access" in page_member)
    # handle_admin_action end-to-end (no bw_auth: local add only)
    msg = handle_admin_action("rian", {"action": "set_level",
                                       "username": "carol", "level": "admin"})
    ok("action route works", member("carol")["level"] == "admin" and "carol" in msg)
    msg = handle_admin_action("bob", {"action": "remove_member", "username": "carol"})
    ok("refusal comes back as a message", "permission" in msg)

    # --- review fixes ---------------------------------------------------------
    # #8: a levels.edit_permissions holder cannot self-escalate via its OWN level
    create_level("rian", "leveladmin", [PERM_LEVELS_EDIT], [])
    add_member("rian", "lex", "leveladmin")
    try:
        set_level_def("lex", "leveladmin", permissions=STANDARD_PERMISSIONS)
        ok("cannot edit own level (self-escalation blocked)", False)
    except AccountsError as exc:
        ok("cannot edit own level (self-escalation blocked)", exc.code == "FORBIDDEN")
    ok("own level unchanged after blocked self-edit",
       level_def("leveladmin")["permissions"] == [PERM_LEVELS_EDIT])
    # but they CAN still edit a DIFFERENT level (the delegation still works)
    set_level_def("lex", "member", assignable=[])
    ok("edit of a different level still allowed", True)
    # #5: CSRF token injected into every form; handle_admin_action enforces it
    page_csrf = admin_page_html("rian", csrf_token="tok123")
    ok("csrf token injected into forms", page_csrf.count('name="csrf" value="tok123"')
       >= 3 and '<input type="hidden" name="csrf"' in page_csrf)
    msg = handle_admin_action("rian", {"action": "set_level", "username": "carol",
                                       "level": "member", "csrf": "WRONG"},
                              expected_csrf="tok123")
    ok("bad CSRF token refused", "Security check failed" in msg
       and member("carol")["level"] == "admin")   # unchanged
    msg = handle_admin_action("rian", {"action": "set_level", "username": "carol",
                                       "level": "member", "csrf": "tok123"},
                              expected_csrf="tok123")
    ok("correct CSRF token passes", member("carol")["level"] == "member")
    # #6: invite pre-validates membership BEFORE any central side effect
    class _FakeBW:
        sent = []
        @staticmethod
        def invite_user(u, e, f, l):
            _FakeBW.sent.append(u); return {"emailed_to": "x***@y.com"}
        @staticmethod
        def report_access(u, level, instances=None): pass
        @staticmethod
        def report_instances(i): pass
    msg = handle_admin_action("rian", {"action": "invite", "username": "carol",
                                       "level": "member", "email": "c@x.com"},
                              bw_auth=_FakeBW)
    ok("invite of an existing member: NO email sent (pre-checked)",
       "already a member" in msg and _FakeBW.sent == [])
    # #7: removing a member clears their central access report
    cleared = []
    class _FakeBW2(_FakeBW):
        @staticmethod
        def report_access(u, level, instances=None):
            if level is None: cleared.append(u)
    handle_admin_action("rian", {"action": "remove_member", "username": "bob"},
                        bw_auth=_FakeBW2)
    ok("remove_member clears the departed user's central report", "bob" in cleared)

    # --- BRING YOUR OWN STORAGE ------------------------------------------------
    # A reference custom store (dicts here; a real app maps these to its own tables
    # / ORM). Proves the whole model runs on non-SQLite storage — the contract an
    # app on Postgres implements. Keep this in sync with the docstring contract.
    class DictStore:
        def __init__(self):
            self.levels, self.members, self.insts, self.grants = {}, {}, {}, {}
        # levels
        def get_level(self, name):
            l = self.levels.get(name)
            return dict(l) if l else None
        def list_levels(self):
            return [dict(self.levels[k]) for k in sorted(self.levels)]
        def add_level(self, name, permissions, assignable):
            self.levels[name] = {"name": name, "permissions": list(permissions),
                                 "assignable": list(assignable)}
        def update_level(self, name, permissions, assignable):
            self.levels[name] = {"name": name, "permissions": list(permissions),
                                 "assignable": list(assignable)}
        def delete_level(self, name):
            self.levels.pop(name, None)
        def count_level_usage(self, name):
            return (sum(1 for m in self.members.values() if m["level"] == name)
                    + sum(1 for g in self.grants.values() if g == name))
        # members
        def get_member(self, username):
            m = self.members.get(username)
            if not m:
                return None
            return {**m, "grants": {i: l for (u, i), l in self.grants.items()
                                    if u == username}}
        def list_members(self):
            return [self.get_member(u) for u in sorted(self.members)]
        def add_member(self, username, level, all_instances, added_by):
            self.members[username] = {"username": username, "level": level,
                                      "all_instances": all_instances}
        def remove_member(self, username):
            self.members.pop(username, None)
            for k in [k for k in self.grants if k[0] == username]:
                del self.grants[k]
        def set_member_level(self, username, level):
            self.members[username]["level"] = level
        def set_all_instances(self, username, value):
            self.members[username]["all_instances"] = value
        # instances
        def list_instances(self):
            return [{"id": i, "label": self.insts[i]} for i in sorted(self.insts)]
        def has_instance(self, instance_id):
            return instance_id in self.insts
        def add_instance(self, instance_id, label, created_by):
            self.insts[instance_id] = label
        def set_grant(self, username, instance_id, level):
            self.grants[(username, instance_id)] = level
        def remove_grant(self, username, instance_id):
            self.grants.pop((username, instance_id), None)

    init(owner="rian", has_instances=True, store=DictStore())
    create_level("rian", "member", [], [])
    create_level("rian", "admin", [PERM_ACCOUNTS_ADD, PERM_ACCOUNTS_VIEW,
                                   PERM_INSTANCES_GRANT], ["member"])
    add_member("rian", "zoe", "admin")
    add_member("zoe", "yan", "member")
    create_instance("rian", "p1", "Project One")
    grant_instance("zoe", "yan", "p1", "member")
    ok("custom store: member + per-instance grant round-trips",
       effective_level("yan", "p1") == "member" and effective_level("yan", "p2") is None)
    ok("custom store: permission model still enforced",
       can("zoe", PERM_ACCOUNTS_ADD) and not can("yan", PERM_ACCOUNTS_ADD))
    try:
        add_member("zoe", "wes", "admin")
        ok("custom store: assignability still enforced", False)
    except AccountsError as exc:
        ok("custom store: assignability still enforced", exc.code == "NOT_ASSIGNABLE")
    ok("custom store: admin UI renders", 'value="invite"' in admin_page_html("rian"))
    ok("custom store: owner still immutable + synthesized",
       member("rian")["owner"] is True)

    # --- Phase 1 additions (one-prompt-standard request) ----------------------
    # PERMISSION_INFO / known_permissions / enforced registry (T2, T3, T11)
    ok("PERMISSION_INFO covers the standard vocabulary",
       all(p in PERMISSION_INFO for p in STANDARD_PERMISSIONS))
    ok("permission_scope distinguishes app vs instance",
       permission_scope(PERM_LEVELS_CREATE) == "app"
       and permission_scope(PERM_INSTANCES_GRANT) == "instance")
    register_enforced("reports.export")
    kp = {k["name"]: k for k in known_permissions()}
    ok("known_permissions marks standard + registered as enforced",
       kp[PERM_ACCOUNTS_VIEW]["enforced"] and kp["reports.export"]["enforced"])
    ok("an unregistered custom string is NOT enforced",
       is_enforced("reports.export") and not is_enforced("reports.never_checked"))
    ok("accounts.view_as is in the standard vocabulary",
       PERM_ACCOUNTS_VIEW_AS in STANDARD_PERMISSIONS)

    # per-instance can() + assignable_by() (T9) — fresh store
    init(owner="rian", has_instances=True, store=DictStore())
    create_level("rian", "member", [], [])
    create_level("rian", "coord",
                 [PERM_INSTANCES_GRANT, PERM_ACCOUNTS_ADD], ["member"])
    create_level("rian", "admin",
                 [PERM_INSTANCES_GRANT, PERM_INSTANCES_CREATE, PERM_ACCOUNTS_ADD,
                  PERM_ACCOUNTS_CHANGE_LEVEL], ["member", "coord"])
    add_member("rian", "ann", "admin")          # app-wide admin, NO all_instances
    add_member("rian", "carl", "member")        # plain member
    add_member("rian", "cora", "member")        # will be coord on p1 only
    create_instance("rian", "p1", "P1")
    create_instance("rian", "p2", "P2")
    grant_instance("ann", "cora", "p1", "coord")   # cora = coordinator on p1
    ok("app-wide admin manages ANY instance (no all_instances needed)",
       can("ann", PERM_INSTANCES_GRANT, "p2"))
    ok("per-instance coordinator manages ONLY their instance",
       can("cora", PERM_INSTANCES_GRANT, "p1")
       and not can("cora", PERM_INSTANCES_GRANT, "p2"))
    ok("coordinator can assign within their instance (assignable_by scoped)",
       "member" in assignable_by("cora", "p1")
       and "member" not in assignable_by("cora", "p2"))
    # the coordinator flow that 403'd before T9 now works
    grant_instance("cora", "carl", "p1", "member")
    ok("coordinator grants on their instance (T9 flow no longer 403s)",
       effective_level("carl", "p1") == "member")
    try:
        grant_instance("cora", "carl", "p2", "member")
        ok("coordinator refused on a foreign instance", False)
    except AccountsError as exc:
        ok("coordinator refused on a foreign instance", exc.code == "FORBIDDEN")

    # access_matrix (G3)
    mx = access_matrix()
    ok("access_matrix lists instances + rows",
       [i["id"] for i in mx["instances"]] == ["p1", "p2"]
       and any(r["username"] == "carl" for r in mx["rows"]))
    carl_row = next(r for r in mx["rows"] if r["username"] == "carl")
    ok("access_matrix cells use effective resolution",
       carl_row["cells"]["p1"] == "member" and carl_row["cells"]["p2"] is None)

    # audit hook (G5)
    seen = []
    init(owner="rian", has_instances=True, store=DictStore(),
         audit=lambda a, actor, target, d: seen.append((a, actor, target)))
    create_level("rian", "member", [], [])
    add_member("rian", "deb", "member")
    remove_member("rian", "deb")
    ok("audit sink fired for mutations",
       ("add_member", "rian", "deb") in seen
       and ("remove_member", "rian", "deb") in seen)
    # a failing audit sink NEVER blocks the mutation
    init(owner="rian", has_instances=False, store=DictStore(),
         audit=lambda *a: (_ for _ in ()).throw(RuntimeError("boom")))
    create_level("rian", "member", [], [])
    add_member("rian", "eve", "member")   # must not raise despite the throwing sink
    ok("a throwing audit sink does not break the mutation",
       member("eve") is not None)
    print("ALL bw_accounts self-checks PASSED")
