Promote Workspace to Full Standard Entity
Table of Contents
Overview
The workspace entity was created with has_history: false, has_pagination: false, and
no detail_fields — all without documented rationale. The entity lifecycle standard
requires every entity to have history, pagination, and a generated detail dialog unless a
written deviation rationale is present. This plan promotes workspace to a full standard
entity, adding history support and a generated detail dialog, and records the legitimate
deviations for the remaining non-standard dimensions.
Justified Deviations (document, do not fix)
The following dimensions legitimately differ from the standard and must have written rationale added to the codegen model:
| Dimension | Value | Rationale |
|---|---|---|
has_workspace_id |
false | A workspace cannot belong to a workspace; self-referential membership is undefined. |
has_pagination |
false | A tenant holds at most ~20 active workspaces by product design; the tree view displaying the parent-workspace hierarchy does not support standard pagination. |
| Tree view (not QTableView) | — | WorkspaceMdiWindow uses QTreeWidget to display the workspace inheritance chain; a flat paginated list loses that hierarchy. Codegen of the MDI window is skipped; only the detail and history dialogs are generated. |
Corrected: The original plan listed has_tenant_id: false as a justified deviation with
the rationale "cross-tenant isolation is handled via IAM." This was a modeling error.
Authentication (IAM) verifies identity but does not substitute for data-layer tenant
scoping — the workspace service would otherwise return rows from any tenant. Workspace is
a party-scoped entity: has_tenant_id: true and has_party_id: true are now set in the
codegen model and the SQL table carries both columns.
What Changes
Unjustified deviations to fix
has_history: false→true: no justification exists; the bitemporal table already stores full version history; the audit trail must be viewable.- No
detail_fields: replace hand-craftedWorkspaceDetailDialogwith a generated version driven by the model. - No generator: add
workspace_generator.hpp/.cppfor test data generation.
Phase 1 — Model Update and C++ API
PR title
[workspace] Add history support and generator to workspace entity
1.1 Update the codegen model
File: projects/ores.codegen/models/workspace/workspace_domain_entity.json
Add to the top-level domain_entity section (alongside existing has_tenant_id):
"deviation_rationale_tenant_id": "Workspace is system-wide; cross-tenant isolation is handled via IAM, not per-tenant row scoping.", "deviation_rationale_workspace_id": "A workspace cannot belong to a workspace; self-referential membership is undefined.",
Add/replace in the qt section:
"has_history": true,
"has_pagination": false,
"deviation_rationale_pagination": "A tenant holds at most ~20 active workspaces by product design; the tree view displaying the parent-workspace hierarchy does not support standard pagination.",
"history_request_class": "workspace::messaging::get_workspace_history_request",
"history_response_class": "workspace::messaging::get_workspace_history_response",
"history_message_type": "get_workspace_history_request",
"detail_fields": [
{"field": "name", "label": "Name", "widget": "nameEdit", "type": "line_edit", "is_key": true, "is_required": true, "placeholder": "Unique workspace name"},
{"field": "description", "label": "Description", "widget": "descriptionEdit", "type": "plain_text_edit"},
{"field": "source_path", "label": "Source Path", "widget": "sourcePathEdit", "type": "line_edit", "placeholder": "Optional data source path"},
{"field": "parent_workspace_id", "label": "Parent", "widget": "parentEdit", "type": "line_edit", "readonly": true, "placeholder": "Inherited from (UUID)"},
{"field": "owner_id", "label": "Owner", "widget": "ownerEdit", "type": "line_edit", "readonly": true},
{"field": "status_code", "label": "Status", "widget": "statusEdit", "type": "line_edit", "readonly": true}
]
Remove the explicit "has_history": false and "has_pagination": false lines (replacing
them with the above — pagination keeps false but now has rationale).
1.2 Regenerate C++ API
cd projects/ores.codegen
./run_generator.sh models/workspace/workspace_domain_entity.json --profile all-cpp
Expected new/changed files in projects/ores.workspace.api/:
include/ores.workspace.api/generators/workspace_generator.hpp— newsrc/generators/workspace_generator.cpp— newinclude/ores.workspace.api/messaging/workspace_protocol.hpp— addget_workspace_history_request/response
1.3 CMakeLists integration
File: projects/ores.workspace.api/CMakeLists.txt
Add the generator source files if they are not picked up automatically via GLOB.
1.4 Build and verify
Ask user to build ores.workspace.api:
make -C build/output/linux-clang-debug-make -j$(nproc) ores.workspace.api
Phase 2 — C++ Core (History Method)
PR title
[workspace] Add workspace history repository and service methods
2.1 Repository
Files:
projects/ores.workspace.core/include/ores.workspace.core/repository/workspace_repository.hppprojects/ores.workspace.core/src/repository/workspace_repository.cpp
The repository already has read_all(ctx, id) which returns all versions of a single
workspace. Add the standard-named alias:
std::vector<domain::workspace> read_history(context ctx, const boost::uuids::uuid& id);
Implementation: same bitemporal query as read_all(id), ordered newest-first by valid_from DESC.
2.2 Service
Files:
projects/ores.workspace.core/include/ores.workspace.core/service/workspace_service.hppprojects/ores.workspace.core/src/service/workspace_service.cpp
Add:
std::vector<domain::workspace> get_workspace_history(const boost::uuids::uuid& id);
Delegates to workspace_repo_.read_history(ctx, id).
2.3 Handler
Files:
projects/ores.workspace.core/include/ores.workspace.core/messaging/workspace_handler.hppprojects/ores.workspace.core/src/messaging/workspace_handler.cpp
Add routing for get_workspace_history_request → call service_.get_workspace_history(id),
return get_workspace_history_response{workspaces}.
2.4 Build and verify
Ask user to build ores.workspace.core:
make -C build/output/linux-clang-debug-make -j$(nproc) ores.workspace.core
Phase 3 — Qt UI
PR title
[qt] Add workspace history dialog and replace hand-crafted detail dialog
3.1 Regenerate Qt layer (selectively)
Run codegen for the Qt profile to get the detail dialog and history dialog:
cd projects/ores.codegen
./run_generator.sh models/workspace/workspace_domain_entity.json --profile qt
This will generate or update all Qt files. Do not commit the regenerated
WorkspaceMdiWindow or ClientWorkspaceModel files — these are justified deviations
(tree view, no pagination). Only integrate:
WorkspaceDetailDialog.hpp/.cpp/.ui— replaces hand-crafted versionWorkspaceHistoryDialog.hpp/.cpp/.ui— new
Review generated WorkspaceDetailDialog carefully: the existing hand-crafted dialog has a
Provenance tab showing read-only audit fields. The generated version may or may not include
this; manually merge the provenance widget if the generator omits it.
3.2 Update WorkspaceMdiWindow
Files:
projects/ores.qt.workspace/include/ores.qt/WorkspaceMdiWindow.hppprojects/ores.qt.workspace/src/WorkspaceMdiWindow.cpp
Add to the private section of the header:
QAction* historyAction_{nullptr}; void onHistoryRequested();
In WorkspaceMdiWindow.cpp, in the toolbar setup block (after editAction_):
historyAction_ = toolbar_->addAction(
QIcon::fromTheme("document-open-recent"), tr("History"));
historyAction_->setEnabled(false);
connect(historyAction_, &QAction::triggered,
this, &WorkspaceMdiWindow::onHistoryRequested);
Enable/disable historyAction_ alongside editAction_ in the selection-changed handler.
Implement onHistoryRequested():
void WorkspaceMdiWindow::onHistoryRequested() { const auto* item = treeWidget_->currentItem(); if (!item) return; const auto id = item->data(0, Qt::UserRole).toString(); auto* dlg = new WorkspaceHistoryDialog(clientManager_, id, this); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->exec(); }
3.3 CMakeLists integration
File: projects/ores.qt.workspace/CMakeLists.txt
Add WorkspaceHistoryDialog.hpp, WorkspaceHistoryDialog.cpp, WorkspaceHistoryDialog.ui
if not picked up by GLOB.
3.4 Build and verify
Ask user to build ores.qt.workspace:
make -C build/output/linux-clang-debug-make -j$(nproc) ores.qt.workspace
Manual smoke test:
- Launch the application and open the Workspace MDI window.
- Select any workspace → History button becomes enabled.
- Click History →
WorkspaceHistoryDialogopens with the full version list. - Click Edit (or Add) → generated
WorkspaceDetailDialogopens with Name, Description, Source Path, Parent, Owner, Status fields.
Files Changed
| File | Change |
|---|---|
projects/ores.codegen/models/workspace/workspace_domain_entity.json |
Add history config, detail_fields, deviation rationales |
projects/ores.workspace.api/include/.../messaging/workspace_protocol.hpp |
Add get_workspace_history_request/response |
projects/ores.workspace.api/include/.../generators/workspace_generator.hpp |
New |
projects/ores.workspace.api/src/generators/workspace_generator.cpp |
New |
projects/ores.workspace.api/CMakeLists.txt |
Add generator sources if needed |
projects/ores.workspace.core/include/.../repository/workspace_repository.hpp |
Add read_history() |
projects/ores.workspace.core/src/repository/workspace_repository.cpp |
Implement read_history() |
projects/ores.workspace.core/include/.../service/workspace_service.hpp |
Add get_workspace_history() |
projects/ores.workspace.core/src/service/workspace_service.cpp |
Implement |
projects/ores.workspace.core/include/.../messaging/workspace_handler.hpp |
Add history handler |
projects/ores.workspace.core/src/messaging/workspace_handler.cpp |
Route history request |
projects/ores.qt.workspace/include/.../WorkspaceDetailDialog.hpp |
Replace with generated |
projects/ores.qt.workspace/src/WorkspaceDetailDialog.cpp |
Replace with generated |
projects/ores.qt.workspace/ui/WorkspaceDetailDialog.ui |
Replace with generated |
projects/ores.qt.workspace/include/.../WorkspaceHistoryDialog.hpp |
New (generated) |
projects/ores.qt.workspace/src/WorkspaceHistoryDialog.cpp |
New (generated) |
projects/ores.qt.workspace/ui/WorkspaceHistoryDialog.ui |
New (generated) |
projects/ores.qt.workspace/include/.../WorkspaceMdiWindow.hpp |
Add historyAction_, onHistoryRequested() |
projects/ores.qt.workspace/src/WorkspaceMdiWindow.cpp |
Wire history action → dialog |
projects/ores.qt.workspace/CMakeLists.txt |
Add history dialog sources if needed |