Skip to content

Bounded contexts

This is the domain-model family of the Explanation quadrant. Each page explains a single bounded context — its ubiquitous language, aggregates, invariants, state machines, and the seams it exposes to other contexts — so you understand why the model is shaped the way it is. For the wire shape of a context's HTTP surface see reference; for a task against it see how-to. The layout under internal/<context>/ mirrors this tree page-for-page, and the depguard rules in .golangci.yml enforce that one context does not reach into another except through the documented seams.

Context groups

  • Identity — bootstrap tokens, registration, tenancy (Domain → Project → Resource → Node), IdP bindings, ReBAC, invitations, and groups. Start with tenancy for the aggregate model, then bootstrap-tokens, registration, idp, rebac, invitations, and groups. The dual-control approvals sub-context layers a generic approval-workflow gate on top — propose → approve / reject / break-glass / expire — keyed on a per-Domain ApprovalPolicy.
  • Mesh — peer manager, per-Node reachability, the reconciliation pull surface, the signed event bus, and the per-Node node-state store. Start with peers, then reachability, reconciliation-pull, and sse. The Node State Service owns the per-Node key/value State Entry aggregate — the platform-owned metadata and data entries that fan out a node_state_updated event and the upstream, NSK-authenticated report entries a workload on the Node pushes back — and is the source the reconciliation pull projects its state and reports snapshot blocks from.
  • Policy — the Network Policy Engine authoring surface, the per-Node Policy Compiler, the closed two-event outbox set and the per-Node SSE fan-out, and the per-Node Capability Manifest ingest pipeline that captures the agent-side snapshot of "what binary am I running, what hooks do I advertise, what host key am I presenting" and emits the diff downstream consumers correlate against. Start with model for the Policy aggregate and L3/L4 rule grammar, then compiler for the per-Node projection, events for the publisher-side dispatch table and per-Node fan-out, and capabilities for the agent-side manifest ingest surface, its NSK-authenticated transport, the exhaustive Problem code catalogue, and the single-transaction diff & event contract, integrity for the agent-side IntegrityViolation ingest surface, the three-by-three violation taxonomy (binary_checksum / hook_checksum / ssh_host_key × startup_scan / inotify / pre_dispatch), the batched persist-and-alert contract, and the integrity_alert outbox event that fans out per-Domain through the denormalised (Node, Resource, Project, Domain) quartet, and hooks for the discovery-only projection of Kubernetes PlexdHook custom resources onto the Node's plexd_hooks capability-manifest list (name, image digest, parameters, timeout, sandbox) — read-only, so plexsphere never writes them back, resolves no registry digest, and never schedules or executes a hook — plus the hook-integrity gate layered on the advertised-hook catalog: a trust-on-first-use known-good baseline pinned per (Node, hook) at capability ingest, the drift integrity_alert (kinds ["hook_checksum"], recommended action quarantine_node), and the Verified / Drift / Uncatalogued verdict the Action Orchestrator consults to refuse a drifted or uncatalogued hook dispatch with a 409 hook_integrity_violation.
  • Bridge — the Bridge Orchestrator: the orchestrator configuration a Resource of kind bridge carries, modelled as four independent aggregates rather than one mega-aggregate so each invariant stays inside its own transaction boundary and the four lifecycles operators manage separately stay decoupled. BridgeRelay is the singleton relay configuration of a bridge Resource (keyed on the Resource alone, no surrogate id); UserAccessProvider is a named WireGuard-family mesh-ingress provider end users dial in through; PublicIngressRule is an SNI-routed public ingress termination forwarding to a target Node; and SiteToSiteTunnel is a directional tunnel to a remote endpoint carrying its allowed-subnets list. Every mutation first reads the target Resource and refuses unless its kind is bridge, and the context emits a closed set of seven past-tense outbox events (*Configured / *Removed, the singleton relay having only a configured form) pinned by an AST gate; every auth secret a provider or tunnel consumes is held as an opaque reference, never as material. See bridge for the aggregate model, the invariants, the event set, and the secret-reference posture, and bridge/validation for the cross-aggregate validation pipeline (host-port, subnet-overlap, and ACME-feasibility refusals).
  • Actions — the Action Orchestrator: one dispatch of a named action (a builtin shipped with the Node agent, or a user-declared hook) across a cohort of Nodes, modelled as a single Execution aggregate whose per-Node ActionInvocation entities each advance independently along the closed status state machine (pending → ack → started → one of succeeded / failed / cancelled, with a timeout edge from every non-terminal status). A dispatch resolves its cohort from a single node_id or an opaque label selector, applies per-node domain-isolation / ReBAC-act / capability / hook-integrity gates (refusing a drifted or uncatalogued hook with a 409 hook_integrity_violation), and fans out to one invocation per surviving Node, persisting the header, the N targets, and the N outbox events in one transaction. The collected output rides a two-tier contract — bounded inline bytes (≤ 16 KiB) stored in the control plane, or a presigned object-store PUT for an over-ceiling body. A per-Domain live-execution cap, a background timeout reconciler, and a closed single-member outbox set (actions.ActionDispatched, pinned by an AST gate) round out the model. See actions for the aggregate, the invocation state machine, the two-tier output contract, the survivor-precedence rules, the ReBAC act matrix, the audit contract, and the Problem-code catalogue, and actions/events for the closed-outbox-vs-wire-literal split, the action_request payload, and the per-Node SSE fan-out. The context is one of the four sanctioned consumers of the Label Registry's SelectorPort seam — it expands "dispatch to every Node matching <selector>" into the concrete Node set at action_request time (see labels).
  • Access — the Access Orchestrator: it mints short-lived, signed session credentials (EdDSA JWTs) for ssh / k8s / tcp access to a Resource's Nodes, modelled as a single Session aggregate that carries the kind, a per-Kind discriminated SessionTarget (an ssh user + optional command allow-list, a k8s user + optional impersonation groups, or a protocol-opaque tcp host:port), the issuing Domain / Project, the target Resource, the requesting Identity, and the TTL / idle / expiry envelope, with the per-Session jti pinned to the SessionID so revoking the Session revokes its token. Issuance runs a resource#act ReBAC check as a hard gate, enforces the per-Domain SessionPolicy (clamping the requested TTL to the Domain's MaxTTL, applying the three concurrency caps and the issuance rate limit, and running the step-up acr/amr/freshness gate), signs the deterministic canonical-claims JWT through the per-Domain Signing Service key, and persists the metadata-only aggregate plus its outbox event in one transaction — the assembled token never rests in the control plane. Revocation is instant: it appends a row to the jti revocation list and fans out the revoke rather than waiting for the token to expire, and a background sweeper revokes expired and idle Sessions through the same path. The two domain events route onto the closed session_setup / session_revoked SSE wire literals so a target plexd provisions or tears down a session immediately, and the per-kind access_session_event callbacks (ssh command_executed / command_exited, k8s api_request, tcp session_started / session_ended) reset the idle timer from the authenticated target. See access for the Session aggregate invariants, the per-Kind SessionTarget, the JWT canonical-bytes contract and its alg-confusion-proof verifier, the SessionPolicy snapshot, the issuance state machine, the audit relations, and the step-up contract.
  • Secrets — the Secret Store: the real-time NSK-rewrap proxy that delivers Project-scoped secret material to Nodes over GET /v1/nodes/{id}/secrets/{name} without ever exposing plaintext. It is the platform's only meet-and-rewrap point between the OpenBao backend and the per-Node Node Secret Key (NSK): the read path reconstructs the NSK plaintext, reads the backend, rewraps the payload under AES-256-GCM in-process with a fresh nonce, and returns only NSK-wrapped ciphertext — plaintext is never persisted, logged, or placed on the event bus. Every read runs a ReBAC hard gate on secret:<id>#read, is audited on grant and denial, and is throttled by per-Node and per-Domain token buckets; the companion node_secrets_updated event fans a names-and-versions-only inventory delta out over the Signed Event Bus so a Node re-fetches changed values without the bus ever carrying secret bytes. See secrets for the Secret aggregate, the rewrap pipeline and its plaintext-never-persists invariant, the audit contract, the rate-limit policy matrix, and the threat model. The read side pairs with the write-side OpenBao Credential Broker under provisioning/credentials.
  • Observability — the Observability Ingest pipeline: the per-Node front door for plexd telemetry over three NSK-authenticated batch-ingest endpoints under /v1/nodes/{id} — metrics routed to Grafana Mimir, structured logs and normalised audit events routed to Grafana Loki. The front door admits each batch (structure, encoding, size, and per-batch record caps), quota-gates it against a byte-weighted per-Node and per-Domain budget, and hands the survivor onto a per-signal JetStream buffer stream where the Domain, Project, and Node identity rides the subject and a header rather than the record body. Records are tagged server-side and de-personalised — the wire body never carries an identity subject or email, so dashboards and alerts scope through the platform label model and PII a record inadvertently carries stays the customer's ingest-side responsibility. See ingest for the ingest aggregate, the admission and quota gates, the buffer-stream subject grammar, the audited node_id_mismatch self-check, and the Problem-code catalogue. The egress half drains the three per-Domain JetStream buffer streams and fans each batch to its downstream sink — Grafana Mimir (metrics, Prometheus remote-write), Grafana Loki (logs + audit), and the audit SIEM — through five durable consumers with deterministic capped redelivery backoff. See routing for the sink mapping, the consumer topology, the error classification, and the routing metrics.
  • Artifacts — the Artifact Registry: indexed plexd release records the control plane verifies before serving. It consumes the upstream OCI release stream (one image index per version, one image per architecture carrying the binary checksum and a Sigstore bundle), runs every artifact through a staged gate pinned to the upstream Fulcio SAN and OIDC issuer, indexes only whole-version-verified records, and serves them over two read endpoints (GET /v1/artifacts/plexd/{version} and its .sigstore companion). See artifacts for the PlexdRelease aggregate, the verify-or-reject invariant, the verdict-as-timestamp persistence, and the seed + refresh reconcilers that gate /readyz under the artifacts-seeds and artifacts-refresh probes.
  • Audit — the platform audit log: hash-chained Postgres primary with object-store mirror, per-Domain residency, and the erasure flow. See audit/.
  • Signing — Ed25519 custody, deployment-scoping rules, and the rotation contract. See signing/.
  • Labels — definitions, assignments, the selector grammar, and the SelectorPort seam consumed by policy and provisioning. See labels/.
  • Provisioning — bootstrap-driven enrolment surfaces, the OpenBao Credential Broker that owns project-scoped secret material, the Cloud Credentials Custodian that owns cloud-scoped secret material, the Management Fleet that owns the Kubernetes clusters hosting the provisioning substrate, the Blueprint Catalog that owns the curated provisioning recipes a Project can request, and the Provisioning Broker that turns a Project's resource declaration into running, mesh-enrolled substrate. See provisioning/credentials for the Credential aggregate, the four lifecycle events, the deterministic KV-v2 path, and the Sweeper. See provisioning/credential-pool for the CloudCredential aggregate, the four lifecycle events, the deterministic KV-v2 path keyed on (cloud_id, credential_id), and the Sweeper that gates /readyz under the cloud-credentials-sweeper probe. See provisioning/management-fleet for the ManagementCluster and ProjectClusterAssignment aggregates, the per-Project namespace-phase state machine, the verify-only readiness gate, and the reconcile loop that gates /readyz under the management-fleet-reconcile probe. See provisioning/blueprints for the Blueprint and BlueprintVersion aggregates, the ProviderKind and InjectionStrategy closed enums, the typed parameter-schema model, the five-blueprint platform seed catalog, and the seed reconcile that gates /readyz under the blueprint-catalog-seeds probe. See provisioning/broker for the ProvisionedResource aggregate, the eight-phase status state machine (Pending → Provisioning → Enrolling → Ready on the converge arm, Failed a sticky terminal sink, and the ordered Deregistering → Deprovisioning → Deleted teardown machine), the render-and-apply pipeline that turns a blueprint into a Crossplane v2 namespaced Composite Resource, and the reconcile loop that gates /readyz under the provisioning-broker-reconcile probe. See provisioning/cloud-init-daemonset for the broker's bootstrap-injection half — the cloud-init user-data document contract and its metadata-service delivery model, the Kubernetes plexd DaemonSet bundle with its embedded-Secret and External-Secrets-Operator token-delivery modes, the per-strategy injected-XR field map (spec.userData / the helmValues triple / the unchanged providerSecret leaf), and the force-apply preservation rule. See provisioning/label-propagation for how the broker projects a tenancy Resource's opted-in labels onto cloud-provider tags — the plexsphere: key prefix, the per-provider constraint profiles, the deterministic count cap, and the skip-observability contract. See provisioning/cloud for the Cloud Inventory aggregate, its closed aws / azure provider enum, the per-provider validator family, and the three Cloud lifecycle events. See provisioning/adoption for the how-to that brings a pre-existing Kubernetes cluster under management with the ExternalSecrets pattern. See provisioning/deletion for the graceful-deletion teardown ordering and the node-before-substrate invariant. See provisioning/rebac for the Credential Assignment sub-context and the request / approve / reject / revoke ReBAC workflow that binds a cloud credential to a Project.

When to read this section

  • You are about to add or change code inside internal/<context>/ and need to know which invariants the existing aggregates enforce.
  • You are reviewing a cross-context change and want to confirm the seam it uses is the documented one.
  • You are debugging an event flow and need the canonical envelope shape.

For the higher-level system map, see architecture. For operator-facing tasks, see how-to.