Skip to content

Data Model

Archived 2026-10-09

This June-2026 catalog page was superseded by the generated page at planes/experience/studio/data-model.md, which is rendered from the architecture model. It is kept for provenance; its content was the seed for the model.

Historical — archived

This page is kept for provenance. Its dates, targets and technology statements are not current direction. See the linked replacement page.

Reviewed 2026-10-09

Target; Studio-owned data is limited to session, saved views and notification state; everything else is a read model with stated freshness.

Target Architecture — Final-State Design

This page describes the final-state Factory Studio domain model. The aggregate roots below are the designed end state; they persist over the same Azure SQL/PostgreSQL and Redis stack used across the factory.

The Factory Studio owns a deliberately small domain: presentation state, human-review state, notifications, and user workspace preferences. It is not a system of record for projects, blueprints, artifacts, or runtime — those belong to the source-of-truth platforms and are read through the BFF tier. The Studio's 7 aggregate roots capture only what the experience itself must own, all following the naming conventions (singular PascalCase nouns) and carrying the cross-cutting metadata schema fields.

Aggregate roots at a glance

Aggregate root Owning service Purpose
DashboardView DashboardAggregationService A composed, cacheable dashboard definition and its widget layout.
UserWorkspace StudioBff A user's per-tenant workspace: preferences, layouts, and saved-view references.
ReviewRequest ReviewCenterService A unit of human review raised against a factory subject.
ReviewDecision ReviewCenterService An immutable record of a human decision on a review.
StudioNotification StudioNotificationService A tenant/role-scoped notification delivered in-app or by digest.
SavedView StudioBff A reusable saved filter/graph/dashboard configuration.
ArtifactPreviewSession ArtifactPreviewService A short-lived, access-checked preview handle over Knowledge artifact memory.

Entity-relationship overview

erDiagram
    UserWorkspace ||--o{ SavedView : owns
    UserWorkspace ||--o{ DashboardView : pins
    ReviewRequest ||--o{ ReviewDecision : resolvedBy
    UserWorkspace ||--o{ StudioNotification : receives
    UserWorkspace ||--o{ ArtifactPreviewSession : opens

    UserWorkspace {
        string workspaceId PK
        string tenantId
        string userId
        string defaultProjectId
    }
    SavedView {
        string savedViewId PK
        string workspaceId FK
        string viewKind
        string definition
    }
    DashboardView {
        string dashboardViewId PK
        string tenantId
        string ownerUserId
        string layout
    }
    ReviewRequest {
        string reviewRequestId PK
        string tenantId
        string subjectType
        string subjectId
        string status
    }
    ReviewDecision {
        string reviewDecisionId PK
        string reviewRequestId FK
        string decision
        string reviewerUserId
    }
    StudioNotification {
        string notificationId PK
        string tenantId
        string recipientUserId
        string kind
        bool read
    }
    ArtifactPreviewSession {
        string sessionId PK
        string tenantId
        string artifactId
        datetime expiresAt
    }
Hold "Alt" / "Option" to enable pan & zoom

DashboardView

Purpose — Defines a composed dashboard: which widgets appear, their layout, their data sources, and the refresh policy. It is the cacheable read-model definition the DashboardAggregationService materializes from Control Plane and Observability data.

Fields — dashboardViewId, tenantId, ownerUserId, name, scope (portfolio | project), projectId?, layout, refreshPolicy, isShared, createdAt, updatedAt, version.

Entities — DashboardWidget (widgetId, type, source, query, position, range).

Value objects — RefreshPolicy (mode: event-driven | interval, fallbackIntervalSeconds), WidgetPosition (row, col, span), DataSourceRef (platform, endpoint).

Invariants

  • tenantId is immutable after creation.
  • A shared dashboard (isShared = true) must have a scope resolvable for all members of the owning tenant (no user-private sources).
  • Every DashboardWidget.source must reference an allowed platform the requesting role may read.
  • refreshPolicy.mode = interval requires a positive fallbackIntervalSeconds.

Domain events — DashboardViewCreated, DashboardViewUpdated, DashboardViewShared, DashboardViewDeleted.

Repository — DashboardViewRepository (tenant-scoped queries by owner and scope).

Persistence — Azure SQL/PostgreSQL (definition); materialized view models cached in Redis keyed by tenantId + dashboardViewId + filter hash with event-driven invalidation.


UserWorkspace

Purpose — A user's personal cockpit configuration within one tenant: default landing project, theme, pinned dashboards, and references to their saved views. One workspace per (tenantId, userId).

Fields — workspaceId, tenantId, userId, defaultProjectId?, theme, pinnedDashboardIds, lastActiveAt, createdAt, updatedAt.

Entities — WorkspacePreference (key, value, scope).

Value objects — LayoutPreference (density, sidebarState), NotificationPreference (channel, digestCadence, mutedKinds).

Invariants

  • Exactly one UserWorkspace exists per (tenantId, userId).
  • defaultProjectId, if set, must belong to tenantId.
  • pinnedDashboardIds may only reference DashboardViews the user can read.

Domain events — UserWorkspaceCreated, WorkspacePreferenceChanged, DashboardPinned, DashboardUnpinned.

Repository — UserWorkspaceRepository (lookup by (tenantId, userId)).

Persistence — Azure SQL/PostgreSQL; hot preferences cached in Redis for the session.


ReviewRequest

Purpose — A unit of human review raised against a factory subject (artifact, decision, deployment promotion, policy exception). Drives the review workflow state machine.

Fields — reviewRequestId, tenantId, projectId, subjectType, subjectId, raisedBy, priority, status, claimedByUserId?, slaDueAt, traceId, correlationId, createdAt, updatedAt.

Entities — ReviewSubjectRef (subjectType, subjectId, version, previewSessionId?), ChangeRequestItem (itemId, description, resolved).

Value objects — ReviewStatus (Pending | InReview | ChangesRequested | Escalated | Approved | Rejected | Overridden | Expired), Priority (low | normal | high | critical), SlaWindow (raisedAt, dueAt).

Invariants

  • State transitions follow the review state machine; illegal transitions are rejected.
  • A ReviewRequest in a terminal state (Approved, Rejected, Overridden, Expired) is immutable.
  • claimedByUserId must be authorized for projectId and the required review role.
  • A request may have at most one non-superseded ReviewDecision for a terminal outcome.
  • subjectId and traceId are immutable.

Domain events — ReviewRequested, ReviewClaimed, ChangesRequested, ReviewEscalated, ReviewApproved, ReviewRejected, AgentDecisionOverridden, ReviewExpired.

Repository — ReviewRequestRepository (tenant- and project-scoped queue queries by status, priority, SLA).

Persistence — Azure SQL/PostgreSQL (transactional state + history); active queue cached in Redis for fast queue rendering and SignalR fan-out.


ReviewDecision

Purpose — The immutable record of a human decision on a ReviewRequest. It is the durable evidence of human authority and the source of the human-in-the-loop events the factory consumes.

Fields — reviewDecisionId, reviewRequestId, tenantId, decision, reviewerUserId, rationale, conditions, isOverride, decidedAt, traceId, correlationId.

Entities — none (leaf aggregate referencing its ReviewRequest).

Value objects — Decision (Approved | Rejected | ChangesRequested | Overridden), DecisionCondition (text, mustResolveBefore).

Invariants

  • A ReviewDecision is append-only and immutable once recorded (corrections create a new decision that supersedes).
  • decision = Overridden requires isOverride = true and an elevated reviewer role; rationale is mandatory.
  • rationale is mandatory for Rejected and Overridden.
  • Every decision references exactly one ReviewRequest in the same tenantId.

Domain events — ReviewDecisionRecorded (plus the request-level outcome event emitted by ReviewRequest).

Repository — ReviewDecisionRepository (lookup by request; audit queries by reviewer and time).

Persistence — Azure SQL/PostgreSQL, append-only table; mirrored to the Governance audit sink. Never cached mutable.


StudioNotification

Purpose — A notification delivered to a user in-app (via SignalR), in a digest, or as an escalation. Scoped to tenant, recipient, and role.

Fields — notificationId, tenantId, recipientUserId, recipientRole?, kind, subjectType, subjectId, severity, read, deliveredChannels, createdAt, expiresAt?.

Entities — none.

Value objects — NotificationKind (ReviewRequested | ReviewEscalated | DeploymentPromoted | RuntimeSignalRaised | CostThresholdExceeded | SecurityFindingRaised), Severity (info | warn | high | critical), DeliveryChannel (inApp | digest | email).

Invariants

  • A notification is delivered only to recipients authorized for its subjectId scope.
  • read is monotonic (once read, cannot revert to unread by another actor).
  • Escalation notifications (ReviewEscalated) must target an Approver-capable recipient.
  • Expired notifications (expiresAt) are excluded from active queries.

Domain events — NotificationCreated, NotificationRead, NotificationDismissed.

Repository — StudioNotificationRepository (per-recipient unread queries; tenant-scoped digests).

Persistence — Azure SQL/PostgreSQL (durable); unread counters and live delivery state in Redis.


SavedView

Purpose — A reusable, named configuration — a dashboard arrangement, a knowledge-graph neighbourhood, or a filtered list — saved by a user and optionally shared within the tenant.

Fields — savedViewId, workspaceId, tenantId, ownerUserId, name, viewKind, definition, isShared, createdAt, updatedAt.

Entities — none.

Value objects — ViewKind (dashboard | knowledgeGraph | reviewQueue | artifactList | flowGraph), ViewDefinition (serialized, kind-specific filter/focus/layout payload).

Invariants

  • definition must validate against the schema for its viewKind.
  • A shared SavedView may only reference data sources readable by all members of the tenant for whom it is shared.
  • viewKind and tenantId are immutable; renaming changes name only.
  • A saved knowledge-graph view's focus node must remain within the owner's classification scope at render time (re-checked on load).

Domain events — SavedViewCreated, SavedViewUpdated, SavedViewShared, SavedViewDeleted.

Repository — SavedViewRepository (by workspace; shared views by tenant + kind).

Persistence — Azure SQL/PostgreSQL.


ArtifactPreviewSession

Purpose — A short-lived, access-checked handle that lets the Artifact Lineage Browser render artifact bodies, diffs, and lineage without the Studio storing artifact content. Bodies stay in Knowledge artifact memory; the session is an authorized, expiring lens.

Fields — sessionId, tenantId, artifactId, version, requestedByUserId, classification, renderKind, compareToVersion?, createdAt, expiresAt, traceId.

Entities — PreviewRendition (renditionId, renderKind, contentType, redacted), LineageEdge (fromArtifactId, toArtifactId, relationship).

Value objects — RenderKind (source | markdown | diagram | diff), Classification (public | internal | confidential | restricted).

Invariants

  • A session expires at expiresAt; expired sessions cannot render and must be re-requested.
  • The session enforces the requesting user's tenantId and classification ceiling; restricted content is redacted (PreviewRendition.redacted = true) or denied.
  • Raw artifact storage URIs are never exposed to the client through the session.
  • renderKind = diff requires a valid compareToVersion.

Domain events — ArtifactPreviewSessionOpened, ArtifactPreviewRendered, ArtifactPreviewSessionExpired.

Repository — ArtifactPreviewSessionRepository (active sessions by user; expiry sweep).

Persistence — Redis (ephemeral session + rendition cache, TTL = expiresAt); session-opened/expired events are emitted for audit but bodies are never persisted in Studio stores.


Persistence summary

Store Used for Aggregates
Azure SQL / PostgreSQL Durable Studio state, transactional review history, append-only decisions and audit DashboardView, UserWorkspace, ReviewRequest, ReviewDecision, StudioNotification, SavedView
Redis Sessions, hot dashboards, active review queue, unread counters, ephemeral previews, SignalR backplane ArtifactPreviewSession, plus caches/projections of the above

Persistence follows the factory standard: NHibernate-backed relational mapping with tenantId as a first-class column on every table, and Redis for low-latency, ephemeral, and fan-out state.

Multi-tenancy and sensitivity

  • Tenant isolation. tenantId is a mandatory, immutable, indexed column on every aggregate and a component of every Redis cache key and SignalR group. No query, cache read, or stream crosses a tenant boundary. This mirrors the factory-wide multi-tenancy model.
  • No source-of-truth duplication. The Studio stores presentation/review/workspace state only. Artifact bodies, project state, and runtime data are never copied at rest into Studio stores — they are read live (or, for previews, rendered through expiring sessions).
  • Sensitivity & classification. ArtifactPreviewSession carries a classification ceiling and redacts or denies restricted content per Knowledge Platform governance. ReviewRequest/ReviewDecision rationales may contain business judgement and are classified internal by default and mirrored to the governance audit sink.
  • Auditability. ReviewDecision is append-only and immutable; every state-changing action emits a canonical-envelope event carrying traceId/correlationId, so the human-in-the-loop is fully reconstructable. See Security.
  • Right-to-be-forgotten. User-scoped aggregates (UserWorkspace, SavedView, personal DashboardView) support tenant-governed deletion; immutable decisions are retained per governance retention policy and anonymized rather than deleted.