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
}
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
tenantIdis immutable after creation.- A shared dashboard (
isShared = true) must have ascoperesolvable for all members of the owning tenant (no user-private sources). - Every
DashboardWidget.sourcemust reference an allowed platform the requesting role may read. refreshPolicy.mode = intervalrequires a positivefallbackIntervalSeconds.
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
UserWorkspaceexists per(tenantId, userId). defaultProjectId, if set, must belong totenantId.pinnedDashboardIdsmay only referenceDashboardViews 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
ReviewRequestin a terminal state (Approved,Rejected,Overridden,Expired) is immutable. claimedByUserIdmust be authorized forprojectIdand the required review role.- A request may have at most one non-superseded
ReviewDecisionfor a terminal outcome. subjectIdandtraceIdare 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
ReviewDecisionis append-only and immutable once recorded (corrections create a new decision that supersedes). decision = OverriddenrequiresisOverride = trueand an elevated reviewer role;rationaleis mandatory.rationaleis mandatory forRejectedandOverridden.- Every decision references exactly one
ReviewRequestin the sametenantId.
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
subjectIdscope. readis monotonic (once read, cannot revert to unread by another actor).- Escalation notifications (
ReviewEscalated) must target anApprover-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
definitionmust validate against the schema for itsviewKind.- A shared
SavedViewmay only reference data sources readable by all members of the tenant for whom it is shared. viewKindandtenantIdare immutable; renaming changesnameonly.- 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
tenantIdand 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 = diffrequires a validcompareToVersion.
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.
tenantIdis 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.
ArtifactPreviewSessioncarries aclassificationceiling and redacts or denies restricted content per Knowledge Platform governance.ReviewRequest/ReviewDecisionrationales may contain business judgement and are classifiedinternalby default and mirrored to the governance audit sink. - Auditability.
ReviewDecisionis append-only and immutable; every state-changing action emits a canonical-envelope event carryingtraceId/correlationId, so the human-in-the-loop is fully reconstructable. See Security. - Right-to-be-forgotten. User-scoped aggregates (
UserWorkspace,SavedView, personalDashboardView) support tenant-governed deletion; immutable decisions are retained per governance retention policy and anonymized rather than deleted.