Skip to content

Data Model

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.

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

EntitiesDashboardWidget (widgetId, type, source, query, position, range).

Value objectsRefreshPolicy (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 eventsDashboardViewCreated, DashboardViewUpdated, DashboardViewShared, DashboardViewDeleted.

RepositoryDashboardViewRepository (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).

FieldsworkspaceId, tenantId, userId, defaultProjectId?, theme, pinnedDashboardIds, lastActiveAt, createdAt, updatedAt.

EntitiesWorkspacePreference (key, value, scope).

Value objectsLayoutPreference (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 eventsUserWorkspaceCreated, WorkspacePreferenceChanged, DashboardPinned, DashboardUnpinned.

RepositoryUserWorkspaceRepository (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.

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

EntitiesReviewSubjectRef (subjectType, subjectId, version, previewSessionId?), ChangeRequestItem (itemId, description, resolved).

Value objectsReviewStatus (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 eventsReviewRequested, ReviewClaimed, ChangesRequested, ReviewEscalated, ReviewApproved, ReviewRejected, AgentDecisionOverridden, ReviewExpired.

RepositoryReviewRequestRepository (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.

FieldsreviewDecisionId, reviewRequestId, tenantId, decision, reviewerUserId, rationale, conditions, isOverride, decidedAt, traceId, correlationId.

Entities — none (leaf aggregate referencing its ReviewRequest).

Value objectsDecision (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 eventsReviewDecisionRecorded (plus the request-level outcome event emitted by ReviewRequest).

RepositoryReviewDecisionRepository (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.

FieldsnotificationId, tenantId, recipientUserId, recipientRole?, kind, subjectType, subjectId, severity, read, deliveredChannels, createdAt, expiresAt?.

Entities — none.

Value objectsNotificationKind (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 eventsNotificationCreated, NotificationRead, NotificationDismissed.

RepositoryStudioNotificationRepository (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.

FieldssavedViewId, workspaceId, tenantId, ownerUserId, name, viewKind, definition, isShared, createdAt, updatedAt.

Entities — none.

Value objectsViewKind (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 eventsSavedViewCreated, SavedViewUpdated, SavedViewShared, SavedViewDeleted.

RepositorySavedViewRepository (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.

FieldssessionId, tenantId, artifactId, version, requestedByUserId, classification, renderKind, compareToVersion?, createdAt, expiresAt, traceId.

EntitiesPreviewRendition (renditionId, renderKind, contentType, redacted), LineageEdge (fromArtifactId, toArtifactId, relationship).

Value objectsRenderKind (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 eventsArtifactPreviewSessionOpened, ArtifactPreviewRendered, ArtifactPreviewSessionExpired.

RepositoryArtifactPreviewSessionRepository (active sessions by user; expiry sweep).

PersistenceRedis (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.