"""typed NATS subject builders.

every NATS subject produced by 3tears applications passes through this
module. callers never construct subject strings by ``f"..."`` —
:class:`Subjects` owns the format, the namespace prefix, and the
sanitization rules. this is the single point where a typo or
namespace-prefix omission gets caught.

design properties
-----------------

- :class:`Subject` is an opaque ``@dataclass(frozen=True, slots=True)``,
  not a ``str`` subclass. consumers that want the raw subject string
  call ``str(subject)``; everything else takes ``Subject`` by type.
  this means a function expecting ``Subject`` cannot accidentally be
  passed a bare interpolated string.
- :class:`Subjects` factory has one classmethod per subject family in
  the canonical 3tears topology
  (``docs/done/design-01-nats-topology.md``). adding a new family is
  one method; reformatting an existing family is one method.
- the namespace prefix (``3tears`` by default, env-driven by
  ``THREETEARS_NATS_SUBJECT_NAMESPACE``) is bound at
  :class:`NatsClient` connect time. :class:`Subjects` reads it from a
  ``ContextVar`` that the client populates; tests and library code can
  set it explicitly via :func:`set_default_namespace`.
- ``Subject.raw(...)`` is the diagnostics escape hatch — for one-off
  subjects in tests or migrations. production code that finds itself
  reaching for ``raw`` is missing a builder; add the builder.

separator sanitization
----------------------

raw segment values may contain ``.``; when interpolated they are
replaced with ``-`` so the dot separator is never overloaded.
mirror's :func:`threetears.core.namespaces.build_namespace_name`
rule.
"""

from __future__ import annotations

import hashlib
import os
from dataclasses import dataclass
from typing import Literal
from uuid import UUID

from threetears.nats.errors import NamespaceNotConfiguredError

__all__ = [
    "PipeDirection",
    "Subject",
    "SubjectKind",
    "Subjects",
    "get_default_namespace",
    "set_default_namespace",
]


PipeDirection = Literal["up", "down"]
"""which half of a byte-pipe stream a subject addresses.

``down`` is the direction the pod that owns the key publishes and the caller
subscribes; ``up`` is the reverse. the two are separate subjects rather than
one bidirectional subject so each side's publish grant reaches only its own
half -- see :meth:`Subjects.pipe_pod_wildcard`.
"""


SubjectKind = Literal["point", "pattern", "reply"]
"""subject category.

- ``point``: concrete fully-qualified subject (e.g.
  ``3tears.tools.heartbeat.pod-abc``). usable for both publish and
  subscribe.
- ``pattern``: contains nats wildcards (``*`` or ``>``); subscribe-only.
- ``reply``: opaque inbox returned by nats-py for request/reply paths;
  treated as opaque by every caller.
"""


# Process-wide default namespace prefix used by :class:`Subjects` when a caller
# builds a subject without threading a namespace explicitly. A plain module
# global -- deliberately NOT a ``ContextVar``: the namespace is a single
# process-wide deployment constant, and a ContextVar set at connect/startup time
# is invisible to the SIBLING task trees that serve later work (e.g. a web
# server's per-request handlers spawned by uvicorn). That invisibility silently
# broke subject resolution on those paths -- ``get_default_namespace()`` saw the
# var unset and raised even though the process had connected with a namespace.
# A module global is visible in every task/context in the process, which is what
# "process-wide" has always meant here.
_default_namespace: str | None = None


def set_default_namespace(namespace: str) -> None:
    """set the process-wide default namespace prefix used by :class:`Subjects`.

    :class:`NatsClient.connect` calls this with its configured
    ``nats_subject_namespace`` so every subject built afterwards -- in ANY task
    or async context in the process -- picks up the correct prefix without
    callers having to thread it through.

    :param namespace: subject namespace prefix; must be non-empty
    :ptype namespace: str
    :return: nothing
    :rtype: None
    :raises ValueError: if namespace is empty
    """
    if not namespace:
        raise ValueError("namespace must be non-empty")
    global _default_namespace
    _default_namespace = namespace


def _reset_default_namespace() -> None:
    """clear the process-wide default namespace back to unconfigured.

    test-isolation helper. the module global carries process-wide, so -- unlike
    the retired ContextVar, which reset itself as each test's task context went
    out of scope -- it does NOT clear between tests on its own. autouse fixtures
    call this to restore the unconfigured state so one test cannot leak a
    namespace into the next.

    :return: nothing
    :rtype: None
    """
    global _default_namespace
    _default_namespace = None


def get_default_namespace() -> str:
    """resolve current subject namespace prefix.

    the namespace has no default. it must be configured explicitly so two
    unconfigured deployments cannot silently collide on the same subjects
    when sharing a NATS cluster.

    resolution order:

    1. value previously set via :func:`set_default_namespace` (process-wide,
       visible in every task/context)
    2. env var ``THREETEARS_NATS_SUBJECT_NAMESPACE`` (read at call
       time, not import time, so changes during process lifetime are
       observed)

    :return: resolved namespace prefix
    :rtype: str
    :raises NamespaceNotConfiguredError: if neither source is set
    """
    if _default_namespace is not None:
        return _default_namespace
    env = os.environ.get("THREETEARS_NATS_SUBJECT_NAMESPACE")
    if env:
        return env
    raise NamespaceNotConfiguredError(
        "no NATS subject namespace configured; call set_default_namespace(), "
        "connect a NatsClient, or set THREETEARS_NATS_SUBJECT_NAMESPACE"
    )


def _sanitize(segment: str | UUID) -> str:
    """coerce a raw segment value to a subject-safe token.

    replaces ``.`` with ``-`` so the dot separator is never
    overloaded. UUID values render as their 36-char canonical string.

    :param segment: raw segment value
    :ptype segment: str | UUID
    :return: subject-safe token
    :rtype: str
    """
    raw = str(segment)
    return raw.replace(".", "-")


def _digest_token(value: str) -> str:
    """hash a raw value into a subject-safe token.

    the SHA-256 hex digest of ``value``: ``[0-9a-f]`` only, so a value carrying
    ``.``, a space, ``*`` or ``>`` (all illegal or ambiguous in a NATS subject
    token) can neither overload the separator nor inject a wildcard.
    collision-resistant and deterministic, so two processes starting from the
    same raw value derive the same token; one-way, which costs nothing because
    both ends already hold the raw value.

    shared by :meth:`Subjects.room`, :meth:`Subjects.forward` and the
    family-scoped forward builders, so the token rule has one implementation
    rather than one per family.

    :param value: raw value to tokenize
    :ptype value: str
    :return: 64-character lowercase hex token
    :rtype: str
    """
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def _routing_token(pod_id: str | UUID) -> str:
    """render a pod-id into its tool-subject routing token(s), preserving structural dots.

    a Tool Pod's id is a single UUID token (no dots) -- :func:`_sanitize` is a no-op for it. an
    agent's IN-PROCESS tool pod-id is the composite ``{agent_id}.{instance}`` (built by
    :meth:`Subjects.agent_inprocess_pod_id`) whose structural dot MUST survive so the subject nests
    as ``tools.internal.{agent_id}.{instance}`` under the authenticated-agent subtree the AGENT_POD
    JWT grants (``tools.internal.{agent_id}.>``). each dot-delimited token is sanitized
    independently (so a token's own characters never overload the separator) while the structural
    dot between tokens is preserved -- unlike :func:`_sanitize`, which would collapse the composite
    to a single token and make the agent-id segment un-wildcard-matchable.

    :param pod_id: tool routing pod-id (single UUID token, or the agent composite)
    :ptype pod_id: str | UUID
    :return: dot-joined sanitized token(s)
    :rtype: str
    """
    # the pod_id is rendered into its NATS-subject routing token(s) here; a single UUID token or the
    # agent composite is split on its structural dots and each token is subject-sanitized.
    # convert at border: pod_id -> published NATS subject string
    return ".".join(_sanitize(token) for token in str(pod_id).split("."))


@dataclass(frozen=True, slots=True)
class Subject:
    """opaque NATS subject token.

    consumers should treat instances as opaque; cast to ``str`` at the
    nats-py boundary. wrapper code in :class:`threetears.nats.NatsClient`
    is the only legitimate ``str(subject)`` call site outside of
    tests / diagnostics.

    :param path: full dotted subject (with namespace prefix already applied)
    :ptype path: str
    :param kind: subject category — point, wildcard pattern, or reply inbox
    :ptype kind: SubjectKind
    """

    path: str
    kind: SubjectKind

    def __str__(self) -> str:
        """return raw subject string for nats-py boundary.

        :return: dotted subject string
        :rtype: str
        """
        return self.path

    @classmethod
    def raw(cls, full_subject: str, *, kind: SubjectKind = "point") -> Subject:
        """construct a :class:`Subject` from an already-formatted string.

        diagnostics / migration escape hatch. production code should
        prefer a builder on :class:`Subjects`; reaching for ``raw`` is a
        signal that a new builder is missing.

        :param full_subject: pre-formatted subject string
        :ptype full_subject: str
        :param kind: subject category
        :ptype kind: SubjectKind
        :return: opaque subject token
        :rtype: Subject
        :raises ValueError: if subject string is empty
        """
        if not full_subject:
            raise ValueError("full_subject must be non-empty")
        return cls(path=full_subject, kind=kind)


def _ns() -> str:
    """resolve namespace prefix at builder-call time.

    :return: namespace prefix
    :rtype: str
    """
    return get_default_namespace()


class Subjects:
    """factory for every canonical 3tears / 3tears NATS subject family.

    classmethods are organized by area:

    - **agent**: hub <-> agent pod routing, registration, heartbeats
    - **tools**: tool registry, tool pod heartbeats, tool calls
    - **gateway**: AI model gateway request/reply + streaming
    - **hub**: handshake, secrets, user resolution, streaming bridge,
      usage tracking
    - **audit**: audit event publishing
    - **l3**: L3 broker query / batch / transactional
    - **acl**: ACL invalidation broadcasts
    - **namespace**: namespace catalog discovery
    - **datasource**: datasource query routing
    - **cache**: cross-pod cache invalidation
    - **config epochs**: per-domain generation-stamped reload signals
      (capabilities, catalog, MCP RBAC)
    - **deadletter**: catch-all for failed callbacks
    """

    # ------------------------------------------------------------------
    # agent (hub <-> agent pod)
    # ------------------------------------------------------------------

    @classmethod
    def agent_register(cls) -> Subject:
        """request/reply subject for agent pod self-registration.

        :return: subject ``{ns}.agents.register``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.register", kind="point")

    @classmethod
    def agent_deregister(cls) -> Subject:
        """fire-and-forget subject an agent pod publishes on graceful shutdown.

        lets a cleanly-stopping pod remove its endpoint from the router catalog
        IMMEDIATELY rather than waiting out the heartbeat timeout (the slow
        backstop that only catches ungraceful kills). every router replica
        subscribes WITHOUT a queue group so each replica's catalog drops the pod.

        :return: subject ``{ns}.agents.deregister``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.deregister", kind="point")

    @classmethod
    def agent_heartbeat(cls, agent_id: str | UUID, pod_id: str | UUID) -> Subject:
        """publish subject for one agent pod's heartbeat.

        The ``agent_id`` leads the subject as the AUTHENTICATED segment
        (the connecting pod's token-verified agent identity); ``pod_id``
        comes from the spoofable connect name. Keying on ``agent_id``
        first means a pod can publish heartbeats only under its own
        authenticated agent and cannot forge a peer agent's heartbeat.

        :param agent_id: authenticated agent identity the pod serves
        :ptype agent_id: str | UUID
        :param pod_id: agent pod identifier
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.agents.heartbeat.{agent_id}.{pod_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.agents.heartbeat.{_sanitize(agent_id)}.{_sanitize(pod_id)}",
            kind="point",
        )

    @classmethod
    def agent_heartbeat_wildcard(cls) -> Subject:
        """wildcard subscribe pattern for all agent-pod heartbeats.

        :return: subject ``{ns}.agents.heartbeat.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.heartbeat.>", kind="pattern")

    @classmethod
    def agent_reregister_request(cls, agent_id: str | UUID, pod_id: str | UUID) -> Subject:
        """router -> agent pod nudge to re-register after deregistration.

        Published by :class:`AgentHeartbeatMonitor.handle_heartbeat` when a
        heartbeat arrives from a pod whose endpoint is missing from the
        catalog (typically because the heartbeat-timeout monitor
        deregistered the pod during a long host pause such as a laptop
        sleep, and the pod is now back alive and emitting heartbeats
        again). The agent pod subscribes to its own
        ``{agent_id}.{pod_id}``-tailed subject and runs the full
        registration handshake on receipt -- restoring the catalog
        endpoint without forcing a process restart.

        The ``agent_id`` leads the subject as the AUTHENTICATED segment so
        a pod subscribes (and the router targets) only its own agent's
        reregister channel; the spoofable ``pod_id`` can no longer stand
        alone to address a peer agent's pod.

        :param agent_id: authenticated agent identity the target pod serves
        :ptype agent_id: str | UUID
        :param pod_id: target agent pod identifier
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.agents.reregister_request.{agent_id}.{pod_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.agents.reregister_request.{_sanitize(agent_id)}.{_sanitize(pod_id)}",
            kind="point",
        )

    @classmethod
    def agent_route(cls, agent_id: str | UUID) -> Subject:
        """request/reply subject for hub -> agent inbound user message.

        :param agent_id: target agent identifier
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.agents.route.{agent_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.route.{_sanitize(agent_id)}", kind="point")

    @classmethod
    def agent_route_wildcard(cls) -> Subject:
        """wildcard subscribe pattern for all agent inbound traffic.

        :return: subject ``{ns}.agents.route.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.route.>", kind="pattern")

    @classmethod
    def agent_internal(cls, agent_id: str | UUID, pod_id: str | UUID) -> Subject:
        """direct agent-pod inbox subject for routed traffic.

        :param agent_id: target agent identifier
        :ptype agent_id: str | UUID
        :param pod_id: target pod identifier
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.agents.internal.{agent_id}.{pod_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.agents.internal.{_sanitize(agent_id)}.{_sanitize(pod_id)}",
            kind="point",
        )

    @classmethod
    def agent_turn(cls, agent_id: str | UUID) -> Subject:
        """durable JetStream subject for an inbound agent turn (resilience-task-07).

        the caller (hub) JetStream-publishes each inbound user turn here instead
        of the at-most-once ``agent_route`` request/reply; the agent-router is the
        durable consumer that forwards it to the sticky pod and acks only after
        the pod confirms completion. redelivery on pod crash / drain makes inbound
        delivery at-least-once. backed by the ``{ns}-agents-turn`` JetStream stream
        over ``{ns}.agents.turn.>`` (plus the sibling dead-letter subject from
        :meth:`agent_turn_deadletter`).

        :param agent_id: target agent identifier
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.agents.turn.{agent_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.turn.{_sanitize(agent_id)}", kind="point")

    @classmethod
    def agent_turn_wildcard(cls) -> Subject:
        """wildcard consumer filter for every agent's inbound turns.

        the agent-router's durable consumer subscribes on this pattern to drain
        every agent's inbound turns. deliberately does NOT cover
        :meth:`agent_turn_deadletter` (a sibling token, not a ``.turn.`` child),
        so exhausted turns park in the stream WITHOUT being re-consumed
        (resilience-task-07).

        :return: subject ``{ns}.agents.turn.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.turn.>", kind="pattern")

    @classmethod
    def agent_turn_deadletter(cls) -> Subject:
        """dead-letter subject for turns the router exhausted (resilience-task-07).

        a turn that stays undeliverable after the bounded redelivery budget is
        republished here. a SIBLING token of ``turn`` (not ``{ns}.agents.turn.*``)
        so :meth:`agent_turn_wildcard` does NOT match it -- the poisoned turn parks
        in the ``{ns}-agents-turn`` stream (which is declared over this subject too)
        with no consumer draining it: inspectable, not lost, not looping.

        :return: subject ``{ns}.agents.turn-deadletter``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.turn-deadletter", kind="point")

    @classmethod
    def agent_complete(cls, correlation_id: str | UUID) -> Subject:
        """per-turn completion-confirmation subject the router awaits (resilience-task-07).

        the pod publishes here once the turn's result is durably produced (the
        answer is in the request/reply reply, streamed, or handed to the durable
        channel-delivery subject). the router awaits this before acking the
        durable turn message; its absence (pod crash / drain refusal / timeout)
        triggers a re-route. keyed on the per-turn ``correlation_id`` so the
        signal maps 1:1 to the turn.

        :param correlation_id: per-turn correlation identifier
        :ptype correlation_id: str | UUID
        :return: subject ``{ns}.agents.complete.{correlation_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.complete.{_sanitize(correlation_id)}", kind="point")

    @classmethod
    def agent_reply(cls, correlation_id: str | UUID) -> Subject:
        """per-turn caller reply inbox for the durable turn path (resilience-task-07).

        the caller subscribes here BEFORE JetStream-publishing the turn and awaits
        the synchronous answer; the router publishes the pod's reply (full answer
        for request/reply callers, ack envelope for streaming / channel callers)
        here once the turn confirms. keyed on the per-turn ``correlation_id`` so
        it is deterministic and unique per turn (no opaque inbox to thread).

        :param correlation_id: per-turn correlation identifier
        :ptype correlation_id: str | UUID
        :return: subject ``{ns}.agents.reply.{correlation_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.agents.reply.{_sanitize(correlation_id)}", kind="point")

    # ------------------------------------------------------------------
    # tools (registry / tool pods)
    # ------------------------------------------------------------------

    @classmethod
    def tools_register(cls) -> Subject:
        """request/reply subject for tool-pod registration with registry.

        :return: subject ``{ns}.tools.register``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.register", kind="point")

    @classmethod
    def tools_heartbeat(cls, pod_id: str | UUID) -> Subject:
        """publish subject for one tool-pod's heartbeat.

        ``pod_id`` is a single UUID token for a Tool Pod, or the
        ``{agent_id}.{instance}`` composite for an agent's IN-PROCESS tool
        server (see :meth:`agent_inprocess_pod_id`); its structural dots are
        preserved (:func:`_routing_token`) so the composite nests under the
        authenticated-agent subtree the AGENT_POD JWT grants.

        :param pod_id: tool routing pod identifier (single token or composite)
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.tools.heartbeat.{pod_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.heartbeat.{_routing_token(pod_id)}", kind="point")

    @classmethod
    def tools_heartbeat_wildcard(cls) -> Subject:
        """wildcard subscribe pattern for all tool-pod heartbeats.

        the ``>`` tail spans both single-token Tool Pod heartbeats
        (``tools.heartbeat.{pod_id}``) and two-token agent in-process
        heartbeats (``tools.heartbeat.{agent_id}.{instance}``), so the
        registry / health monitor sees every pod's heartbeat regardless of
        pod-id shape.

        :return: subject ``{ns}.tools.heartbeat.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.heartbeat.>", kind="pattern")

    @classmethod
    def tools_heartbeat_agent_subtree(cls, agent_id: str | UUID) -> Subject:
        """publish-grant pattern for an agent pod's OWN in-process tool heartbeats.

        the agent serves its in-process tools (workspace / knowledge_drafts /
        devx builtins) under the ``{agent_id}.{instance}`` composite pod-id, so
        its heartbeat publishes land on ``tools.heartbeat.{agent_id}.{instance}``.
        the auth-callout responder knows only the AUTHENTICATED ``agent_id`` (not
        the per-replica instance), so it grants this ``{agent_id}.>`` subtree:
        the pod may publish heartbeats only under its OWN authenticated agent and
        can never forge a peer agent's in-process heartbeat.

        :param agent_id: the authenticated agent identity to scope the subtree on
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.tools.heartbeat.{agent_id}.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.heartbeat.{_sanitize(agent_id)}.>", kind="pattern")

    @classmethod
    def tools_discover(cls) -> Subject:
        """request/reply subject for agent -> registry tool catalog discovery.

        :return: subject ``{ns}.tools.discover``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.discover", kind="point")

    @classmethod
    def tools_call(cls) -> Subject:
        """request/reply subject for agent -> registry tool invocation.

        :return: subject ``{ns}.tools.call``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.call", kind="point")

    @classmethod
    def tools_internal(cls, pod_id: str | UUID) -> Subject:
        """request/reply subject for registry -> tool-pod proxied call.

        ``pod_id`` is a single UUID token for a Tool Pod, or the
        ``{agent_id}.{instance}`` composite for an agent's IN-PROCESS tool
        server; its structural dots are preserved (:func:`_routing_token`) so
        the composite renders as ``tools.internal.{agent_id}.{instance}`` --
        the registry forwards on the registered pod-id verbatim, and the
        agent's AGENT_POD JWT grants the ``tools.internal.{agent_id}.>``
        subtree, so only the authenticated agent can receive its own proxied
        in-process tool calls.

        :param pod_id: target tool routing pod identifier (single token or composite)
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.tools.internal.{pod_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.internal.{_routing_token(pod_id)}", kind="point")

    @classmethod
    def tools_internal_wildcard(cls) -> Subject:
        """registry-side forward-grant pattern spanning EVERY tool pod's internal subject.

        the registry proxy forwards each agent call to the target pod's
        ``tools.internal.{pod_id}``; the ``>`` tail covers both single-token
        Tool Pods and two-token agent in-process pods
        (``tools.internal.{agent_id}.{instance}``). granted ONLY to the trusted
        registry router (never a pod), which legitimately reaches every pod.

        :return: subject ``{ns}.tools.internal.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.internal.>", kind="pattern")

    @classmethod
    def tools_internal_agent_subtree(cls, agent_id: str | UUID) -> Subject:
        """subscribe-grant pattern for an agent pod's OWN in-process tool calls.

        the agent's in-process tool server subscribes
        ``tools.internal.{agent_id}.{instance}``; the auth-callout responder
        knows only the AUTHENTICATED ``agent_id`` (not the per-replica
        instance), so it grants this ``{agent_id}.>`` subtree. a peer agent is
        granted only its OWN ``{peer_agent_id}.>`` subtree and can NEVER
        subscribe another agent's in-process tool-call traffic -- closing the
        wiretap a connect-name-scoped grant left open.

        :param agent_id: the authenticated agent identity to scope the subtree on
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.tools.internal.{agent_id}.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.internal.{_sanitize(agent_id)}.>", kind="pattern")

    @classmethod
    def tools_probe(cls, pod_id: str | UUID) -> Subject:
        """request/reply subject for liveness probe against one tool pod.

        ``pod_id`` is a single UUID token for a Tool Pod, or the
        ``{agent_id}.{instance}`` composite for an agent's IN-PROCESS tool
        server; its structural dots are preserved (:func:`_routing_token`).

        :param pod_id: target tool routing pod identifier (single token or composite)
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.tools.probe.{pod_id}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.probe.{_routing_token(pod_id)}", kind="point")

    @classmethod
    def tools_probe_wildcard(cls) -> Subject:
        """registry-side probe-grant pattern spanning EVERY tool pod's probe subject.

        the registry probes each registered pod's ``tools.probe.{pod_id}``
        during the registration reachability handshake; the ``>`` tail covers
        both single-token Tool Pods and two-token agent in-process pods. granted
        ONLY to the trusted registry router.

        :return: subject ``{ns}.tools.probe.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.probe.>", kind="pattern")

    @classmethod
    def tools_probe_agent_subtree(cls, agent_id: str | UUID) -> Subject:
        """subscribe-grant pattern for the registry's probe of an agent's OWN in-process pod.

        mirrors :meth:`tools_internal_agent_subtree`: the agent's in-process
        tool server subscribes ``tools.probe.{agent_id}.{instance}`` and the
        responder grants the ``{agent_id}.>`` subtree, so a pod answers
        reachability probes only for its OWN authenticated agent.

        :param agent_id: the authenticated agent identity to scope the subtree on
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.tools.probe.{agent_id}.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.probe.{_sanitize(agent_id)}.>", kind="pattern")

    @classmethod
    def agent_inprocess_pod_id(cls, agent_id: str | UUID, instance_id: str | UUID) -> str:
        """compose the NATS routing pod-id for an agent's IN-PROCESS tool server.

        Returns a plain routing-key STRING (not a :class:`Subject`):
        ``{agent_id}.{instance_id}`` with each token sanitized. This is the
        value the SDK hands :class:`ToolServer` as its ``pod_id`` when standing
        up an in-process tool server on the agent's OWN ``AGENT_POD`` connection
        (the devx ``DevInProcessStrategy`` builtins, and the prod
        ``ProdExternalPodsStrategy`` workspace + ``knowledge_drafts`` tools).

        Leading the routing-key with the AUTHENTICATED ``agent_id`` -- the same
        identity the auth-callout responder resolved from the bootstrap-token
        hash -- is what binds the in-process tool subjects
        (:meth:`tools_internal` / :meth:`tools_probe` / :meth:`tools_heartbeat`,
        which preserve the structural dot) to the authenticated agent rather
        than the spoofable connect-name pod id: they nest under the
        ``tools.internal.{agent_id}.>`` subtree the AGENT_POD JWT grants
        (:meth:`tools_internal_agent_subtree`). The ``instance_id`` tail keeps
        each replica of the same agent routable as a distinct endpoint without
        widening the grant. Tool Pods (separate ``TOOL_POD`` processes) keep
        their single-token UUID pod-id and are unaffected.

        :param agent_id: the authenticated agent identity (leads the routing key)
        :ptype agent_id: str | UUID
        :param instance_id: this pod replica's unique instance id (the connect-name pod id)
        :ptype instance_id: str | UUID
        :return: routing pod-id string ``{agent_id}.{instance_id}``
        :rtype: str
        """
        return f"{_sanitize(agent_id)}.{_sanitize(instance_id)}"

    # ------------------------------------------------------------------
    # tools -- asynchronous result delivery
    #
    # A request/reply answer may only be published by the connection that
    # RECEIVED the request (NATS ``allow_responses``), and a credential refresh
    # is a reconnect, so any responder whose work outlives one connection
    # lifetime loses the right to answer at the moment it finishes. The families
    # below replace that per-request right with a STANDING grant on a subject
    # the responder names with its OWN identity: derived from ids the
    # auth-callout already knows, so every refresh re-mints the identical grant
    # and reconnects stop mattering.
    #
    # Two families rather than one, because the two hops have different
    # responder shapes:
    #
    # - ``tools.result.{pod_id}.{call_id}`` -- pod -> registry. The responder is
    #   a NAMED pod, so the subject carries the responder's id and only that pod
    #   holds the publish grant. The caller subscribes the exact subject it
    #   dispatched, so it accepts a result only from the pod it called.
    # - ``tools.reply.{agent_id}.{call_id}`` -- registry -> agent. The responder
    #   is an ANONYMOUS member of the ``registry`` queue group; the caller cannot
    #   know which replica will take the call, so it cannot subscribe a
    #   responder-named subject in advance. The subject therefore carries the
    #   CALLER's authenticated agent id (the shape ``gateway.stream.{agent_id}.
    #   {correlation_id}`` already uses): the registry publishes ``*.*`` and each
    #   agent subscribes only its OWN ``{agent_id}.*``, so no pod can forge into
    #   it and no agent can read a peer's results. The registry additionally
    #   refuses a caller-supplied subject that does not name the caller's
    #   VERIFIED agent id, so the wildcard is never a way to redirect a result.
    #
    # ``call_id`` is per-CALL, not the per-turn ``correlation_id``: one turn can
    # dispatch several tool calls, so a correlation-keyed subject would collide
    # between them and hand a caller the wrong tool's result.
    # ------------------------------------------------------------------

    @classmethod
    def tools_result(cls, pod_id: str | UUID, call_id: str | UUID) -> Subject:
        """durable result subject one tool pod publishes ONE call's answer to.

        the pod holds a standing publish grant on its own
        :meth:`tools_result_pod_wildcard` subtree, so the right to deliver
        survives every credential refresh -- unlike the request/reply inbox,
        where the right dies with the connection that received the call.

        ``pod_id`` is a single UUID token for a Tool Pod, or the
        ``{agent_id}.{instance}`` composite for an agent's IN-PROCESS tool
        server; its structural dots are preserved (:func:`_routing_token`) so
        the composite nests under the ``tools.result.{agent_id}.>`` subtree the
        AGENT_POD JWT grants.

        :param pod_id: answering tool routing pod identifier (single token or composite)
        :ptype pod_id: str | UUID
        :param call_id: per-call identifier minted by the caller (never the per-turn
            correlation id, which is not unique across a turn's tool calls)
        :ptype call_id: str | UUID
        :return: subject ``{ns}.tools.result.{pod_id}.{call_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.tools.result.{_routing_token(pod_id)}.{_sanitize(call_id)}",
            kind="point",
        )

    @classmethod
    def tools_result_pod_wildcard(cls, pod_id: str | UUID) -> Subject:
        """standing publish-grant pattern for ONE tool pod's own results.

        minted from the pod's own id at every connect, so a reconnect re-mints
        the identical grant. only this pod holds it, so no pod can forge a
        result into another pod's in-flight call.

        :param pod_id: the connecting pod's own routing identifier
        :ptype pod_id: str | UUID
        :return: subject ``{ns}.tools.result.{pod_id}.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.result.{_routing_token(pod_id)}.>", kind="pattern")

    @classmethod
    def tools_result_agent_subtree(cls, agent_id: str | UUID) -> Subject:
        """standing publish-grant pattern for an agent pod's OWN in-process tool results.

        mirrors :meth:`tools_internal_agent_subtree`: an agent serving tools
        in-process answers under the ``{agent_id}.{instance}`` composite pod-id,
        and the auth-callout knows only the AUTHENTICATED ``agent_id``, so the
        grant is the ``{agent_id}.>`` subtree. a peer agent gets only its own
        subtree and can never publish a result under another agent's identity.

        :param agent_id: the authenticated agent identity to scope the subtree on
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.tools.result.{agent_id}.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.result.{_sanitize(agent_id)}.>", kind="pattern")

    @classmethod
    def tools_result_wildcard(cls) -> Subject:
        """pattern spanning EVERY pod's result subtree.

        the stream declaration's subject filter -- the registry declares one
        JetStream stream over this family so a result published while the caller
        is mid-reconnect is retained rather than dropped. NOT granted as a
        subscribe permission to anyone: the registry consumes each result
        through a JetStream pull consumer delivered on its own scoped inbox, so
        it needs no standing subscribe right on the family itself.

        :return: subject ``{ns}.tools.result.>``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.result.>", kind="pattern")

    @classmethod
    def tools_reply(cls, agent_id: str | UUID, call_id: str | UUID) -> Subject:
        """durable reply subject the registry publishes ONE call's answer to.

        keyed on the CALLING agent rather than the answering registry replica:
        ``{ns}.tools.call`` is queue-grouped, so the caller cannot know which
        replica will serve it and cannot subscribe a responder-named subject
        before dispatching. the registry validates that a caller-supplied
        subject names the caller's VERIFIED agent id before publishing to it.

        :param agent_id: the calling agent's authenticated identity
        :ptype agent_id: str | UUID
        :param call_id: per-call identifier minted by the caller
        :ptype call_id: str | UUID
        :return: subject ``{ns}.tools.reply.{agent_id}.{call_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.tools.reply.{_sanitize(agent_id)}.{_sanitize(call_id)}",
            kind="point",
        )

    @classmethod
    def tools_reply_agent_subtree(cls, agent_id: str | UUID) -> Subject:
        """the tool-reply family belonging to ONE agent.

        the ownership prefix the registry validates a caller-supplied delivery subject against, built
        here rather than assembled by hand so the check can never drift from what
        :meth:`tools_reply` produces. a single ``*`` tail, not ``>``: the family is exactly two tokens
        deep, and widening it would match a future sibling family under the same prefix.

        :param agent_id: the authenticated agent identity to scope on
        :ptype agent_id: str | UUID
        :return: subject ``{ns}.tools.reply.{agent_id}.*``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.reply.{_sanitize(agent_id)}.*", kind="pattern")

    @classmethod
    def tools_reply_wildcard(cls) -> Subject:
        """registry-side publish-grant pattern spanning EVERY agent's reply subject.

        the mirror of :meth:`tools_internal_wildcard`: one registry connection
        fronts every agent, so there is no per-connection list of agent ids to
        mint exact literals from. the two-token ``*.*`` shape is deliberate --
        it names the family precisely and is granted ONLY to the trusted
        registry, which already reaches every pod. the containment that matters
        is at the registry itself, which refuses to publish to a subject not
        naming the call's VERIFIED agent id.

        also the second subject the results stream is declared over.

        :return: subject ``{ns}.tools.reply.*.*``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.tools.reply.*.*", kind="pattern")

    # ------------------------------------------------------------------
    # gateway (AI model gateway)
    # ------------------------------------------------------------------

    @classmethod
    def gateway_completion(cls) -> Subject:
        """request/reply subject for completion request to AI gateway.

        :return: subject ``{ns}.gateway.completion``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.gateway.completion", kind="point")

    @classmethod
    def gateway_embedding(cls) -> Subject:
        """request/reply subject for embedding request to AI gateway.

        :return: subject ``{ns}.gateway.embedding``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.gateway.embedding", kind="point")

    @classmethod
    def gateway_health(cls) -> Subject:
        """request/reply subject for gateway health check.

        :return: subject ``{ns}.gateway.health``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.gateway.health", kind="point")

    @classmethod
    def gateway_stream(cls, agent_id: str | UUID, correlation_id: str | UUID) -> Subject:
        """publish subject for gateway -> agent token streaming.

        The ``agent_id`` leads the subject as the AUTHENTICATED segment so
        an agent subscribes only its OWN gateway token streams; a bare
        ``gateway.stream.*`` wildcard would let any agent sniff every
        customer's in-flight token stream.

        :param agent_id: authenticated agent the stream is destined for
        :ptype agent_id: str | UUID
        :param correlation_id: correlation identifier shared with originating request
        :ptype correlation_id: str | UUID
        :return: subject ``{ns}.gateway.stream.{agent_id}.{correlation_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.gateway.stream.{_sanitize(agent_id)}.{_sanitize(correlation_id)}",
            kind="point",
        )

    # ------------------------------------------------------------------
    # hub (handshake, user resolve, secrets, streaming bridge, usage)
    # ------------------------------------------------------------------

    @classmethod
    def hub_handshake(cls) -> Subject:
        """request/reply subject for agent / tool-pod handshake with hub.

        :return: subject ``{ns}.hub.handshake``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.handshake", kind="point")

    @classmethod
    def hub_jwks(cls) -> Subject:
        """request/reply subject for the Hub identity-token JWKS fetched by verifiers.

        The registry proxy + tool pods request this to fetch the public keys identity tokens are
        signed under, then verify tokens against it before RBAC (platform-auth Option B).

        :return: subject ``{ns}.hub.jwks``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.jwks", kind="point")

    @classmethod
    def hub_secrets_request(cls) -> Subject:
        """request/reply subject for encrypted secret delivery from hub.

        :return: subject ``{ns}.hub.secrets.request``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.secrets.request", kind="point")

    @classmethod
    def hub_user_resolve(cls) -> Subject:
        """request/reply subject for user identity lookup against hub.

        :return: subject ``{ns}.hub.user.resolve``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.user.resolve", kind="point")

    @classmethod
    def hub_object_commit(cls) -> Subject:
        """request/reply subject for committing a produced object to the hub catalog.

        The agent sends its produced-object handle here (Path-2); the hub verifies
        the agent's session, derives the owning customer server-side, asserts the
        object key is under that tenant, and writes the ``platform.objects`` row.

        :return: subject ``{ns}.hub.object.commit``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.object.commit", kind="point")

    @classmethod
    def hub_object_resolve(cls) -> Subject:
        """request/reply subject for resolving an object id to its stored key.

        The consuming agent asks the hub to resolve an ``object_id`` to its
        ``s3_key`` (Path-2 S0 keystone); the hub verifies the agent's session,
        derives the owning customer, and returns the key only when the row is
        owned by that tenant (fail-closed).

        :return: subject ``{ns}.hub.object.resolve``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.object.resolve", kind="point")

    @classmethod
    def hub_engagement_scope(cls) -> Subject:
        """request/reply subject for resolving an engagement's authorized target scope.

        A consuming TOOL POD asks the hub for the in-scope targets of the
        ``engagement_id`` on its call context (engagement scope, consumer A of
        the §2 keystone). The pod has no hub session of its own, so it forwards
        the invoking agent's ``identity_token``; the hub verifies it, derives the
        owning customer from the signed claim, and returns the active target set
        only when the engagement is owned by that tenant (fail-closed). The
        read-side twin of :meth:`hub_object_resolve`.

        :return: subject ``{ns}.hub.engagement.scope``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.engagement.scope", kind="point")

    @classmethod
    def hub_approval_record(cls) -> Subject:
        """request/reply subject for recording a pending human-approval marker.

        A consuming AGENT that has PAUSED a graph on a ``requires_confirmation``
        tool call asks the hub to record a pending-approval marker for the
        conversation (the hub half of the exploit HITL gate). The agent forwards
        its ``identity_token``; the hub verifies it, derives the owning customer,
        writes the marker, and audits the request. Mirrors the auth path of
        :meth:`hub_engagement_scope`.

        :return: subject ``{ns}.hub.approval.record``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.approval.record", kind="point")

    @classmethod
    def hub_approval_resolve(cls) -> Subject:
        """request/reply subject for resolving an operator reply against a pending approval.

        The channel router (sandboxed, NATS-only) forwards an inbound operator
        reply for a conversation that may be awaiting approval. The hub looks up
        the pending marker, parses approve/deny, authorizes the replier
        (``exploit.approve`` on the paused agent's namespace), audits the
        decision, clears the marker, and returns the resume directive to inject
        onto the agent's resume rail. Keeps DB + ACL + audit HUB-side, out of the
        adapter sandbox.

        :return: subject ``{ns}.hub.approval.resolve``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.approval.resolve", kind="point")

    @classmethod
    def hub_channel_installs(cls) -> Subject:
        """request/reply subject for a channel adapter to fetch its installs.

        the adapter is sandboxed (NATS-only, no DB credentials), so it
        asks the hub for the active bot installs of a channel type
        (bot token refs + the agent each routes to) instead of reading
        ``platform.channel_configs`` directly.

        :return: subject ``{ns}.hub.channel.installs``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.channel.installs", kind="point")

    @classmethod
    def channels_deliver(cls, channel_type: str) -> Subject:
        """durable JetStream subject for an agent answer awaiting channel delivery.

        the agent publishes a finished answer here (with the channel routing
        lifted off the inbound message) on completion; the channel adapter is a
        durable consumer that posts it to the destination thread. durable so an
        answer that completes while the adapter is restarting is redelivered,
        never lost. backed by the ``{ns}_channels_deliver`` JetStream stream
        over ``{ns}.channels.deliver.*``.

        :param channel_type: channel family (e.g. ``slack``, ``discord``)
        :ptype channel_type: str
        :return: subject ``{ns}.channels.deliver.{channel_type}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.channels.deliver.{_sanitize(channel_type)}",
            kind="point",
        )

    @classmethod
    def channels_deliver_wildcard(cls) -> Subject:
        """wildcard subject covering every channel-delivery family.

        the JetStream ``{ns}_channels_deliver`` stream is declared over this
        pattern; durable consumers filter to one ``channel_type``.

        :return: subject ``{ns}.channels.deliver.*``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.channels.deliver.*", kind="pattern")

    @classmethod
    def hub_usage_track(cls) -> Subject:
        """publish subject for usage-tracking events posted to hub.

        :return: subject ``{ns}.hub.usage.track``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.usage.track", kind="point")

    @classmethod
    def hub_endpoint_usage_track(cls) -> Subject:
        """publish subject for per-tool-call endpoint usage events posted to hub.

        distinct from :meth:`hub_usage_track` (the gateway model-usage stream) so
        the two usage streams / queue groups never collide: the registry usage
        emitter (gu-task-16) publishes here and the hub ``EndpointUsageConsumer``
        subscribes.

        :return: subject ``{ns}.hub.usage.endpoint``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.hub.usage.endpoint", kind="point")

    @classmethod
    def hub_stream(cls, agent_id: str | UUID, correlation_id: str | UUID) -> Subject:
        """publish subject for agent -> hub token streaming.

        The ``agent_id`` leads the subject as the AUTHENTICATED segment so
        an agent publishes tokens only under its OWN identity; a bare
        ``hub.stream.*`` publish grant would let a malicious agent forge
        or inject tokens onto a peer's in-flight request.

        :param agent_id: authenticated agent publishing the stream
        :ptype agent_id: str | UUID
        :param correlation_id: correlation identifier for client request
        :ptype correlation_id: str | UUID
        :return: subject ``{ns}.hub.stream.{agent_id}.{correlation_id}``
        :rtype: Subject
        """
        return Subject(
            path=f"{_ns()}.hub.stream.{_sanitize(agent_id)}.{_sanitize(correlation_id)}",
            kind="point",
        )

    # ------------------------------------------------------------------
    # audit
    # ------------------------------------------------------------------

    @classmethod
    def audit_event(cls, event_type: str) -> Subject:
        """publish subject for one audit event.

        :param event_type: dotted event type (e.g. ``workspace.doc_set``)
        :ptype event_type: str
        :return: subject ``{ns}.audit.{event_type}`` (event_type passed through verbatim — its dots are part of the addressable subject hierarchy)
        :rtype: Subject
        :raises ValueError: if event_type is empty
        """
        if not event_type:
            raise ValueError("event_type must be non-empty")
        # NOTE: event_type intentionally NOT sanitized — its dots are
        # the namespace separators audit consumers subscribe against
        # (e.g. wildcard `3tears.audit.workspace.>` for workspace events).
        return Subject(path=f"{_ns()}.audit.{event_type}", kind="point")

    @classmethod
    def audit_wildcard(cls, *, area: str | None = None) -> Subject:
        """wildcard subscribe pattern for audit events.

        :param area: optional sub-area filter; when ``None`` matches every audit event
        :ptype area: str | None
        :return: subject ``{ns}.audit.>`` or ``{ns}.audit.{area}.>``
        :rtype: Subject
        """
        result: Subject
        if area is None:
            result = Subject(path=f"{_ns()}.audit.>", kind="pattern")
        else:
            result = Subject(path=f"{_ns()}.audit.{area}.>", kind="pattern")
        return result

    @classmethod
    def audit_deadletter(cls) -> Subject:
        """dead-letter subject for audit envelopes the consumer exhausted.

        an audit envelope that stays un-persistable after the durable
        consumer's bounded redelivery budget is republished here. a SIBLING
        token of ``audit`` (``audit-deadletter``, NOT ``{ns}.audit.*``) so
        :meth:`audit_wildcard` does NOT match it -- the poisoned envelope parks
        in the ``{ns}-audit`` stream (which is declared over this subject too)
        with no consumer draining it: inspectable, not lost, not looping.

        :return: subject ``{ns}.audit-deadletter``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.audit-deadletter", kind="point")

    # ------------------------------------------------------------------
    # workspaces
    # ------------------------------------------------------------------

    @classmethod
    def workspaces_create(cls) -> Subject:
        """publish subject for workspace-create namespace emission.

        every successful agent-side ``threetears.workspace.create``
        publishes one event on this subject after the workspace + file
        rows commit in the per-agent schema. the hub-side
        :class:`3tears.hub.workspace.namespace_emitter
        .WorkspaceNamespaceEmitter` subscribes (no queue group, every
        replica observes) and upserts the paired ``platform.namespaces``
        row of type ``workspace``.

        decoupling the namespace upsert from the agent is the canonical
        platform pattern -- the agent-side L3 proxy routes writes to
        the agent's own ``agent_<hex>`` schema, which has no
        ``namespaces`` table; the hub owns direct DB access via
        :class:`HubNamespaceCollection` and is the SOLE writer of
        platform-scoped catalog rows. mirrors :meth:`tools_register`.

        :return: subject ``{ns}.workspaces.create``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.workspaces.create", kind="point")

    # ------------------------------------------------------------------
    # op-log (durable write-path WAL)
    # ------------------------------------------------------------------

    @classmethod
    def oplog(cls, repo: str, branch: str) -> Subject:
        """publish/replay subject for one ``(repo, branch)`` op-log.

        the durable write path keeps one JetStream stream per
        ``(repo, branch)``; this is the single subject that stream is
        bound to. ``repo`` and ``branch`` are sanitized (``.`` -> ``-``)
        so a dotted ref name never overloads the subject separator.
        consumed by :class:`threetears.nats.OpLog`.

        :param repo: repository identifier
        :ptype repo: str
        :param branch: branch / ref name
        :ptype branch: str
        :return: subject ``{ns}.oplog.{repo}.{branch}``
        :rtype: Subject
        :raises ValueError: if repo or branch is empty
        """
        if not repo:
            raise ValueError("repo must be non-empty")
        if not branch:
            raise ValueError("branch must be non-empty")
        return Subject(
            path=f"{_ns()}.oplog.{_sanitize(repo)}.{_sanitize(branch)}",
            kind="point",
        )

    # ------------------------------------------------------------------
    # channels (cross-pod room fanout)
    # ------------------------------------------------------------------

    @classmethod
    def room(cls, room_id: str) -> Subject:
        """publish/subscribe subject for one room's cross-pod message fanout.

        the live room fanout (channels cross-pod design D1 / shard B)
        publishes one frame to this subject; every pod holding a local
        member of the room subscribes and delivers to its own sockets.

        the room id is ``{customer}:{story}:{branch}:{file}`` — arbitrary
        app-supplied segments may carry ``.``, spaces, ``*``, ``>`` (all
        illegal or ambiguous in a NATS subject token), so the room id is
        NOT :func:`_sanitize`-mapped (a colon/space room id would still
        leave illegal characters or collide across distinct ids). instead
        the token is the **SHA-256 hex digest** of the room id: a
        subject-safe (``[0-9a-f]`` only), collision-resistant,
        deterministic token. the raw room id rides in the wire envelope
        (:class:`threetears.channels.presence.wire.RoomFrame`), so the
        one-way digest needs no reversibility — the same robustness move
        the presence KV key uses. consumed by
        :class:`threetears.channels.presence.fanout.RoomFanout`.

        :param room_id: ``{customer}:{story}:{branch}:{file}`` room key
        :ptype room_id: str
        :return: subject ``{ns}.channels.room.{sha256hex(room_id)}``
        :rtype: Subject
        :raises ValueError: if room_id is empty
        """
        if not room_id:
            raise ValueError("room_id must be non-empty")
        return Subject(path=f"{_ns()}.channels.room.{_digest_token(room_id)}", kind="point")

    # ------------------------------------------------------------------
    # owner-routed forward (request -> whichever pod serves a key)
    # ------------------------------------------------------------------

    @classmethod
    def forward(cls, key: str) -> Subject:
        """request/reply subject for owner-routed forwarding of one key.

        the owner-routed forward primitive
        (:func:`threetears.nats.forward` / :func:`serve_owner`) sends a
        request to whichever pod currently *serves* ``key`` and returns
        its reply. this is the single subject that request/reply rides.

        the ``key`` is arbitrary, app-supplied, and may carry ``.``,
        spaces, ``*`` or ``>`` — all illegal or ambiguous in a NATS
        subject token. as with :meth:`room`, the key is therefore NOT
        :func:`_sanitize`-mapped (which only handles ``.``); instead the
        token is the **SHA-256 hex digest** of the key: subject-safe
        (``[0-9a-f]`` only), collision-resistant, and deterministic, so
        every pod derives the same subject for the same key. the digest
        is one-way and needs no reversibility — both ends start from the
        same ``key``. consumed by
        :func:`threetears.nats.serve_owner` (subscribe, queue-grouped on
        this subject's path) and :func:`threetears.nats.forward`
        (request).

        :param key: ownership key (arbitrary app-supplied string)
        :ptype key: str
        :return: subject ``{ns}.forward.{sha256hex(key)}``
        :rtype: Subject
        :raises ValueError: if key is empty
        """
        if not key:
            raise ValueError("key must be non-empty")
        return Subject(path=f"{_ns()}.forward.{_digest_token(key)}", kind="point")

    @classmethod
    def forward_scoped(cls, family: str, key: str) -> Subject:
        """request/reply subject for owner-routed forwarding of one key WITHIN a family.

        the family-scoped sibling of :meth:`forward`, which renders
        ``{ns}.forward.{sha256hex(key)}`` -- one variable segment, and it is a
        digest of an arbitrary application string. a permission grant over that
        shape can therefore only be the whole subtree, and any principal
        holding it may serve EVERY owner-routed key in the namespace. the
        family segment is what a grant can discriminate on: a principal is
        granted :meth:`forward_scoped_wildcard` for the families it serves and
        nothing else.

        BOTH segments are hashed here, and ``family`` is deliberately taken as
        its raw value rather than as a precomputed token. a family typically
        carries an identity that nothing upstream validates (a tool's
        registered name is a bare ``str`` with no pattern), so a caller allowed
        to supply the token ready-made could put a space, a ``*`` or a ``>``
        into the subject. this classmethod is the one place that property can
        be enforced for every caller at once, which is the reasoning that put
        the digest inside :meth:`forward` and :meth:`room` rather than at their
        call sites.

        :meth:`forward` is unaffected: its subject carries one segment after
        ``forward`` and this one carries two, so the two shapes never collide
        and no existing caller changes.

        :param family: raw family value (a deployment constant supplied by the
            calling module, e.g. :meth:`hitl_forward_family`; never user data)
        :ptype family: str
        :param key: ownership key (arbitrary app-supplied string)
        :ptype key: str
        :return: subject ``{ns}.forward.{sha256hex(family)}.{sha256hex(key)}``
        :rtype: Subject
        :raises ValueError: if family or key is empty
        """
        if not family:
            raise ValueError("family must be non-empty")
        if not key:
            raise ValueError("key must be non-empty")
        return Subject(
            path=f"{_ns()}.forward.{_digest_token(family)}.{_digest_token(key)}",
            kind="point",
        )

    @classmethod
    def forward_scoped_wildcard(cls, family: str) -> Subject:
        """grant pattern spanning every owner-routed key in ONE forward family.

        the shape :mod:`threetears.nats.subject_permissions` mints for a
        principal that serves or calls a single family: the family segment is
        an exact literal and only the key segment is wildcarded. exact rather
        than wildcarded on the family because the token is computable from the
        same row that authorizes the principal, so widening it buys nothing.

        there is no narrower form available for the family segment either: a
        NATS wildcard matches a WHOLE token, so ``{sha256hex(family)}`` and
        ``*`` are the only two options and a prefix such as ``hitl-*`` matches
        nothing but the literal token ``hitl-*``.

        :param family: raw family value (hashed here, exactly as
            :meth:`forward_scoped` hashes it)
        :ptype family: str
        :return: subject ``{ns}.forward.{sha256hex(family)}.*``
        :rtype: Subject
        :raises ValueError: if family is empty
        """
        if not family:
            raise ValueError("family must be non-empty")
        return Subject(path=f"{_ns()}.forward.{_digest_token(family)}.*", kind="pattern")

    @classmethod
    def hitl_pipe_family(cls, tool_namespace_name: str) -> str:
        """derive the forward family a session's DISPLAY STREAM attaches on.

        distinct from :meth:`hitl_forward_family`, and the distinctness is load-bearing rather
        than tidy. both a session's control plane and its display stream are owner-routed on the
        SAME key -- the session id -- so if they shared a family they would derive the same
        subject, and :func:`threetears.nats.serve_owner` subscribes with a queue group keyed on
        that subject. a pod serving both would then have the broker split its messages between
        the two handlers: roughly half its control messages delivered to the stream's attach
        handler and half its attaches to the control handler, each looking like a malformed
        request rather than a misrouted one.

        two families on one key is the fix because the key is what identifies the SESSION, and a
        session legitimately has two owner-routed surfaces. the alternative -- multiplexing both
        onto one subject behind a discriminator -- would put a routing decision inside a handler
        that the subject layer can make correctly on its own.

        :param tool_namespace_name: the serving tool's registered namespace name
        :ptype tool_namespace_name: str
        :return: the raw forward family for that tool's display streams
        :rtype: str
        :raises ValueError: if tool_namespace_name is empty
        """
        if not tool_namespace_name:
            raise ValueError("tool_namespace_name must be non-empty")
        return f"hitl-pipe-{tool_namespace_name}"

    @classmethod
    def forward_scoped_any_family(cls) -> Subject:
        """grant pattern spanning EVERY family's owner-routed keys, and no unscoped key.

        the shape a principal that fronts every family needs: a caller which cannot know at
        connect time which families it will address, because one connection serves them all.
        both segments are wildcards, which is as narrow as this case can be -- there is no
        per-family list to mint exact literals from.

        it deliberately does NOT reach the unscoped :meth:`forward` subject, which carries one
        segment after ``forward`` where this carries two. that family stays granted to nobody, so
        widening this pattern is not a route to it.

        exists so the grant and the tests that assert it are rendered by the same builder as the
        subjects they authorize; a hand-written literal drifts from the builder silently, and
        drifts in the test at the same moment it drifts in the grant.

        :return: subject pattern ``{ns}.forward.*.*``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.forward.*.*", kind="pattern")

    @classmethod
    def hitl_forward_family(cls, tool_namespace_name: str) -> str:
        """derive the forward family for one tool's human-in-the-loop session control plane.

        returns a plain family STRING (not a :class:`Subject`), for
        :meth:`forward_scoped` / :meth:`forward_scoped_wildcard` to hash into a
        subject token. it is derived from the tool's REGISTERED namespace name
        (``tools.{mcp_name}.{version}``, as
        ``threetears.agent.tools.server.tool_namespace_name`` builds it) rather
        than from a separately configured zone identifier, so there is no
        second value that can disagree with the tool a pod actually registered:
        a pod's grant is minted by hashing the entries of the same
        ``allowed_namespaces`` list that filters its registration.

        the tool name is carried RAW in the returned family because the subject
        builders hash it; nothing here needs it to be subject-safe, and it is
        not, since a registered tool name is unvalidated.

        one family PER TOOL rather than a flat ``hitl`` family, because
        :func:`threetears.nats.serve_owner` queue-groups on the subject: a pod
        serving one tool that could subscribe another tool's session-control
        subject would take a SHARE of its messages, not merely observe them.

        :param tool_namespace_name: the serving tool's registered namespace name
        :ptype tool_namespace_name: str
        :return: the raw forward family for that tool's sessions
        :rtype: str
        :raises ValueError: if tool_namespace_name is empty
        """
        if not tool_namespace_name:
            raise ValueError("tool_namespace_name must be non-empty")
        return f"hitl-{tool_namespace_name}"

    # ------------------------------------------------------------------
    # byte pipe (a stream between a caller and the pod that owns a key)
    # ------------------------------------------------------------------

    @classmethod
    def pipe(
        cls,
        tool_namespace_name: str,
        pod_id: str | UUID,
        nonce: str,
        direction: PipeDirection,
    ) -> Subject:
        """one direction of one byte-pipe stream.

        the stream :mod:`threetears.nats.pipe` moves bytes over, once a caller
        and the pod that owns a key have met on a forward subject and agreed
        the coordinates. ``down`` is what the owner publishes and the caller
        subscribes; ``up`` is the reverse.

        ``pod_id`` leads the variable segments as the AUTHENTICATED one, for
        the reason :meth:`gateway_stream` and :meth:`hub_stream` already state:
        an owner's publish grant names its own pod id exactly, so no pod can
        paint bytes onto a peer's live stream. keying on the application's own
        key instead would force the caller side to hold a wildcard publish
        grant, which is exactly that hazard.

        the ``nonce`` is minted by the owner per attach rather than being the
        application key. that keeps a stale reconnect from landing on a live
        stream, and keeps an application key (a session id, a tenant-bearing
        string) off a subject a wildcard subscriber could enumerate.

        ``tool_namespace_name`` is hashed for the reason :meth:`forward_scoped`
        hashes its family: a registered tool name is unvalidated, so a name
        carrying a space, a ``*`` or a ``>`` would otherwise reach the subject.
        ``pod_id`` takes :func:`_sanitize` rather than :func:`_routing_token`
        because ``direction`` sits AFTER it: a composite pod-id would spend two
        tokens and shift every following segment, so this family requires the
        pod id to be exactly one token.

        :param tool_namespace_name: the serving tool's registered namespace name
        :ptype tool_namespace_name: str
        :param pod_id: the owning pod's identifier (a single token)
        :ptype pod_id: str | UUID
        :param nonce: per-attach nonce minted by the owner
        :ptype nonce: str
        :param direction: ``down`` (owner publishes) or ``up`` (caller publishes)
        :ptype direction: PipeDirection
        :return: subject ``{ns}.pipe.{sha256hex(tool)}.{pod_id}.{nonce}.{direction}``
        :rtype: Subject
        :raises ValueError: if tool_namespace_name or nonce is empty
        """
        if not tool_namespace_name:
            raise ValueError("tool_namespace_name must be non-empty")
        if not nonce:
            raise ValueError("nonce must be non-empty")
        return Subject(
            path=(
                f"{_ns()}.pipe.{_digest_token(tool_namespace_name)}.{_sanitize(pod_id)}.{_sanitize(nonce)}.{direction}"
            ),
            kind="point",
        )

    @classmethod
    def pipe_pod_wildcard(
        cls,
        tool_namespace_name: str,
        pod_id: str | UUID,
        direction: PipeDirection,
    ) -> Subject:
        """grant pattern for ONE pod's streams of ONE tool, in one direction.

        the shape :mod:`threetears.nats.subject_permissions` mints for a tool
        pod: both the tool digest and the pod id are exact literals, and only
        the nonce is wildcarded -- it cannot be anything else, because the
        owner mints it per attach and the grant is issued at connect time.

        the direction segment is part of the pattern rather than a ``>`` tail
        so a pod's PUBLISH grant reaches only ``down`` and its SUBSCRIBE grant
        only ``up``. a ``>`` tail would collapse the two into one grant that
        also lets the owner publish onto the caller's half.

        :param tool_namespace_name: the serving tool's registered namespace name
        :ptype tool_namespace_name: str
        :param pod_id: the owning pod's identifier (a single token)
        :ptype pod_id: str | UUID
        :param direction: ``down`` (owner publishes) or ``up`` (owner subscribes)
        :ptype direction: PipeDirection
        :return: subject ``{ns}.pipe.{sha256hex(tool)}.{pod_id}.*.{direction}``
        :rtype: Subject
        :raises ValueError: if tool_namespace_name is empty
        """
        if not tool_namespace_name:
            raise ValueError("tool_namespace_name must be non-empty")
        return Subject(
            path=f"{_ns()}.pipe.{_digest_token(tool_namespace_name)}.{_sanitize(pod_id)}.*.{direction}",
            kind="pattern",
        )

    @classmethod
    def pipe_any_pod_wildcard(cls, direction: PipeDirection) -> Subject:
        """grant pattern spanning EVERY pod's streams, in one direction.

        the shape a principal that fronts every tool needs, for the reason
        :meth:`forward_scoped_any_family` records: one connection serves them
        all, so there is no per-connection list to mint exact literals from and
        a NATS wildcard matches a whole token. the direction segment stays
        exact, which is what keeps the caller side unable to publish onto the
        owner's half of any stream.

        :param direction: ``up`` (caller publishes) or ``down`` (caller subscribes)
        :ptype direction: PipeDirection
        :return: subject ``{ns}.pipe.*.*.*.{direction}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.pipe.*.*.*.{direction}", kind="pattern")

    # knowledge (correction-harvest drafts)
    # ------------------------------------------------------------------

    @classmethod
    def knowledge_draft(cls) -> Subject:
        """publish subject for correction-harvest draft events + commands.

        every agent-side correction harvest (knowledge-task-06) publishes
        one :class:`threetears.knowledge.KnowledgeDraftEvent` (new draft)
        or :class:`threetears.knowledge.KnowledgeDraftCommand` (confirm /
        edit / discard of the author's own draft) on this subject. the
        hub-side ``KnowledgeDraftEmitter`` subscribes (no queue group,
        every replica observes) and materializes / mutates the
        ``status='draft'`` row through the hub's knowledge Collections.

        decoupling the knowledge write from the agent is the canonical
        platform pattern (mirrors :meth:`workspaces_create`): the
        agent-side L3 proxy is admitted only SELECT traffic against the
        ``platform.*`` knowledge tables (the broker's read-only
        carve-out), so the hub — sole writer of platform-scoped rows —
        owns the write. the deterministic ``draft_id`` keying makes the
        upsert idempotent under at-least-once delivery.

        :return: subject ``{ns}.knowledge.draft``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.knowledge.draft", kind="point")

    # ------------------------------------------------------------------
    # l3 broker
    # ------------------------------------------------------------------

    @classmethod
    def l3_query(cls) -> Subject:
        """request/reply subject for agent -> L3 broker single-statement query.

        :return: subject ``{ns}.l3.query``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.l3.query", kind="point")

    @classmethod
    def l3_batch(cls) -> Subject:
        """request/reply subject for agent -> L3 broker transactional batch.

        :return: subject ``{ns}.l3.batch``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.l3.batch", kind="point")

    @classmethod
    def l3_tx(
        cls,
        op: Literal["begin", "execute", "fetchrow", "fetch", "commit", "rollback"],
    ) -> Subject:
        """request/reply subject for L3 broker explicit-transaction operations.

        the platform broker exposes six per-op subjects (versus the
        original four-method shape) so DML ``execute``, single-row
        ``fetchrow``, and multi-row ``fetch`` are addressable
        independently from a generic ``exec``. the asyncpg session
        on the broker side dispatches each op to the matching
        connection method without pre-classifying the SQL verb.

        :param op: transaction operation phase
        :ptype op: Literal["begin", "execute", "fetchrow", "fetch", "commit", "rollback"]
        :return: subject ``{ns}.l3.tx.{op}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.l3.tx.{op}", kind="point")

    # ------------------------------------------------------------------
    # acl
    # ------------------------------------------------------------------

    @classmethod
    def acl_invalidate(
        cls,
        kind: Literal["membership", "assignment", "role"],
    ) -> Subject:
        """publish subject for ACL cache invalidation broadcasts.

        :param kind: which ACL surface to invalidate
        :ptype kind: Literal["membership", "assignment", "role"]
        :return: subject ``{ns}.acl.{kind}.invalidate``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.acl.{kind}.invalidate", kind="point")

    # ------------------------------------------------------------------
    # namespace
    # ------------------------------------------------------------------

    @classmethod
    def namespace_discover(cls) -> Subject:
        """request/reply subject for agent -> hub namespace catalog discovery.

        :return: subject ``{ns}.namespace.discover``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.namespace.discover", kind="point")

    # ------------------------------------------------------------------
    # datasource
    # ------------------------------------------------------------------

    @classmethod
    def datasource_query(cls, name: str) -> Subject:
        """request/reply subject for datasource query routing.

        :param name: datasource name
        :ptype name: str
        :return: subject ``{ns}.datasource.{name}.query``
        :rtype: Subject
        :raises ValueError: if name is empty
        """
        if not name:
            raise ValueError("datasource name must be non-empty")
        return Subject(path=f"{_ns()}.datasource.{_sanitize(name)}.query", kind="point")

    # ------------------------------------------------------------------
    # cache invalidation
    # ------------------------------------------------------------------

    @classmethod
    def cache_invalidate(cls) -> Subject:
        """publish subject for cross-pod cache invalidation broadcasts.

        the cache invalidation subject is NOT namespace-prefixed: it is
        a cross-platform constant (``threetears.cache.invalidate``) so
        that every 3tears collection in every consumer process listens
        on the same subject regardless of the env-specific 3tears
        prefix.

        :return: subject ``threetears.cache.invalidate``
        :rtype: Subject
        """
        return Subject(path="threetears.cache.invalidate", kind="point")

    # ------------------------------------------------------------------
    # config epochs (cross-pod cache reload coherence)
    # ------------------------------------------------------------------
    #
    # epoch subjects live in the publishing product's namespace --
    # subscribers are always sibling pods of the same product, never
    # cross-product, so the standard namespace-prefixed shape applies.
    # multi-env deployments on a shared NATS cluster get partitioned by
    # the prefix.
    #
    # the path shape is asymmetric between single-product builders and
    # 3tears-family builders BY DESIGN: a single-product namespace has
    # only one product per namespace, so its subjects have nothing after
    # the namespace to disambiguate against (``<ns>.capabilities.epoch``).
    # the 3tears namespace multiplexes the broker / catalog / agents /
    # tools / channels / MCP under one prefix, so 3tears subjects always
    # include a product segment as the second token
    # (``3tears.gateway.catalog.epoch``). the asymmetry mirrors the
    # underlying namespace shape; do not "normalize" it. consumed by
    # :class:`threetears.epoch.client.EpochClient` and
    # :class:`threetears.epoch.listener.EpochListener`.

    @classmethod
    def capabilities_epoch(cls) -> Subject:
        """publish + subscribe subject for the capabilities-registry epoch.

        bumped by an admin POST/PATCH on the ``models`` table
        after the in-process ``register_model_capabilities_bulk(...)``
        call. sibling pods subscribe to reload their local
        :class:`threetears.models.tracking.ModelCapabilities` registry
        from the row in the next read. intended call site: a
        process bound to a single-product namespace.

        constraint (single-product-per-namespace): the path has no
        product segment after ``{ns}`` because the namespace is
        single-product. if a second product ever joins the namespace,
        this subject must be renamed to add a product segment
        (``{ns}.<product>.capabilities.epoch``) and every subscriber
        must roll forward together -- a coordinated wire-protocol
        break, not a hot-deploy change. the constraint is documented
        here at the call site (not just in the section header) so
        the implication is visible from the builder's own docstring.

        :return: subject ``{ns}.capabilities.epoch``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.capabilities.epoch", kind="point")

    @classmethod
    def gateway_catalog_epoch(cls) -> Subject:
        """publish + subscribe subject for 3tears-gateway catalog epoch.

        bumped by hub admin endpoints that mutate ``gateway_models``,
        ``gateway_providers``, or ``gateway_credit_rates``. gateway
        sibling pods subscribe to re-run ``_load_catalog`` immediately
        rather than waiting for the next ``cache_ttl_seconds`` tick.
        intended call site: 3tears-bound process (hub or gateway).

        :return: subject ``{ns}.gateway.catalog.epoch`` (3tears-bound:
            ``3tears.gateway.catalog.epoch``)
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.gateway.catalog.epoch", kind="point")

    @classmethod
    def datasource_tile_epoch(cls, datasource_id: str | UUID, layer: str) -> Subject:
        """publish + subscribe subject for one geo layer's tile version.

        bumped when a layer's tiles are rebuilt from a new source
        generation -- a reseed, a redistricting, a corrected boundary.
        sibling pods subscribe so every pod flips to the new version
        together rather than one serving new tiles while others serve
        old ones.

        the version this carries is the ``v{n}`` segment of a tile URL,
        so a bump changes every tile address for the layer and the
        superseded version ages out of browser and CDN caches on its
        own. that is why the versioning is per (datasource, layer) and
        not global: a single global version would discard every
        layer's edge cache worldwide whenever one of them was reseeded.

        higher cardinality than this family's other members, which are
        parameterless platform-wide config domains -- there is one row
        in ``platform.config_epochs`` per registered geo layer, and one
        subscription per layer per pod. that is the cost of not sharing
        a version across layers, and it is the point.

        :param datasource_id: owning datasource's id
        :ptype datasource_id: str | UUID
        :param layer: geo layer name within that datasource
        :ptype layer: str
        :return: subject ``{ns}.datasource.{id}.tiles.{layer}.epoch``
        :rtype: Subject
        :raises ValueError: if datasource_id or layer is empty
        """
        if not datasource_id:
            raise ValueError("datasource_id must be non-empty")
        if not layer:
            raise ValueError("layer must be non-empty")
        return Subject(
            path=f"{_ns()}.datasource.{_sanitize(datasource_id)}.tiles.{_sanitize(layer)}.epoch",
            kind="point",
        )

    @classmethod
    def mcp_rbac_epoch(cls) -> Subject:
        """publish + subscribe subject for MCP per-tool RBAC epoch.

        bumped by MCP admin endpoints that grant or revoke per-tool
        access. MCP-host sibling pods subscribe to reload their RBAC
        view from the row before the next tool-call authorization
        check, so a revoked grant cannot ride a stale cache forward.
        intended call site: 3tears-bound MCP-host process.

        :return: subject ``{ns}.mcp.rbac.epoch`` (3tears-bound:
            ``3tears.mcp.rbac.epoch``)
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.mcp.rbac.epoch", kind="point")

    @classmethod
    def identity_epoch(cls) -> Subject:
        """publish + subscribe subject for the principal-status epoch.

        bumped when a principal's ability to act changes in a way an
        already-issued access token cannot express -- the canonical case
        being an account disabled mid-session. An identity token is
        deliberately claims-only and self-contained, so nothing in it
        goes stale on its own: without this, a disabled account keeps
        authorizing until its token expires, which is the whole window
        the ``IDN-9``-style "a disabled account never authenticates"
        rule is supposed to close.

        Sibling pods subscribe and drop their cached principal status,
        so the next request re-reads it rather than trusting a token
        minted before the change. Same write -> bump -> broadcast ->
        reload shape as :meth:`mcp_rbac_epoch`, and it recovers a missed
        broadcast the same way (periodic catch-up + per-message echo).

        payload convention: publishers SHOULD send
        ``{"user_id": "<uuid>"}`` naming the affected principal, so a
        subscriber can also close that user's live sockets -- which a
        cache drop alone never reaches. It is a hint, not a contract:
        the catch-up path carries no payload at all, so a subscriber
        must stay correct without one.

        :return: subject ``{ns}.identity.epoch`` (3tears-bound:
            ``3tears.identity.epoch``)
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.identity.epoch", kind="point")

    # ------------------------------------------------------------------
    # deadletter
    # ------------------------------------------------------------------

    @classmethod
    def deadletter(cls, original_path: str) -> Subject:
        """publish subject for deadlettering a failed callback's message.

        produced by :class:`NatsClient` when a subscribe callback raises
        and the subscription has ``deadletter_on_failure=True`` (default).

        :param original_path: dotted path of subject the failed message arrived on
        :ptype original_path: str
        :return: subject ``{ns}.deadletter.{original_path}``
        :rtype: Subject
        """
        return Subject(path=f"{_ns()}.deadletter.{original_path}", kind="point")
