--- id: CatalogMetadataRefreshed name: Catalog Metadata Refreshed version: 0.0.1 summary: | Raised when catalog metadata is refreshed, updating dataset list and schema versions. producers: - CatalogService --- ## Description This event records that the metadata for a catalog has been refreshed from the DuckLake source. Metadata includes the list of available datasets, table counts, and schema version information. ## Trigger Raised in response to the `RefreshCatalogMetadata` command after successfully fetching updated metadata from the catalog source. --- id: CatalogSelected name: Catalog Selected version: 0.0.1 summary: | Raised when user selects a DuckLake catalog from the available catalogs list. producers: - CatalogService --- ## Description This event records that a user has selected a specific DuckLake catalog to work with. The catalog becomes the active context for all subsequent query operations. ## Trigger Raised in response to the `SelectCatalog` command after validating catalog availability. --- id: ChartAdded name: Chart Added version: 0.0.1 summary: | Recorded when a Data Analyst adds a chart to a dashboard with position and size. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user adds a chart visualization to an existing dashboard. The event carries a `ChartPlacement` record containing the chart ID, reference to the ChartDefinition, grid position, size, and optional tab assignment. The `ChartPlacement` record structure is: - `chartId`: Unique identifier for this chart placement - `chartDefRef`: Reference to the ChartDefinition from Analytics context (contains `refId` and optional `chartTypeHint`) - `position`: Grid position as a record with `row` and `col` (0-indexed) - `size`: Grid size as a record with `width` and `height` (in grid units) - `tabId`: Optional tab ID if chart is placed within a specific tab ## Business Rules - Chart definition reference must point to a valid ChartDefinition from Analytics - Grid position must be valid (non-negative row and column) - Grid size must be positive (width and height >= 1) - Chart can be placed on the default view or within a specific tab - If tabId is specified, it must reference an existing tab in the dashboard ## Given-When-Then Given a dashboard exists and chart definitions from prior queries are available, When the Data Analyst adds a chart with placement information (position, size, and optional tab), Then ChartAdded is recorded with the complete ChartPlacement record. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardChartAdded", "type": "object", "title": "DashboardChartAdded", "description": "Recorded when a Data Analyst adds a chart to a dashboard with position and size.", "required": ["chartPlacement", "timestamp"], "properties": { "chartPlacement": { "type": "object", "description": "The chart placement with position, size, and references", "required": ["chartId", "chartDefRef", "position", "size"], "properties": { "chartId": { "type": "string", "description": "Unique identifier for this chart placement" }, "chartDefRef": { "type": "object", "description": "Reference to the ChartDefinition from Analytics context", "required": ["refId"], "properties": { "refId": { "type": "string", "description": "Unique identifier for the ChartDefinition in Analytics context" }, "chartTypeHint": { "type": ["string", "null"], "description": "Optional cached chart type for UI hints (may be stale)" } } }, "position": { "type": "object", "description": "Position in grid layout (0-indexed)", "required": ["row", "col"], "properties": { "row": { "type": "integer", "minimum": 0, "description": "Row position on the grid (0-indexed)" }, "col": { "type": "integer", "minimum": 0, "description": "Column position on the grid (0-indexed)" } } }, "size": { "type": "object", "description": "Size in grid units", "required": ["width", "height"], "properties": { "width": { "type": "integer", "minimum": 1, "description": "Width in grid units" }, "height": { "type": "integer", "minimum": 1, "description": "Height in grid units" } } }, "tabId": { "type": ["string", "null"], "description": "Optional tab ID if chart is placed within a tab" } } }, "timestamp": { "type": "string", "format": "date-time", "description": "Timestamp when the chart was added" } } } --- id: ChartMovedToTab name: Chart Moved To Tab version: 0.0.1 summary: | Recorded when a Data Analyst moves a chart from one tab to another within a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user reorganizes charts by moving them between tabs. This enables iterative refinement of dashboard layout to tell a coherent data story. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Source and destination tabs must exist in the dashboard - Chart must exist and be currently placed in the source tab (or default view) - Moving to the same tab is a no-op (not recorded) ## Given-When-Then Given a dashboard with multiple tabs and charts, When the Data Analyst moves a chart to another tab, Then Dashboard Chart Moved To Tab is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardChartMovedToTab", "type": "object", "title": "DashboardChartMovedToTab", "description": "Recorded when a Data Analyst moves a chart from one tab to another within a dashboard.", "required": ["dashboardId", "chartId", "toTabId", "movedAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard containing the chart" }, "chartId": { "type": "string", "format": "uuid", "description": "Chart being moved" }, "fromTabId": { "type": "string", "format": "uuid", "description": "Source tab ID (null if from default view)" }, "toTabId": { "type": "string", "format": "uuid", "description": "Destination tab ID" }, "newGridX": { "type": "integer", "description": "New column position in destination tab" }, "newGridY": { "type": "integer", "description": "New row position in destination tab" }, "movedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the chart was moved" } } } --- id: ChartRemoved name: Chart Removed version: 0.0.1 summary: | Recorded when a Data Analyst removes a chart from a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user removes a chart placement from a dashboard. The underlying ChartDefinition in Analytics remains unaffected. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Chart must exist in the dashboard - Removing the last chart from a tab does not delete the tab - ChartDefinition reference remains valid in Analytics ## Given-When-Then Given a dashboard with charts, When the Data Analyst removes an unneeded chart, Then Dashboard Chart Removed is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardChartRemoved", "type": "object", "title": "DashboardChartRemoved", "description": "Recorded when a Data Analyst removes a chart from a dashboard.", "required": ["dashboardId", "chartId", "removedAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard from which the chart is removed" }, "chartId": { "type": "string", "format": "uuid", "description": "Chart being removed" }, "removedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the chart was removed" } } } --- id: DashboardCreated name: Dashboard Created version: 0.0.1 summary: | Recorded when a Data Analyst creates a new dashboard with a name and initial layout. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a dashboard is created within a workspace. The event captures the dashboard identity, workspace scope, name, and creation timestamp. Dashboards are workspace-scoped resources; the aggregate ID is `workspace_{workspaceId}/dashboard_{name}`. The event contains all data needed to reconstruct dashboard state through event replay. ## Business Rules - Dashboard names must be unique within a workspace - Dashboards are created empty (no initial charts or tabs) - Workspaces are the ownership boundary; dashboards belong to workspaces, not individual users ## Event Schema | Field | Type | Description | |-------|------|-------------| | `dashboardId` | string | Unique identifier for the dashboard | | `workspaceId` | string | Unique identifier for the containing workspace | | `name` | string | Dashboard name (human-readable identifier within workspace) | | `timestamp` | ISO 8601 datetime | When the dashboard was created | ## Given-When-Then Given a workspace exists, When a dashboard is created with a name, Then DashboardCreated is recorded with dashboardId, workspaceId, name, and timestamp. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardCreated", "type": "object", "title": "DashboardCreated", "description": "Recorded when a dashboard is created within a workspace.", "required": ["dashboardId", "workspaceId", "name", "timestamp"], "properties": { "dashboardId": { "type": "string", "description": "Unique identifier for the dashboard" }, "workspaceId": { "type": "string", "description": "Unique identifier for the containing workspace" }, "name": { "type": "string", "description": "Dashboard name (human-readable identifier within workspace)" }, "timestamp": { "type": "string", "format": "date-time", "description": "Timestamp when the dashboard was created" } } } --- id: DashboardRenamed name: Dashboard Renamed version: 0.0.1 summary: | Recorded when a Data Analyst renames a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes the display title of a dashboard. Stable IDs ensure external references remain valid despite title changes. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - New title must be non-empty - New title should be unique per owner (warning, not hard error) - Dashboard ID remains unchanged for stable deep links ## Given-When-Then Given a dashboard exists, When the Data Analyst renames it, Then Dashboard Renamed is recorded with the new title. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardRenamed", "type": "object", "title": "DashboardRenamed", "description": "Recorded when a Data Analyst renames a dashboard.", "required": ["dashboardId", "newTitle", "renamedAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard being renamed" }, "previousTitle": { "type": "string", "description": "Previous display title" }, "newTitle": { "type": "string", "description": "New display title" }, "renamedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the dashboard was renamed" } } } --- id: LayoutDefaultsUpdated name: LayoutDefaultsUpdated version: 0.1.0 summary: Layout defaults for new dashboards have been updated --- Indicates that the layout defaults JSON blob has been updated. These defaults will be applied when workspace members create new dashboards. ## Schema | Field | Type | Description | |-------|------|-------------| | layoutDefaults | String | JSON blob containing the new layout defaults | | timestamp | Timestamp | When the layout defaults were updated | ## Notes JSON validity is enforced at the boundary layer before the command is accepted. The exact structure of the JSON blob is application-specific. --- id: QuerySessionCancelled name: Query Session Cancelled version: 0.0.1 summary: | Raised when a user cancels a running query. producers: - QuerySessionService --- ## Description This event records user-initiated query cancellation. The query execution is terminated and resources are released. ## Trigger Raised in response to the `CancelQuery` command for a query in Running state. --- id: QuerySessionCompleted name: Query Session Completed version: 0.0.1 summary: | Raised when a query returns results successfully. producers: - QuerySessionService --- ## Description This event records the successful completion of a query execution. It captures result metadata (row count, columns) but not the full result data. ## Trigger Raised when query execution completes without error and results are available. --- id: QuerySessionFailed name: Query Session Failed version: 0.0.1 summary: | Raised when a query encounters an error during execution. producers: - QuerySessionService --- ## Description This event records query execution failure with error details. The error message provides diagnostic information for troubleshooting. ## Trigger Raised when query execution encounters a DuckDB error, timeout, or other execution failure. --- id: QuerySessionStarted name: Query Session Started version: 0.0.1 summary: | Raised when a SQL query begins execution against a DuckLake dataset. producers: - QuerySessionService --- ## Description This event records the initiation of a query execution session. It captures the query text, target dataset, and execution context. ## Trigger Raised in response to the `StartQuery` command after validating the SQL and dataset reference. --- id: SavedQueryCreated name: Saved Query Created version: 0.0.1 summary: | Recorded when a Data Analyst saves a composed SQL statement with a name and dataset reference. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user saves a new reusable query. The query captures the SQL text, target dataset, and user-provided name. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Query name must be non-empty and unique per user - SQL text must be non-empty - Dataset reference must be a valid URI format ## Given-When-Then Given a composed SQL statement and target dataset exist, When the Data Analyst saves the query with a name, Then Saved Query Created is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SavedQueryCreated", "type": "object", "title": "SavedQueryCreated", "description": "Recorded when a Data Analyst saves a composed SQL statement with a name and dataset reference.", "required": [ "savedQueryId", "ownerId", "name", "sqlText", "datasetRef", "createdAt" ], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Unique identifier for the saved query" }, "ownerId": { "type": "string", "format": "uuid", "description": "User ID of the query owner" }, "name": { "type": "string", "description": "Display name for the saved query" }, "sqlText": { "type": "string", "description": "The SQL query text" }, "datasetRef": { "type": "object", "description": "Reference to the target dataset decomposed into catalog and dataset components", "required": ["catalogUri", "datasetName"], "properties": { "catalogUri": { "type": "string", "description": "Catalog URI identifier (e.g., 'ducklake:hf://datasets/sciexp')" }, "datasetName": { "type": "string", "description": "Dataset name within the catalog (e.g., 'fixtures')" } } }, "createdAt": { "type": "string", "format": "date-time", "description": "Timestamp when the query was saved" } } } --- id: SavedQueryDatasetRefUpdated name: Saved Query Dataset Ref Updated version: 0.0.1 summary: | Recorded when a Data Analyst updates the dataset reference of a saved query. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user retargets a saved query to a different dataset. Schema compatibility should be validated when changing datasets. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Saved query must exist and be owned by the user - New dataset reference must be a valid URI format - Schema diff should be provided if available ## Given-When-Then Given a saved query exists, When the Data Analyst updates its dataset reference, Then Saved Query Dataset Ref Updated is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SavedQueryDatasetRefUpdated", "type": "object", "title": "SavedQueryDatasetRefUpdated", "description": "Recorded when a Data Analyst updates the dataset reference of a saved query.", "required": ["savedQueryId", "newDatasetRef", "updatedAt"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query being updated" }, "previousDatasetRef": { "type": "object", "description": "Previous dataset reference decomposed into catalog and dataset components", "required": ["catalogUri", "datasetName"], "properties": { "catalogUri": { "type": "string", "description": "Catalog URI identifier (e.g., 'ducklake:hf://datasets/sciexp')" }, "datasetName": { "type": "string", "description": "Dataset name within the catalog (e.g., 'fixtures')" } } }, "newDatasetRef": { "type": "object", "description": "New dataset reference decomposed into catalog and dataset components", "required": ["catalogUri", "datasetName"], "properties": { "catalogUri": { "type": "string", "description": "Catalog URI identifier (e.g., 'ducklake:hf://datasets/sciexp')" }, "datasetName": { "type": "string", "description": "Dataset name within the catalog (e.g., 'fixtures')" } } }, "updatedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the dataset reference was updated" } } } --- id: SavedQueryDeleted name: Saved Query Deleted version: 0.0.1 summary: | Recorded when a Data Analyst deletes a saved query that is not required by active dashboards. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user removes a saved query from their workspace. Dependency checks should warn if dashboards rely on this query. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Saved query must exist and be owned by the user - Deletion should be blocked or warned if dashboards depend on this query - Soft delete with restore capability is recommended ## Given-When-Then Given a saved query exists and is not required by active dashboards, When the Data Analyst deletes it, Then Saved Query Deleted is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SavedQueryDeleted", "type": "object", "title": "SavedQueryDeleted", "description": "Recorded when a Data Analyst deletes a saved query that is not required by active dashboards.", "required": ["savedQueryId", "deletedAt"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query being deleted" }, "deletedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the query was deleted" } } } --- id: SavedQueryRenamed name: Saved Query Renamed version: 0.0.1 summary: | Recorded when a Data Analyst renames an existing saved query. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes the display name of a saved query. Rename history can be tracked to show recent aliases in search. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Saved query must exist and be owned by the user - New name must be non-empty - New name should be unique per user ## Given-When-Then Given a saved query exists, When the Data Analyst renames it, Then Saved Query Renamed is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SavedQueryRenamed", "type": "object", "title": "SavedQueryRenamed", "description": "Recorded when a Data Analyst renames an existing saved query.", "required": ["savedQueryId", "newName", "renamedAt"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query being renamed" }, "previousName": { "type": "string", "description": "Previous display name" }, "newName": { "type": "string", "description": "New display name" }, "renamedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the query was renamed" } } } --- id: SavedQuerySqlUpdated name: Saved Query SQL Updated version: 0.0.1 summary: | Recorded when a Data Analyst updates the SQL text of an existing saved query. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user modifies the SQL statement of a saved query. Impact analysis should be performed to identify dashboards using this query. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Saved query must exist and be owned by the user - New SQL text must be non-empty - Changes may break dependent dashboards (warning provided) ## Given-When-Then Given a saved query exists, When the Data Analyst updates its SQL text, Then Saved Query SQL Updated is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SavedQuerySqlUpdated", "type": "object", "title": "SavedQuerySqlUpdated", "description": "Recorded when a Data Analyst updates the SQL text of an existing saved query.", "required": ["savedQueryId", "newSqlText", "updatedAt"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query being updated" }, "previousSqlText": { "type": "string", "description": "Previous SQL text" }, "newSqlText": { "type": "string", "description": "New SQL text" }, "updatedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the SQL was updated" } } } --- id: SessionCreated name: Session Created version: 0.0.1 summary: | Emitted when a new authenticated session is created following successful OAuth callback. producers: - SessionService --- ## Description This event is emitted when an OAuth callback is successfully processed and a new user session is established. The event captures the session identifier, associated user, OAuth provider, initial TTL expiration timestamp, and request metadata for security audit purposes. ## Session metadata The metadata field captures request context at session creation time, enabling security audit capabilities: - IP address and User-Agent for login history review - Geolocation (populated at the boundary layer) for anomaly detection - Comparison with SessionRefreshed metadata to detect location changes during a session ## When emitted Given a user completes OAuth authentication with a supported provider (GitHub, Google), when the SessionService processes the callback and validates the authorization code, then a SessionCreated event is emitted with the new session details. ## Downstream effects - Session projection updated with new active session - User activity timestamp initialized - Session cookie issued to client --- id: SessionExpired name: Session Expired version: 0.0.1 summary: | Emitted when a session's TTL elapses without activity and the session is expired. producers: - SessionService --- ## Description This event is emitted when a session's time-to-live has elapsed without being refreshed. A background process or lazy evaluation detects expired sessions and emits this event. ## When emitted Given an active session exists with a defined expiresAt timestamp, when the current time exceeds expiresAt and no refresh has occurred, then a SessionExpired event is emitted and the session transitions to the Expired state. ## Downstream effects - Session projection marks session as expired - Session can no longer be used for authentication - User must re-authenticate via OAuth to obtain a new session --- id: SessionInvalidated name: Session Invalidated version: 0.0.1 summary: | Emitted when a user explicitly logs out and their session is invalidated. producers: - SessionService --- ## Description This event is emitted when a user explicitly requests to log out. The session is immediately invalidated and cannot be reused, even if the original TTL has not elapsed. ## When emitted Given an active session exists for a user, when the user initiates a logout action, then a SessionInvalidated event is emitted and the session transitions to the Invalidated state. ## Downstream effects - Session projection marks session as invalidated - Session cookie cleared from client - Any cached session data invalidated --- id: SessionRefreshed name: Session Refreshed version: 0.0.1 summary: | Emitted when an active session's TTL is extended due to user activity. producers: - SessionService --- ## Description This event is emitted when an active session's time-to-live is extended due to detected user activity. Sessions are refreshed on meaningful interactions to prevent premature expiration during active use. ## Session metadata The metadata field captures request context at refresh time, enabling location change detection: - Compare IP address and geolocation against SessionCreated metadata - Detect if session is being used from an unexpected location (potential session hijacking) - Track device changes via User-Agent differences during active sessions ## When emitted Given an active session exists for a user, when the user performs an action that triggers TTL refresh (configurable activity threshold), then a SessionRefreshed event is emitted with the new expiration timestamp. ## Downstream effects - Session projection updated with new expiresAt - Activity metrics updated for session analytics --- id: TabAdded name: Tab Added version: 0.0.1 summary: | Recorded when a Data Analyst adds a new tab to organize charts within a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user adds a new tab to a dashboard for grouping related charts. Tabs provide organizational structure for complex dashboards with many visualizations. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Tab titles should be descriptive and concise - Tabs are ordered by creation time by default - Empty tabs are allowed (charts can be moved in later) ## Given-When-Then Given a dashboard exists, When the Data Analyst adds a new tab with a title, Then Dashboard Tab Added is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DashboardTabAdded", "type": "object", "title": "DashboardTabAdded", "description": "Recorded when a Data Analyst adds a new tab to organize charts within a dashboard.", "required": ["dashboardId", "tabId", "tabTitle", "addedAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard receiving the tab" }, "tabId": { "type": "string", "format": "uuid", "description": "Unique identifier for this tab" }, "tabTitle": { "type": "string", "description": "Display title for the tab" }, "tabOrder": { "type": "integer", "description": "Position in tab order (0-indexed)" }, "addedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the tab was added" } } } --- id: TabRemoved name: Tab Removed version: 0.0.1 summary: | Recorded when a Data Analyst removes a tab from a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user removes a tab from a dashboard. Any charts assigned to the removed tab are also removed from the dashboard. Event payload includes `workspaceId` identifying the containing workspace. ## Business Rules - Removing a tab cascades to remove all charts assigned to that tab - The tab must exist in the dashboard - This operation cannot be undone (charts must be re-added manually) ## Given-When-Then Given a dashboard with one or more tabs, When the Data Analyst removes a tab, Then Tab Removed is recorded and all charts on that tab are removed. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "TabRemoved", "type": "object", "title": "TabRemoved", "description": "Recorded when a Data Analyst removes a tab from a dashboard.", "required": ["dashboardId", "tabId", "removedAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard from which the tab was removed" }, "tabId": { "type": "string", "format": "uuid", "description": "Unique identifier of the removed tab" }, "removedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the tab was removed" } } } --- id: UserPreferencesInitialized name: User Preferences Initialized version: 0.0.1 summary: | Recorded when a new user without stored preferences initializes their preferences. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when preferences are created for a new user. Sensible organization-level defaults are applied during initialization. User-scoped event that applies across all workspaces. ## Business Rules - Each user has exactly one preferences aggregate - Initialization sets default theme and empty UI state - Triggered automatically on first authenticated access ## Given-When-Then Given a new user without stored preferences, When the Data Analyst initializes preferences, Then User Preferences Initialized is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UserPreferencesInitialized", "type": "object", "title": "UserPreferencesInitialized", "description": "Recorded when a new user without stored preferences initializes their preferences.", "required": ["preferencesId", "initializedAt"], "properties": { "preferencesId": { "type": "string", "description": "Unique identifier for the preferences (typically matches user ID)" }, "initializedAt": { "type": "string", "format": "date-time", "description": "Timestamp when preferences were initialized" } } } --- id: UserPreferencesLocaleSet name: User Preferences Locale Set version: 0.0.1 summary: | Recorded when a Data Analyst sets their preferred locale for UI localization. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes their locale preference. Locale changes affect date formats, number formats, and UI translations. User-scoped event that applies across all workspaces. ## Business Rules - Locale must be a valid BCP-47 language tag - Preferences are synced across devices - Default locale is en-US ## Given-When-Then Given user preferences exist, When the Data Analyst sets the locale, Then User Preferences Locale Set is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UserPreferencesLocaleSet", "type": "object", "title": "UserPreferencesLocaleSet", "description": "Recorded when a Data Analyst sets their preferred locale for UI localization.", "required": ["preferencesId", "locale", "setAt"], "properties": { "preferencesId": { "type": "string", "format": "uuid", "description": "Preferences being updated" }, "previousLocale": { "type": "string", "description": "Previous locale value" }, "locale": { "type": "string", "description": "New locale value (BCP-47 language tag)" }, "setAt": { "type": "string", "format": "date-time", "description": "Timestamp when the locale was set" } } } --- id: UserPreferencesThemeSet name: User Preferences Theme Set version: 0.0.1 summary: | Recorded when a Data Analyst sets the theme to Light, Dark, or System. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes their theme preference. Theme changes are applied instantly without requiring page reload. User-scoped event that applies across all workspaces. ## Business Rules - Theme must be one of: Light, Dark, System - Preferences are synced across devices - System theme follows OS preference ## Given-When-Then Given user preferences exist, When the Data Analyst sets the theme to Light, Dark, or System, Then User Preferences Theme Set is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UserPreferencesThemeSet", "type": "object", "title": "UserPreferencesThemeSet", "description": "Recorded when a Data Analyst sets the theme to Light, Dark, or System.", "required": ["preferencesId", "theme", "setAt"], "properties": { "preferencesId": { "type": "string", "format": "uuid", "description": "Preferences being updated" }, "previousTheme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "Previous theme value" }, "theme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "New theme value" }, "setAt": { "type": "string", "format": "date-time", "description": "Timestamp when the theme was set" } } } --- id: UserPreferencesUiStateUpdated name: User Preferences UI State Updated version: 0.0.1 summary: | Recorded when a Data Analyst updates stored UI state JSON (e.g., panel sizes, collapsed sections). owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when UI state changes are persisted. UI state includes panel dimensions, collapsed sections, and other layout preferences. User-scoped event that applies across all workspaces. ## Business Rules - UI state must be valid JSON - Stale keys should be pruned periodically - State is versioned for partial restores ## Given-When-Then Given preferences exist, When the Data Analyst updates stored UI state JSON (e.g., panel sizes, collapsed sections), Then User Preferences UI State Updated is recorded. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UserPreferencesUiStateUpdated", "type": "object", "title": "UserPreferencesUiStateUpdated", "description": "Recorded when a Data Analyst updates stored UI state JSON (e.g., panel sizes, collapsed sections).", "required": ["preferencesId", "uiStateJson", "updatedAt"], "properties": { "preferencesId": { "type": "string", "format": "uuid", "description": "Preferences being updated" }, "uiStateJson": { "type": "string", "description": "Complete UI state as JSON blob" }, "stateVersion": { "type": "integer", "description": "Version number for state schema" }, "updatedAt": { "type": "string", "format": "date-time", "description": "Timestamp when UI state was updated" } } } --- id: VisibilityChanged name: Visibility Changed version: 0.0.1 summary: | Recorded when a workspace's visibility setting is changed. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes the visibility of an existing workspace. The event captures the new visibility setting and timestamp. ## Visibility Semantics - **Public**: Visible to all authenticated users - **Private**: Visible only to owner (and explicitly shared users in future) ## Business Rules - Workspace must exist before changing visibility - Visibility transitions in either direction are valid (Public to Private, Private to Public) - Timestamp is captured at the boundary ## Given-When-Then Given a workspace exists, When the user changes the visibility setting, Then VisibilityChanged is recorded with the new visibility. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "VisibilityChanged", "type": "object", "title": "VisibilityChanged", "description": "Recorded when a workspace's visibility setting is changed.", "required": ["visibility", "changedAt"], "properties": { "visibility": { "type": "string", "enum": ["Public", "Private"], "description": "New visibility setting" }, "changedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the visibility was changed" } } } --- id: WorkspaceCreated name: Workspace Created version: 0.0.1 summary: | Recorded when a new workspace is created with owner, name, and visibility settings. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user creates a new workspace to organize dashboards, saved queries, and preferences. The event captures the workspace identity, owner, name, and initial visibility setting. ## Business Rules - WorkspaceId is generated at the boundary and becomes immutable - OwnerId is optional (null for anonymous workspaces) - Visibility defaults based on command input (no implicit default) - Timestamp is captured at the boundary ## Given-When-Then Given no workspace exists for this aggregate, When the user creates a workspace with name and visibility, Then WorkspaceCreated is recorded with new workspace identity. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "WorkspaceCreated", "type": "object", "title": "WorkspaceCreated", "description": "Recorded when a new workspace is created with owner, name, and visibility settings.", "required": ["workspaceId", "name", "visibility", "createdAt"], "properties": { "workspaceId": { "type": "string", "format": "uuid", "description": "Unique identifier for the workspace" }, "name": { "type": "string", "description": "Display name for the workspace" }, "ownerId": { "type": ["string", "null"], "format": "uuid", "description": "Owner's user ID (null for anonymous workspaces)" }, "visibility": { "type": "string", "enum": ["Public", "Private"], "description": "Initial visibility setting" }, "createdAt": { "type": "string", "format": "date-time", "description": "Timestamp when the workspace was created" } } } --- id: WorkspaceDefaultCatalogCleared name: WorkspaceDefaultCatalogCleared version: 0.1.0 summary: Default catalog selection has been removed --- Indicates that the workspace no longer has a default catalog configured. Workspace members will need to explicitly select a catalog when creating new queries. ## Schema | Field | Type | Description | |-------|------|-------------| | timestamp | Timestamp | When the catalog was cleared | --- id: WorkspaceDefaultCatalogSet name: WorkspaceDefaultCatalogSet version: 0.1.0 summary: Default catalog has been set for the workspace --- Indicates that the default DuckDB catalog for the workspace has been configured. Workspace members will use this catalog by default when creating new queries. ## Schema | Field | Type | Description | |-------|------|-------------| | catalogName | CatalogName | The catalog set as default | | timestamp | Timestamp | When the catalog was set | ## Notes The catalog name references a catalog registered in DuckDB (e.g., "ducklake:hf://datasets/sciexp"). Catalog accessibility is validated at the boundary layer before the command is accepted. --- id: WorkspacePreferencesInitialized name: WorkspacePreferencesInitialized version: 0.1.0 summary: Workspace preferences aggregate has been created --- Indicates that the WorkspacePreferences aggregate has been initialized for a workspace. This event is emitted once per workspace when preferences are first created. ## Schema | Field | Type | Description | |-------|------|-------------| | preferencesId | WorkspacePreferencesId | Unique identifier for this preferences record | | workspaceId | WorkspaceId | The workspace these preferences belong to | | timestamp | Timestamp | When the preferences were initialized | ## Invariants established - The workspace now has exactly one preferences record - The workspaceId is immutable after initialization --- id: WorkspaceRenamed name: Workspace Renamed version: 0.0.1 summary: | Recorded when a workspace's display name is changed. owners: - engineering schemaPath: schema.json --- ## Description This event is emitted when a user changes the display name of an existing workspace. The event captures only the new name and timestamp, as workspace identity remains immutable. ## Business Rules - Workspace must exist before renaming - New name replaces the previous name entirely - Timestamp is captured at the boundary ## Given-When-Then Given a workspace exists, When the user renames the workspace, Then WorkspaceRenamed is recorded with the new name. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "WorkspaceRenamed", "type": "object", "title": "WorkspaceRenamed", "description": "Recorded when a workspace's display name is changed.", "required": ["name", "renamedAt"], "properties": { "name": { "type": "string", "description": "New display name for the workspace" }, "renamedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the workspace was renamed" } } } --- id: AddChart name: Add Chart version: 0.0.1 summary: | Command to add a chart visualization to an existing dashboard with position and size. owners: - engineering schemaPath: schema.json --- ## Description This command adds a chart from a ChartDefinition to a dashboard at specified grid coordinates. The chart can optionally be placed within a specific tab. ## Validation Rules - Dashboard must exist and be owned by the user - ChartDefinitionId must reference a valid Analytics ChartDefinition - Grid position must not overlap with existing charts - If tabId is specified, the tab must exist in the dashboard ## Emits - `DashboardChartAdded` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "AddChart", "type": "object", "title": "AddChart", "description": "Command to add a chart visualization to an existing dashboard with position and size.", "required": [ "dashboardId", "chartDefinitionId", "gridX", "gridY", "gridWidth", "gridHeight" ], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard to add the chart to" }, "chartDefinitionId": { "type": "string", "format": "uuid", "description": "Reference to the ChartDefinition from Analytics" }, "tabId": { "type": "string", "format": "uuid", "description": "Optional tab to place the chart within" }, "gridX": { "type": "integer", "description": "Column position on the grid (0-indexed)" }, "gridY": { "type": "integer", "description": "Row position on the grid (0-indexed)" }, "gridWidth": { "type": "integer", "description": "Width in grid units" }, "gridHeight": { "type": "integer", "description": "Height in grid units" }, "chartConfig": { "type": "string", "description": "Optional chart-specific configuration as JSON blob" } } } --- id: AddTab name: Add Tab version: 0.0.1 summary: | Command to add a new tab to a dashboard for grouping related charts. owners: - engineering schemaPath: schema.json --- ## Description This command creates a new tab within a dashboard to organize charts into logical groups. Charts can be moved to the tab after creation. ## Validation Rules - Dashboard must exist and be owned by the user - Tab title must be non-empty - Tab titles should be unique within the dashboard (warning if duplicate) ## Emits - `DashboardTabAdded` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "AddTab", "type": "object", "title": "AddTab", "description": "Command to add a new tab to a dashboard for grouping related charts.", "required": ["dashboardId", "tabTitle"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard to add the tab to" }, "tabTitle": { "type": "string", "description": "Display title for the new tab" } } } --- id: CancelQuery name: Cancel Query version: 0.0.1 summary: | Cancels a running query execution. --- ## Description This command requests cancellation of an in-progress query. The query execution is terminated and resources are released. ## Preconditions - The query must be in Running state - The requesting user must have permission to cancel the query ## Postconditions - On success: `QuerySessionCancelled` event is raised - On failure: Command is rejected if query is not running or not found --- id: ClearWorkspaceDefaultCatalog name: ClearWorkspaceDefaultCatalog version: 0.1.0 summary: Removes the default catalog selection from workspace preferences --- Clears the default catalog setting, leaving workspace members to explicitly select a catalog for new queries. ## Preconditions - Workspace preferences must be initialized ## Schema This command has no fields (operates on the current workspace context). ## Behavior On success, emits `WorkspaceDefaultCatalogCleared`. Fails if workspace preferences have not been initialized. --- id: CreateDashboard name: Create Dashboard version: 0.0.1 summary: | Command to create a new dashboard with a name for organizing and sharing insights. owners: - engineering schemaPath: schema.json --- ## Description This command initiates the creation of a new dashboard. The authenticated user becomes the owner with initial empty layout. ## Validation Rules - User must have an active session - Dashboard name must be non-empty - Name should be unique per owner (warning if duplicate) ## Emits - `DashboardCreated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "CreateDashboard", "type": "object", "title": "CreateDashboard", "description": "Command to create a new dashboard with a name for organizing and sharing insights.", "required": ["title"], "properties": { "title": { "type": "string", "description": "Display title for the new dashboard" } } } --- id: CreateSession name: Create Session version: 0.0.1 summary: | Command to create a new authenticated session after successful OAuth callback. --- ## Description This command initiates the creation of a new user session following a successful OAuth authentication flow. It validates the OAuth callback data and, if valid, produces a SessionCreated event. ## Preconditions - Valid OAuth authorization code received from provider - User profile successfully retrieved from OAuth provider - No conflicting active session exists (or existing session will be replaced) ## Command handler behavior The Decider's `decide` function validates: 1. OAuth callback data is complete and unexpired 2. State token matches expected value (CSRF protection) 3. Provider is supported (GitHub or Google) If validation passes, emits SessionCreated event. If validation fails, returns appropriate error without emitting events. --- id: CreateWorkspace name: Create Workspace version: 0.0.1 summary: | Command to create a new workspace with owner, name, and visibility settings. owners: - engineering schemaPath: schema.json --- ## Description This command initiates the creation of a new workspace. The workspace serves as an organizational container for dashboards, saved queries, and preferences. ## Validation Rules - No existing workspace may exist for this aggregate - Workspace name must be non-empty - Visibility must be either Public or Private - OwnerId is optional (anonymous workspaces for unauthenticated exploration) ## Emits - `WorkspaceCreated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "CreateWorkspace", "type": "object", "title": "CreateWorkspace", "description": "Command to create a new workspace with owner, name, and visibility settings.", "required": ["name", "visibility"], "properties": { "name": { "type": "string", "description": "Display name for the workspace" }, "ownerId": { "type": ["string", "null"], "format": "uuid", "description": "Optional owner's user ID (null for anonymous workspaces)" }, "visibility": { "type": "string", "enum": ["Public", "Private"], "description": "Workspace visibility setting" } } } --- id: DeleteSavedQuery name: Delete Saved Query version: 0.0.1 summary: | Command to delete an obsolete saved query from the workspace. owners: - engineering schemaPath: schema.json --- ## Description This command removes a saved query from the user's workspace. Dependency checks prevent breaking dashboards. ## Validation Rules - Saved query must exist and be owned by the user - Deletion should warn or block if dashboards depend on this query - Soft delete with restore is recommended ## Emits - `SavedQueryDeleted` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "DeleteSavedQuery", "type": "object", "title": "DeleteSavedQuery", "description": "Command to delete an obsolete saved query from the workspace.", "required": ["savedQueryId"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query to delete" } } } --- id: ExpireSession name: Expire Session version: 0.0.1 summary: | Command to mark a session as expired when its TTL has elapsed without activity. --- ## Description This command transitions a session to the Expired state when its time-to-live has elapsed. Typically invoked by a background cleanup process or lazily when an expired session is accessed. ## Preconditions - Session exists and is in Active state - Current time exceeds the session's expiresAt timestamp ## Command handler behavior The Decider's `decide` function validates: 1. Session ID exists in current state 2. Session is Active (not already Expired or Invalidated) 3. Current time exceeds expiresAt If validation passes, emits SessionExpired event. If session is already terminated, operation is idempotent. --- id: InitializePreferences name: Initialize Preferences version: 0.0.1 summary: | Command to initialize preferences for a new user with sensible defaults. owners: - engineering schemaPath: schema.json --- ## Description This command creates the preferences aggregate for a new user. Typically triggered automatically on first authenticated access. ## Validation Rules - User must have an active session - Preferences must not already exist for this user - Default theme is System ## Emits - `UserPreferencesInitialized` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "InitializePreferences", "type": "object", "title": "InitializePreferences", "description": "Command to initialize preferences for a new user with sensible defaults.", "properties": { "theme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "Optional initial theme (defaults to System)" }, "defaultCatalogUri": { "type": "string", "format": "uri", "description": "Optional initial default catalog" } } } --- id: InitializeWorkspacePreferences name: InitializeWorkspacePreferences version: 0.1.0 summary: Creates default preferences for a new workspace --- Initializes the WorkspacePreferences aggregate for a newly created workspace. This command should be dispatched when a workspace is created to establish workspace-scoped settings. ## Preconditions - Workspace preferences must not already exist for this workspace ## Schema | Field | Type | Description | |-------|------|-------------| | workspaceId | WorkspaceId | The workspace to initialize preferences for | ## Behavior On success, emits `WorkspacePreferencesInitialized` with a generated `WorkspacePreferencesId`. Fails if preferences already exist for the workspace. --- id: InvalidateSession name: Invalidate Session version: 0.0.1 summary: | Command to explicitly invalidate a session when a user logs out. --- ## Description This command invalidates an active session, typically triggered by a user logout action. Once invalidated, the session cannot be reused even if the original TTL has not elapsed. ## Preconditions - Session exists and is in Active state - Request originates from the session owner or an authorized admin ## Command handler behavior The Decider's `decide` function validates: 1. Session ID exists in current state 2. Session is Active (already expired/invalidated sessions are no-ops) 3. Requester is authorized to invalidate the session If validation passes, emits SessionInvalidated event. Invalidating an already-terminated session is idempotent (no error, no event). --- id: MoveChartToTab name: Move Chart To Tab version: 0.0.1 summary: | Command to move a chart from one tab to another within a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This command reorganizes dashboard layout by moving a chart between tabs. Optional grid coordinates allow repositioning during the move. ## Validation Rules - Dashboard must exist and be owned by the user - Chart must exist in the dashboard - Destination tab must exist in the dashboard - If source and destination are the same, command is rejected ## Emits - `DashboardChartMovedToTab` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "MoveChartToTab", "type": "object", "title": "MoveChartToTab", "description": "Command to move a chart from one tab to another within a dashboard.", "required": ["dashboardId", "chartId", "toTabId"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard containing the chart" }, "chartId": { "type": "string", "format": "uuid", "description": "Chart to move" }, "toTabId": { "type": "string", "format": "uuid", "description": "Destination tab ID" }, "newGridX": { "type": "integer", "description": "Optional new column position in destination tab" }, "newGridY": { "type": "integer", "description": "Optional new row position in destination tab" } } } --- id: RefreshCatalogMetadata name: Refresh Catalog Metadata version: 0.0.1 summary: | Refreshes metadata for the active catalog from the DuckLake source. --- ## Description This command triggers a metadata refresh operation, fetching the current dataset list, table counts, and schema version information from the catalog source. ## Preconditions - A catalog must be currently selected - The catalog source must be reachable ## Postconditions - On success: `CatalogMetadataRefreshed` event is raised with updated metadata - On failure: Command is rejected with connectivity or access error --- id: RefreshSession name: Refresh Session version: 0.0.1 summary: | Command to extend an active session's TTL due to user activity. --- ## Description This command extends the time-to-live of an active session when meaningful user activity is detected. It produces a SessionRefreshed event with the updated expiration timestamp. ## Preconditions - Session exists and is in Active state - Current time is before the session's expiresAt - Activity qualifies for TTL refresh (configurable threshold) ## Command handler behavior The Decider's `decide` function validates: 1. Session ID exists in current state 2. Session is Active (not Expired or Invalidated) 3. Sufficient time has passed since last refresh (debounce) If validation passes, emits SessionRefreshed event with new expiresAt. If session is expired or invalidated, returns error. --- id: RemoveChart name: Remove Chart version: 0.0.1 summary: | Command to remove a chart from a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This command removes a chart placement from a dashboard. The underlying ChartDefinition in Analytics remains unaffected. ## Validation Rules - Dashboard must exist and be owned by the user - Chart must exist in the dashboard ## Emits - `DashboardChartRemoved` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "RemoveChart", "type": "object", "title": "RemoveChart", "description": "Command to remove a chart from a dashboard.", "required": ["dashboardId", "chartId"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard containing the chart" }, "chartId": { "type": "string", "format": "uuid", "description": "Chart to remove" } } } --- id: RemoveTab name: Remove Tab version: 0.0.1 summary: | Command to remove a tab from a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This command removes a tab from a dashboard. The tab must be empty (contain no charts) before removal, or charts must be relocated to another tab first. ## Validation Rules - Dashboard must exist and be owned by the user - Tab must exist within the specified dashboard - Tab must be empty (no charts assigned to it) ## Emits - `TabRemoved` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "RemoveTab", "type": "object", "title": "RemoveTab", "description": "Remove a tab from a dashboard. The tab must be empty (no charts) or charts must be relocated first.", "required": ["dashboardId", "tabId"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "The dashboard containing the tab" }, "tabId": { "type": "string", "format": "uuid", "description": "The tab to remove" } } } --- id: RenameDashboard name: Rename Dashboard version: 0.0.1 summary: | Command to change the display title of a dashboard. owners: - engineering schemaPath: schema.json --- ## Description This command updates the display title of an existing dashboard. The dashboard ID remains stable for external references. ## Validation Rules - Dashboard must exist and be owned by the user - New title must be non-empty - New title should be unique per owner (warning if duplicate) ## Emits - `DashboardRenamed` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "RenameDashboard", "type": "object", "title": "RenameDashboard", "description": "Command to change the display title of a dashboard.", "required": ["dashboardId", "newTitle"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard to rename" }, "newTitle": { "type": "string", "description": "New display title" } } } --- id: RenameSavedQuery name: Rename Saved Query version: 0.0.1 summary: | Command to change the display name of an existing saved query. owners: - engineering schemaPath: schema.json --- ## Description This command updates the name of an existing saved query. Recent aliases are tracked for discoverability. ## Validation Rules - Saved query must exist and be owned by the user - New name must be non-empty - New name should be unique per user ## Emits - `SavedQueryRenamed` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "RenameSavedQuery", "type": "object", "title": "RenameSavedQuery", "description": "Command to change the display name of an existing saved query.", "required": ["savedQueryId", "newName"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query to rename" }, "newName": { "type": "string", "description": "New display name" } } } --- id: RenameWorkspace name: Rename Workspace version: 0.0.1 summary: | Command to change the display name of an existing workspace. owners: - engineering schemaPath: schema.json --- ## Description This command changes the display name of an existing workspace. The workspace must already exist for this command to succeed. ## Validation Rules - Workspace must exist (workspaceId is set) - New name must be non-empty - Caller must have permission to modify workspace (future: owner or admin) ## Emits - `WorkspaceRenamed` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "RenameWorkspace", "type": "object", "title": "RenameWorkspace", "description": "Command to change the display name of an existing workspace.", "required": ["name"], "properties": { "name": { "type": "string", "description": "New display name for the workspace" } } } --- id: SaveQuery name: Save Query version: 0.0.1 summary: | Command to save a new query with a name, SQL text, and dataset reference. owners: - engineering schemaPath: schema.json --- ## Description This command creates a new saved query in the user's workspace. The authenticated user becomes the owner. ## Validation Rules - User must have an active session - Name must be non-empty and unique per user - SQL text must be non-empty - Dataset reference must be a valid URI ## Emits - `SavedQueryCreated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SaveQuery", "type": "object", "title": "SaveQuery", "description": "Command to save a new query with a name, SQL text, and dataset reference.", "required": ["name", "sqlText", "datasetRef"], "properties": { "name": { "type": "string", "description": "Display name for the saved query" }, "sqlText": { "type": "string", "description": "The SQL query text" }, "datasetRef": { "type": "string", "format": "uri", "description": "URI reference to the target dataset" } } } --- id: SelectCatalog name: Select Catalog version: 0.0.1 summary: | Selects a DuckLake catalog as the active context for query operations. --- ## Description This command initiates catalog selection, making the specified catalog the active context for all subsequent query operations. ## Preconditions - The catalog URI must reference a valid DuckLake catalog - The user must have access permissions to the catalog ## Postconditions - On success: `CatalogSelected` event is raised - On failure: Command is rejected with validation error --- id: SetLocale name: Set Locale version: 0.0.1 summary: | Command to set the user's preferred locale for UI localization. owners: - engineering schemaPath: schema.json --- ## Description This command sets the user's preferred locale using BCP-47 language tags (e.g., "en-US", "de-DE"). The locale affects date formats, number formats, and UI translations. ## Validation Rules - Preferences must exist for this user - Locale must be a valid BCP-47 language tag ## Emits - `UserPreferencesLocaleSet` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SetLocale", "type": "object", "title": "SetLocale", "description": "Command to set the user's preferred locale for UI localization.", "required": ["locale"], "properties": { "locale": { "type": "string", "description": "BCP-47 language tag (e.g., 'en-US', 'de-DE')" } } } --- id: SetTheme name: Set Theme version: 0.0.1 summary: | Command to change the user's theme preference to Light, Dark, or System. owners: - engineering schemaPath: schema.json --- ## Description This command updates the user's theme preference. Changes are applied instantly without page reload. ## Validation Rules - Preferences must exist for this user - Theme must be one of: Light, Dark, System ## Emits - `UserPreferencesThemeSet` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SetTheme", "type": "object", "title": "SetTheme", "description": "Command to change the user's theme preference to Light, Dark, or System.", "required": ["theme"], "properties": { "theme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "New theme preference" } } } --- id: SetVisibility name: Set Visibility version: 0.0.1 summary: | Command to change the visibility setting of an existing workspace. owners: - engineering schemaPath: schema.json --- ## Description This command changes the visibility of an existing workspace between Public and Private. The workspace must already exist for this command to succeed. ## Validation Rules - Workspace must exist (workspaceId is set) - Visibility must be either Public or Private - Caller must have permission to modify workspace (future: owner or admin) ## Visibility Semantics - **Public**: Visible to all authenticated users - **Private**: Visible only to owner (and explicitly shared users in future) ## Emits - `VisibilityChanged` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "SetVisibility", "type": "object", "title": "SetVisibility", "description": "Command to change the visibility setting of an existing workspace.", "required": ["visibility"], "properties": { "visibility": { "type": "string", "enum": ["Public", "Private"], "description": "New visibility setting for the workspace" } } } --- id: SetWorkspaceDefaultCatalog name: SetWorkspaceDefaultCatalog version: 0.1.0 summary: Sets the default catalog for the workspace --- Sets the default DuckDB catalog that workspace members will use by default when creating new queries. The catalog name references a catalog registered in DuckDB (e.g., "ducklake:hf://datasets/sciexp"). ## Preconditions - Workspace preferences must be initialized - Catalog validation is deferred to boundary layer ## Schema | Field | Type | Description | |-------|------|-------------| | catalogName | CatalogName | The catalog to set as default | ## Behavior On success, emits `WorkspaceDefaultCatalogSet`. Fails if workspace preferences have not been initialized. --- id: StartQuery name: Start Query version: 0.0.1 summary: | Initiates SQL query execution against a DuckLake dataset. --- ## Description This command starts a new query execution session with the provided SQL and target dataset. The query is validated and then executed asynchronously. ## Preconditions - A catalog must be currently selected - The dataset reference must be valid within the selected catalog - The SQL must be syntactically valid ## Postconditions - On success: `QuerySessionStarted` event is raised and query execution begins - On failure: Command is rejected with validation error --- id: UpdateDatasetReference name: Update Dataset Reference version: 0.0.1 summary: | Command to retarget a saved query to a different dataset. owners: - engineering schemaPath: schema.json --- ## Description This command changes the dataset reference of a saved query. Schema compatibility should be validated when changing datasets. ## Validation Rules - Saved query must exist and be owned by the user - New dataset reference must be a valid URI - Schema diff should be computed if possible ## Emits - `SavedQueryDatasetRefUpdated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UpdateDatasetReference", "type": "object", "title": "UpdateDatasetReference", "description": "Command to retarget a saved query to a different dataset.", "required": ["savedQueryId", "newDatasetRef"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query to update" }, "newDatasetRef": { "type": "string", "format": "uri", "description": "New dataset URI" } } } --- id: UpdateLayoutDefaults name: UpdateLayoutDefaults version: 0.1.0 summary: Persists default layout settings for new dashboard creation --- Updates the layout defaults JSON blob that will be used when workspace members create new dashboards. JSON validation is enforced at the boundary layer before this command is dispatched. ## Preconditions - Workspace preferences must be initialized - JSON blob must be valid (enforced at boundary) ## Schema | Field | Type | Description | |-------|------|-------------| | layoutDefaults | String | JSON blob containing layout defaults | ## Behavior On success, emits `LayoutDefaultsUpdated`. Fails if workspace preferences have not been initialized. --- id: UpdateSavedQuerySql name: Update Saved Query SQL version: 0.0.1 summary: | Command to update the SQL text of an existing saved query. owners: - engineering schemaPath: schema.json --- ## Description This command modifies the SQL statement of a saved query. Impact analysis identifies dependent dashboards. ## Validation Rules - Saved query must exist and be owned by the user - New SQL text must be non-empty - Dependent dashboards should be warned ## Emits - `SavedQuerySqlUpdated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UpdateSavedQuerySql", "type": "object", "title": "UpdateSavedQuerySql", "description": "Command to update the SQL text of an existing saved query.", "required": ["savedQueryId", "newSqlText"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query to update" }, "newSqlText": { "type": "string", "description": "New SQL text" } } } --- id: UpdateUiState name: Update UI State version: 0.0.1 summary: | Command to persist UI state changes (panel sizes, collapsed sections, etc.). owners: - engineering schemaPath: schema.json --- ## Description This command saves UI layout preferences. State includes panel dimensions, collapsed sections, and other layout settings. ## Validation Rules - Preferences must exist for this user - UI state must be valid JSON - State size limits are enforced ## Emits - `UserPreferencesUiStateUpdated` on success ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "UpdateUiState", "type": "object", "title": "UpdateUiState", "description": "Command to persist UI state changes (panel sizes, collapsed sections, etc.).", "required": ["uiStateJson"], "properties": { "uiStateJson": { "type": "string", "description": "Complete UI state as JSON blob" } } } --- id: DatasetBrowserView name: Dataset Browser View version: 0.0.1 summary: | Returns dataset structure for browsing tables and columns. --- ## Description This query provides the dataset structure needed for the SQL query builder, including table names, column metadata, and sample data. ## Use Cases - Populating dataset browser tree view - Providing autocomplete suggestions in SQL editor --- id: GetActiveSession name: Get Active Session version: 0.0.1 summary: | Query to retrieve the current active session for a user. --- ## Description This query returns the active session for a given user or session cookie. Used for authentication checks and session context retrieval. ## Query parameters - `sessionId`: The session identifier from the session cookie - Or `userId`: The user identifier to find their active session ## Response Returns the full session details including user information, provider, and expiration. Returns null/error if no active session exists. ## Read model The query reads from the session projection, filtering for sessions in Active status with expiresAt in the future. --- id: GetCatalogDetails name: Get Catalog Details version: 0.0.1 summary: | Returns detailed metadata for the selected catalog including dataset list. --- ## Description This query retrieves comprehensive metadata for a specific catalog, including all datasets, table counts, and schema information. ## Use Cases - Displaying catalog detail view - Populating dataset browser after catalog selection --- id: GetCurrentUiState name: Get Current UI State version: 0.0.1 summary: | Query to retrieve the user's persisted UI state for layout restoration. owners: - engineering schemaPath: schema.json --- ## Description This query returns the stored UI state JSON for the authenticated user. Used to restore panel sizes, collapsed sections, and other layout preferences. ## Read Model Built from `UserPreferencesUiStateUpdated` events. Returns the most recent state version. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetCurrentUiState", "type": "object", "title": "GetCurrentUiState", "description": "Query to retrieve the user's persisted UI state for layout restoration.", "properties": { "response": { "type": "object", "properties": { "uiStateJson": { "type": "string", "description": "Complete UI state as JSON blob" }, "stateVersion": { "type": "integer", "description": "Version number for state schema" }, "lastUpdatedAt": { "type": "string", "format": "date-time", "description": "When UI state was last updated" } } } } } --- id: GetDashboardDetails name: Get Dashboard Details version: 0.0.1 summary: | Query to retrieve metadata and summary information for a specific dashboard. owners: - engineering schemaPath: schema.json --- ## Description This query returns dashboard metadata including title, owner, creation date, and summary statistics. Used for dashboard list items and detail views. ## Read Model Built from `DashboardCreated`, `DashboardRenamed`, and aggregate chart/tab counts from related events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetDashboardDetails", "type": "object", "title": "GetDashboardDetails", "description": "Query to retrieve metadata and summary information for a specific dashboard.", "required": ["dashboardId"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard to retrieve details for" }, "response": { "type": "object", "required": ["dashboardId", "title", "ownerId", "createdAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard identifier" }, "title": { "type": "string", "description": "Current display title" }, "ownerId": { "type": "string", "format": "uuid", "description": "User ID of the dashboard owner" }, "chartCount": { "type": "integer", "description": "Number of charts in the dashboard" }, "tabCount": { "type": "integer", "description": "Number of tabs in the dashboard" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the dashboard was created" }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When the dashboard was last modified" } } } } } --- id: GetDashboardLayout name: Get Dashboard Layout version: 0.0.1 summary: | Query to retrieve the complete layout configuration of a dashboard including charts and tabs. owners: - engineering schemaPath: schema.json --- ## Description This query returns the full layout state of a specific dashboard including all chart placements, tab organization, and grid positions. Used to render the dashboard editor and viewer. ## Read Model Built from `DashboardCreated`, `DashboardChartAdded`, `DashboardTabAdded`, `DashboardChartMovedToTab`, and `DashboardChartRemoved` events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetDashboardLayout", "type": "object", "title": "GetDashboardLayout", "description": "Query to retrieve the complete layout configuration of a dashboard including charts and tabs.", "required": ["dashboardId"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard to retrieve layout for" }, "response": { "type": "object", "required": ["dashboardId", "title", "tabs", "charts"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Dashboard identifier" }, "title": { "type": "string", "description": "Current display title" }, "tabs": { "type": "array", "items": { "type": "object", "required": ["tabId", "tabTitle", "tabOrder"], "properties": { "tabId": { "type": "string", "format": "uuid", "description": "Tab identifier" }, "tabTitle": { "type": "string", "description": "Tab display title" }, "tabOrder": { "type": "integer", "description": "Position in tab order" } } } }, "charts": { "type": "array", "items": { "type": "object", "required": [ "chartId", "chartDefinitionId", "gridX", "gridY", "gridWidth", "gridHeight" ], "properties": { "chartId": { "type": "string", "format": "uuid", "description": "Chart placement identifier" }, "chartDefinitionId": { "type": "string", "format": "uuid", "description": "Reference to Analytics ChartDefinition" }, "tabId": { "type": "string", "format": "uuid", "description": "Tab containing this chart (null for default view)" }, "gridX": { "type": "integer", "description": "Column position" }, "gridY": { "type": "integer", "description": "Row position" }, "gridWidth": { "type": "integer", "description": "Width in grid units" }, "gridHeight": { "type": "integer", "description": "Height in grid units" }, "chartConfig": { "type": "string", "description": "Chart-specific configuration JSON" } } } } } } } } --- id: GetPreferences name: Get Preferences version: 0.0.1 summary: | Query to retrieve the current user's preferences including theme and default catalog. owners: - engineering schemaPath: schema.json --- ## Description This query returns all preference settings for the authenticated user. Used for settings pages and theme initialization. ## Read Model Built from `UserPreferencesInitialized`, `UserPreferencesThemeSet`, `UserPreferencesDefaultCatalogSet`, and `UserPreferencesDefaultCleared` events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetPreferences", "type": "object", "title": "GetPreferences", "description": "Query to retrieve the current user's preferences including theme and default catalog.", "properties": { "response": { "type": "object", "required": ["preferencesId", "theme"], "properties": { "preferencesId": { "type": "string", "format": "uuid", "description": "Preferences identifier" }, "theme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "Current theme preference" }, "defaultCatalogUri": { "type": "string", "format": "uri", "description": "Default catalog URI if set" }, "defaultCatalogName": { "type": "string", "description": "Human-readable name of default catalog" }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When preferences were last modified" } } } } } --- id: GetQueryEditorState name: Get Query Editor State version: 0.0.1 summary: | Query to retrieve the current state of the query editor including active query and dataset. owners: - engineering schemaPath: schema.json --- ## Description This query returns the current query editor state for the authenticated user. Used to restore editor state when returning to the query builder. ## Read Model Built from query execution history and current editor buffer state. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetQueryEditorState", "type": "object", "title": "GetQueryEditorState", "description": "Query to retrieve the current state of the query editor including active query and dataset.", "properties": { "response": { "type": "object", "properties": { "currentSqlText": { "type": "string", "description": "Current SQL in the editor buffer" }, "currentDatasetRef": { "type": "string", "format": "uri", "description": "Currently selected dataset" }, "activeSavedQueryId": { "type": "string", "format": "uuid", "description": "ID of saved query being edited, if any" }, "isDirty": { "type": "boolean", "description": "Whether editor has unsaved changes" }, "lastRunAt": { "type": "string", "format": "date-time", "description": "When the query was last executed" } } } } } --- id: GetQueryStatus name: Get Query Status version: 0.0.1 summary: | Returns the final status and results metadata for a completed query. --- ## Description This query retrieves the final state of a query execution after completion, failure, or cancellation. Includes result metadata for completed queries and error details for failed queries. ## Use Cases - Displaying query completion status - Retrieving result metadata before loading full results --- id: GetSavedQueryDetails name: Get Saved Query Details version: 0.0.1 summary: | Query to retrieve full details of a specific saved query. owners: - engineering schemaPath: schema.json --- ## Description This query returns complete information about a saved query including SQL, dataset, and metadata. Used when loading a saved query into the editor. ## Read Model Built from `SavedQueryCreated`, `SavedQueryRenamed`, `SavedQuerySqlUpdated`, and `SavedQueryDatasetRefUpdated` events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetSavedQueryDetails", "type": "object", "title": "GetSavedQueryDetails", "description": "Query to retrieve full details of a specific saved query.", "required": ["savedQueryId"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query to retrieve" }, "response": { "type": "object", "required": [ "savedQueryId", "name", "sqlText", "datasetRef", "ownerId", "createdAt" ], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query identifier" }, "name": { "type": "string", "description": "Current display name" }, "sqlText": { "type": "string", "description": "Current SQL text" }, "datasetRef": { "type": "string", "format": "uri", "description": "Current dataset URI" }, "ownerId": { "type": "string", "format": "uuid", "description": "User ID of the owner" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the query was created" }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When the query was last modified" }, "usageCount": { "type": "integer", "description": "Number of times the query has been executed" } } } } } --- id: GetSessionTTL name: Get Session TTL version: 0.0.1 summary: | Query to retrieve the remaining time-to-live for an active session. --- ## Description This query returns the remaining TTL for a session, used by clients to display session expiration warnings or schedule refresh requests. ## Query parameters - `sessionId`: The session identifier to check ## Response Returns the current expiration timestamp and calculated remaining seconds. Returns an error if the session is not found or already terminated. ## Read model The query reads from the session projection, which is updated by SessionCreated and SessionRefreshed events. --- id: GetUserProfile name: Get User Profile version: 0.0.1 summary: | Query to retrieve user profile information including preferences and authentication details. owners: - engineering schemaPath: schema.json --- ## Description This query returns the user's profile combining identity from Session and preferences from Workspace. Used for displaying user information in the UI header and settings. ## Read Model Built from Session `SessionCreated` events (for identity) and Workspace `UserPreferencesInitialized` events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetUserProfile", "type": "object", "title": "GetUserProfile", "description": "Query to retrieve user profile information including preferences and authentication details.", "properties": { "response": { "type": "object", "required": ["userId", "theme"], "properties": { "userId": { "type": "string", "format": "uuid", "description": "User identifier" }, "displayName": { "type": "string", "description": "User display name from OAuth provider" }, "email": { "type": "string", "format": "email", "description": "User email from OAuth provider" }, "avatarUrl": { "type": "string", "format": "uri", "description": "User avatar URL from OAuth provider" }, "theme": { "type": "string", "enum": ["Light", "Dark", "System"], "description": "Current theme preference" }, "defaultCatalogUri": { "type": "string", "format": "uri", "description": "Default catalog if set" }, "preferencesInitializedAt": { "type": "string", "format": "date-time", "description": "When preferences were initialized" } } } } } --- id: GetWorkspacePreferences name: Get Workspace Preferences version: 0.0.1 summary: | Query to retrieve workspace-scoped preferences including default catalog and layout defaults. owners: - engineering schemaPath: schema.json --- ## Description This query returns all preference settings for a specific workspace. Used for workspace settings pages and dashboard initialization. ## Read Model Built from `WorkspacePreferencesInitialized`, `WorkspacePreferencesDefaultCatalogSet`, `WorkspacePreferencesDefaultCleared`, and `WorkspacePreferencesLayoutDefaultsSet` events. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "GetWorkspacePreferences", "type": "object", "title": "GetWorkspacePreferences", "description": "Retrieve workspace-scoped preferences including default catalog and layout defaults.", "required": ["workspaceId"], "properties": { "workspaceId": { "type": "string", "format": "uuid", "description": "The workspace to retrieve preferences for" }, "response": { "type": "object", "required": ["preferencesId", "workspaceId"], "properties": { "preferencesId": { "type": "string", "format": "uuid", "description": "Preferences identifier" }, "workspaceId": { "type": "string", "format": "uuid", "description": "The workspace these preferences belong to" }, "defaultCatalogUri": { "type": "string", "format": "uri", "description": "Default catalog URI for this workspace if set" }, "defaultCatalogName": { "type": "string", "description": "Human-readable name of default catalog" }, "layoutDefaults": { "type": "object", "description": "Default layout configuration for dashboards in this workspace", "properties": { "columnCount": { "type": "integer", "description": "Default number of columns for dashboard layouts" }, "rowHeight": { "type": "integer", "description": "Default row height in pixels" } } }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When preferences were last modified" } } } } } --- id: ListAvailableCatalogs name: List Available Catalogs version: 0.0.1 summary: | Query to retrieve all catalogs available for selection as the user's default. owners: - engineering schemaPath: schema.json --- ## Description This query returns catalogs that can be set as the user's default. Used for the catalog selection dropdown in preferences. ## Read Model Built from Analytics `CatalogRegistered` events. Filtered by user access permissions. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "ListAvailableCatalogs", "type": "object", "title": "ListAvailableCatalogs", "description": "Query to retrieve all catalogs available for selection as the user's default.", "properties": { "response": { "type": "array", "items": { "type": "object", "required": ["catalogUri", "catalogName"], "properties": { "catalogUri": { "type": "string", "format": "uri", "description": "Catalog URI" }, "catalogName": { "type": "string", "description": "Human-readable catalog name" }, "description": { "type": "string", "description": "Optional catalog description" }, "isHealthy": { "type": "boolean", "description": "Whether the catalog is currently accessible" } } } } } } --- id: ListChartDefinitions name: List Chart Definitions version: 0.0.1 summary: | Query to retrieve available chart definitions that can be added to dashboards. owners: - engineering schemaPath: schema.json --- ## Description This query returns chart definitions from the Analytics bounded context that are available for placement on dashboards. Acts as a Customer-Supplier integration with Analytics as the upstream supplier. ## Read Model Built from Analytics `ChartDefinitionCreated` events. Provides chart metadata without the full visualization configuration. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "ListChartDefinitions", "type": "object", "title": "ListChartDefinitions", "description": "Query to retrieve available chart definitions that can be added to dashboards.", "properties": { "response": { "type": "array", "items": { "type": "object", "required": ["chartDefinitionId", "name", "chartType"], "properties": { "chartDefinitionId": { "type": "string", "format": "uuid", "description": "Unique chart definition identifier" }, "name": { "type": "string", "description": "Display name for the chart" }, "chartType": { "type": "string", "description": "Type of chart (bar, line, pie, etc.)" }, "description": { "type": "string", "description": "Optional description of the chart" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the chart definition was created" } } } } } } --- id: ListDashboards name: List Dashboards version: 0.0.1 summary: | Query to retrieve all dashboards owned by or shared with the current user. owners: - engineering schemaPath: schema.json --- ## Description This query returns a list of dashboards accessible to the authenticated user. Results include owned dashboards and those shared via collaboration. ## Read Model Built from `DashboardCreated` and `DashboardRenamed` events. Filtered by owner ID matching the authenticated user. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "ListDashboards", "type": "object", "title": "ListDashboards", "description": "Query to retrieve all dashboards owned by or shared with the current user.", "properties": { "response": { "type": "array", "items": { "type": "object", "required": ["dashboardId", "title", "ownerId", "createdAt"], "properties": { "dashboardId": { "type": "string", "format": "uuid", "description": "Unique dashboard identifier" }, "title": { "type": "string", "description": "Current display title" }, "ownerId": { "type": "string", "format": "uuid", "description": "User ID of the dashboard owner" }, "chartCount": { "type": "integer", "description": "Number of charts in the dashboard" }, "tabCount": { "type": "integer", "description": "Number of tabs in the dashboard" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the dashboard was created" }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When the dashboard was last modified" } } } } } } --- id: ListIdleSessions name: List Idle Sessions version: 0.0.1 summary: | Query to list sessions approaching expiration due to inactivity. --- ## Description This query returns sessions that are still active but approaching their expiration threshold. Used by background cleanup processes to identify sessions that need expiration. ## Query parameters - `thresholdSeconds`: Return sessions expiring within this many seconds (default: 0 for already expired) - `limit`: Maximum number of sessions to return ## Response Returns a list of session summaries with their expiration timestamps and idle durations. ## Read model The query reads from the session projection, filtering for Active sessions where expiresAt is less than `now + thresholdSeconds`. --- id: ListSavedQueries name: List Saved Queries version: 0.0.1 summary: | Query to retrieve all saved queries owned by the current user. owners: - engineering schemaPath: schema.json --- ## Description This query returns a list of saved queries for the authenticated user. Results include name, dataset, and usage statistics. ## Read Model Built from `SavedQueryCreated`, `SavedQueryRenamed`, and `SavedQueryDeleted` events. Filtered by owner ID matching the authenticated user. ## Raw Schema:schema.json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "ListSavedQueries", "type": "object", "title": "ListSavedQueries", "description": "Query to retrieve all saved queries owned by the current user.", "properties": { "response": { "type": "array", "items": { "type": "object", "required": ["savedQueryId", "name", "datasetRef", "createdAt"], "properties": { "savedQueryId": { "type": "string", "format": "uuid", "description": "Saved query identifier" }, "name": { "type": "string", "description": "Display name" }, "datasetRef": { "type": "string", "format": "uri", "description": "Dataset URI" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the query was created" }, "lastModifiedAt": { "type": "string", "format": "date-time", "description": "When the query was last modified" }, "usageCount": { "type": "integer", "description": "Number of times the query has been executed" } } } } } } --- id: LoadOAuthCallbackData name: Load OAuth Callback Data version: 0.0.1 summary: | Query to retrieve and validate OAuth callback parameters for session creation. --- ## Description This query retrieves the OAuth callback data from a pending authorization flow. It validates the state token against the stored value and returns the authorization code and provider details needed for session creation. ## Query parameters - `stateToken`: The CSRF state token from the callback URL - `provider`: The OAuth provider (GitHub or Google) ## Response Returns the stored OAuth flow data including authorization code, redirect URI, and requested scopes. Returns an error if the state token is invalid, expired, or not found. ## Read model The query reads from a temporary store of pending OAuth flows, keyed by state token with short TTL. --- id: LoadRunningQuerySession name: Load Running Query Session version: 0.0.1 summary: | Returns the current state of a running query session. --- ## Description This query retrieves the current state of a query execution, including elapsed time and progress indicators when available. ## Use Cases - Displaying query execution progress - Resuming UI state after page reload --- id: CatalogService name: Catalog Service version: 0.0.1 summary: | Manages DuckLake catalog selection and metadata synchronization. owners: - engineering sends: - id: CatalogSelected version: 0.0.1 - id: CatalogMetadataRefreshed version: 0.0.1 receives: - id: SelectCatalog version: 0.0.1 - id: RefreshCatalogMetadata version: 0.0.1 --- ## Responsibilities - Select and switch DuckLake catalogs - Refresh catalog metadata (dataset list, schema versions) - Provide catalog context for query execution ## Aggregate: Catalog The Catalog aggregate enforces single active catalog invariant. ### State Machine ``` NoCatalogSelected → CatalogActive(ref, metadata) ``` ### Key Value Objects - **CatalogRef**: URI reference to DuckLake catalog (e.g., `ducklake://hf/sciexp/fixtures`) - **DatasetInfo**: Dataset name, table count, schema version - **CatalogMetadata**: Collection of datasets with refresh timestamp --- id: DashboardService name: Dashboard Service version: 0.0.1 summary: | Manages dashboard layout configuration including chart placements, tabs, and grid positioning. owners: - engineering sends: - id: DashboardCreated version: 0.0.1 - id: ChartAdded version: 0.0.1 - id: ChartRemoved version: 0.0.1 - id: TabAdded version: 0.0.1 - id: TabRemoved version: 0.0.1 - id: ChartMovedToTab version: 0.0.1 - id: DashboardRenamed version: 0.0.1 receives: - id: CreateDashboard version: 0.0.1 - id: RenameDashboard version: 0.0.1 - id: AddTab version: 0.0.1 - id: RemoveTab version: 0.0.1 - id: AddChart version: 0.0.1 - id: RemoveChart version: 0.0.1 - id: MoveChartToTab version: 0.0.1 --- ## Responsibilities - Create and rename dashboards - Add, remove, and position charts on grid - Organize charts into tabs - Persist layout configuration across sessions ## Workspace Context Dashboard operations occur within a Workspace context. All dashboards belong to exactly one Workspace, referenced via `workspaceId`. ## Aggregate: Dashboard The Dashboard aggregate manages UI layout state with grid-based chart positioning. ### Key Value Objects - **DashboardId**: Unique dashboard identifier - **WorkspaceId**: Reference to containing Workspace - **TabId**: Tab identifier within a dashboard - **ChartId**: Chart placement identifier - **ChartDefinitionRef**: Reference to Analytics ChartDefinition (Customer-Supplier) - **GridPosition**: Row/column placement (0-indexed) - **GridSize**: Width/height in grid units - **ChartPlacement**: Associates chart reference with position, size, and optional tab --- id: QuerySessionService name: Query Session Service version: 0.0.1 summary: | Manages analytical query execution lifecycle from start through completion, failure, or cancellation. owners: - engineering sends: - id: QuerySessionStarted version: 0.0.1 - id: QuerySessionCompleted version: 0.0.1 - id: QuerySessionFailed version: 0.0.1 - id: QuerySessionCancelled version: 0.0.1 receives: - id: StartQuery version: 0.0.1 - id: CancelQuery version: 0.0.1 --- ## Responsibilities - Execute SQL queries against DuckLake datasets via DuckDB - Track query execution state and timing - Handle query cancellation - Emit completion/failure events with results or error messages ## Aggregate: QuerySession The QuerySession aggregate models query execution as a state machine. ### State Machine ``` Idle → Running(queryId, datasetRef, sql) → Completed(queryId, results) | Failed(queryId, error) ↓ (cancel) Idle ``` ### Key Value Objects - **QueryId**: Unique identifier for query execution - **DatasetRef**: Reference to catalog + dataset (e.g., `hf://datasets/sciexp/fixtures/experiment_results`) - **SqlQuery**: The SQL string to execute - **QueryResults**: Row count, column names (results cached separately) - **ErrorMessage**: Execution error details --- id: SavedQueryService name: Saved Query Service version: 0.0.1 summary: | Manages user-defined reusable SQL queries with dataset references. owners: - engineering sends: - id: SavedQueryCreated version: 0.0.1 - id: SavedQueryRenamed version: 0.0.1 - id: SavedQuerySqlUpdated version: 0.0.1 - id: SavedQueryDeleted version: 0.0.1 - id: SavedQueryDatasetRefUpdated version: 0.0.1 receives: - id: SaveQuery version: 0.0.1 - id: RenameSavedQuery version: 0.0.1 - id: UpdateSavedQuerySql version: 0.0.1 - id: DeleteSavedQuery version: 0.0.1 - id: UpdateDatasetReference version: 0.0.1 --- ## Responsibilities - Save new queries with name, SQL, and dataset reference - Rename, update SQL, or change dataset reference - Delete saved queries ## Workspace Context SavedQuery operations occur within a Workspace context. All saved queries belong to exactly one Workspace, referenced via `workspaceId`. ## Aggregate: SavedQuery One aggregate instance per saved query (keyed by workspace_id/user_id/query_name). ### State Machine ``` NoQuery → QueryExists(id, name, sql, datasetRef) ↑___________________________| (delete) ``` ### Key Value Objects - **SavedQueryId**: Unique query identifier - **WorkspaceId**: Reference to containing Workspace - **DatasetRef**: URI reference to dataset (hf://, s3://, etc.) ### Invariants - Query names must be non-empty - SQL must be non-empty - Names are unique per user (enforced by aggregate ID) --- id: SessionService name: Session Service version: 0.0.1 summary: | Handles OAuth authentication callbacks and session lifecycle management. owners: - engineering sends: - id: SessionCreated version: 0.0.1 - id: SessionRefreshed version: 0.0.1 - id: SessionExpired version: 0.0.1 - id: SessionInvalidated version: 0.0.1 receives: - id: CreateSession version: 0.0.1 - id: RefreshSession version: 0.0.1 - id: ExpireSession version: 0.0.1 - id: InvalidateSession version: 0.0.1 --- ## Responsibilities - Process OAuth callbacks from GitHub (and future Google) - Create, refresh, and invalidate user sessions - Enforce session TTL expiration - Emit session events for downstream consumers ## Aggregate: Session The Session aggregate follows the Decider pattern from fmodel-rust: - **decide(command, state) -> events**: Pure function validating commands against current state - **evolve(state, event) -> state**: Pure function applying events to produce new state ### State Machine ``` NoSession -> Active -> (Expired | Invalidated) ``` - `NoSession`: Initial state, no authenticated user - `Active(sessionId, userId, expiresAt)`: Authenticated session with TTL - `Expired(sessionId)`: Session TTL elapsed - `Invalidated(sessionId)`: Explicit logout --- id: UserPreferencesService name: User Preferences Service version: 0.0.1 summary: | Manages user-scoped settings (theme, locale, UI state) that apply globally across all workspaces. owners: - engineering sends: - id: UserPreferencesInitialized version: 0.0.1 - id: UserPreferencesThemeSet version: 0.0.1 - id: UserPreferencesLocaleSet version: 0.0.1 - id: UserPreferencesUiStateUpdated version: 0.0.1 receives: - id: InitializePreferences version: 0.0.1 - id: SetTheme version: 0.0.1 - id: SetLocale version: 0.0.1 - id: UpdateUiState version: 0.0.1 --- ## Responsibilities - Initialize preferences for new users - Set theme (Light, Dark, System) - Set locale for internationalization - Store arbitrary UI state as JSON blob See WorkspacePreferencesService for workspace-scoped settings such as default catalog selection. ## Aggregate: UserPreferences One aggregate per authenticated user (keyed by user_id). ### Key Value Objects - **PreferencesId**: Typically matches UserId - **Theme**: Light | Dark | System (enum) - **Locale**: User's preferred locale string (e.g., en-US) ### Invariants - UI state must be valid JSON (enforced at boundary) - Settings apply globally across all workspaces --- id: WorkspacePreferencesService name: Workspace Preferences Service version: 0.0.1 summary: | Manages workspace-scoped settings: default catalog selection and layout defaults for new dashboards. owners: - engineering sends: - id: WorkspacePreferencesInitialized version: 0.0.1 - id: WorkspaceDefaultCatalogSet version: 0.0.1 - id: WorkspaceDefaultCatalogCleared version: 0.0.1 - id: LayoutDefaultsUpdated version: 0.0.1 receives: - id: InitializeWorkspacePreferences version: 0.0.1 - id: SetWorkspaceDefaultCatalog version: 0.0.1 - id: ClearWorkspaceDefaultCatalog version: 0.0.1 - id: UpdateLayoutDefaults version: 0.0.1 --- ## Responsibilities - Initialize preferences when workspace is created - Configure default catalog for queries in workspace - Set layout defaults for new dashboard creation - Clear default catalog selection ## Aggregate: WorkspacePreferences One aggregate per workspace (keyed by workspace_id). ### Commands | Command | Description | |---------|-------------| | InitializeWorkspacePreferences | Create default preferences for a new workspace | | SetDefaultCatalog | Set the default catalog for new queries | | ClearDefaultCatalog | Remove the default catalog selection | | UpdateLayoutDefaults | Persist default layout settings for new dashboards | ### Key Value Objects - **PreferencesId**: Unique identifier for the preferences record - **WorkspaceId**: Reference to the parent workspace - **CatalogId**: Optional reference to default catalog - **LayoutDefaultsJson**: JSON blob with dashboard creation defaults ### Invariants - Layout defaults must be valid JSON (enforced at boundary) - Default catalog must be accessible to workspace members (runtime validation) - Exactly one preferences record per workspace ## Distinction from UserPreferencesService WorkspacePreferencesService manages settings shared by all workspace members, while UserPreferencesService manages personal settings that follow the individual user across workspaces. | Service | Scope | Example Settings | |---------|-------|------------------| | WorkspacePreferencesService | Workspace members | Default catalog, layout defaults | | UserPreferencesService | Individual user | Theme, locale, UI state | --- id: WorkspaceService name: Workspace Service version: 0.0.1 summary: | Manages workspace lifecycle including creation, naming, ownership, and visibility settings. owners: - engineering sends: - id: WorkspaceCreated version: 0.0.1 - id: WorkspaceRenamed version: 0.0.1 - id: VisibilityChanged version: 0.0.1 receives: - id: CreateWorkspace version: 0.0.1 - id: RenameWorkspace version: 0.0.1 - id: SetVisibility version: 0.0.1 --- ## Responsibilities - Create new workspaces with owner and visibility settings - Rename workspaces - Change workspace visibility (Public/Private) - Maintain workspace identity immutability after creation ## Aggregate: Workspace The Workspace aggregate serves as the organizational container for dashboards, saved queries, and preferences. It manages workspace identity, naming, ownership, and visibility settings. ### Key Value Objects - **WorkspaceId**: Unique identifier for a workspace (immutable after creation) - **WorkspaceName**: User-facing display name - **Visibility**: Access control setting (Public or Private) - **UserId**: Reference to owner from SharedKernel ### Invariants - WorkspaceId is immutable after creation - OwnerId references a valid UserId from SharedKernel - Visibility is always a valid Visibility value (Public or Private) ## Visibility Semantics - **Public**: Visible to all authenticated users - **Private**: Visible only to owner (and explicitly shared users in future) --- id: Analytics name: Analytics version: 0.0.1 summary: | Core domain for scientific data analysis. Manages DuckLake catalog selection and query execution with chart visualization. owners: - engineering services: - id: CatalogService version: 0.0.1 - id: QuerySessionService version: 0.0.1 entities: - id: Catalog version: 0.0.1 - id: QuerySession version: 0.0.1 - id: Dataset version: 0.0.1 - id: CatalogRef version: 0.0.1 - id: QueryId version: 0.0.1 - id: SqlQuery version: 0.0.1 - id: DatasetRef version: 0.0.1 - id: QueryResults version: 0.0.1 - id: ErrorMessage version: 0.0.1 - id: ChartConfig version: 0.0.1 - id: ChartType version: 0.0.1 - id: ChartOptions version: 0.0.1 - id: CatalogMetadata version: 0.0.1 - id: DatasetInfo version: 0.0.1 --- ## Overview The Analytics bounded context is the Core Domain of ironstar, enabling data analysts to explore DuckLake catalogs, execute SQL queries against datasets, and visualize results. ## Strategic Classification **Core Domain**: Primary business differentiator providing scientific data analysis capabilities. ## Key Aggregates - **Catalog**: Manages DuckLake catalog selection and metadata refresh - **QuerySession**: Handles query execution lifecycle (start → completed/failed/cancelled) ## Integration Points - Downstream: Workspace consumes QuerySession results for Dashboard charts - Upstream: Session provides authenticated user context ## Zenoh Channel Events published to: `events/analytics/**` --- id: Core name: Core version: 0.0.1 summary: | Shared Kernel containing cross-cutting value objects used across all bounded contexts. owners: - engineering entities: - id: Timestamp version: 0.0.1 - id: Duration version: 0.0.1 --- ## Overview The Core bounded context provides the Shared Kernel: domain-agnostic value objects and algebraic types that establish common vocabulary across all bounded contexts in ironstar. These types embody key event sourcing invariants and are reused throughout Analytics, Workspace, and Session domains. ## Strategic Classification Shared Kernel: Foundation for cross-context communication and state representation. ## Key Value Objects - **Timestamp**: Unix timestamp in milliseconds with total ordering for event replay and temporal queries - **Duration**: Elapsed time in milliseconds for tracking query execution and cache TTLs ## Shared Invariants All Core value objects embody principles from Hoffman's Laws of Event Sourcing: - **Immutability**: Value objects are permanently immutable after construction - **Totality**: Comparison operations and ordering are defined for all instances - **Determinism**: Equal values always produce identical behavior in projections ## Integration Points - Consumed by: All bounded contexts (Analytics, Workspace, Session) - Used in: EventEnvelope metadata (timestamps), QuerySession tracking (duration) - Zenoh channels depend on Timestamp for monotonic ordering --- id: Session name: Session version: 0.0.1 summary: | Supporting domain managing OAuth-based authentication and session lifecycle. Provides authenticated User identity to other bounded contexts via Shared Kernel pattern. owners: - engineering services: - id: SessionService version: 0.0.1 entities: - id: Session version: 0.0.1 - id: User version: 0.1.0 - id: SessionId version: 0.0.1 --- ## Overview The Session bounded context handles authentication through OAuth providers (GitHub primary, Google planned). Sessions follow a strict lifecycle: create -> active -> expired/invalidated. ## Strategic Classification **Supporting Domain**: Enables core business capabilities by providing secure authentication infrastructure. ## Key Aggregates - **Session**: Manages session lifecycle with TTL-based expiration ## Shared Kernel Exports - `UserId`: User identity combining OAuth provider and external ID (consumed by Workspace) - `SessionId`: Reference type for session identification ## Zenoh Channel Events published to: `events/session/**` --- id: Workspace name: Workspace version: 0.0.1 summary: | Supporting domain for persistent workspace configuration. Manages workspaces containing dashboards, saved queries, and workspace preferences, plus user-scoped preferences. owners: - engineering services: - id: DashboardService version: 0.0.1 - id: SavedQueryService version: 0.0.1 - id: WorkspacePreferencesService version: 0.0.1 - id: UserPreferencesService version: 0.0.1 entities: - id: Workspace version: 0.0.1 - id: Dashboard version: 0.0.1 - id: SavedQuery version: 0.0.1 - id: WorkspacePreferences version: 0.0.1 - id: UserPreferences version: 0.0.1 - id: DashboardId version: 0.0.1 - id: ChartId version: 0.0.1 - id: TabId version: 0.0.1 - id: SavedQueryId version: 0.0.1 - id: GridPosition version: 0.0.1 - id: UiState version: 0.0.1 - id: Theme version: 0.0.1 --- ## Overview The Workspace bounded context provides a two-tier configuration hierarchy: Workspace aggregates serve as containers for dashboards, saved queries, and workspace-scoped preferences, while UserPreferences stores user-scoped settings that apply across all workspaces. ## Strategic Classification **Supporting Domain**: Enhances user experience by providing persistent workspace and user state. ## Key Aggregates - **Workspace**: Primary aggregate root containing dashboards, queries, and workspace preferences - **Dashboard**: Layout configuration with chart placements and tabs (contained by Workspace) - **SavedQuery**: Named, reusable SQL queries with dataset references (contained by Workspace) - **WorkspacePreferences**: Workspace-scoped settings like default catalog and layout defaults - **UserPreferences**: User-scoped settings (theme, locale, UI state) applied across workspaces ## Containment Hierarchy ``` Workspace (aggregate root) ├── Dashboard (child aggregate) ├── SavedQuery (child aggregate) └── WorkspacePreferences (child aggregate) UserPreferences (separate aggregate, user-scoped) ``` ## Integration Points - Upstream: Consumes ChartDefinition references from Analytics - Upstream: Consumes UserId from Session via Shared Kernel ## Zenoh Channel Events published to: `events/workspace/**` --- id: engineering name: Engineering Team summary: Placeholder engineering team for EventCatalog skeleton. email: engineering@example.com members: - placeholder --- This is a placeholder team. Replace with actual team information. --- id: placeholder name: Placeholder User avatarUrl: https://api.dicebear.com/7.x/avataaars/svg?seed=placeholder role: Developer email: placeholder@example.com --- Placeholder user for EventCatalog development. Replace with actual team members when onboarding contributors. --- id: Catalog name: Catalog version: 0.0.1 summary: | Aggregate root representing a DuckLake catalog with health monitoring and metadata refresh. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique catalog identifier - name: name type: string required: true summary: Human-readable catalog name - name: healthStatus type: string required: true enum: [Healthy, Degraded, Unavailable] summary: Current catalog availability status - name: lastMetadataRefresh type: string required: true format: date-time summary: Timestamp of most recent metadata sync from DuckLake --- ## Overview The Catalog aggregate manages DuckLake catalog connections, tracking their health status and metadata freshness. Catalogs serve as the entry point for dataset discovery and query execution in the Analytics domain. ## Decider Pattern The Catalog aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `SelectCatalog`: Sets a catalog as the active working context - `RefreshCatalogMetadata`: Triggers metadata sync from DuckLake backend **Events emitted:** - `CatalogSelected`: User selected a catalog for querying - `CatalogMetadataRefreshed`: Metadata sync completed successfully ## Relationships - **Dataset**: A Catalog contains zero or more Datasets (one-to-many) - **UserPreferences** (Workspace): References Catalog as default selection - **CatalogService**: Catalog is the aggregate root managed by CatalogService ## Health Status Semantics | Status | Meaning | |--------|---------| | Healthy | All datasets queryable, metadata current | | Degraded | Some datasets unavailable, queries may fail | | Unavailable | Catalog connection failed, no queries possible | ## DuckLake Integration Catalogs connect to DuckLake backends via the `hf://` protocol for HuggingFace-hosted datasets or S3-compatible storage. Metadata refresh pulls table schemas and statistics without loading full datasets. ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: CatalogCommand, state: &CatalogState) -> Result, CatalogError> ``` **Command handling logic:** - **SelectCatalog(ref)** when `NoCatalogSelected`: - Validates: No preconditions (can always select when none active) - Produces: `[CatalogSelected(ref, timestamp)]` - **SelectCatalog(ref)** when `CatalogActive(currentRef, _)`: - Validates: Checks if `ref == currentRef` - Produces: `[]` (no-op) if catalog already selected, otherwise returns error "Cannot change active catalog; deselect first" - **RefreshCatalogMetadata** when `NoCatalogSelected`: - Validates: Fails with error "No catalog selected" - Produces: No events (validation failure) - **RefreshCatalogMetadata** when `CatalogActive(_, _)`: - Validates: No preconditions (can always refresh when catalog active) - Produces: `[CatalogMetadataRefreshed(metadata, timestamp)]` **Key invariant:** Only one catalog can be active at a time. ### evolve ```rust fn evolve(state: CatalogState, event: &CatalogEvent) -> CatalogState ``` **Event application logic:** - **CatalogSelected(ref, ts)**: - Updates state: `NoCatalogSelected` → `CatalogActive(ref, CatalogMetadata { datasets: [], lastRefreshed: ts })` - Initializes metadata with empty dataset list and selection timestamp - **CatalogMetadataRefreshed(metadata, _)**: - Updates state: `CatalogActive(ref, _)` → `CatalogActive(ref, metadata)` - Preserves catalog reference, replaces metadata ### initial_state ```rust fn initial_state() -> CatalogState ``` Returns `NoCatalogSelected`, representing the initial state before any catalog has been selected. --- id: CatalogMetadata name: CatalogMetadata version: 0.0.1 summary: | Metadata about a DuckLake catalog including dataset inventory. owners: - engineering aggregateRoot: false properties: - name: datasets type: array required: true summary: List of DatasetInfo for datasets available in the catalog - name: lastRefreshed type: Timestamp required: true summary: Timestamp of the most recent metadata refresh --- ## Overview CatalogMetadata is a value object representing metadata about a DuckLake catalog, including the inventory of available datasets and freshness timestamp. This metadata is refreshed periodically to synchronize with upstream catalog changes. ## Invariants - Datasets list may be empty (for newly selected catalogs before first refresh) - Last refreshed timestamp must be valid and non-future - Dataset list order is stable across refreshes - Dataset uniqueness determined by name within the catalog ## Usage - **Catalog aggregate**: Stores metadata in CatalogActive state - **CatalogMetadataRefreshed event**: Records updated metadata - **CatalogSelected event**: Initializes with empty dataset list - **Analytics read model**: Projects catalog metadata for queries --- id: CatalogRef name: CatalogRef version: 0.0.1 summary: | URI reference to a DuckLake catalog. owners: - engineering aggregateRoot: false identifier: uri properties: - name: uri type: string required: true format: uri summary: DuckLake catalog URI (e.g., "ducklake:hf://datasets/sciexp/fixtures") --- ## Overview CatalogRef is a value object representing a reference to a DuckLake catalog through a URI. The URI scheme follows the pattern `ducklake:{provider}://{path}` where provider indicates the data source (e.g., `hf` for Hugging Face). ## URI Format | Component | Example | Description | |-----------|---------|-------------| | Scheme | `ducklake:` | DuckLake protocol identifier | | Provider | `hf:` | Hugging Face datasets | | Path | `//datasets/sciexp/fixtures` | Resource path within provider | Full example: `ducklake:hf://datasets/sciexp/fixtures` ## Invariants - URI must follow DuckLake URI format conventions - URI uniqueness determines catalog identity - Two CatalogRef instances with the same URI are considered equal ## Usage - **Catalog aggregate**: Tracks the currently active catalog via CatalogActive state - **CatalogSelected event**: Records which catalog was selected - **SelectCatalog command**: Specifies which catalog to activate --- id: ChartConfig name: ChartConfig version: 0.0.1 summary: | Complete chart specification combining type and rendering options. owners: - engineering aggregateRoot: false properties: - name: chartType type: ChartType required: true summary: Type of chart (Line, Bar, Scatter, etc.) - name: options type: ChartOptions required: true summary: Chart rendering options (title, axes, legend, grid, palette, interactivity) --- ## Overview ChartConfig is a value object representing a complete chart specification combining chart type with rendering options. This configuration is produced by Analytics and consumed by Workspace for visualization rendering. The configuration supports both ECharts and Vega-Lite libraries with a unified abstraction layer. ## Invariants - Chart type must be a valid ChartType variant - Chart options must be well-formed according to ChartOptions structure - Chart configuration must be serializable for SSE transmission ## Usage - **StartQuery command**: Optionally specifies chart configuration - **QueryStarted event**: Records chart configuration when provided - **Workspace Dashboard**: Consumes chart configuration to render visualizations --- id: ChartId name: ChartId version: 0.0.1 summary: | Unique identifier for a chart within a dashboard. owners: - engineering aggregateRoot: false identifier: unChartId properties: - name: unChartId type: string required: true summary: Opaque chart identifier string unique within a dashboard --- ## Overview ChartId is a value object representing a unique identifier for a chart within a dashboard. Charts are identified by their ID within the context of their parent dashboard. ## Invariants - ChartIds are unique within a dashboard - Must exist in placements list to be valid for RemoveChart or MoveChartToTab - Immutable after creation ## Usage - **Dashboard aggregate**: Identifies charts in placements list - **ChartAdded event**: Records the assigned ChartId - **RemoveChart command**: Specifies which chart to remove - **MoveChartToTab command**: Specifies which chart to move --- id: ChartOptions name: ChartOptions version: 0.0.1 summary: | Chart rendering options for visualization configuration. owners: - engineering aggregateRoot: false properties: - name: title type: string required: false summary: Optional chart title - name: xAxis type: AxisConfig required: false summary: Optional X-axis configuration - name: yAxis type: AxisConfig required: false summary: Optional Y-axis configuration - name: legend type: LegendConfig required: true summary: Legend display configuration - name: grid type: GridConfig required: true summary: Grid positioning configuration (padding) - name: palette type: ColorPalette required: true summary: Color palette for chart series - name: interactive type: boolean required: true summary: Enable interactive features (zoom, pan, tooltips) --- ## Overview ChartOptions is a value object representing the complete set of rendering options for chart visualization. This includes axis configuration, legend placement, grid layout, color palettes, and interactivity settings. ## Invariants - Default values are provided via defaultChartOptions for zero-config rendering - Axis configurations are optional to support charts without explicit axes (e.g., Pie) - Grid configuration values are in pixels - Color palette must contain at least one valid hex color code - Interactive flag defaults to true ## Usage - **ChartConfig**: Combines with ChartType for complete chart specification - **Frontend visualization**: Consumes options to configure ECharts/Vega-Lite - **Default configuration**: Provides sensible defaults for quick chart creation --- id: ChartType name: ChartType version: 0.0.1 summary: | Enumeration of supported visualization types. owners: - engineering aggregateRoot: false --- ## Overview ChartType is a sum type enumerating supported visualization types in the Analytics bounded context. Each variant maps to both ECharts and Vega-Lite chart types, providing a unified abstraction. ## Variants | Variant | Description | |---------|-------------| | Line | Line chart for continuous data trends | | Bar | Bar chart for categorical comparisons | | Scatter | Scatter plot for correlation and distribution | | Pie | Pie chart for proportional data (single series) | | Heatmap | Heatmap for 2D density visualization | | Area | Area chart for cumulative trends | | Histogram | Histogram for frequency distribution | | Boxplot | Box plot for statistical distribution summary | ## Invariants - ChartType is exhaustive; all variants must be handled in pattern matching - Pie charts are restricted to single series data ## Usage - **ChartConfig**: Specifies the chart type for visualization - **Chart validation**: Validates that data matches chart type constraints --- id: Dashboard name: Dashboard version: 0.0.1 summary: | Aggregate root managing persistent dashboard layouts with chart placements and tabs. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique dashboard identifier - name: workspaceId type: reference required: true referenceTo: Workspace summary: Reference to the Workspace this dashboard belongs to - name: title type: string required: true summary: Dashboard display title - name: ownerId type: reference required: true referenceTo: User summary: Reference to the user who owns this dashboard - name: layoutJson type: string required: true summary: JSON blob encoding chart placements, tab structure, and grid positions - name: createdAt type: string required: true format: date-time summary: Timestamp when dashboard was created - name: updatedAt type: string required: true format: date-time summary: Timestamp of most recent layout modification --- ## Overview The Dashboard aggregate manages persistent visual layouts for data analysis. Dashboards contain multiple charts organized into tabs, with each chart referencing query results from the Analytics domain. ## Decider Pattern The Dashboard aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `CreateDashboard`: Creates a new empty dashboard - `AddChart`: Adds a chart definition to the dashboard - `AddTab`: Creates a new organizational tab - `MoveChartToTab`: Relocates a chart to a different tab - `RemoveChart`: Removes a chart from the dashboard - `RenameDashboard`: Updates the dashboard title **Events emitted:** - `DashboardCreated`: New dashboard initialized - `DashboardChartAdded`: Chart added to layout - `DashboardTabAdded`: New tab created - `DashboardChartMovedToTab`: Chart relocated - `DashboardChartRemoved`: Chart removed - `DashboardRenamed`: Title updated ## Relationships - **Workspace**: Dashboard belongs to exactly one Workspace (many-to-one) - **User** (Session): Dashboard has single owner (many-to-one) - **SavedQuery**: Charts reference SavedQuery for data source - **DashboardService**: Dashboard is managed by DashboardService ## Layout JSON Schema The `layoutJson` field encodes a hierarchical structure: ```json { "tabs": [ { "id": "tab-uuid", "name": "Overview", "charts": [ { "id": "chart-uuid", "queryRef": "saved-query-uuid", "chartType": "line", "position": { "x": 0, "y": 0, "w": 6, "h": 4 } } ] } ] } ``` ## Invariants - Dashboard must belong to exactly one Workspace - Dashboard must have at least one tab after creation - Chart positions must not overlap within a tab - Only the owner can modify the dashboard ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: DashboardCommand, state: &DashboardState) -> Result, String> ``` **Command handling logic:** - **CreateDashboard(name)** when `dashboardId` is `None`: - Validates: No preconditions - Produces: `DashboardCreated(dashboard_id, name, timestamp)` - Error if dashboard exists: "Dashboard already exists" - **AddChart(placement)** when dashboard exists: - Validates: Dashboard must exist - Produces: `ChartAdded(placement, timestamp)` - Error if no dashboard: "No dashboard to add chart to" - **RemoveChart(chart_id)** when dashboard exists: - Validates: Dashboard must exist (idempotent for non-existent charts) - Produces: `ChartRemoved(chart_id, timestamp)` - **AddTab(tab_name)** when dashboard exists: - Validates: Dashboard must exist - Produces: `TabAdded(tab_id, tab_name, timestamp)` - **MoveChartToTab(chart_id, tab_id)** when dashboard exists: - Validates: Dashboard must exist (full validation at boundary) - Produces: `ChartMovedToTab(chart_id, tab_id, timestamp)` - **RenameDashboard(new_name)** when dashboard exists: - Validates: Dashboard must exist - Produces: `DashboardRenamed(new_name, timestamp)` ### evolve ```rust fn evolve(state: DashboardState, event: &DashboardEvent) -> DashboardState ``` **Event application logic:** - **DashboardCreated(dashboard_id, name, _)**: - Updates: `dashboardId` → `Some(dashboard_id)`, `name` → provided name - **ChartAdded(placement, _)**: - Updates: Prepends `placement` to `placements` list - **ChartRemoved(chart_id, _)**: - Updates: Filters out placement with matching `chartId` - **TabAdded(tab_info, _)**: - Updates: Prepends `tab_info` to `tabs` list - **ChartMovedToTab(chart_id, tab_id, _)**: - Updates: Sets `tabId` to `Some(tab_id)` for matching chart placement - **DashboardRenamed(new_name, _)**: - Updates: `name` → `new_name` ### initial_state ```rust fn initial_state() -> DashboardState ``` Returns `DashboardState` with: - `dashboardId`: `None` - `name`: `""` - `placements`: `[]` - `tabs`: `[]` --- id: DashboardId name: DashboardId version: 0.0.1 summary: | Unique identifier for a dashboard. owners: - engineering aggregateRoot: false identifier: unDashboardId properties: - name: unDashboardId type: string required: true summary: Opaque dashboard identifier string generated at boundary layer --- ## Overview DashboardId is a value object representing a unique identifier for a dashboard within the Workspace bounded context. Each dashboard created receives a unique identifier that persists through its lifecycle. ## Invariants - Generated at boundary layer during dashboard creation - Immutable after creation - Uniqueness guaranteed by generation algorithm ## Usage - **Dashboard aggregate**: Primary identifier for dashboard lifecycle - **DashboardCreated event**: Records the assigned DashboardId - **Dashboard commands**: Reference DashboardId for authorization --- id: Dataset name: Dataset version: 0.0.1 summary: | Value object representing a queryable table within a DuckLake catalog. owners: - engineering aggregateRoot: false identifier: id properties: - name: id type: string required: true format: uuid summary: Unique dataset identifier - name: name type: string required: true summary: Human-readable dataset name - name: catalogId type: reference required: true referenceTo: Catalog summary: Reference to the parent catalog - name: tableName type: string required: true summary: Fully qualified table name in DuckDB (e.g., catalog.schema.table) --- ## Overview The Dataset entity is a value object within the Catalog aggregate, representing a single queryable table in a DuckLake catalog. Datasets provide the schema and location information needed for SQL query construction. ## Value Object Semantics Dataset is not an aggregate root; it is managed as part of the Catalog aggregate's consistency boundary. Dataset identity is derived from its parent Catalog and tableName combination. **Immutability constraints:** - Dataset properties are refreshed during `CatalogMetadataRefreshed` events - No direct commands target Dataset; changes flow through Catalog ## Relationships - **Catalog**: Parent aggregate containing this dataset (many-to-one) - **SavedQuery** (Workspace): References Dataset via `datasetReference` URI - **QuerySession**: Queries execute against one or more Datasets ## Table Name Format The `tableName` property uses DuckDB's fully qualified naming: ``` {catalog_name}.{schema_name}.{table_name} ``` For DuckLake catalogs, the schema typically matches the Parquet file organization. ## Dataset Discovery Datasets are discovered during catalog metadata refresh, not created explicitly. The QuerySessionService's `DatasetBrowserView` query provides dataset listing with schema preview. --- id: DatasetInfo name: DatasetInfo version: 0.0.1 summary: | Summary information about a dataset within a catalog. owners: - engineering aggregateRoot: false properties: - name: name type: string required: true summary: Dataset name within the catalog - name: tableCount type: integer required: true summary: Number of tables in the dataset (non-negative) - name: schemaVersion type: string required: true summary: Schema version identifier for the dataset --- ## Overview DatasetInfo is a value object representing summary information about a dataset within a DuckLake catalog. This provides essential metadata for dataset discovery without requiring full schema introspection. ## Invariants - Dataset name must be unique within a catalog - Table count must be non-negative (may be zero for empty datasets) - Schema version follows semantic versioning or custom versioning scheme - Dataset equality determined by name, table count, and schema version ## Usage - **CatalogMetadata**: Contains list of DatasetInfo for catalog inventory - **CatalogMetadataRefreshed event**: Records dataset inventory changes - **Catalog read model**: Provides dataset listing for UI display --- id: DatasetRef name: DatasetRef version: 0.0.1 summary: | Reference to a dataset within a DuckLake catalog. owners: - engineering aggregateRoot: false identifier: catalogUri,datasetName properties: - name: catalogUri type: string required: true summary: URI of the parent DuckLake catalog - name: datasetName type: string required: true summary: Name of the dataset within the catalog --- ## Overview DatasetRef is a value object representing a reference to a specific dataset within a DuckLake catalog. The 2-field structure separates catalog location from dataset identity, enabling filtering and grouping by catalog. ## Invariants - Dataset equality is determined by both catalogUri and datasetName - Dataset names must be valid identifiers within the DuckLake catalog schema - Catalog URI must reference an accessible DuckLake catalog ## Usage - **StartQuery command**: Specifies which dataset the query targets - **QueryStarted event**: Records the dataset reference - **QuerySession aggregate**: Stores dataset reference for running queries - **SavedQuery aggregate**: Stores dataset reference for reusable queries --- id: Duration name: Duration version: 0.0.1 summary: | Elapsed time in milliseconds representing a span between two points in time. owners: - engineering aggregateRoot: false identifier: milliseconds properties: - name: milliseconds type: integer required: true summary: Elapsed time in milliseconds --- ## Overview Duration is a value object representing the elapsed time between two instants. Ironstar uses Duration to track query execution time and cache entry lifetimes. ## Invariants - Non-negative values: All durations must be >= 0 (time cannot flow backward) - Millisecond precision: Values represent elapsed milliseconds (1/1000th of a second) ## Usage - **QuerySession aggregate**: Records execution duration in QueryCompleted events - **moka cache**: TTL values specified as Duration for cache eviction policy - **Analytics projections**: Tracks query performance metrics --- id: ErrorMessage name: ErrorMessage version: 0.0.1 summary: | Error message wrapper for failed query executions. owners: - engineering aggregateRoot: false identifier: message properties: - name: message type: string required: true summary: Human-readable error message describing the query failure --- ## Overview ErrorMessage is a value object wrapping an error message string for failed query executions. The wrapper distinguishes error messages from arbitrary strings, providing type safety for error handling. ## Invariants - Error message is immutable once constructed - Error message content should be human-readable and suitable for user display ## Usage - **QueryFailed event**: Records error message when query fails - **QuerySession aggregate**: Stores error in Failed state - **Analytics read model**: Tracks failed query errors for history --- id: GridPosition name: GridPosition version: 0.0.1 summary: | Grid coordinates for chart placement within a dashboard. owners: - engineering aggregateRoot: false properties: - name: row type: integer required: true summary: Row index in the dashboard grid (0-indexed, non-negative) - name: col type: integer required: true summary: Column index in the dashboard grid (0-indexed, non-negative) --- ## Overview GridPosition is a value object representing coordinates within a dashboard's grid layout system. Charts are positioned using row and column indices, enabling responsive grid-based layouts. ## Invariants - Row and column values must be non-negative (0-indexed grid) - Chart placements should not overlap within a tab - Overlap detection: two charts overlap if their grid rectangles intersect ## Usage - **ChartPlacement**: Contains GridPosition for chart positioning - **Dashboard aggregate**: Uses GridPosition in placements list - **Layout validation**: Checks for overlapping chart positions --- id: QueryId name: QueryId version: 0.0.1 summary: | Unique identifier for a query execution session. owners: - engineering aggregateRoot: false identifier: unQueryId properties: - name: unQueryId type: string required: true summary: Unique string identifier for the query session --- ## Overview QueryId is a value object representing a unique identifier for a query execution session within the Analytics bounded context. Each query execution receives a unique identifier that tracks it through its lifecycle. ## Invariants - Query ID must be unique across all query executions - Query ID equality is determined by string equality - Query ID is immutable once assigned ## Usage - **QuerySession aggregate**: Tracks running, completed, and failed queries - **QueryStarted event**: Assigns a unique ID to new query executions - **QueryCompleted event**: Records which query completed - **QueryFailed event**: Records which query failed - **QueryCancelled event**: Records which query was cancelled - **CancelQuery command**: Specifies which running query to cancel --- id: QueryResults name: QueryResults version: 0.0.1 summary: | Query execution result metadata. owners: - engineering aggregateRoot: false properties: - name: rowCount type: integer required: true summary: Number of rows returned by the query (non-negative) - name: columnNames type: array required: true summary: Ordered list of column names in the result set --- ## Overview QueryResults is a value object representing metadata about a successful query execution. This is a simplified representation for the event sourcing specification; production implementations include actual row data via separate caching. ## Invariants - Row count must be non-negative - Column names list must be non-empty for non-zero row counts - Column names must be unique within a result set - Column name order matches the column order in the result set ## Usage - **QueryCompleted event**: Records result metadata when query succeeds - **QuerySession aggregate**: Stores results in Completed state - **Analytics read model**: Tracks completed query results for history --- id: QuerySession name: Query Session version: 0.0.1 summary: | Aggregate root managing the lifecycle of an interactive SQL query execution. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique query session identifier - name: userId type: reference required: true referenceTo: User summary: Reference to the user who initiated the query - name: sqlStatement type: string required: true summary: The SQL query being executed - name: status type: string required: true enum: [Idle, Running, Completed, Failed] summary: Current query execution state - name: startedAt type: string required: true format: date-time summary: Timestamp when query execution began - name: durationMs type: integer required: true summary: Query execution duration in milliseconds (0 if still running) - name: errorDetails type: string required: false summary: Error message if status is Failed, null otherwise --- ## Overview The QuerySession aggregate manages interactive SQL query execution within the Analytics domain. Each query session tracks a single SQL statement through its complete lifecycle from submission to completion or failure. ## Decider Pattern The QuerySession aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `StartQuery`: Initiates query execution with SQL statement - `CancelQuery`: User-requested cancellation of running query **Events emitted:** - `QuerySessionStarted`: Query execution began - `QuerySessionCompleted`: Query finished successfully with results - `QuerySessionFailed`: Query terminated with error - `QuerySessionCancelled`: User cancelled running query **State transitions:** ``` Idle -> Running (StartQuery) Running -> Completed (success) Running -> Failed (error) Running -> Cancelled (CancelQuery) ``` ## Relationships - **User** (Session): Each query is associated with an authenticated user - **Dataset**: Queries reference one or more datasets (implicit via SQL) - **Dashboard** (Workspace): Chart definitions may reference QuerySession results - **QuerySessionService**: QuerySession is managed by QuerySessionService ## Execution Model Queries execute against DuckDB via async-duckdb with streaming results. Long-running queries support cancellation through the `CancelQuery` command. Results are cached in moka with TTL-based eviction. ## Audit Trail The QuerySession provides audit capabilities by recording: - Who executed the query (userId) - What was executed (sqlStatement) - When it ran (startedAt, durationMs) - Outcome (status, errorDetails) ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: QueryCommand, state: &QueryState) -> Result, QueryError> ``` **Command handling logic:** - **StartQuery(dsRef, query, chartCfg)** when `Idle`: - Validates: No preconditions (can start query from idle state) - Produces: `[QueryStarted(qid, dsRef, query, chartCfg, timestamp)]` - **StartQuery(_, _, _)** when `Running(_, _, _)`: - Validates: Fails with error "Query already running; cancel or wait for completion" - **StartQuery(dsRef, query, chartCfg)** when `Completed(_)` or `Failed(_)`: - Validates: No preconditions (can start new query after terminal state) - Produces: `[QueryStarted(qid, dsRef, query, chartCfg, timestamp)]` - **CancelQuery(qid)** when `Running(currentId, _, _)`: - Validates: Checks if `qid == currentId` - Produces: `[QueryCancelled(qid, timestamp)]` if IDs match, otherwise error "Query ID mismatch" - **CancelQuery(_)** when `Idle`, `Completed(_)`, or `Failed(_)`: - Validates: Fails with error "No running query to cancel" **Key invariant:** Only one query can be running at a time. ### evolve ```rust fn evolve(state: QueryState, event: &QueryEvent) -> QueryState ``` **Event application logic:** - **QueryStarted(qid, dsRef, query, _, _)**: - Updates state: Any → `Running(qid, dsRef, query)` - **QueryCompleted(qid, results, _, _)**: - Updates state: `Running(_, _, _)` → `Completed(qid, results)` - **QueryFailed(qid, err, _)**: - Updates state: `Running(_, _, _)` → `Failed(qid, err)` - **QueryCancelled(_, _)**: - Updates state: `Running(_, _, _)` → `Idle` ### initial_state ```rust fn initial_state() -> QueryState ``` Returns `Idle`, representing the initial state before any query has been started. --- id: SavedQuery name: Saved Query version: 0.0.1 summary: | Aggregate root representing a named, reusable SQL query with dataset reference. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique saved query identifier - name: workspaceId type: reference required: true referenceTo: Workspace summary: Reference to the Workspace this saved query belongs to - name: name type: string required: true summary: Human-readable query name for display - name: ownerId type: reference required: true referenceTo: User summary: Reference to the user who owns this query - name: sqlStatement type: string required: true summary: The SQL query text - name: datasetReference type: string required: true format: uri summary: URI reference to the target dataset (e.g., ducklake://catalog/dataset) --- ## Overview The SavedQuery aggregate persists named SQL queries for reuse across sessions. Saved queries provide a library of pre-built analyses that can be referenced by Dashboard charts or executed interactively. ## Decider Pattern The SavedQuery aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `SaveQuery`: Creates a new saved query - `RenameSavedQuery`: Updates the query name - `UpdateSavedQuerySql`: Modifies the SQL statement - `UpdateDatasetReference`: Changes the target dataset - `DeleteSavedQuery`: Removes the saved query **Events emitted:** - `SavedQueryCreated`: New query saved - `SavedQueryRenamed`: Name updated - `SavedQuerySqlUpdated`: SQL statement modified - `SavedQueryDatasetRefUpdated`: Dataset reference changed - `SavedQueryDeleted`: Query removed ## Relationships - **Workspace**: SavedQuery belongs to exactly one Workspace (many-to-one) - **User** (Session): Each saved query has a single owner (many-to-one) - **Dataset** (Analytics): References target dataset via URI - **Dashboard**: Charts reference SavedQuery as data source - **SavedQueryService**: SavedQuery is managed by SavedQueryService ## Dataset Reference URI Format The `datasetReference` field uses a URI scheme for flexible dataset addressing: | Scheme | Example | Description | |--------|---------|-------------| | `ducklake://` | `ducklake://catalog/schema/table` | DuckLake catalog reference | | `hf://` | `hf://datasets/org/dataset` | HuggingFace dataset | | `s3://` | `s3://bucket/path` | S3-compatible storage | ## Invariants - SavedQuery must belong to exactly one Workspace - Query name must be unique per owner within a Workspace - SQL statement must be syntactically valid (validated at save time) - Only the owner can modify or delete the saved query ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: SavedQueryCommand, state: &SavedQueryState) -> Result, String> ``` **Command handling logic:** - **SaveQuery(name, sql, dataset_ref)** when `NoQuery`: - Validates: `name` and `sql` must be non-empty - Produces: `QuerySaved(query_id, name, sql, dataset_ref, timestamp)` - Error if name empty: "Query name cannot be empty" - Error if SQL empty: "Query SQL cannot be empty" - Error if query exists: "Query already exists" - **DeleteQuery** when `QueryExists(_)`: - Validates: Query must exist - Produces: `QueryDeleted(timestamp)` - Error if no query: "No query to delete" - **RenameQuery(new_name)** when `QueryExists(_)`: - Validates: `new_name` must be non-empty - Produces: `QueryRenamed(new_name, timestamp)` - Error if name empty: "Query name cannot be empty" - **UpdateQuerySql(new_sql)** when `QueryExists(_)`: - Validates: `new_sql` must be non-empty - Produces: `QuerySqlUpdated(new_sql, timestamp)` - Error if SQL empty: "Query SQL cannot be empty" - **UpdateDatasetRef(new_ref)** when `QueryExists(_)`: - Validates: Query must exist - Produces: `DatasetRefUpdated(new_ref, timestamp)` ### evolve ```rust fn evolve(state: SavedQueryState, event: &SavedQueryEvent) -> SavedQueryState ``` **Event application logic:** - **QuerySaved(query_id, name, sql, dataset_ref, _)**: - Updates: `NoQuery` → `QueryExists(query_id, name, sql, dataset_ref)` - **QueryDeleted(_)**: - Updates: `QueryExists(_)` → `NoQuery` - **QueryRenamed(new_name, _)**: - Updates: `name` field in `QueryExists`, preserving other fields - **QuerySqlUpdated(new_sql, _)**: - Updates: `sql` field in `QueryExists`, preserving other fields - **DatasetRefUpdated(new_ref, _)**: - Updates: `dataset_ref` field in `QueryExists`, preserving other fields ### initial_state ```rust fn initial_state() -> SavedQueryState ``` Returns `SavedQueryState::NoQuery`. **Note:** Each SavedQuery is a separate aggregate. Multiple queries per user use separate aggregates with IDs like `user_id/query_name`. --- id: SavedQueryId name: SavedQueryId version: 0.0.1 summary: | Unique identifier for a saved query. owners: - engineering aggregateRoot: false identifier: unSavedQueryId properties: - name: unSavedQueryId type: string required: true summary: Opaque saved query identifier string --- ## Overview SavedQueryId is a value object representing a unique identifier for a saved query. Query names are unique per user, enforced at boundary via aggregate ID pattern like "user_123/my-query-name". ## Invariants - Generated at boundary layer during query save - Immutable after creation - Per-user uniqueness enforced via aggregate ID construction ## Usage - **SavedQuery aggregate**: Primary identifier for saved query lifecycle - **QuerySaved event**: Records the assigned SavedQueryId - **Dashboard charts**: Reference SavedQueryId as data source --- id: Session name: Session version: 0.0.1 summary: | Aggregate root managing authenticated user sessions with TTL-based lifecycle. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique session identifier - name: userId type: reference required: true referenceTo: User summary: Reference to the authenticated user - name: status type: string required: true enum: [Active, Expired, Invalidated] summary: Current session lifecycle state - name: expiresAt type: string required: true format: date-time summary: Timestamp when session will automatically expire - name: createdAt type: string required: true format: date-time summary: Timestamp when session was created --- ## Overview The Session aggregate is the core entity in the Session bounded context, managing authenticated user sessions from creation through expiration or invalidation. Sessions are created after successful OAuth callback processing and maintain a TTL-based lifecycle. ## Decider Pattern The Session aggregate follows the fmodel-rust Decider pattern with pure synchronous decision logic. **Commands handled:** - `CreateSession`: Initiates a new session after OAuth callback - `RefreshSession`: Extends session TTL for continued activity - `InvalidateSession`: Explicit logout terminates session - `ExpireSession`: System-triggered TTL expiration **State transitions:** ``` NoSession -> Active(id, userId, expiresAt) Active -> Active (refresh extends TTL) Active -> Expired (TTL elapsed) Active -> Invalidated (explicit logout) ``` ## Relationships - **User**: Each Session references exactly one User (many-to-one relationship) - **SessionService**: Session is the aggregate root managed by SessionService ## Invariants - A session cannot be refreshed after expiration or invalidation - Session TTL must be positive and within configured bounds - Only one active session per user (enforced at application layer) ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: SessionCommand, state: &SessionState) -> Result, SessionError> ``` **Command handling logic:** - **CreateSession(UserId, OAuthProvider)** when `NoSession`: - Validates: No preconditions - Produces: `SessionCreated(SessionId, UserId, OAuthProvider, Timestamp, ExpiresAt)` - Boundary layer fills: SessionId, Timestamp, ExpiresAt - **CreateSession(_, _)** when `Active(_)`, `Expired(_)`, or `Invalidated(_)`: - Validates: Fails with error "Session already exists" - **RefreshSession(SessionId)** when `Active(currentId, _, _)`: - Validates: Checks if SessionId matches - Produces: `SessionRefreshed(SessionId, ExpiresAt, Timestamp)` - **RefreshSession(_)** when `NoSession`, `Expired(_)`, or `Invalidated(_)`: - Validates: Fails with error "Cannot refresh non-active session" - **InvalidateSession(SessionId)** when `Active(currentId, _, _)`: - Validates: Checks if SessionId matches - Produces: `SessionInvalidated(SessionId, Timestamp)` - **InvalidateSession(_)** when `NoSession`, `Expired(_)`, or `Invalidated(_)`: - Validates: Fails with error "No active session to invalidate" **Note:** `SessionExpired` events are generated by the boundary layer when TTL elapses, not by the decide function. ### evolve ```rust fn evolve(state: SessionState, event: &SessionEvent) -> SessionState ``` **Event application logic:** - **SessionCreated(SessionId, UserId, _, _, ExpiresAt)**: - Updates state: `NoSession` → `Active(SessionId, UserId, ExpiresAt)` - **SessionRefreshed(SessionId, ExpiresAt, _)**: - Updates state: `Active(_, UserId, _)` → `Active(SessionId, UserId, ExpiresAt)` - Preserves UserId, updates expiration - **SessionInvalidated(SessionId, _)**: - Updates state: `Active(_, _, _)` → `Invalidated(SessionId)` - **SessionExpired(SessionId, _)**: - Updates state: `Active(_, _, _)` → `Expired(SessionId)` ### initial_state ```rust fn initial_state() -> SessionState ``` Returns `NoSession`, representing the initial state before authentication. --- id: SessionId name: SessionId version: 0.0.1 summary: | Unique identifier for an authenticated user session. owners: - engineering aggregateRoot: false identifier: unSessionId properties: - name: unSessionId type: string required: true summary: Opaque session identifier string generated at boundary layer --- ## Overview SessionId is a value object representing a unique identifier for an authenticated user session. Each session created after OAuth callback receives a unique SessionId that persists until the session expires or is invalidated. ## Invariants - Generated at boundary layer during session creation (not in pure domain logic) - Immutable after creation - Uniqueness guaranteed by generation algorithm (UUID or similar) ## Usage - **Session aggregate**: Primary identifier for session lifecycle tracking - **SessionCreated event**: Records the assigned SessionId - **RefreshSession command**: References SessionId for authorization - **InvalidateSession command**: References SessionId for logout - **Zenoh key expressions**: Used in event routing (`events/session/{session_id}/...`) --- id: SqlQuery name: SqlQuery version: 0.0.1 summary: | SQL query string wrapper for type-safe query execution. owners: - engineering aggregateRoot: false identifier: sql properties: - name: sql type: string required: true summary: SQL query text to be executed against DuckDB --- ## Overview SqlQuery is a value object wrapping a SQL query string for type-safe query execution. The wrapper distinguishes SQL query strings from arbitrary strings in the type system, providing compile-time safety and clear intent. ## Invariants - SQL string is immutable once constructed - No SQL syntax validation at value object level (validation happens at execution time) - Non-empty validation enforced at aggregate level (SavedQuery decider) ## Usage - **StartQuery command**: Specifies the SQL query to execute - **QueryStarted event**: Records the SQL query text submitted - **QuerySession aggregate**: Stores the SQL query for running queries - **SavedQuery aggregate**: Stores the SQL query for reusable queries --- id: TabId name: TabId version: 0.0.1 summary: | Unique identifier for a tab within a dashboard. owners: - engineering aggregateRoot: false identifier: unTabId properties: - name: unTabId type: string required: true summary: Opaque tab identifier string unique within a dashboard --- ## Overview TabId is a value object representing a unique identifier for an organizational tab within a dashboard. Tabs group related charts and are referenced when moving charts between organizational sections. ## Invariants - TabIds are unique within a dashboard - Referenced TabIds in ChartPlacement should exist in tabs list - Immutable after creation ## Usage - **Dashboard aggregate**: Identifies tabs in tabs list - **TabAdded event**: Records the assigned TabId - **MoveChartToTab command**: Specifies destination tab for chart - **ChartPlacement**: References TabId for chart organization --- id: Theme name: Theme version: 0.0.1 summary: | User's preferred color scheme. owners: - engineering aggregateRoot: false --- ## Overview Theme is a sum type representing the user's preferred color scheme. The theme preference integrates with Open Props' native theming via CSS custom properties. ## Variants | Variant | Description | |---------|-------------| | Light | Force light color scheme | | Dark | Force dark color scheme | | System | Follow OS preference via `prefers-color-scheme` media query | ## Invariants - Theme is exhaustive; all variants must be handled - Always a valid Theme value (enforced by type) - Implements Eq for comparison - parseTheme function handles string deserialization ## Integration Theme values map to Open Props + Open Props UI theming: | Theme | CSS Implementation | |-------|-------------------| | Light | `color-scheme: light` | | Dark | `color-scheme: dark` | | System | `color-scheme: light dark` (browser decides) | ## Usage - **UserPreferences aggregate**: Stores theme preference - **ThemeSet event**: Records theme changes - **SetTheme command**: Updates user's theme preference - **Frontend**: Applies theme via CSS custom properties --- id: Timestamp name: Timestamp version: 0.0.1 summary: | Unix timestamp in milliseconds representing a point in time. owners: - engineering aggregateRoot: false identifier: unTimestamp properties: - name: unTimestamp type: integer required: true summary: Unix timestamp in milliseconds since epoch (January 1, 1970 UTC) --- ## Overview Timestamp is a value object representing an instant in time as Unix milliseconds. Ironstar uses Timestamp for two distinct purposes: 1. **Event Time**: When an event occurred in the domain (business time) 2. **Recorded Time**: When the event was stored in the event log (system time) Both times are required in the EventEnvelope to support temporal queries, point-in-time projections, and SSE replay semantics. ## Invariants - Non-negative values: All timestamps must be >= 0 - Total ordering: For any two timestamps, exactly one of t1 < t2, t1 == t2, or t1 > t2 holds - Epoch reference: All values are measured in milliseconds since January 1, 1970 UTC ## Usage - **EventEnvelope**: Contains two Timestamps (event time and recorded time) - **Session aggregate**: Tracks session expiration via ExpiresAt (Timestamp alias) - **QuerySession aggregate**: Records query start times - **CatalogMetadata**: Tracks last refresh time - **SSE resumption**: Timestamp ordering ensures monotonic event delivery via Last-Event-ID --- id: UiState name: UiState version: 0.0.1 summary: | Opaque JSON blob for persistent UI state. owners: - engineering aggregateRoot: false properties: - name: value type: string required: true format: json summary: JSON string encoding arbitrary UI state --- ## Overview UiState is an opaque JSON blob by design. The domain layer treats it as an uninterpreted string; the boundary layer validates JSON structure. This isolation prevents UI implementation details from leaking into domain types, preserving the separation between pure domain logic and presentation concerns. ## Design Rationale UI state includes transient presentation concerns like: - Panel collapse states - Scroll positions - Selected tabs - Editor preferences These are inherently UI-specific and may vary as features evolve. Forcing algebraic structure would couple domain to UI implementation details. ## Invariants - Must be valid JSON (validated at boundary layer, not in domain) - Empty object `"{}"` is the default value - No schema enforcement at domain level ## Usage - **UserPreferences aggregate**: Stores uiState field - **UiStateUpdated event**: Records UI state changes - **Frontend components**: Serialize/deserialize for state persistence ## Evolution Path If specific UI state fields need domain validation, extract them as explicit PreferencesCommand variants rather than adding schema to UiState. This keeps UiState as the "escape hatch" for truly arbitrary UI concerns. --- id: User name: User version: 0.1.0 summary: | Shared kernel entity representing an authenticated user identity across bounded contexts. Identity is the composite key (provider, externalId) from OAuth authentication. owners: - engineering aggregateRoot: false identifier: provider,externalId properties: - name: provider type: OAuthProvider required: true summary: OAuth provider that authenticated this user (GitHub, Google) - name: externalId type: string required: true summary: Provider-assigned unique identifier (immutable, stable across sessions) - name: fullName type: string required: true summary: User's display name from OAuth provider profile (denormalized, updated on re-auth) - name: roleName type: string required: true summary: User's role for authorization (e.g., admin, analyst, viewer) --- ## Overview The User entity represents an authenticated identity obtained from OAuth providers (GitHub primary, Google planned). Identity is the composite key `(provider, externalId)` which is immutable and stable across sessions. As part of the Shared Kernel, User is defined in Session but exported for consumption by Workspace and Analytics bounded contexts. Contact information (email) is not stored in the User entity. Email is captured in `SessionStarted` events as an OAuth claim and projected for UI display. This design ensures the OAuth provider remains the single source of truth for contact information. ## Shared Kernel Pattern User is not an aggregate root within Session; instead, it is a reference type exported via the Shared Kernel pattern. Other bounded contexts receive `UserId` references through session context rather than managing User entities directly. **Exported types:** - `UserId`: Opaque identifier for user references in other contexts - `UserProfile`: Read-only projection of user display information ## Relationships - **Session**: Users are associated with one or more Sessions (one-to-many) - **Dashboard** (Workspace): References User as owner - **SavedQuery** (Workspace): References User as owner - **UserPreferences** (Workspace): One-to-one relationship with User - **QuerySession** (Analytics): References User for audit trail ## OAuth Provider Mapping The User entity abstracts over OAuth provider-specific identities. | Field | GitHub Source | Google Source | |-------|---------------|---------------| | provider | `"GitHub"` literal | `"Google"` literal | | externalId | `id` (numeric string) | `sub` claim | | fullName | `name` or `login` | `name` | | roleName | derived from org membership | derived from domain | Contact information (email) is captured in SessionStarted events, not the User entity. ## Invariants - Composite key `(provider, externalId)` is immutable after creation - `externalId` is the OAuth provider's stable identifier, not a display name or email - Role assignment follows principle of least privilege - User identity cannot be transferred between providers ## Design Rationale The composite key pattern aligns with algebraic functional domain modeling principles: 1. **Event immutability** (Hoffman Law 1): Events containing `(provider, externalId)` remain interpretable without external lookups during replay. Surrogate UUIDs require mapping tables that may not exist in future replays. 2. **Algebraic correctness**: `UserId = OAuthProvider × String` represents the actual identity source. A surrogate UUID adds indirection that can become stale after user deletion/recreation. 3. **Shared Kernel self-sufficiency**: The exported `UserId` type is complete without requiring mapping tables in consuming bounded contexts. 4. **FRP signal stability**: Datastar signals referencing user identities remain valid across session boundaries because the composite key is stable. ## Evolution Path For future account linking (multiple OAuth identities → one human), introduce a `VerifiedContact` entity: - `User` retains identity: `(provider, externalId)` - `VerifiedContact` links verified emails to users with explicit events - Account linking becomes an explicit domain event, not implicit email matching This evolution preserves backward compatibility with existing events while adding the flexibility to unify identities when business requirements demand it. --- id: UserPreferences name: User Preferences version: 0.0.1 summary: | Aggregate root managing user-scoped settings that follow the user across all workspaces. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique preferences identifier - name: userId type: reference required: true referenceTo: User summary: Reference to the user these preferences belong to - name: theme type: string required: true enum: [Light, Dark, System] summary: User's preferred color scheme - name: locale type: string required: false summary: User's preferred locale for internationalization (e.g., en-US, de-DE) - name: uiStateJson type: string required: true summary: JSON blob encoding persistent UI state (panel sizes, collapsed sections, etc.) --- ## Overview The UserPreferences aggregate persists user-scoped settings that follow the user across all workspaces. These are global user settings independent of any particular workspace context. Preferences are initialized on first login and updated through explicit user actions. See WorkspacePreferences for workspace-scoped settings such as default catalog selection. ## Decider Pattern The UserPreferences aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `InitializePreferences`: Creates default preferences on first login - `SetTheme`: Updates color scheme preference - `SetLocale`: Updates locale preference - `UpdateUiState`: Persists current UI layout state **Events emitted:** - `UserPreferencesInitialized`: Default preferences created - `UserPreferencesThemeSet`: Theme updated - `UserPreferencesLocaleSet`: Locale updated - `UserPreferencesUiStateUpdated`: UI state persisted ## Relationships - **User** (Session): One-to-one relationship with User - **UserPreferencesService**: Managed by UserPreferencesService - **WorkspacePreferences**: See WorkspacePreferences for workspace-scoped settings ## Theme Integration The theme preference integrates with Open Props' native theming: | Value | Behavior | |-------|----------| | Light | Force light color scheme | | Dark | Force dark color scheme | | System | Follow OS preference via `prefers-color-scheme` | ## UI State Schema The `uiStateJson` field persists transient UI state: ```json { "sidebarCollapsed": false, "queryEditorHeight": 300, "resultPanelTab": "table", "recentCatalogs": ["uuid1", "uuid2"] } ``` ## Invariants - Each user has exactly one UserPreferences aggregate - UserPreferences is created lazily on first authenticated access - Settings apply globally across all workspaces the user has access to ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: PreferencesCommand, state: &PreferencesState) -> Result, String> ``` **Command handling logic:** - **InitializePreferences** when `preferencesId` is `None`: - Validates: No preconditions - Produces: `PreferencesInitialized(preferences_id, timestamp)` - Error if already initialized: "Preferences already initialized" - **SetTheme(theme)** when preferences exist: - Validates: Preferences must exist - Produces: `ThemeSet(theme, timestamp)` - Error if not initialized: "Preferences not initialized" - **SetLocale(locale)** when preferences exist: - Validates: Preferences must exist - Produces: `LocaleSet(locale, timestamp)` - **UpdateUiState(json_blob)** when preferences exist: - Validates: Preferences must exist (JSON validation at boundary) - Produces: `UiStateUpdated(json_blob, timestamp)` ### evolve ```rust fn evolve(state: PreferencesState, event: &PreferencesEvent) -> PreferencesState ``` **Event application logic:** - **PreferencesInitialized(preferences_id, _)**: - Updates: `preferencesId` → `Some(preferences_id)` - **ThemeSet(theme, _)**: - Updates: `theme` → provided value (Light, Dark, or System) - **LocaleSet(locale, _)**: - Updates: `locale` → `Some(locale)` - **UiStateUpdated(json_blob, _)**: - Updates: `uiState` → provided JSON string ### initial_state ```rust fn initial_state() -> PreferencesState ``` Returns `PreferencesState` with: - `preferencesId`: `None` - `theme`: `Theme::System` (defer to OS preference) - `locale`: `None` (defer to browser locale) - `uiState`: `"{}"` (empty JSON object) **Note:** One PreferencesState per authenticated user, enforced by aggregate ID construction (e.g., `AggregateId("UserPreferences", "user_123")`). --- id: Workspace name: Workspace version: 0.0.1 summary: | Primary aggregate root for the Workspace domain, containing dashboards, saved queries, and workspace preferences. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique workspace identifier - name: name type: string required: true summary: Human-readable workspace name - name: ownerId type: reference required: false referenceTo: User summary: Reference to the owning user (null for system workspaces) - name: visibility type: string required: true enum: [Public, Private] summary: Access control setting for the workspace - name: createdAt type: string required: true format: date-time summary: Timestamp when workspace was created - name: updatedAt type: string required: true format: date-time summary: Timestamp of most recent modification --- ## Overview The Workspace aggregate serves as the primary container for organizing analytics artifacts. A workspace groups related dashboards, saved queries, and workspace-scoped preferences into a cohesive unit with shared access control. ## Decider Pattern The Workspace aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `CreateWorkspace`: Creates a new workspace with name, owner, and visibility - `RenameWorkspace`: Updates the workspace name - `SetVisibility`: Changes the access control setting - `TransferOwnership`: Assigns a new owner to the workspace *(future scope)* **Events emitted:** - `WorkspaceCreated`: New workspace initialized - `WorkspaceRenamed`: Name updated - `WorkspaceVisibilityChanged`: Access control modified - `WorkspaceOwnershipTransferred`: Owner changed *(future scope)* ## Relationships - **User** (Session): Optional owner (many-to-one, null for system workspaces) - **Dashboard**: Workspace contains multiple dashboards (one-to-many) - **SavedQuery**: Workspace contains multiple saved queries (one-to-many) - **WorkspacePreferences**: Workspace has exactly one preferences record (one-to-one) ## Visibility Model | Value | Access | |-------|--------| | Public | All authenticated users can view | | Private | Only owner and explicit members can view | System workspaces (ownerId null) typically use Public visibility to provide shared defaults. ## Invariants - Workspace name must be non-empty - Visibility must be set at creation - System workspaces (ownerId null) cannot be transferred - Only the owner can modify Private workspaces ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: WorkspaceCommand, state: &WorkspaceState) -> Result, String> ``` **Command handling logic:** - **CreateWorkspace(name, owner_id, visibility)** when `workspaceId` is `None`: - Validates: Name non-empty - Produces: `WorkspaceCreated(workspace_id, name, owner_id, visibility, timestamp)` - Error if workspace exists: "Workspace already exists" - **RenameWorkspace(new_name)** when workspace exists: - Validates: Workspace must exist, name non-empty - Produces: `WorkspaceRenamed(new_name, timestamp)` - Error if not initialized: "Workspace not initialized" - **SetVisibility(visibility)** when workspace exists: - Validates: Workspace must exist - Produces: `WorkspaceVisibilityChanged(visibility, timestamp)` - **TransferOwnership(new_owner_id)** when workspace exists *(future scope)*: - Validates: Workspace must exist, must have current owner (not system workspace) - Produces: `WorkspaceOwnershipTransferred(new_owner_id, timestamp)` - Error if system workspace: "Cannot transfer system workspace" ### evolve ```rust fn evolve(state: WorkspaceState, event: &WorkspaceEvent) -> WorkspaceState ``` **Event application logic:** - **WorkspaceCreated(workspace_id, name, owner_id, visibility, _)**: - Updates: `workspaceId` -> `Some(workspace_id)`, `name`, `ownerId`, `visibility` - **WorkspaceRenamed(new_name, _)**: - Updates: `name` -> `new_name` - **WorkspaceVisibilityChanged(visibility, _)**: - Updates: `visibility` -> provided value - **WorkspaceOwnershipTransferred(new_owner_id, _)** *(future scope)*: - Updates: `ownerId` -> `Some(new_owner_id)` ### initial_state ```rust fn initial_state() -> WorkspaceState ``` Returns `WorkspaceState` with: - `workspaceId`: `None` - `name`: `""` - `ownerId`: `None` - `visibility`: `Visibility::Private` --- id: WorkspacePreferences name: Workspace Preferences version: 0.0.1 summary: | Aggregate managing workspace-scoped settings including default catalog and layout defaults. owners: - engineering aggregateRoot: true identifier: id properties: - name: id type: string required: true format: uuid summary: Unique preferences identifier - name: workspaceId type: reference required: true referenceTo: Workspace summary: Reference to the workspace these preferences belong to - name: defaultCatalogId type: reference required: false referenceTo: Catalog summary: Optional default catalog selection for new queries in this workspace - name: layoutDefaultsJson type: string required: true summary: JSON blob encoding default layout settings for new dashboards --- ## Overview The WorkspacePreferences aggregate manages settings that apply to a specific workspace. These are distinct from UserPreferences, which apply to a user across all workspaces. ## Distinction from UserPreferences | Aspect | WorkspacePreferences | UserPreferences | |--------|---------------------|-----------------| | Scope | Single workspace | All workspaces | | Examples | Default catalog, layout defaults | Theme, locale, UI state | | Lifecycle | Created with workspace | Created on first login | | Visibility | Shared with workspace members | Private to user | ## Decider Pattern The WorkspacePreferences aggregate follows the fmodel-rust Decider pattern. **Commands handled:** - `InitializeWorkspacePreferences`: Creates default preferences for a new workspace - `SetDefaultCatalog`: Sets the default catalog for new queries - `ClearDefaultCatalog`: Removes the default catalog selection - `UpdateLayoutDefaults`: Persists default layout settings for new dashboards **Events emitted:** - `WorkspacePreferencesInitialized`: Default preferences created - `WorkspacePreferencesDefaultCatalogSet`: Default catalog selected - `WorkspacePreferencesDefaultCatalogCleared`: Default catalog removed - `WorkspacePreferencesLayoutDefaultsUpdated`: Layout defaults modified ## Relationships - **Workspace**: One-to-one relationship (child of Workspace) - **Catalog** (Analytics): Optional reference to default catalog - **WorkspacePreferencesService**: Managed by WorkspacePreferencesService ## Layout Defaults Schema The `layoutDefaultsJson` field provides defaults for new dashboards: ```json { "gridColumns": 12, "defaultChartHeight": 4, "defaultChartWidth": 6, "autoCreateOverviewTab": true } ``` ## Invariants - Each workspace has exactly one WorkspacePreferences aggregate - WorkspacePreferences is created when the workspace is created - Default catalog must reference an existing, accessible Catalog ## fmodel-rust Implementation This aggregate implements the Decider pattern from fmodel-rust. ### decide ```rust fn decide(cmd: WorkspacePreferencesCommand, state: &WorkspacePreferencesState) -> Result, String> ``` **Command handling logic:** - **InitializeWorkspacePreferences(workspace_id)** when `preferencesId` is `None`: - Validates: No preconditions - Produces: `WorkspacePreferencesInitialized(preferences_id, workspace_id, timestamp)` - Error if already initialized: "Workspace preferences already initialized" - **SetDefaultCatalog(catalog_id)** when preferences exist: - Validates: Preferences must exist - Produces: `DefaultCatalogSet(catalog_id, timestamp)` - Error if not initialized: "Workspace preferences not initialized" - **ClearDefaultCatalog** when preferences exist: - Validates: Preferences must exist - Produces: `DefaultCatalogCleared(timestamp)` - **UpdateLayoutDefaults(json_blob)** when preferences exist: - Validates: Preferences must exist (JSON validation at boundary) - Produces: `LayoutDefaultsUpdated(json_blob, timestamp)` ### evolve ```rust fn evolve(state: WorkspacePreferencesState, event: &WorkspacePreferencesEvent) -> WorkspacePreferencesState ``` **Event application logic:** - **WorkspacePreferencesInitialized(preferences_id, workspace_id, _)**: - Updates: `preferencesId` -> `Some(preferences_id)`, `workspaceId` -> `workspace_id` - **DefaultCatalogSet(catalog_id, _)**: - Updates: `defaultCatalogId` -> `Some(catalog_id)` - **DefaultCatalogCleared(_)**: - Updates: `defaultCatalogId` -> `None` - **LayoutDefaultsUpdated(json_blob, _)**: - Updates: `layoutDefaultsJson` -> provided JSON string ### initial_state ```rust fn initial_state() -> WorkspacePreferencesState ``` Returns `WorkspacePreferencesState` with: - `preferencesId`: `None` - `workspaceId`: `None` - `defaultCatalogId`: `None` - `layoutDefaultsJson`: `"{}"` (empty JSON object) --- id: events.analytics name: Analytics Events Channel version: 0.0.1 summary: | Zenoh key expression pattern for Analytics domain events including catalog selection and query execution. address: events/analytics/** protocols: - zenoh owners: - engineering --- ## Overview This channel carries all analytics domain events using Zenoh's key expression filtering. Subscribers can filter to specific event types, catalogs, or query sessions. ## Key expression patterns The channel uses hierarchical key expressions for flexible subscription. `events/analytics/**` subscribes to all analytics events. `events/analytics/catalog/**` subscribes to catalog-related events. `events/analytics/catalog/selected` subscribes only to catalog selection events. `events/analytics/catalog/metadata-refreshed` subscribes only to metadata refresh events. `events/analytics/query/**` subscribes to all query session events. `events/analytics/query/started` subscribes to query start events. `events/analytics/query/completed` subscribes to query completion events. `events/analytics/query/failed` subscribes to query failure events. `events/analytics/query/cancelled` subscribes to query cancellation events. ## Event types carried This channel carries events from CatalogService and QuerySessionService. *CatalogSelected* is published when a user selects a DuckLake catalog. *CatalogMetadataRefreshed* is published when catalog schema information is refreshed. *QuerySessionStarted* is published when a query begins execution. *QuerySessionCompleted* is published when a query finishes successfully. *QuerySessionFailed* is published when a query encounters an error. *QuerySessionCancelled* is published when a user cancels a running query. ## Subscriber patterns Analytics events typically interest the following consumers. *Cache invalidation* listens for catalog changes to invalidate stale metadata caches. *Query monitoring* tracks active queries and resource usage. *Usage analytics* aggregates query patterns for capacity planning. *Real-time feedback* updates UI during long-running query execution. ## Performance considerations Query events may be high-volume during intensive analysis sessions. Subscribers should handle backpressure appropriately. Use specific key expressions rather than wildcards when possible to reduce message routing overhead. --- id: events.session name: Session Events Channel version: 0.0.1 summary: | Zenoh key expression pattern for Session domain events including creation, refresh, and termination. address: events/session/** protocols: - zenoh owners: - engineering --- ## Overview This channel carries all session lifecycle events using Zenoh's key expression filtering. Subscribers can filter to specific event types or receive all session events. ## Key expression patterns The channel uses hierarchical key expressions for flexible subscription. `events/session/**` subscribes to all session events. `events/session/created` subscribes only to session creation events. `events/session/refreshed` subscribes only to session refresh events. `events/session/invalidated` subscribes only to session invalidation events. `events/session/expired` subscribes only to session expiration events. ## Event types carried This channel carries the following events from the SessionService. *SessionCreated* is published when a new authenticated session is established after OAuth callback. *SessionRefreshed* is published when an active session's TTL is extended due to user activity. *SessionInvalidated* is published when a user explicitly logs out. *SessionExpired* is published when a session's TTL elapses without refresh. ## Subscriber patterns Session events typically interest the following consumers. *Analytics projections* track active user counts and session durations. *Audit logging* records all session lifecycle changes for compliance. *Real-time dashboard* displays current active session metrics. ## Zenoh configuration In embedded mode, Zenoh operates in-process without network overhead. Session events are distributed to all in-process subscribers. For multi-node deployments, configure Zenoh peer mode to distribute events across nodes. --- id: events.workspace name: Workspace Events Channel version: 0.0.1 summary: | Zenoh key expression pattern for Workspace domain events including dashboards, saved queries, and user preferences. address: events/workspace/** protocols: - zenoh owners: - engineering --- ## Overview This channel carries all workspace domain events using Zenoh's key expression filtering. The workspace domain covers user-created artifacts like dashboards, saved queries, and preferences. ## Key expression patterns The channel uses hierarchical key expressions for flexible subscription. `events/workspace/**` subscribes to all workspace events. `events/workspace/dashboard/**` subscribes to dashboard events. `events/workspace/dashboard/created` subscribes to dashboard creation. `events/workspace/dashboard/chart/**` subscribes to chart management events. `events/workspace/dashboard/tab/**` subscribes to tab management events. `events/workspace/query/**` subscribes to saved query events. `events/workspace/query/created` subscribes to new saved queries. `events/workspace/query/deleted` subscribes to query deletions. `events/workspace/preferences/**` subscribes to user preferences events. `events/workspace/preferences/theme` subscribes to theme changes. `events/workspace/preferences/ui-state` subscribes to UI state updates. ## Event types carried This channel carries events from DashboardService, SavedQueryService, and UserPreferencesService. Dashboard events include DashboardCreated, DashboardChartAdded, DashboardTabAdded, DashboardChartMovedToTab, DashboardChartRemoved, and DashboardRenamed. Saved query events include SavedQueryCreated, SavedQueryRenamed, SavedQuerySqlUpdated, SavedQueryDatasetRefUpdated, and SavedQueryDeleted. Preferences events include UserPreferencesInitialized, UserPreferencesThemeSet, UserPreferencesDefaultCatalogSet, UserPreferencesDefaultCleared, and UserPreferencesUiStateUpdated. ## Subscriber patterns Workspace events typically interest the following consumers. *Collaborative editing* synchronizes dashboard changes across multiple browser tabs. *Recent items* updates the user's recent dashboards and queries lists. *Search indexing* updates the search index when queries are created or renamed. *Backup processes* trigger incremental backup on workspace changes. ## Workspace-scoped filtering Workspace events include workspace identifiers enabling workspace-specific subscriptions. For per-workspace event delivery, use workspace-scoped key expressions. `events/workspace/{workspace_id}/**` filters to all events within a specific workspace. `events/workspace/{workspace_id}/dashboard/**` filters to dashboard events within a specific workspace. `events/workspace/{workspace_id}/query/**` filters to saved query events within a specific workspace. ## User-scoped filtering User preference events are user-scoped rather than workspace-scoped. For per-user event delivery, combine with user-scoped key expressions. `events/workspace/preferences/{user_id}/**` filters to a specific user's preference events. --- id: DashboardManagement name: Dashboard Management version: 0.0.1 summary: | Dashboard CRUD operations from creation through chart and tab management to renaming. steps: - id: analyst title: Data Analyst actor: name: Data Analyst next_step: id: create-dashboard label: "creates dashboard" - id: create-dashboard title: Dashboard Created message: id: DashboardCreated version: "0.0.1" next_steps: - id: add-chart label: "add visualization" - id: rename-dashboard label: "rename" - id: add-chart title: Chart Added message: id: DashboardChartAdded version: "0.0.1" next_steps: - id: add-chart label: "add another" - id: add-tab label: "organize into tabs" - id: remove-chart label: "remove" - id: add-tab title: Tab Added message: id: DashboardTabAdded version: "0.0.1" next_steps: - id: move-chart label: "organize charts" - id: add-tab label: "add another tab" - id: move-chart title: Chart Moved to Tab message: id: DashboardChartMovedToTab version: "0.0.1" next_steps: - id: move-chart label: "move another" - id: add-chart label: "add more charts" - id: remove-chart title: Chart Removed message: id: DashboardChartRemoved version: "0.0.1" next_steps: - id: add-chart label: "add replacement" - id: remove-chart label: "remove another" - id: rename-dashboard title: Dashboard Renamed message: id: DashboardRenamed version: "0.0.1" --- ## Overview This flow documents the dashboard management operations for organizing and visualizing query results. Dashboards provide a persistent workspace for charts and visualizations derived from saved queries. All dashboard operations occur within the context of a Workspace container. Each dashboard belongs to exactly one workspace, and all dashboard events include a `workspaceId` field enabling workspace-scoped subscriptions and filtering. ## Dashboard creation Users create dashboards to organize related visualizations. A dashboard starts empty and is populated with charts over time. The DashboardCreated event captures the dashboard identifier, owner, and initial metadata. ## Chart management Charts are added to dashboards to visualize query results. Each chart references a saved query or inline SQL and a chart configuration (type, axes, styling). Charts can be removed when no longer needed. The DashboardChartRemoved event enables audit trailing of dashboard evolution. ## Tab organization Tabs provide logical grouping within dashboards. Large dashboards benefit from organizing related charts into tabs. Charts can be moved between tabs using the DashboardChartMovedToTab event. Tab order and naming are configurable. ## Chart types The platform supports common chart types via ECharts integration. *Line charts* for time series and trend visualization. *Bar charts* for categorical comparisons. *Scatter plots* for correlation analysis. *Pie charts* for proportion visualization. *Tables* for detailed data inspection. Additional chart types are extensible through the ds-echarts web component. ## Layout persistence Dashboard layout (chart positions, sizes, tab order) is persisted and restored on subsequent visits. Layout changes emit events for collaborative editing scenarios. ## Sharing and access Dashboards may be private (owner only) or shared with team members. Access control is managed through user preferences and session context. --- id: DataAnalysisWorkflow name: Data Analysis Workflow version: 0.0.1 summary: | End-to-end golden path from OAuth authentication through catalog selection, query execution, and dashboard creation. steps: - id: authenticate title: User Authentication actor: name: Data Analyst next_step: id: session-created label: "initiates OAuth" - id: session-created title: Session Established message: id: SessionCreated version: "0.0.1" next_step: id: select-catalog label: "authenticated" - id: select-catalog title: Select Catalog message: id: CatalogSelected version: "0.0.1" next_step: id: refresh-metadata label: "catalog chosen" - id: refresh-metadata title: Refresh Catalog Metadata message: id: CatalogMetadataRefreshed version: "0.0.1" next_step: id: start-query label: "metadata loaded" - id: start-query title: Execute Query message: id: QuerySessionStarted version: "0.0.1" next_steps: - id: query-completed label: "success" - id: query-failed label: "error" - id: query-cancelled label: "cancelled" - id: query-completed title: Query Completed message: id: QuerySessionCompleted version: "0.0.1" next_step: id: create-dashboard label: "results available" - id: query-failed title: Query Failed message: id: QuerySessionFailed version: "0.0.1" - id: query-cancelled title: Query Cancelled message: id: QuerySessionCancelled version: "0.0.1" - id: create-dashboard title: Create Dashboard message: id: DashboardCreated version: "0.0.1" next_step: id: add-chart label: "dashboard ready" - id: add-chart title: Add Chart to Dashboard message: id: DashboardChartAdded version: "0.0.1" next_steps: - id: add-tab label: "organize charts" - id: save-query label: "persist query" - id: add-tab title: Add Tab to Dashboard message: id: DashboardTabAdded version: "0.0.1" next_step: id: move-chart label: "organize" - id: move-chart title: Move Chart to Tab message: id: DashboardChartMovedToTab version: "0.0.1" - id: save-query title: Save Query message: id: SavedQueryCreated version: "0.0.1" next_step: id: session-end label: "work complete" - id: session-end title: Session Lifecycle End service: id: SessionService version: "0.0.1" --- ## Overview This flow represents the golden path through the ironstar platform, from initial authentication through data analysis and dashboard creation. It captures the typical journey of a data analyst using the system. ## Workflow stages The workflow progresses through distinct phases. *Authentication* establishes user identity via OAuth (GitHub or Google) and creates an authenticated session. *Catalog selection* allows the user to choose which DuckLake catalog to work with, triggering metadata refresh to populate the dataset browser. *Query execution* represents the core analytical work, where SQL queries are executed against the selected catalog. This phase has three possible outcomes: successful completion, failure (with error details), or cancellation by the user. *Dashboard creation* enables visualization of query results through chart widgets organized into tabbed layouts. *Query persistence* allows saving queries for reuse, completing the typical work session. ## Branching logic The flow includes decision points where execution may diverge. Query execution branches into three outcomes based on whether the query completes successfully, encounters an error, or is cancelled by the user. After adding charts, users may either organize them into tabs or proceed directly to saving queries. ## Session lifecycle Sessions are refreshed periodically while active. The session may terminate through explicit logout (invalidation) or TTL expiration. See the SessionLifecycle flow for detailed session state transitions. --- id: QueryExecution name: Query Execution version: 0.0.1 summary: | Analytics query workflow from catalog selection through metadata refresh to query execution and result handling. steps: - id: analyst title: Data Analyst actor: name: Data Analyst next_step: id: select-catalog label: "chooses catalog" - id: select-catalog title: Catalog Selected message: id: CatalogSelected version: "0.0.1" next_step: id: refresh-metadata label: "catalog chosen" - id: refresh-metadata title: Catalog Metadata Refreshed message: id: CatalogMetadataRefreshed version: "0.0.1" next_step: id: browse-datasets label: "metadata loaded" - id: browse-datasets title: Browse Available Datasets service: id: QuerySessionService version: "0.0.1" next_step: id: start-query label: "query composed" - id: start-query title: Query Session Started message: id: QuerySessionStarted version: "0.0.1" next_steps: - id: query-completed label: "success" - id: query-failed label: "error" - id: query-cancelled label: "cancelled" - id: query-completed title: Query Session Completed message: id: QuerySessionCompleted version: "0.0.1" - id: query-failed title: Query Session Failed message: id: QuerySessionFailed version: "0.0.1" - id: query-cancelled title: Query Session Cancelled message: id: QuerySessionCancelled version: "0.0.1" --- ## Overview This flow documents the analytics query workflow from initial catalog selection through query execution. It represents the core data analysis capability of the ironstar platform. ## Catalog selection Users select a DuckLake catalog from the available catalogs list. Catalog selection establishes the context for all subsequent query operations. When a catalog is selected, the CatalogService emits a CatalogSelected event and triggers metadata refresh. ## Metadata refresh Catalog metadata refresh populates the dataset browser with available tables, schemas, and column information. This operation queries the DuckLake catalog's information schema and caches results for the dataset browser UI. Metadata is refreshed on catalog selection and may be manually triggered if the user expects schema changes. ## Query execution lifecycle Query execution follows a three-outcome pattern common in long-running operations. *Started* indicates the query has been submitted for execution. The QuerySessionStarted event captures the SQL, target dataset, and initiating user. *Completed* indicates successful query execution. The QuerySessionCompleted event includes result metadata (row count, execution time) but not result data. Results are streamed to the client via SSE as they become available. *Failed* indicates query execution encountered an error. The QuerySessionFailed event captures the error type and message for display and debugging. *Cancelled* indicates the user requested query termination. The QuerySessionCancelled event records the cancellation for audit purposes. ## DuckDB execution Queries execute against DuckDB with DuckLake catalog extensions. DuckDB's columnar processing provides efficient analytical query execution. Long-running queries may be cancelled via the CancelQuery command. Query timeout limits prevent runaway queries from exhausting resources. ## Result streaming Query results are streamed to the client as datastar SSE events. Large result sets are paginated to avoid memory exhaustion. The client UI updates incrementally as results arrive. --- id: SavedQueryManagement name: Saved Query Management version: 0.0.1 summary: | Saved query CRUD operations from creation through SQL and dataset updates to deletion. steps: - id: analyst title: Data Analyst actor: name: Data Analyst next_step: id: create-query label: "saves query" - id: create-query title: Query Created message: id: SavedQueryCreated version: "0.0.1" next_steps: - id: rename-query label: "rename" - id: update-sql label: "modify SQL" - id: update-dataset label: "change dataset" - id: delete-query label: "delete" - id: rename-query title: Query Renamed message: id: SavedQueryRenamed version: "0.0.1" next_steps: - id: update-sql label: "modify SQL" - id: delete-query label: "delete" - id: update-sql title: Query SQL Updated message: id: SavedQuerySqlUpdated version: "0.0.1" next_steps: - id: update-sql label: "further refinement" - id: update-dataset label: "change dataset" - id: delete-query label: "delete" - id: update-dataset title: Dataset Reference Updated message: id: SavedQueryDatasetRefUpdated version: "0.0.1" next_steps: - id: update-sql label: "adjust SQL" - id: delete-query label: "delete" - id: delete-query title: Query Deleted message: id: SavedQueryDeleted version: "0.0.1" --- ## Overview This flow documents the saved query management operations for persisting and maintaining reusable queries. Saved queries enable analysts to build a library of useful analytical queries. All saved query operations occur within the context of a Workspace container. Each saved query belongs to exactly one workspace, and all query events include a `workspaceId` field enabling workspace-scoped subscriptions and filtering. ## Query creation Users save queries after successful execution to reuse them later. The SavedQueryCreated event captures the SQL, target dataset reference, and descriptive name. Saved queries are associated with the creating user and may be shared. ## Query modification Saved queries can be modified over time as requirements evolve. *SQL updates* allow refining the query logic without creating a new saved query. The SavedQuerySqlUpdated event maintains an audit trail of query evolution. *Dataset reference updates* allow retargeting queries to different catalogs or tables. This is useful when migrating between development and production datasets. *Renaming* allows improving query organization without affecting query logic. ## Query deletion Queries may be deleted when no longer needed. The SavedQueryDeleted event enables soft-delete patterns if audit requirements demand it. Charts referencing deleted queries require attention (broken reference handling). ## Query editor integration The saved query system integrates with the query editor UI. Saved queries appear in a sidebar for quick loading. The query editor state (current SQL, cursor position, results) is tracked separately from saved query state. ## Version considerations Saved queries do not currently version their SQL content. Each modification overwrites the previous version. If versioning becomes necessary, the SavedQuerySqlUpdated events provide a full audit trail for reconstructing any historical version. --- id: SessionLifecycle name: Session Lifecycle version: 0.0.1 summary: | Session state machine from creation through refresh cycles to termination via invalidation or expiration. steps: - id: oauth-start title: OAuth Flow Initiated actor: name: User next_step: id: oauth-callback label: "completes OAuth" - id: oauth-callback title: OAuth Callback Processed service: id: SessionService version: "0.0.1" next_step: id: session-created label: "validated" - id: session-created title: Session Created message: id: SessionCreated version: "0.0.1" next_steps: - id: session-refreshed label: "activity detected" - id: session-invalidated label: "logout" - id: session-expired label: "TTL elapsed" - id: session-refreshed title: Session Refreshed message: id: SessionRefreshed version: "0.0.1" next_steps: - id: session-refreshed label: "continued activity" - id: session-invalidated label: "logout" - id: session-expired label: "TTL elapsed" - id: session-invalidated title: Session Invalidated message: id: SessionInvalidated version: "0.0.1" - id: session-expired title: Session Expired message: id: SessionExpired version: "0.0.1" --- ## Overview This flow documents the complete session lifecycle from initial OAuth authentication through termination. Sessions are the security boundary for all user interactions with the ironstar platform. ## State machine The Session aggregate follows the Decider pattern with a finite state machine. *NoSession* is the initial state before any authenticated session exists. *Active* represents an authenticated session with a TTL expiration timestamp. The session transitions to Active upon successful OAuth callback processing. *Expired* is a terminal state reached when the session TTL elapses without refresh. *Invalidated* is a terminal state reached through explicit user logout. ## Session refresh mechanics Active sessions are refreshed on user activity to extend the TTL. The refresh operation is idempotent and updates only the expiration timestamp. Refresh occurs when any authenticated request is processed, preventing session timeout during active use. The refresh cadence is configurable but defaults to extending TTL by the original duration on each activity. ## Termination paths Sessions terminate through one of two paths. *Invalidation* occurs when the user explicitly logs out. The session cookie is cleared and the session record marked as invalidated. This is the expected termination for interactive users. *Expiration* occurs when the TTL elapses without activity. A background process identifies expired sessions and emits SessionExpired events. This handles abandoned sessions and browser windows left open. ## Security considerations Session tokens are cryptographically random and stored server-side in SQLite. The client receives only a signed session cookie with the session identifier. Failed OAuth callbacks do not create sessions. Invalid or expired session tokens result in 401 responses and redirect to OAuth flow. --- id: UserPreferencesSetup name: User Preferences Setup version: 0.0.1 summary: | User preferences configuration from initialization through theme and default catalog settings to UI state persistence. steps: - id: new-user title: New User actor: name: User next_step: id: initialize-prefs label: "first login" - id: initialize-prefs title: Preferences Initialized message: id: UserPreferencesInitialized version: "0.0.1" next_steps: - id: set-theme label: "configure theme" - id: set-default-catalog label: "set default catalog" - id: update-ui-state label: "interact with UI" - id: set-theme title: Theme Set message: id: UserPreferencesThemeSet version: "0.0.1" next_steps: - id: set-theme label: "change theme" - id: set-default-catalog label: "set defaults" - id: update-ui-state label: "interact with UI" - id: set-default-catalog title: Default Catalog Set message: id: UserPreferencesDefaultCatalogSet version: "0.0.1" next_steps: - id: clear-default label: "clear default" - id: set-default-catalog label: "change default" - id: update-ui-state label: "interact with UI" - id: clear-default title: Default Cleared message: id: UserPreferencesDefaultCleared version: "0.0.1" next_steps: - id: set-default-catalog label: "set new default" - id: update-ui-state label: "interact with UI" - id: update-ui-state title: UI State Updated message: id: UserPreferencesUiStateUpdated version: "0.0.1" next_steps: - id: update-ui-state label: "continued interaction" - id: set-theme label: "change theme" --- ## Overview This flow documents user preferences configuration and UI state persistence. Preferences enable personalization and restore user context across sessions. ## Initialization User preferences are initialized on first login. The UserPreferencesInitialized event establishes default values for all configurable settings. Initialization occurs automatically during session creation if no preferences exist for the user. ## Theme configuration Users can select between light and dark themes. The platform uses Open Props design tokens with OKLch color space for perceptually uniform theming. Theme selection is persisted and restored on subsequent sessions. The `light-dark()` CSS function enables native theme switching. ## Default catalog selection Users can set a default catalog that is automatically selected on login. This streamlines the workflow for users who consistently work with the same data. The default catalog can be cleared to restore the catalog selection prompt on login. ## UI state persistence UI state captures ephemeral preferences that enhance user experience. *Panel positions* and sizes are remembered across sessions. *Sidebar collapse state* is persisted. *Query editor preferences* (font size, line numbers, etc.) are maintained. *Recent items* lists are updated as users interact with the system. UI state updates occur frequently during normal usage. These events are batched to avoid excessive event volume. ## Privacy considerations User preferences are stored per-user and are not shared. Preferences do not contain sensitive data and are not encrypted separately from the event store. Users may clear their preferences to reset to defaults. --- id: WorkspaceLifecycle name: Workspace Lifecycle version: 0.0.1 summary: | Workspace creation, configuration, and deletion lifecycle managing the container for dashboards and saved queries. steps: - id: analyst title: Data Analyst actor: name: Data Analyst next_step: id: create-workspace label: "creates workspace" - id: create-workspace title: Workspace Created custom: title: WorkspaceCreated (placeholder) type: placeholder description: "WorkspaceCreated event (to be implemented)" next_steps: - id: init-preferences label: "initialize preferences" - id: add-dashboard label: "add dashboard" - id: add-query label: "add saved query" - id: init-preferences title: Preferences Initialized message: id: UserPreferencesInitialized version: "0.0.1" next_steps: - id: add-dashboard label: "add dashboard" - id: add-query label: "add saved query" - id: add-dashboard title: Dashboard Added message: id: DashboardCreated version: "0.0.1" next_steps: - id: add-dashboard label: "add another" - id: add-query label: "add query" - id: delete-workspace label: "delete workspace" - id: add-query title: Query Added message: id: SavedQueryCreated version: "0.0.1" next_steps: - id: add-query label: "add another" - id: add-dashboard label: "add dashboard" - id: delete-workspace label: "delete workspace" - id: delete-workspace title: Workspace Deleted custom: title: WorkspaceDeleted (placeholder) type: placeholder description: "WorkspaceDeleted event (to be implemented)" --- ## Overview This flow documents the workspace lifecycle from creation through configuration and eventual deletion. A workspace serves as the container for dashboards and saved queries, providing organizational structure and access control boundaries. The workspace lifecycle involves creating the container, initializing user preferences, populating with content, and eventual cleanup. ## Workspace creation Users create workspaces to organize related analytical artifacts. A workspace provides isolation for dashboards and saved queries, enabling focused analysis on specific projects or data domains. The WorkspaceCreated event captures the workspace identifier, owner, and initial metadata such as name and description. ## Preference initialization After workspace creation, user preferences are initialized with sensible defaults. Preferences include theme settings, default catalog selection, and UI state persistence. Preferences are user-scoped rather than workspace-scoped, applying across all workspaces the user accesses. ## Content population Workspaces are populated with dashboards and saved queries over time. Each dashboard and query belongs to exactly one workspace, identified by the `workspaceId` field in all workspace domain events. Content can be added in any order. Users typically create saved queries first, then visualize results in dashboards. ## Workspace deletion Workspaces may be deleted when no longer needed. Deletion should cascade to contained dashboards and queries, or require explicit cleanup first depending on policy. Soft delete with recovery period is recommended for enterprise deployments.