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: falsetrue: no justification exists; the bitemporal table already stores full version history; the audit trail must be viewable.
  • No detail_fields: replace hand-crafted WorkspaceDetailDialog with a generated version driven by the model.
  • No generator: add workspace_generator.hpp/.cpp for 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.hppnew
  • src/generators/workspace_generator.cppnew
  • include/ores.workspace.api/messaging/workspace_protocol.hpp — add get_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.hpp
  • projects/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.hpp
  • projects/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.hpp
  • projects/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 version
  • WorkspaceHistoryDialog.hpp/.cpp/.uinew

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.hpp
  • projects/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:

  1. Launch the application and open the Workspace MDI window.
  2. Select any workspace → History button becomes enabled.
  3. Click History → WorkspaceHistoryDialog opens with the full version list.
  4. Click Edit (or Add) → generated WorkspaceDetailDialog opens 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

Date: 2026-05-21

Emacs 29.1 (Org mode 9.6.6)